From 2cb302d4893696bef2a2789e1a1d58573aee3b9a Mon Sep 17 00:00:00 2001 From: cophus Date: Tue, 2 Dec 2025 13:11:35 -0800 Subject: [PATCH 001/113] adding support for 3d dm4 files --> 4dstem --- src/quantem/core/io/file_readers.py | 222 +++++++++++++++++++++++++--- 1 file changed, 200 insertions(+), 22 deletions(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index cb36f1dee..c07d9fbba 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -3,6 +3,7 @@ from pathlib import Path import h5py +import numpy as np from quantem.core.datastructures import Dataset as Dataset from quantem.core.datastructures import Dataset2d as Dataset2d @@ -14,57 +15,234 @@ def read_4dstem( file_path: str | PathLike, file_type: str | None = None, dataset_index: int | None = None, + scan_length: int | None = None, + scan_axis: int = 0, + transpose_scan_axes: bool = False, **kwargs, ) -> Dataset4dstem: """ - File reader for 4D-STEM data + File reader for 4D-STEM data. Parameters ---------- - file_path: str | PathLike - Path to data - file_type: str - The type of file reader needed. See rosettasciio for supported formats + file_path : str | PathLike + Path to data. + file_type : str, optional + The type of file reader needed. See RosettaSciIO for supported formats: https://hyperspy.org/rosettasciio/supported_formats/index.html - dataset_index: int, optional + dataset_index : int, optional Index of the dataset to load if file contains multiple datasets. If None, automatically selects the first 4D dataset found. - **kwargs: dict + If no 4D dataset is found but a 3D stack exists, a 3D dataset can be + interpreted as 4D if `scan_length` is provided. + scan_length : int, optional + For 3D datasets shaped (n_frames, ny, nx) (after possibly moving the + scan axis to the front), interpret the data as a raster scan with shape + (scan_y, scan_x, ny, nx), where scan_y = n_frames // scan_length and + scan_x = scan_length. Required if you want to treat a 3D stack as 4D. + scan_axis : int, default 0 + Which axis of a 3D dataset is the scan/time axis before reshaping. + Must be 0 or 1. The specified axis is moved to axis 0 before the + (scan_y, scan_x) reshape. + transpose_scan_axes : bool, default False + Only used when interpreting a 3D dataset as 4D via `scan_length`. + If True, transpose the scan axes after reshaping so that + (scan_y, scan_x) -> (scan_x, scan_y). This effectively swaps the + interpretation of scan rows and columns in the final 4D array. + + **kwargs : dict Additional keyword arguments to pass to the Dataset4dstem constructor. Returns - -------- + ------- Dataset4dstem """ + + def _reshape_3d_to_4d( + imported_data: dict, + *, + dataset_index_local: int | None, + scan_length_local: int, + scan_axis_local: int, + transpose_scan_axes_local: bool, + ) -> dict: + data = imported_data["data"] + if data.ndim != 3: + raise ValueError( + f"Expected 3D data to reshape, got ndim={data.ndim} " + f"with shape {data.shape}" + ) + + if scan_axis_local not in (0, 1): + raise ValueError(f"scan_axis must be 0 or 1, got {scan_axis_local}") + + # Move scan axis to front so it becomes the frame axis + if scan_axis_local != 0: + data = np.moveaxis(data, scan_axis_local, 0) + + n_frames, ny, nx = data.shape + + if scan_length_local <= 0: + raise ValueError(f"scan_length must be positive, got {scan_length_local}") + if n_frames % scan_length_local != 0: + raise ValueError( + f"scan_length={scan_length_local} is not compatible with n_frames={n_frames}; " + f"n_frames % scan_length = {n_frames % scan_length_local}" + ) + + scan_y = n_frames // scan_length_local + scan_x = scan_length_local + + data_4d = data.reshape(scan_y, scan_x, ny, nx) + + if transpose_scan_axes_local: + data_4d = np.transpose(data_4d, (1, 0, 2, 3)) + scan_y, scan_x = scan_x, scan_y + + old_axes = imported_data.get("axes", None) + if old_axes is None or len(old_axes) != 3: + raise ValueError( + "Expected 3 axes for 3D data when reshaping to 4D; " + f"got axes={old_axes}" + ) + + ax_scan_y = { + "scale": 1.0, + "offset": 0.0, + "units": "pixels", + "name": "scan_y", + } + ax_scan_x = { + "scale": 1.0, + "offset": 0.0, + "units": "pixels", + "name": "scan_x", + } + + ax_qy = dict(old_axes[1]) + ax_qx = dict(old_axes[2]) + + imported_data_4d = imported_data.copy() + imported_data_4d["data"] = data_4d + imported_data_4d["axes"] = [ax_scan_y, ax_scan_x, ax_qy, ax_qx] + + original_shape = imported_data["data"].shape + new_shape = data_4d.shape + if dataset_index_local is not None: + print( + f"Using 3D dataset {dataset_index_local} with shape {original_shape} " + f"interpreted as 4D with shape={new_shape} " + f"(scan_axis={scan_axis_local}, scan_length={scan_length_local}, " + f"transpose_scan_axes={transpose_scan_axes_local})." + ) + else: + print( + f"Using 3D dataset with shape {original_shape} " + f"interpreted as 4D with shape={new_shape} " + f"(scan_axis={scan_axis_local}, scan_length={scan_length_local}, " + f"transpose_scan_axes={transpose_scan_axes_local})." + ) + + return imported_data_4d + if file_type is None: file_type = Path(file_path).suffix.lower().lstrip(".") file_reader = importlib.import_module(f"rsciio.{file_type}").file_reader data_list = file_reader(file_path) - # If specific index provided, use it + if not data_list: + raise ValueError(f"No datasets returned by rsciio.{file_type} for '{file_path}'") + + # Case 1: dataset_index specified explicitly if dataset_index is not None: imported_data = data_list[dataset_index] - if imported_data["data"].ndim != 4: + ndim = imported_data["data"].ndim + + if ndim == 4: + # Use 4D as-is + pass + elif ndim == 3: + if scan_length is None: + raise ValueError( + f"Dataset at index {dataset_index} is 3D (shape={imported_data['data'].shape}). " + "To interpret it as 4D-STEM, please provide scan_length." + ) + imported_data = _reshape_3d_to_4d( + imported_data, + dataset_index_local=dataset_index, + scan_length_local=scan_length, + scan_axis_local=scan_axis, + transpose_scan_axes_local=transpose_scan_axes, + ) + else: raise ValueError( - f"Dataset at index {dataset_index} has {imported_data['data'].ndim} dimensions, " - f"expected 4D. Shape: {imported_data['data'].shape}" + f"Dataset at index {dataset_index} has ndim={ndim}, " + f"expected 4D or 3D. Shape: {imported_data['data'].shape}" ) + else: - # Automatically find first 4D dataset + # Case 2: auto-select dataset four_d_datasets = [(i, d) for i, d in enumerate(data_list) if d["data"].ndim == 4] - if len(four_d_datasets) == 0: - print(f"No 4D datasets found in {file_path}. Available datasets:") - for i, d in enumerate(data_list): - print(f" Dataset {i}: shape {d['data'].shape}, ndim={d['data'].ndim}") - raise ValueError("No 4D dataset found in file") + if four_d_datasets: + dataset_index, imported_data = four_d_datasets[0] + if len(data_list) > 1: + print( + f"File contains {len(data_list)} dataset(s). Using 4D dataset " + f"{dataset_index} with shape {imported_data['data'].shape}" + ) + else: + three_d_datasets = [(i, d) for i, d in enumerate(data_list) if d["data"].ndim == 3] - dataset_index, imported_data = four_d_datasets[0] + if not three_d_datasets: + print(f"No 4D datasets found in {file_path}. Available datasets:") + for i, d in enumerate(data_list): + print(f" Dataset {i}: shape {d['data'].shape}, ndim={d['data'].ndim}") + raise ValueError("No 4D or 3D dataset found in file") - if len(data_list) > 1: - print( - f"File contains {len(data_list)} dataset(s). Using dataset {dataset_index} with shape {imported_data['data'].shape}" + if scan_length is None: + print(f"No 4D datasets found in {file_path}. Available datasets:") + for i, d in enumerate(data_list): + print(f" Dataset {i}: shape {d['data'].shape}, ndim={d['data'].ndim}") + raise ValueError( + "File contains only 3D datasets. To interpret one as 4D-STEM, " + "please specify scan_length so that n_frames % scan_length == 0." + ) + + # Choose first 3D dataset compatible with scan_length along scan_axis + candidates: list[tuple[int, dict]] = [] + for i, d in three_d_datasets: + shape = d["data"].shape + if scan_axis < 0 or scan_axis > 2: + raise ValueError(f"scan_axis must be in [0, 2] for 3D data, got {scan_axis}") + n_frames_axis = shape[scan_axis] + if n_frames_axis % scan_length == 0: + candidates.append((i, d)) + + if not candidates: + print(f"3D datasets in {file_path}:") + for i, d in three_d_datasets: + print(f" Dataset {i}: shape {d['data'].shape}") + raise ValueError( + f"No 3D dataset has length along scan_axis={scan_axis} " + f"divisible by scan_length={scan_length}." + ) + + dataset_index, imported_data = candidates[0] + if len(candidates) > 1: + print( + f"Multiple 3D datasets compatible with scan_length={scan_length} " + f"along scan_axis={scan_axis}. Using dataset {dataset_index} " + f"with shape {imported_data['data'].shape}" + ) + + imported_data = _reshape_3d_to_4d( + imported_data, + dataset_index_local=dataset_index, + scan_length_local=scan_length, + scan_axis_local=scan_axis, + transpose_scan_axes_local=transpose_scan_axes, ) imported_axes = imported_data["axes"] From d88ffb5be0cae76f01b9c85f6f2a8c2751519ec2 Mon Sep 17 00:00:00 2001 From: cophus Date: Wed, 3 Dec 2025 09:11:15 -0800 Subject: [PATCH 002/113] initial construction of polar4dstem class --- src/quantem/core/datastructures/__init__.py | 1 + .../core/datastructures/dataset4dstem.py | 5 + .../core/datastructures/polar4dstem.py | 330 ++++++++++++++++++ 3 files changed, 336 insertions(+) create mode 100644 src/quantem/core/datastructures/polar4dstem.py diff --git a/src/quantem/core/datastructures/__init__.py b/src/quantem/core/datastructures/__init__.py index dfb5b47ac..ac8f3d643 100644 --- a/src/quantem/core/datastructures/__init__.py +++ b/src/quantem/core/datastructures/__init__.py @@ -2,6 +2,7 @@ from quantem.core.datastructures.vector import Vector as Vector from quantem.core.datastructures.dataset4dstem import Dataset4dstem as Dataset4dstem +from quantem.core.datastructures.polar4dstem import Polar4dstem as Polar4dstem from quantem.core.datastructures.dataset4d import Dataset4d as Dataset4d from quantem.core.datastructures.dataset3d import Dataset3d as Dataset3d from quantem.core.datastructures.dataset2d import Dataset2d as Dataset2d diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index 283286369..2d4c860eb 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -7,6 +7,8 @@ from quantem.core.datastructures.dataset2d import Dataset2d from quantem.core.datastructures.dataset4d import Dataset4d +from quantem.core.datastructures.polar4dstem import dataset4dstem_polar_transform + from quantem.core.utils.validators import ensure_valid_array from quantem.core.visualization import show_2d from quantem.core.visualization.visualization_utils import ScalebarConfig @@ -751,3 +753,6 @@ def median_filter_masked_pixels(self, mask: np.ndarray, kernel_width: int = 3): self.array[:, :, index_x, index_y] = np.median( self.array[:, :, x_min:x_max, y_min:y_max], axis=(2, 3) ) + + + polar_transform = dataset4dstem_polar_transform \ No newline at end of file diff --git a/src/quantem/core/datastructures/polar4dstem.py b/src/quantem/core/datastructures/polar4dstem.py new file mode 100644 index 000000000..848469ec7 --- /dev/null +++ b/src/quantem/core/datastructures/polar4dstem.py @@ -0,0 +1,330 @@ +import numpy as np +from numpy.typing import NDArray +from typing import Any, TYPE_CHECKING + +from quantem.core.datastructures.dataset4d import Dataset4d +# from quantem.core.datastructures.dataset4dstem import Dataset4dstem + +if TYPE_CHECKING: + from .dataset4dstem import Dataset4dstem + +class Polar4dstem(Dataset4d): + """4D-STEM dataset in polar coordinates (scan_y, scan_x, phi, r).""" + + 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, + signal_units: str = "arb. units", + metadata: dict | None = None, + _token: object | None = None, + ): + if metadata is None: + metadata = {} + mdata_keys_polar = [ + "polar_radial_min", + "polar_radial_max", + "polar_radial_step", + "polar_num_annular_bins", + "polar_two_fold_rotation_symmetry", + "polar_origin_row", + "polar_origin_col", + "polar_ellipse_params", + ] + for k in mdata_keys_polar: + if k not in metadata: + metadata[k] = None + + super().__init__( + array=array, + name=name, + origin=origin, + sampling=sampling, + units=units, + signal_units=signal_units, + metadata=metadata, + _token=_token, + ) + + @classmethod + def from_array( + cls, + array: NDArray | Any, + 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, + ) -> "Polar4dstem": + array = ensure_valid_array(array, ndim=4) + if origin is None: + origin = np.zeros(4) + if sampling is None: + sampling = np.ones(4) + if units is None: + units = ["pixels", "pixels", "deg", "pixels"] + if metadata is None: + metadata = {} + return cls( + array=array, + name=name if name is not None else "Polar 4D-STEM dataset", + origin=origin, + sampling=sampling, + units=units, + signal_units=signal_units, + metadata=metadata, + _token=cls._token, + ) + + @property + def n_phi(self) -> int: + return int(self.array.shape[2]) + + @property + def n_r(self) -> int: + return int(self.array.shape[3]) + + +def dataset4dstem_polar_transform( + self: "Dataset4dstem", + origin_row: float | NDArray, + origin_col: float | NDArray, + ellipse_params: tuple[float, float, float] | None = None, + num_annular_bins: int = 180, + radial_min: float = 0.0, + radial_max: float | None = None, + radial_step: float = 1.0, + two_fold_rotation_symmetry: bool = False, + name: str | None = None, + signal_units: str | None = None, +) -> Polar4dstem: + """Return a Polar4dstem with shape (scan_y, scan_x, phi, r).""" + if self.array.ndim != 4: + raise ValueError("polar_transform requires a 4D-STEM dataset (ndim=4).") + + scan_y, scan_x, ny, nx = self.array.shape + + mapping = _precompute_polar_mapping( + ny=ny, + nx=nx, + origin_row=float(origin_row), + origin_col=float(origin_col), + ellipse_params=ellipse_params, + num_annular_bins=num_annular_bins, + radial_min=radial_min, + radial_max=radial_max, + radial_step=radial_step, + two_fold_rotation_symmetry=two_fold_rotation_symmetry, + ) + + result_dtype = np.result_type(self.array.dtype, np.float32) + out = np.empty( + (scan_y, scan_x, mapping["n_phi"], mapping["n_r"]), + dtype=result_dtype, + ) + + for iy in range(scan_y): + for ix in range(scan_x): + out[iy, ix] = _apply_polar_mapping_single( + self.array[iy, ix], + mapping, + dtype=result_dtype, + ) + + phi_step_deg = mapping["phi_step"] * 180.0 / np.pi + phi_units = "deg" + radial_units = self.units[-1] + + sampling = np.array( + [ + self.sampling[0], + self.sampling[1], + phi_step_deg, + self.sampling[-1] * mapping["radial_step"], + ], + dtype=float, + ) + origin = np.array( + [ + self.origin[0], + self.origin[1], + 0.0, + self.sampling[-1] * mapping["radial_min"], + ], + dtype=float, + ) + units = [ + self.units[0], + self.units[1], + phi_units, + radial_units, + ] + + metadata = dict(self.metadata) + metadata.update( + { + "polar_radial_min": mapping["radial_min"], + "polar_radial_max": mapping["radial_max"], + "polar_radial_step": mapping["radial_step"], + "polar_num_annular_bins": mapping["n_phi"], + "polar_two_fold_rotation_symmetry": two_fold_rotation_symmetry, + "polar_origin_row": float(origin_row), + "polar_origin_col": float(origin_col), + "polar_ellipse_params": tuple(ellipse_params) + if ellipse_params is not None + else None, + } + ) + + return Polar4dstem( + array=out, + name=name if name is not None else f"{self.name}_polar", + origin=origin, + sampling=sampling, + units=units, + signal_units=signal_units if signal_units is not None else self.signal_units, + metadata=metadata, + _token=Polar4dstem._token, + ) + + +def _precompute_polar_mapping( + ny: int, + nx: int, + origin_row: float, + origin_col: float, + ellipse_params: tuple[float, float, float] | None, + num_annular_bins: int, + radial_min: float, + radial_max: float | None, + radial_step: float, + two_fold_rotation_symmetry: bool, +) -> dict[str, Any]: + origin_row = float(origin_row) + origin_col = float(origin_col) + annular_range = np.pi if two_fold_rotation_symmetry else 2.0 * np.pi + + rows = np.arange(ny, dtype=np.float64) + cols = np.arange(nx, dtype=np.float64) + cc, rr = np.meshgrid(cols, rows, indexing="xy") + x = cc - origin_col + y = rr - origin_row + + if ellipse_params is None: + rr_pix = np.sqrt(x * x + y * y) + tt = np.mod(np.arctan2(y, x), annular_range) + else: + if len(ellipse_params) != 3: + raise ValueError("ellipse_params must be a length-3 tuple (a, b, theta_deg).") + a, b, theta_deg = ellipse_params + theta = np.deg2rad(theta_deg) + cos_t = np.cos(theta) + sin_t = np.sin(theta) + xc = x * cos_t + y * sin_t + yc = (y * cos_t - x * sin_t) * (a / b) + rr_pix = (b / a) * np.hypot(xc, yc) + tt = np.mod(np.arctan2(yc, xc) + theta, annular_range) + + if radial_step <= 0: + raise ValueError("radial_step must be > 0.") + radial_min = float(radial_min) + + if radial_max is None: + radial_max_eff = float(rr_pix.max()) + else: + radial_max_eff = float(radial_max) + if radial_max_eff <= radial_min + radial_step: + radial_max_eff = radial_min + radial_step + + radial_bins = np.arange(radial_min, radial_max_eff, radial_step, dtype=np.float64) + n_r = radial_bins.size + if n_r < 1: + raise ValueError("No radial bins defined. Check radial_min, radial_max, and radial_step.") + + n_phi = int(num_annular_bins) + if n_phi < 1: + raise ValueError("num_annular_bins must be >= 1.") + phi_step = annular_range / n_phi + + r_bin = (rr_pix - radial_min) / radial_step + t_bin = tt / phi_step + + r0 = np.floor(r_bin).astype(np.int64) + t0 = np.floor(t_bin).astype(np.int64) + dr = (r_bin - r0).astype(np.float64) + dt = (t_bin - t0).astype(np.float64) + + valid = (r0 >= 0) & (r0 < n_r - 1) + t0 = np.clip(t0, 0, n_phi - 1) + + flat_valid = valid.ravel() + r0v = r0.ravel()[flat_valid] + t0v = t0.ravel()[flat_valid] + drv = dr.ravel()[flat_valid] + dtv = dt.ravel()[flat_valid] + + n_bins = n_phi * n_r + idx00 = r0v + n_r * t0v + idx01 = r0v + n_r * ((t0v + 1) % n_phi) + idx10 = (r0v + 1) + n_r * t0v + idx11 = (r0v + 1) + n_r * ((t0v + 1) % n_phi) + + w00 = (1.0 - drv) * (1.0 - dtv) + w01 = (1.0 - drv) * dtv + w10 = drv * (1.0 - dtv) + w11 = drv * dtv + + weights_sum = np.bincount(idx00, weights=w00, minlength=n_bins) + weights_sum += np.bincount(idx01, weights=w01, minlength=n_bins) + weights_sum += np.bincount(idx10, weights=w10, minlength=n_bins) + weights_sum += np.bincount(idx11, weights=w11, minlength=n_bins) + weights_sum = weights_sum.reshape(n_phi, n_r) + + weights_inv = np.zeros_like(weights_sum, dtype=np.float64) + mask_bins = weights_sum > 0 + weights_inv[mask_bins] = 1.0 / weights_sum[mask_bins] + + return { + "flat_valid": flat_valid, + "idx00": idx00, + "idx01": idx01, + "idx10": idx10, + "idx11": idx11, + "w00": w00, + "w01": w01, + "w10": w10, + "w11": w11, + "weights_inv": weights_inv, + "n_phi": n_phi, + "n_r": n_r, + "radial_bins": radial_bins, + "phi_step": phi_step, + "annular_range": annular_range, + "radial_min": radial_min, + "radial_max": radial_min + radial_step * n_r, + "radial_step": radial_step, + } + + +def _apply_polar_mapping_single( + image: NDArray, + mapping: dict[str, Any], + dtype: Any, +) -> NDArray: + data = np.asarray(image, dtype=np.float64) + flat = data.ravel()[mapping["flat_valid"]] + n_bins = mapping["n_phi"] * mapping["n_r"] + + acc = np.bincount(mapping["idx00"], weights=flat * mapping["w00"], minlength=n_bins) + acc += np.bincount(mapping["idx01"], weights=flat * mapping["w01"], minlength=n_bins) + acc += np.bincount(mapping["idx10"], weights=flat * mapping["w10"], minlength=n_bins) + acc += np.bincount(mapping["idx11"], weights=flat * mapping["w11"], minlength=n_bins) + + acc = acc.reshape(mapping["n_phi"], mapping["n_r"]) + acc *= mapping["weights_inv"] + return acc.astype(dtype, copy=False) + From 13b8d3d1232840adcfc3f93a059bd3df48a4a663 Mon Sep 17 00:00:00 2001 From: cophus Date: Thu, 4 Dec 2025 10:53:10 -0800 Subject: [PATCH 003/113] Changing sampling direction for polar4dstem --- .../core/datastructures/polar4dstem.py | 307 ++++++------------ src/quantem/diffraction/polar.py | 0 2 files changed, 107 insertions(+), 200 deletions(-) create mode 100644 src/quantem/diffraction/polar.py diff --git a/src/quantem/core/datastructures/polar4dstem.py b/src/quantem/core/datastructures/polar4dstem.py index 848469ec7..6619af5c9 100644 --- a/src/quantem/core/datastructures/polar4dstem.py +++ b/src/quantem/core/datastructures/polar4dstem.py @@ -1,13 +1,14 @@ import numpy as np from numpy.typing import NDArray from typing import Any, TYPE_CHECKING - -from quantem.core.datastructures.dataset4d import Dataset4d -# from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from scipy.ndimage import map_coordinates if TYPE_CHECKING: from .dataset4dstem import Dataset4dstem +from quantem.core.datastructures.dataset4d import Dataset4d + + class Polar4dstem(Dataset4d): """4D-STEM dataset in polar coordinates (scan_y, scan_x, phi, r).""" @@ -37,7 +38,6 @@ def __init__( for k in mdata_keys_polar: if k not in metadata: metadata[k] = None - super().__init__( array=array, name=name, @@ -60,11 +60,13 @@ def from_array( signal_units: str = "arb. units", metadata: dict | None = None, ) -> "Polar4dstem": - array = ensure_valid_array(array, ndim=4) + array = np.asarray(array) + if array.ndim != 4: + raise ValueError("Polar4dstem.from_array expects a 4D array.") if origin is None: - origin = np.zeros(4) + origin = np.zeros(4, dtype=float) if sampling is None: - sampling = np.ones(4) + sampling = np.ones(4, dtype=float) if units is None: units = ["pixels", "pixels", "deg", "pixels"] if metadata is None: @@ -89,10 +91,68 @@ def n_r(self) -> int: return int(self.array.shape[3]) +def _precompute_polar_coords( + ny: int, + nx: int, + origin_row: float, + origin_col: float, + ellipse_params: tuple[float, float, float] | None, + num_annular_bins: int, + radial_min: float, + radial_max: float | None, + radial_step: float, + two_fold_rotation_symmetry: bool, +) -> tuple[NDArray, NDArray, NDArray, float]: + origin_row = float(origin_row) + origin_col = float(origin_col) + if radial_step <= 0: + raise ValueError("radial_step must be > 0.") + if num_annular_bins < 1: + raise ValueError("num_annular_bins must be >= 1.") + if radial_max is None: + r_row_pos = origin_row + r_row_neg = (ny - 1) - origin_row + r_col_pos = origin_col + r_col_neg = (nx - 1) - origin_col + radial_max_eff = float(min(r_row_pos, r_row_neg, r_col_pos, r_col_neg)) + else: + radial_max_eff = float(radial_max) + if radial_max_eff <= radial_min: + radial_max_eff = radial_min + radial_step + radial_bins = np.arange(radial_min, radial_max_eff, radial_step, dtype=np.float64) + if radial_bins.size == 0: + radial_bins = np.array([radial_min], dtype=np.float64) + if two_fold_rotation_symmetry: + phi_range = np.pi + else: + phi_range = 2.0 * np.pi + phi_bins = np.linspace(0.0, phi_range, num_annular_bins, endpoint=False, dtype=np.float64) + phi_grid, r_grid = np.meshgrid(phi_bins, radial_bins, indexing="ij") + if ellipse_params is None: + x = r_grid * np.cos(phi_grid) + y = r_grid * np.sin(phi_grid) + else: + if len(ellipse_params) != 3: + raise ValueError("ellipse_params must be (a, b, theta_deg).") + a, b, theta_deg = ellipse_params + theta = np.deg2rad(theta_deg) + alpha = phi_grid - theta + u = (a / b) * r_grid * np.cos(alpha) + v_prime = r_grid * np.sin(alpha) + cos_t = np.cos(theta) + sin_t = np.sin(theta) + x = u * cos_t - v_prime * sin_t + y = u * sin_t + v_prime * cos_t + coords_y = y + origin_row + coords_x = x + origin_col + coords = np.stack((coords_y, coords_x), axis=0) + return coords, phi_bins, radial_bins, radial_max_eff + + def dataset4dstem_polar_transform( self: "Dataset4dstem", - origin_row: float | NDArray, - origin_col: float | NDArray, + origin_row: float | int | NDArray, + origin_col: float | int | NDArray, ellipse_params: tuple[float, float, float] | None = None, num_annular_bins: int = 180, radial_min: float = 0.0, @@ -102,17 +162,16 @@ def dataset4dstem_polar_transform( name: str | None = None, signal_units: str | None = None, ) -> Polar4dstem: - """Return a Polar4dstem with shape (scan_y, scan_x, phi, r).""" if self.array.ndim != 4: raise ValueError("polar_transform requires a 4D-STEM dataset (ndim=4).") - scan_y, scan_x, ny, nx = self.array.shape - - mapping = _precompute_polar_mapping( + origin_row_f = float(origin_row) + origin_col_f = float(origin_col) + coords, phi_bins, radial_bins, radial_max_eff = _precompute_polar_coords( ny=ny, nx=nx, - origin_row=float(origin_row), - origin_col=float(origin_col), + origin_row=origin_row_f, + origin_col=origin_col_f, ellipse_params=ellipse_params, num_annular_bins=num_annular_bins, radial_min=radial_min, @@ -120,66 +179,52 @@ def dataset4dstem_polar_transform( radial_step=radial_step, two_fold_rotation_symmetry=two_fold_rotation_symmetry, ) - + n_phi = phi_bins.size + n_r = radial_bins.size result_dtype = np.result_type(self.array.dtype, np.float32) - out = np.empty( - (scan_y, scan_x, mapping["n_phi"], mapping["n_r"]), - dtype=result_dtype, - ) - + out = np.empty((scan_y, scan_x, n_phi, n_r), dtype=result_dtype) for iy in range(scan_y): for ix in range(scan_x): - out[iy, ix] = _apply_polar_mapping_single( - self.array[iy, ix], - mapping, - dtype=result_dtype, + dp = self.array[iy, ix] + out[iy, ix] = map_coordinates( + dp, + coords, + order=1, + mode="constant", + cval=0.0, ) - - phi_step_deg = mapping["phi_step"] * 180.0 / np.pi - phi_units = "deg" - radial_units = self.units[-1] - - sampling = np.array( - [ - self.sampling[0], - self.sampling[1], - phi_step_deg, - self.sampling[-1] * mapping["radial_step"], - ], - dtype=float, - ) - origin = np.array( - [ - self.origin[0], - self.origin[1], - 0.0, - self.sampling[-1] * mapping["radial_min"], - ], - dtype=float, - ) + if two_fold_rotation_symmetry: + phi_range = np.pi + else: + phi_range = 2.0 * np.pi + phi_step_deg = (phi_range / float(n_phi)) * (180.0 / np.pi) + sampling = np.zeros(4, dtype=float) + origin = np.zeros(4, dtype=float) + sampling[0:2] = np.asarray(self.sampling)[0:2] + sampling[2] = phi_step_deg + sampling[3] = float(np.asarray(self.sampling)[-1]) * radial_step + origin[0:2] = np.asarray(self.origin)[0:2] + origin[2] = 0.0 + origin[3] = radial_min * float(np.asarray(self.sampling)[-1]) units = [ self.units[0], self.units[1], - phi_units, - radial_units, + "deg", + self.units[-1], ] - metadata = dict(self.metadata) metadata.update( { - "polar_radial_min": mapping["radial_min"], - "polar_radial_max": mapping["radial_max"], - "polar_radial_step": mapping["radial_step"], - "polar_num_annular_bins": mapping["n_phi"], - "polar_two_fold_rotation_symmetry": two_fold_rotation_symmetry, - "polar_origin_row": float(origin_row), - "polar_origin_col": float(origin_col), - "polar_ellipse_params": tuple(ellipse_params) - if ellipse_params is not None - else None, + "polar_radial_min": float(radial_min), + "polar_radial_max": float(radial_max_eff), + "polar_radial_step": float(radial_step), + "polar_num_annular_bins": int(n_phi), + "polar_two_fold_rotation_symmetry": bool(two_fold_rotation_symmetry), + "polar_origin_row": origin_row_f, + "polar_origin_col": origin_col_f, + "polar_ellipse_params": tuple(ellipse_params) if ellipse_params is not None else None, } ) - return Polar4dstem( array=out, name=name if name is not None else f"{self.name}_polar", @@ -190,141 +235,3 @@ def dataset4dstem_polar_transform( metadata=metadata, _token=Polar4dstem._token, ) - - -def _precompute_polar_mapping( - ny: int, - nx: int, - origin_row: float, - origin_col: float, - ellipse_params: tuple[float, float, float] | None, - num_annular_bins: int, - radial_min: float, - radial_max: float | None, - radial_step: float, - two_fold_rotation_symmetry: bool, -) -> dict[str, Any]: - origin_row = float(origin_row) - origin_col = float(origin_col) - annular_range = np.pi if two_fold_rotation_symmetry else 2.0 * np.pi - - rows = np.arange(ny, dtype=np.float64) - cols = np.arange(nx, dtype=np.float64) - cc, rr = np.meshgrid(cols, rows, indexing="xy") - x = cc - origin_col - y = rr - origin_row - - if ellipse_params is None: - rr_pix = np.sqrt(x * x + y * y) - tt = np.mod(np.arctan2(y, x), annular_range) - else: - if len(ellipse_params) != 3: - raise ValueError("ellipse_params must be a length-3 tuple (a, b, theta_deg).") - a, b, theta_deg = ellipse_params - theta = np.deg2rad(theta_deg) - cos_t = np.cos(theta) - sin_t = np.sin(theta) - xc = x * cos_t + y * sin_t - yc = (y * cos_t - x * sin_t) * (a / b) - rr_pix = (b / a) * np.hypot(xc, yc) - tt = np.mod(np.arctan2(yc, xc) + theta, annular_range) - - if radial_step <= 0: - raise ValueError("radial_step must be > 0.") - radial_min = float(radial_min) - - if radial_max is None: - radial_max_eff = float(rr_pix.max()) - else: - radial_max_eff = float(radial_max) - if radial_max_eff <= radial_min + radial_step: - radial_max_eff = radial_min + radial_step - - radial_bins = np.arange(radial_min, radial_max_eff, radial_step, dtype=np.float64) - n_r = radial_bins.size - if n_r < 1: - raise ValueError("No radial bins defined. Check radial_min, radial_max, and radial_step.") - - n_phi = int(num_annular_bins) - if n_phi < 1: - raise ValueError("num_annular_bins must be >= 1.") - phi_step = annular_range / n_phi - - r_bin = (rr_pix - radial_min) / radial_step - t_bin = tt / phi_step - - r0 = np.floor(r_bin).astype(np.int64) - t0 = np.floor(t_bin).astype(np.int64) - dr = (r_bin - r0).astype(np.float64) - dt = (t_bin - t0).astype(np.float64) - - valid = (r0 >= 0) & (r0 < n_r - 1) - t0 = np.clip(t0, 0, n_phi - 1) - - flat_valid = valid.ravel() - r0v = r0.ravel()[flat_valid] - t0v = t0.ravel()[flat_valid] - drv = dr.ravel()[flat_valid] - dtv = dt.ravel()[flat_valid] - - n_bins = n_phi * n_r - idx00 = r0v + n_r * t0v - idx01 = r0v + n_r * ((t0v + 1) % n_phi) - idx10 = (r0v + 1) + n_r * t0v - idx11 = (r0v + 1) + n_r * ((t0v + 1) % n_phi) - - w00 = (1.0 - drv) * (1.0 - dtv) - w01 = (1.0 - drv) * dtv - w10 = drv * (1.0 - dtv) - w11 = drv * dtv - - weights_sum = np.bincount(idx00, weights=w00, minlength=n_bins) - weights_sum += np.bincount(idx01, weights=w01, minlength=n_bins) - weights_sum += np.bincount(idx10, weights=w10, minlength=n_bins) - weights_sum += np.bincount(idx11, weights=w11, minlength=n_bins) - weights_sum = weights_sum.reshape(n_phi, n_r) - - weights_inv = np.zeros_like(weights_sum, dtype=np.float64) - mask_bins = weights_sum > 0 - weights_inv[mask_bins] = 1.0 / weights_sum[mask_bins] - - return { - "flat_valid": flat_valid, - "idx00": idx00, - "idx01": idx01, - "idx10": idx10, - "idx11": idx11, - "w00": w00, - "w01": w01, - "w10": w10, - "w11": w11, - "weights_inv": weights_inv, - "n_phi": n_phi, - "n_r": n_r, - "radial_bins": radial_bins, - "phi_step": phi_step, - "annular_range": annular_range, - "radial_min": radial_min, - "radial_max": radial_min + radial_step * n_r, - "radial_step": radial_step, - } - - -def _apply_polar_mapping_single( - image: NDArray, - mapping: dict[str, Any], - dtype: Any, -) -> NDArray: - data = np.asarray(image, dtype=np.float64) - flat = data.ravel()[mapping["flat_valid"]] - n_bins = mapping["n_phi"] * mapping["n_r"] - - acc = np.bincount(mapping["idx00"], weights=flat * mapping["w00"], minlength=n_bins) - acc += np.bincount(mapping["idx01"], weights=flat * mapping["w01"], minlength=n_bins) - acc += np.bincount(mapping["idx10"], weights=flat * mapping["w10"], minlength=n_bins) - acc += np.bincount(mapping["idx11"], weights=flat * mapping["w11"], minlength=n_bins) - - acc = acc.reshape(mapping["n_phi"], mapping["n_r"]) - acc *= mapping["weights_inv"] - return acc.astype(dtype, copy=False) - diff --git a/src/quantem/diffraction/polar.py b/src/quantem/diffraction/polar.py new file mode 100644 index 000000000..e69de29bb From 9b1ecbf9fb43ac3417e855a6e977577d267f66b8 Mon Sep 17 00:00:00 2001 From: cophus Date: Thu, 4 Dec 2025 13:22:56 -0800 Subject: [PATCH 004/113] initial commit for RDF class --- src/quantem/__init__.py | 1 + src/quantem/core/datastructures/dataset.py | 34 +++ src/quantem/diffraction/__init__.py | 1 + src/quantem/diffraction/polar.py | 326 +++++++++++++++++++++ 4 files changed, 362 insertions(+) diff --git a/src/quantem/__init__.py b/src/quantem/__init__.py index db96bb4f1..36f3c68b8 100644 --- a/src/quantem/__init__.py +++ b/src/quantem/__init__.py @@ -3,4 +3,5 @@ from quantem.core import visualization as visualization from quantem import imaging as imaging +from quantem import diffraction as diffraction from quantem import diffractive_imaging as diffractive_imaging diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index d04c08c5a..317b669b9 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -147,6 +147,11 @@ def sampling(self) -> NDArray: def sampling(self, value: NDArray | tuple | list | float | int) -> None: self._sampling = validate_ndinfo(value, self.ndim, "sampling") + @property + def origin_units(self) -> NDArray: + # Origin expressed in physical units: origin * sampling + return np.asarray(self.origin) * np.asarray(self.sampling) + @property def units(self) -> list[str]: return self._units @@ -289,6 +294,35 @@ def _copy_custom_attributes(self, new_dataset: Self) -> None: # Skip attributes that can't be copied pass + def coords(self, axis: int) -> Any: + """ + Coordinate array for a given axis in pixel units. + + coords(d) = arange(shape[d]) - origin[d] + """ + axis = int(axis) + if axis < 0 or axis >= self.ndim: + raise ValueError(f"axis {axis} out of bounds for ndim={self.ndim}") + + xp = self._xp + n = int(self.shape[axis]) + origin_d = float(np.asarray(self.origin)[axis]) + + return xp.arange(n, dtype=float) - origin_d + + def coords_units(self, axis: int) -> Any: + """ + Coordinate array for a given axis in physical units. + + coords_units(d) = (arange(shape[d]) - origin[d]) * sampling[d] + """ + axis = int(axis) + if axis < 0 or axis >= self.ndim: + raise ValueError(f"axis {axis} out of bounds for ndim={self.ndim}") + + sampling_d = float(np.asarray(self.sampling)[axis]) + return self.coords(axis) * sampling_d + def mean(self, axes: int | tuple[int, ...] | None = None) -> Any: """ Computes and returns mean of the data array. diff --git a/src/quantem/diffraction/__init__.py b/src/quantem/diffraction/__init__.py index e69de29bb..fdbb2b3da 100644 --- a/src/quantem/diffraction/__init__.py +++ b/src/quantem/diffraction/__init__.py @@ -0,0 +1 @@ +from quantem.diffraction.polar import RDF as RDF diff --git a/src/quantem/diffraction/polar.py b/src/quantem/diffraction/polar.py index e69de29bb..7e87eff3f 100644 --- a/src/quantem/diffraction/polar.py +++ b/src/quantem/diffraction/polar.py @@ -0,0 +1,326 @@ +from __future__ import annotations + +from collections.abc import Sequence +from typing import Any, List, Union + +import matplotlib.pyplot as plt +import numpy as np +from numpy.typing import NDArray + +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.datastructures.dataset3d import Dataset3d +from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from quantem.core.datastructures.polar4dstem import Polar4dstem +from quantem.core.io.serialize import AutoSerialize +from quantem.core.utils.validators import ensure_valid_array + + +class RDF(AutoSerialize): + """ + Radial distribution / fluctuation electron microscopy analysis helper. + + This class wraps a 4D-STEM (or 2D diffraction) dataset and stores a + polar-transformed representation as a Polar4dstem instance in `self.polar`. + Analysis methods (radial statistics, PDF, FEM, clustering, etc.) are + provided as stubs for now and will be implemented in future revisions. + """ + + _token = object() + + def __init__( + self, + polar: Polar4dstem, + input_data: Any | None = None, + _token: object | None = None, + ): + if _token is not self._token: + raise RuntimeError( + "Use RadialDistributionFunction.from_data() to instantiate this class." + ) + + super().__init__() + self.polar = polar + self.input_data = input_data + + # Placeholders for analysis results (to be populated by future methods) + self.radial_mean: NDArray | None = None + self.radial_var: NDArray | None = None + self.radial_var_norm: NDArray | None = None + + self.pdf_r: NDArray | None = None + self.pdf_reduced: NDArray | None = None + self.pdf: NDArray | None = None + + self.Sk: NDArray | None = None + self.fk: NDArray | None = None + self.bg: NDArray | None = None + self.offset: float | None = None + self.Sk_mask: NDArray | None = None + + # ------------------------------------------------------------------ + # Constructors + # ------------------------------------------------------------------ + @classmethod + def from_data( + cls, + data: Union[NDArray, Dataset2d, Dataset3d, Dataset4dstem, Polar4dstem], + *, + origin_row: float | None = None, + origin_col: float | None = None, + ellipse_params: tuple[float, float, float] | None = None, + num_annular_bins: int = 180, + radial_min: float = 0.0, + radial_max: float | None = None, + radial_step: float = 1.0, + two_fold_rotation_symmetry: bool = False, + ) -> "RadialDistributionFunction": + """ + Create a RadialDistributionFunction object from various input types. + + Parameters + ---------- + data + Supported inputs: + - 2D numpy array (single diffraction pattern) + - 4D numpy array (scan_y, scan_x, ky, kx) + - Dataset2d + - Dataset4dstem + - Polar4dstem + origin_row, origin_col + Diffraction-space origin (in pixels). If None, defaults to the + central pixel of the diffraction pattern. + Other parameters + Passed through to Dataset4dstem.polar_transform when needed. + """ + # Polar input: use directly + if isinstance(data, Polar4dstem): + polar = data + return cls(polar=polar, input_data=data, _token=cls._token) + + # Dataset4dstem input: polar-transform it + if isinstance(data, Dataset4dstem): + scan_y, scan_x, ny, nx = data.array.shape + if origin_row is None: + origin_row = (ny - 1) / 2.0 + if origin_col is None: + origin_col = (nx - 1) / 2.0 + + polar = data.polar_transform( + origin_row=origin_row, + origin_col=origin_col, + ellipse_params=ellipse_params, + num_annular_bins=num_annular_bins, + radial_min=radial_min, + radial_max=radial_max, + radial_step=radial_step, + two_fold_rotation_symmetry=two_fold_rotation_symmetry, + ) + return cls(polar=polar, input_data=data, _token=cls._token) + + # Dataset2d input: wrap as a trivial 4D-STEM (1x1 scan) then polar-transform + if isinstance(data, Dataset2d): + arr2d = data.array + if arr2d.ndim != 2: + raise ValueError("Dataset2d for RDF must be 2D.") + arr4 = arr2d[None, None, ...] # (1, 1, ky, kx) + + ds4 = Dataset4dstem.from_array( + array=arr4, + name=f"{data.name}_as4dstem" if getattr(data, "name", None) else "rdf_4dstem_from_2d", + origin=np.concatenate( + [np.zeros(2, dtype=float), np.asarray(data.origin, dtype=float)] + ), + sampling=np.concatenate( + [np.ones(2, dtype=float), np.asarray(data.sampling, dtype=float)] + ), + units=["pixels", "pixels"] + list(data.units), + signal_units=data.signal_units, + ) + ny, nx = ds4.array.shape[-2:] + if origin_row is None: + origin_row = (ny - 1) / 2.0 + if origin_col is None: + origin_col = (nx - 1) / 2.0 + + polar = ds4.polar_transform( + origin_row=origin_row, + origin_col=origin_col, + ellipse_params=ellipse_params, + num_annular_bins=num_annular_bins, + radial_min=radial_min, + radial_max=radial_max, + radial_step=radial_step, + two_fold_rotation_symmetry=two_fold_rotation_symmetry, + ) + return cls(polar=polar, input_data=data, _token=cls._token) + + # Dataset3d input: not yet specified how to interpret + if isinstance(data, Dataset3d): + raise NotImplementedError( + "RadialDistributionFunction.from_data does not yet support Dataset3d inputs." + ) + + # Numpy array input + arr = ensure_valid_array(data) + if arr.ndim == 2: + ds2 = Dataset2d.from_array(arr, name="rdf_input_2d") + return cls.from_data( + ds2, + origin_row=origin_row, + origin_col=origin_col, + ellipse_params=ellipse_params, + num_annular_bins=num_annular_bins, + radial_min=radial_min, + radial_max=radial_max, + radial_step=radial_step, + two_fold_rotation_symmetry=two_fold_rotation_symmetry, + ) + elif arr.ndim == 4: + ds4 = Dataset4dstem.from_array(arr, name="rdf_input_4dstem") + return cls.from_data( + ds4, + origin_row=origin_row, + origin_col=origin_col, + ellipse_params=ellipse_params, + num_annular_bins=num_annular_bins, + radial_min=radial_min, + radial_max=radial_max, + radial_step=radial_step, + two_fold_rotation_symmetry=two_fold_rotation_symmetry, + ) + else: + raise ValueError( + "RadialDistributionFunction.from_data only supports 2D or 4D arrays." + ) + + # ------------------------------------------------------------------ + # Convenience accessors + # ------------------------------------------------------------------ + @property + def qq(self) -> Any: + """ + Scattering vector coordinate array along the radial dimension of `self.polar`, + in physical units (using Polar4dstem.sampling and origin). + """ + # Polar4dstem dims: (scan_y, scan_x, phi, r) + # radial axis is 3 + return self.polar.coords_units(3) + + @property + def radial_bins(self) -> Any: + """ + Radial bin centers in pixel units (convenience alias). + """ + return self.polar.coords(3) + + # ------------------------------------------------------------------ + # Analysis method stubs (py4DSTEM-style API) + # ------------------------------------------------------------------ + def calculate_radial_statistics( + self, + mask_realspace: NDArray | None = None, + plot_results_mean: bool = False, + plot_results_var: bool = False, + figsize: tuple[float, float] = (8, 4), + returnval: bool = False, + returnfig: bool = False, + progress_bar: bool = True, + ): + """ + Stub for radial statistics (FEM-style) calculation on the polar data. + + Intended to compute radial mean, variance, and normalized variance + from self.polar. Not implemented yet. + """ + raise NotImplementedError("calculate_radial_statistics is not implemented yet.") + + def plot_radial_mean( + self, + log_x: bool = False, + log_y: bool = False, + figsize: tuple[float, float] = (8, 4), + returnfig: bool = False, + ): + """ + Stub for plotting radial mean intensity vs scattering vector. + """ + raise NotImplementedError("plot_radial_mean is not implemented yet.") + + def plot_radial_var_norm( + self, + figsize: tuple[float, float] = (8, 4), + returnfig: bool = False, + ): + """ + Stub for plotting normalized radial variance vs scattering vector. + """ + raise NotImplementedError("plot_radial_var_norm is not implemented yet.") + + def calculate_pair_dist_function( + self, + k_min: float = 0.05, + k_max: float | None = None, + k_width: float = 0.25, + k_lowpass: float | None = None, + k_highpass: float | None = None, + r_min: float = 0.0, + r_max: float = 20.0, + r_step: float = 0.02, + damp_origin_fluctuations: bool = True, + enforce_positivity: bool = True, + density: float | None = None, + plot_background_fits: bool = False, + plot_sf_estimate: bool = False, + plot_reduced_pdf: bool = True, + plot_pdf: bool = False, + figsize: tuple[float, float] = (8, 4), + maxfev: int | None = None, + returnval: bool = False, + returnfig: bool = False, + ): + """ + Stub for pair distribution function (PDF) calculation from radial statistics. + + Intended to estimate S(k), background, and transform to real-space g(r)/G(r). + """ + raise NotImplementedError("calculate_pair_dist_function is not implemented yet.") + + def plot_background_fits( + self, + figsize: tuple[float, float] = (8, 4), + returnfig: bool = False, + ): + """ + Stub for plotting background fit vs radial mean intensity. + """ + raise NotImplementedError("plot_background_fits is not implemented yet.") + + def plot_sf_estimate( + self, + figsize: tuple[float, float] = (8, 4), + returnfig: bool = False, + ): + """ + Stub for plotting reduced structure factor S(k). + """ + raise NotImplementedError("plot_sf_estimate is not implemented yet.") + + def plot_reduced_pdf( + self, + figsize: tuple[float, float] = (8, 4), + returnfig: bool = False, + ): + """ + Stub for plotting reduced PDF g(r). + """ + raise NotImplementedError("plot_reduced_pdf is not implemented yet.") + + def plot_pdf( + self, + figsize: tuple[float, float] = (8, 4), + returnfig: bool = False, + ): + """ + Stub for plotting full PDF G(r). + """ + raise NotImplementedError("plot_pdf is not implemented yet.") From 1d0ec612daf018a45bbfee04ad640c8f0eb8c902 Mon Sep 17 00:00:00 2001 From: cophus Date: Thu, 1 Jan 2026 12:42:17 -0800 Subject: [PATCH 005/113] initial commit for StrainMap --- .../core/datastructures/dataset4dstem.py | 2 +- src/quantem/core/utils/imaging_utils.py | 88 +++++++++ src/quantem/diffraction/__init__.py | 1 + src/quantem/diffraction/strain.py | 179 ++++++++++++++++++ 4 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 src/quantem/diffraction/strain.py diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index 2d4c860eb..eac15ac5c 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -74,7 +74,7 @@ def __init__( _token : object | None, optional Token to prevent direct instantiation, by default None """ - mdata_keys_4dstem = ["r_to_q_rotation_cw_deg", "ellipticity"] + mdata_keys_4dstem = ["q_to_r_rotation_cw_deg", 'q_transpose', "ellipticity"] for k in mdata_keys_4dstem: if k not in metadata.keys(): metadata[k] = None diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index c72fb9c31..3d86000d3 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -7,6 +7,7 @@ import torch from numpy.typing import NDArray from scipy.ndimage import gaussian_filter +from scipy.ndimage import map_coordinates from quantem.core.utils.utils import generate_batches @@ -546,3 +547,90 @@ def fourier_cropping( result[-h2:, -w2:] = corner_centered_array[-h2:, -w2:] return result + + +def rotate_image( + im, + rotation_deg: float, + origin: tuple[float, float] | None = None, + clockwise: bool = True, + interpolation: str = "bilinear", + mode: str = "constant", + cval: float = 0.0, +): + """Rotate an array about a pixel origin using bilinear/bicubic interpolation. + + Parameters + ---------- + im + Input array; last two dimensions are treated as (H, W). Any leading + dimensions are treated as batch and rotated independently. + rotation_deg + Rotation angle in degrees. Interpreted as clockwise if clockwise=True, + otherwise counterclockwise. + origin + Rotation origin (row, col) in pixel coordinates. If None, uses (H//2, W//2). + clockwise + If True, interpret rotation_deg as clockwise; if False, as counterclockwise. + interpolation + "bilinear" (order=1) or "bicubic" (order=3). + mode, cval + Boundary handling passed to scipy.ndimage.map_coordinates. + + Returns + ------- + out + Rotated array with the same shape as `im`. + """ + im = np.asarray(im) + if im.ndim < 2: + raise ValueError("im must have at least 2 dimensions") + + H, W = im.shape[-2], im.shape[-1] + if origin is None: + r0 = float(H // 2) + c0 = float(W // 2) + else: + r0 = float(origin[0]) + c0 = float(origin[1]) + + interp = str(interpolation).lower() + if interp in {"bilinear", "linear"}: + order = 1 + elif interp in {"bicubic", "cubic"}: + order = 3 + else: + raise ValueError("interpolation must be 'bilinear' or 'bicubic'") + + theta = float(np.deg2rad(rotation_deg)) + if clockwise: + theta = -theta + + ct = float(np.cos(theta)) + st = float(np.sin(theta)) + + r_out, c_out = np.meshgrid( + np.arange(H, dtype=np.float64), + np.arange(W, dtype=np.float64), + indexing="ij", + ) + + c_rel = c_out - c0 + r_rel = r_out - r0 + + c_in = ct * c_rel + st * r_rel + c0 + r_in = -st * c_rel + ct * r_rel + r0 + + coords = np.vstack((r_in.ravel(), c_in.ravel())) + + if im.ndim == 2: + out = map_coordinates(im, coords, order=order, mode=mode, cval=cval) + return out.reshape(H, W) + + prefix = im.shape[:-2] + n = int(np.prod(prefix)) if prefix else 1 + im_flat = im.reshape(n, H, W) + out_flat = np.empty((n, H * W), dtype=np.result_type(im_flat.dtype, np.float64)) + for i in range(n): + out_flat[i] = map_coordinates(im_flat[i], coords, order=order, mode=mode, cval=cval) + return out_flat.reshape(*prefix, H, W) diff --git a/src/quantem/diffraction/__init__.py b/src/quantem/diffraction/__init__.py index fdbb2b3da..6d0e4202a 100644 --- a/src/quantem/diffraction/__init__.py +++ b/src/quantem/diffraction/__init__.py @@ -1 +1,2 @@ from quantem.diffraction.polar import RDF as RDF +from quantem.diffraction.strain import StrainMap as StrainMap diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py new file mode 100644 index 000000000..55c57d9dc --- /dev/null +++ b/src/quantem/diffraction/strain.py @@ -0,0 +1,179 @@ +from __future__ import annotations + +from typing import Any + +import numpy as np +from numpy.typing import NDArray + +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from quantem.core.io.serialize import AutoSerialize +from quantem.core.utils.imaging_utils import rotate_image +from quantem.core.utils.utils import electron_wavelength_angstrom +from quantem.core.utils.validators import ensure_valid_array +from quantem.core.visualization import ScalebarConfig, show_2d + + +class StrainMap(AutoSerialize): + """ + Nanobeam strain mapping + """ + + _token = object() + + def __init__( + self, + dataset: Dataset4dstem, + input_data: Any | None = None, + _token: object | None = None, + ): + if _token is not self._token: + raise RuntimeError("Use StrainMap.from_data() to instantiate this class.") + super().__init__() + self.dataset = dataset + self.input_data = input_data + self.strain = None + self.metadata: dict[str, Any] = {} + self.transform: Dataset2d | None = None + self.transform_rotated: Dataset2d | None = None + + @classmethod + def from_data(cls, data: NDArray | Dataset4dstem, *, name: str = "strain_map") -> StrainMap: + if isinstance(data, Dataset4dstem): + return cls(dataset=data, input_data=data, _token=cls._token) + + arr = ensure_valid_array(data) + if arr.ndim != 4: + raise ValueError( + "StrainMap.from_data expects a 4D array with shape (scan_r, scan_c, dp_r, dp_c)." + ) + + ds4 = Dataset4dstem.from_array(arr, name=name) + return cls(dataset=ds4, input_data=data, _token=cls._token) + + def preprocess( + self, + mode: str = "linear", + plot_transform: bool = True, + cropping_factor: float = 0.5, + **plot_kwargs: Any, + ) -> StrainMap: + if self.dataset.units[2] == "A": + qrow_sampling_ang = float(self.dataset.sampling[2]) + elif self.dataset.units[2] == "mrad": + wavelength = float(electron_wavelength_angstrom(float(self.dataset.metadata["energy"]))) + qrow_sampling_ang = float(self.dataset.sampling[2]) / 1000.0 / wavelength + else: + raise ValueError(f"unrecognized diffraction-space unit for axis 2: {self.dataset.units[2]}") + + if self.dataset.units[3] == "A": + qcol_sampling_ang = float(self.dataset.sampling[3]) + elif self.dataset.units[3] == "mrad": + wavelength = float(electron_wavelength_angstrom(float(self.dataset.metadata["energy"]))) + qcol_sampling_ang = float(self.dataset.sampling[3]) / 1000.0 / wavelength + else: + raise ValueError(f"unrecognized diffraction-space unit for axis 3: {self.dataset.units[3]}") + + self.metadata["sampling_real"] = np.array( + ( + 1.0 / (qrow_sampling_ang * float(self.dataset.shape[2])), + 1.0 / (qcol_sampling_ang * float(self.dataset.shape[3])), + ), + dtype=float, + ) + + if mode == "linear": + im = np.mean(np.abs(np.fft.fft2(self.dataset.array)), axis=(0, 1)) + elif mode == "log": + im = np.mean(np.abs(np.fft.fft2(np.log(self.dataset.array + 1.0))), axis=(0, 1)) + else: + raise ValueError("mode must be 'linear' or 'log'") + + self.transform = Dataset2d.from_array( + np.fft.fftshift(im), + origin=(im.shape[0] // 2, im.shape[1] // 2), + sampling=(1.0, 1.0), + units=("A", "A"), + signal_units="intensity", + ) + + self.transform_rotated = Dataset2d.from_array( + rotate_image( + self.transform.array, + float(self.dataset.metadata["q_to_r_rotation_cw_deg"]), + clockwise=True, + ), + origin=(im.shape[0] // 2, im.shape[1] // 2), + sampling=(1.0, 1.0), + units=("A", "A"), + signal_units="intensity", + ) + + if plot_transform: + self.plot_transform(cropping_factor=cropping_factor, **plot_kwargs) + + return self + + + + + + + + + + def plot_transform( + self, + cropping_factor: float = 0.25, + **plot_kwargs: Any + ): + if self.transform is None or self.transform_rotated is None: + raise ValueError("Run preprocess() first to compute transform images.") + + defaults = dict( + vmax=1.0, + title=("Original Transform", "Rotated Transform"), + scalebar=ScalebarConfig( + sampling=self.metadata["sampling_real"], + units=r"$\mathrm{\AA}$", + length=2, + ), + ) + defaults.update(plot_kwargs) + + fig, ax = show_2d([self.transform, self.transform_rotated], **defaults) + + axes = np.atleast_1d(ax) + for a in axes: + _apply_center_crop_limits(a, self.transform.shape, cropping_factor) + + return fig, ax + + + + + + + +def _apply_center_crop_limits(ax, shape: tuple[int, int], cropping_factor: float) -> None: + cf = float(cropping_factor) + if cf >= 1.0: + return + if not (0.0 < cf <= 1.0): + raise ValueError("cropping_factor must be in (0, 1].") + + H, W = int(shape[0]), int(shape[1]) + r0 = (H - 1) / 2.0 + c0 = (W - 1) / 2.0 + half_h = 0.5 * cf * H + half_w = 0.5 * cf * W + + ax.set_xlim(c0 - half_w, c0 + half_w) + + y0, y1 = ax.get_ylim() + if y0 > y1: + ax.set_ylim(r0 + half_h, r0 - half_h) + else: + ax.set_ylim(r0 - half_h, r0 + half_h) + + From 5ace6c73d0ac44268063d3bb36009773cddb6a9c Mon Sep 17 00:00:00 2001 From: cophus Date: Thu, 1 Jan 2026 19:32:20 -0800 Subject: [PATCH 006/113] all basic functions implemented --- .../core/datastructures/dataset4dstem.py | 2 +- src/quantem/core/utils/imaging_utils.py | 27 +- src/quantem/diffraction/strain.py | 794 +++++++++++++++++- 3 files changed, 754 insertions(+), 69 deletions(-) diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index eac15ac5c..dd92b9146 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -74,7 +74,7 @@ def __init__( _token : object | None, optional Token to prevent direct instantiation, by default None """ - mdata_keys_4dstem = ["q_to_r_rotation_cw_deg", 'q_transpose', "ellipticity"] + mdata_keys_4dstem = ["q_to_r_rotation_ccw_deg", 'q_transpose', "ellipticity"] for k in mdata_keys_4dstem: if k not in metadata.keys(): metadata[k] = None diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 3d86000d3..8a07b3c17 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -558,30 +558,7 @@ def rotate_image( mode: str = "constant", cval: float = 0.0, ): - """Rotate an array about a pixel origin using bilinear/bicubic interpolation. - - Parameters - ---------- - im - Input array; last two dimensions are treated as (H, W). Any leading - dimensions are treated as batch and rotated independently. - rotation_deg - Rotation angle in degrees. Interpreted as clockwise if clockwise=True, - otherwise counterclockwise. - origin - Rotation origin (row, col) in pixel coordinates. If None, uses (H//2, W//2). - clockwise - If True, interpret rotation_deg as clockwise; if False, as counterclockwise. - interpolation - "bilinear" (order=1) or "bicubic" (order=3). - mode, cval - Boundary handling passed to scipy.ndimage.map_coordinates. - - Returns - ------- - out - Rotated array with the same shape as `im`. - """ + """Rotate an array about a pixel origin using bilinear/bicubic interpolation.""" im = np.asarray(im) if im.ndim < 2: raise ValueError("im must have at least 2 dimensions") @@ -603,7 +580,7 @@ def rotate_image( raise ValueError("interpolation must be 'bilinear' or 'bicubic'") theta = float(np.deg2rad(rotation_deg)) - if clockwise: + if not clockwise: theta = -theta ct = float(np.cos(theta)) diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index 55c57d9dc..b5549ff7e 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -2,23 +2,23 @@ from typing import Any +import matplotlib.pyplot as plt import numpy as np from numpy.typing import NDArray +from scipy.ndimage import distance_transform_edt from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.datastructures.dataset3d import Dataset3d +from quantem.core.datastructures.dataset4d import Dataset4d from quantem.core.datastructures.dataset4dstem import Dataset4dstem from quantem.core.io.serialize import AutoSerialize -from quantem.core.utils.imaging_utils import rotate_image +from quantem.core.utils.imaging_utils import dft_upsample, rotate_image from quantem.core.utils.utils import electron_wavelength_angstrom from quantem.core.utils.validators import ensure_valid_array from quantem.core.visualization import ScalebarConfig, show_2d class StrainMap(AutoSerialize): - """ - Nanobeam strain mapping - """ - _token = object() def __init__( @@ -36,9 +36,13 @@ def __init__( self.metadata: dict[str, Any] = {} self.transform: Dataset2d | None = None self.transform_rotated: Dataset2d | None = None + self.u: NDArray | None = None + self.v: NDArray | None = None + self.mask_diffraction = np.ones(self.dataset.array.shape[2:]) + self.mask_diffraction_inv = np.zeros(self.dataset.array.shape[2:]) @classmethod - def from_data(cls, data: NDArray | Dataset4dstem, *, name: str = "strain_map") -> StrainMap: + def from_data(cls, data: NDArray | Dataset4dstem, *, name: str = "strain_map") -> "StrainMap": if isinstance(data, Dataset4dstem): return cls(dataset=data, input_data=data, _token=cls._token) @@ -51,28 +55,81 @@ def from_data(cls, data: NDArray | Dataset4dstem, *, name: str = "strain_map") - ds4 = Dataset4dstem.from_array(arr, name=name) return cls(dataset=ds4, input_data=data, _token=cls._token) + + def diffraction_mask( + self, + threshold = None, + edge_blend = 32.0, + plot_mask = True, + figsize = (8,4), + ): + dp_mean = np.mean(self.dataset.array,axis=(0,1)) + mask_init = dp_mean < threshold + mask_init[:,0] = True + mask_init[0,:] = True + mask_init[:,-1] = True + mask_init[-1,:] = True + + self.mask_diffraction = np.sin( + np.clip( + distance_transform_edt(np.logical_not(mask_init)) / edge_blend, + 0.0, + 1.0, + )*np.pi/2, + )**2 + # int_edge = np.sum(dp_mean*self.mask_diffraction) / np.sum(self.mask_diffraction) + int_edge = np.min(dp_mean[self.mask_diffraction>0.99]) + self.mask_diffraction_inv = (1 - self.mask_diffraction) * int_edge + + if plot_mask: + fig,ax = plt.subplots(1,2,figsize=figsize) + ax[0].imshow( + np.log(np.maximum(dp_mean,np.min(dp_mean[dp_mean>0]))), + cmap = 'gray', + ) + ax[1].imshow( + np.log( + dp_mean*self.mask_diffraction + \ + self.mask_diffraction_inv, + ), + cmap = 'gray', + ) + + return self + + def preprocess( self, mode: str = "linear", + q_to_r_rotation_ccw_deg: float | None = None, + q_transpose: bool | None = None, plot_transform: bool = True, - cropping_factor: float = 0.5, + cropping_factor: float = 0.25, **plot_kwargs: Any, - ) -> StrainMap: - if self.dataset.units[2] == "A": + ) -> "StrainMap": + + self.metadata["mode"] = mode + + qrow_unit = str(self.dataset.units[2]) + qcol_unit = str(self.dataset.units[3]) + + if qrow_unit in {"A", "Å"}: qrow_sampling_ang = float(self.dataset.sampling[2]) - elif self.dataset.units[2] == "mrad": + elif qrow_unit == "mrad": wavelength = float(electron_wavelength_angstrom(float(self.dataset.metadata["energy"]))) qrow_sampling_ang = float(self.dataset.sampling[2]) / 1000.0 / wavelength else: - raise ValueError(f"unrecognized diffraction-space unit for axis 2: {self.dataset.units[2]}") + qrow_sampling_ang = 1.0 + qrow_unit = "pixels" - if self.dataset.units[3] == "A": + if qcol_unit in {"A", "Å"}: qcol_sampling_ang = float(self.dataset.sampling[3]) - elif self.dataset.units[3] == "mrad": + elif qcol_unit == "mrad": wavelength = float(electron_wavelength_angstrom(float(self.dataset.metadata["energy"]))) qcol_sampling_ang = float(self.dataset.sampling[3]) / 1000.0 / wavelength else: - raise ValueError(f"unrecognized diffraction-space unit for axis 3: {self.dataset.units[3]}") + qcol_sampling_ang = 1.0 + qcol_unit = "pixels" self.metadata["sampling_real"] = np.array( ( @@ -82,30 +139,81 @@ def preprocess( dtype=float, ) - if mode == "linear": - im = np.mean(np.abs(np.fft.fft2(self.dataset.array)), axis=(0, 1)) - elif mode == "log": - im = np.mean(np.abs(np.fft.fft2(np.log(self.dataset.array + 1.0))), axis=(0, 1)) + if qrow_unit == "pixels" and qcol_unit == "pixels": + self.metadata["real_units"] = "1/pixels" + else: + self.metadata["real_units"] = r"$\mathrm{\AA}$" + + if q_to_r_rotation_ccw_deg is None or q_transpose is None: + parent_rot = self.dataset.metadata.get("q_to_r_rotation_ccw_deg", None) + parent_tr = self.dataset.metadata.get("q_transpose", None) + if q_to_r_rotation_ccw_deg is None and parent_rot is not None: + q_to_r_rotation_ccw_deg = float(parent_rot) + if q_transpose is None and parent_tr is not None: + q_transpose = bool(parent_tr) + if (parent_rot is not None or parent_tr is not None) and ( + q_to_r_rotation_ccw_deg is not None or q_transpose is not None + ): + import warnings + + warnings.warn( + f"StrainMap.preprocess: using parent Dataset4dstem metadata " + f"(q_to_r_rotation_ccw_deg={float(q_to_r_rotation_ccw_deg or 0.0)}, " + f"q_transpose={bool(q_transpose or False)}).", + UserWarning, + ) + + if q_to_r_rotation_ccw_deg is None or q_transpose is None: + import warnings + + q_to_r_rotation_ccw_deg = ( + 0.0 if q_to_r_rotation_ccw_deg is None else float(q_to_r_rotation_ccw_deg) + ) + q_transpose = False if q_transpose is None else bool(q_transpose) + warnings.warn( + "StrainMap.preprocess: setting q_to_r_rotation_ccw_deg=0.0 and q_transpose=False.", + UserWarning, + ) + + self.metadata["q_to_r_rotation_ccw_deg"] = float(q_to_r_rotation_ccw_deg) + self.metadata["q_transpose"] = bool(q_transpose) + + if self.metadata["mode"] == "linear": + im = np.mean(np.abs(np.fft.fft2( + self.dataset.array * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + )), axis=(0, 1)) + elif self.metadata["mode"] == "log": + im = np.mean(np.abs(np.fft.fft2(np.log( + self.dataset.array * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + ))), axis=(0, 1)) else: raise ValueError("mode must be 'linear' or 'log'") + im = np.fft.fftshift(im) + self.transform = Dataset2d.from_array( - np.fft.fftshift(im), + im, origin=(im.shape[0] // 2, im.shape[1] // 2), sampling=(1.0, 1.0), - units=("A", "A"), + units=(qrow_unit, qcol_unit), signal_units="intensity", ) + im_plot = self.transform.array + if bool(self.metadata["q_transpose"]): + im_plot = im_plot.T + self.transform_rotated = Dataset2d.from_array( rotate_image( - self.transform.array, - float(self.dataset.metadata["q_to_r_rotation_cw_deg"]), - clockwise=True, + im_plot, + float(self.metadata["q_to_r_rotation_ccw_deg"]), + clockwise=False, ), origin=(im.shape[0] // 2, im.shape[1] // 2), sampling=(1.0, 1.0), - units=("A", "A"), + units=(self.metadata["real_units"], self.metadata["real_units"]), signal_units="intensity", ) @@ -114,48 +222,419 @@ def preprocess( return self - - - - - - - - def plot_transform( - self, - cropping_factor: float = 0.25, - **plot_kwargs: Any + self, + cropping_factor: float = 0.25, + scalebar_fraction: float = 0.25, + **plot_kwargs: Any, ): if self.transform is None or self.transform_rotated is None: raise ValueError("Run preprocess() first to compute transform images.") + sampling = float(np.mean(self.metadata["sampling_real"])) + units = str(self.metadata.get("real_units", r"$\mathrm{\AA}$")) + + W = int(self.transform.shape[1]) + view_w_px = float(W) * float(cropping_factor) + target_units = float(scalebar_fraction) * view_w_px * sampling + sb_len = _nice_length_units(target_units) + defaults = dict( vmax=1.0, title=("Original Transform", "Rotated Transform"), scalebar=ScalebarConfig( - sampling=self.metadata["sampling_real"], - units=r"$\mathrm{\AA}$", - length=2, + sampling=sampling, + units=units, + length=sb_len if sb_len > 0 else None, ), ) defaults.update(plot_kwargs) fig, ax = show_2d([self.transform, self.transform_rotated], **defaults) - axes = np.atleast_1d(ax) - for a in axes: + for a in _flatten_axes(ax): _apply_center_crop_limits(a, self.transform.shape, cropping_factor) return fig, ax + def choose_lattice_vector( + self, + *, + u: tuple[float, float] | NDArray, + v: tuple[float, float] | NDArray, + define_in_rotated: bool = False, + refine_subpixel: bool = True, + refine_subpixel_dft: bool = False, + refine_radius_px: float = 2.0, + refine_log: bool = False, + upsample: int = 16, + plot: bool = True, + cropping_factor: float = 0.25, + **plot_kwargs: Any, + ) -> "StrainMap": + if self.transform is None or self.transform_rotated is None: + raise ValueError("Run preprocess() first to compute transform images.") + + u_rc = np.asarray(u, dtype=float).reshape(2) + v_rc = np.asarray(v, dtype=float).reshape(2) + + rot_ccw = float(self.metadata.get("q_to_r_rotation_ccw_deg", 0.0)) + q_transpose = bool(self.metadata.get("q_transpose", False)) + + if define_in_rotated: + u_rc = _display_vec_to_raw(u_rc, rotation_ccw_deg=rot_ccw, transpose=q_transpose) + v_rc = _display_vec_to_raw(v_rc, rotation_ccw_deg=rot_ccw, transpose=q_transpose) + + if refine_subpixel_dft: + refine_subpixel = True + + if refine_subpixel: + u_rc, v_rc = _refine_lattice_vectors( + self.transform.array, + u_rc=u_rc, + v_rc=v_rc, + radius_px=float(refine_radius_px), + log_fit=bool(refine_log), + refine_dft=bool(refine_subpixel_dft), + upsample=int(upsample), + ) + + self.u = u_rc + self.v = v_rc + self.metadata["lattice_u_rc"] = self.u.copy() + self.metadata["lattice_v_rc"] = self.v.copy() + + if plot: + fig, ax = self.plot_transform(cropping_factor=cropping_factor, **plot_kwargs) + _overlay_lattice_vectors( + ax=ax, + shape=self.transform.shape, + u_rc=self.u, + v_rc=self.v, + rot_ccw_deg=rot_ccw, + q_transpose=q_transpose, + ) + return self + + return self + + + def fit_lattice_vectors( + self, + refine_subpixel: bool = True, + refine_subpixel_dft: bool = False, + refine_radius_px: float = 2.0, + upsample: int = 16, + refine_log: bool = False, + progressbar: bool = True, + ) -> "StrainMap": + from quantem.core.datastructures.dataset3d import Dataset3d + + if self.u is None or self.v is None: + raise ValueError("Run choose_lattice_vector() first to set initial lattice vectors (self.u, self.v).") + + if refine_subpixel_dft: + refine_subpixel = True + + scan_r = self.dataset.shape[0] + scan_c = self.dataset.shape[1] + self.u_fit = Dataset3d.from_shape( + (scan_r, scan_c, 2), + name="u_fits", + signal_units="pixels", + ) + self.v_fit = Dataset3d.from_shape( + (scan_r, scan_c, 2), + name="v_fits", + signal_units="pixels", + ) + + mode = str(self.metadata.get("mode", "linear")).lower() + + it = np.ndindex(scan_r, scan_c) + if progressbar: + try: + from tqdm.auto import tqdm # type: ignore + + it = tqdm(it, total=scan_r * scan_c, desc="fit_lattice_vectors", leave=True) + except Exception: + pass + + u0 = np.asarray(self.u, dtype=float).reshape(2) + v0 = np.asarray(self.v, dtype=float).reshape(2) + + for r, c in it: + dp = self.dataset.array[r, c]*self.mask_diffraction + \ + self.mask_diffraction_inv + + if mode == "linear": + im = np.fft.fftshift(np.abs(np.fft.fft2(dp))) + elif mode == "log": + int_range = np.max(dp) - np.min(dp) + im = np.fft.fftshift(np.abs(np.fft.fft2(np.log(dp + int_range*0.01)))) + else: + raise ValueError("metadata['mode'] must be 'linear' or 'log'") + + if refine_subpixel: + u_rc, v_rc = _refine_lattice_vectors( + im, + u_rc=u0, + v_rc=v0, + radius_px=float(refine_radius_px), + log_fit=bool(refine_log), + refine_dft=bool(refine_subpixel_dft), + upsample=int(upsample), + ) + else: + u_rc = u0 + v_rc = v0 + + self.u_fit.array[r, c, :] = u_rc + self.v_fit.array[r, c, :] = v_rc + + self.metadata["fit_refine_subpixel"] = bool(refine_subpixel) + self.metadata["fit_refine_subpixel_dft"] = bool(refine_subpixel_dft) + self.metadata["fit_refine_radius_px"] = float(refine_radius_px) + self.metadata["fit_refine_log"] = bool(refine_log) + self.metadata["fit_upsample"] = int(upsample) + + return self + + + def plot_lattice_vectors( + self, + subtract_mean: bool = True, + scalebar: bool = False, + **plot_kwargs: Any, + ): + if getattr(self, "u_fit", None) is None or getattr(self, "v_fit", None) is None: + raise ValueError("Run fit_lattice_vectors() first to compute u_fit and v_fit.") + + im0 = self.u_fit.array[:,:,0] + im1 = self.u_fit.array[:,:,1] + im2 = self.v_fit.array[:,:,0] + im3 = self.v_fit.array[:,:,1] + + if subtract_mean: + im0 = im0 - float(np.nanmean(im0)) + im1 = im1 - float(np.nanmean(im1)) + im2 = im2 - float(np.nanmean(im2)) + im3 = im3 - float(np.nanmean(im3)) + + vlim = float(np.nanmax(np.abs(np.stack([im0, im1, im2, im3], axis=0)))) + vmin = -vlim + vmax = vlim + + defaults: dict[str, Any] = dict( + title=("u_r", "u_c", "v_r", "v_c"), + vmin=vmin, + vmax=vmax, + ) + + if scalebar: + s0 = float(self.dataset.sampling[0]) if len(self.dataset.sampling) > 0 else 1.0 + s1 = float(self.dataset.sampling[1]) if len(self.dataset.sampling) > 1 else s0 + sampling_scan = float(np.mean([s0, s1])) + units_scan = str(self.dataset.units[0]) if len(self.dataset.units) > 0 else "pixels" + defaults["scalebar"] = ScalebarConfig(sampling=sampling_scan, units=units_scan) + + defaults.update(plot_kwargs) + + fig, ax = show_2d([im0, im1, im2, im3], **defaults) + return fig, ax + + + def fit_strain( + self, + mask_reference = None, + plot_strain = True, + ): + if self.u_fit is None or self.v_fit is None: + raise ValueError("Run fit_lattice_vectors() first to compute u_fit and v_fit.") + u_fit = self.u_fit.array + v_fit = self.v_fit.array + scan_r, scan_c = int(u_fit.shape[0]), int(u_fit.shape[1]) + if mask_reference is None: + self.u_ref = np.median(u_fit.reshape(-1, 2), axis=0) + self.v_ref = np.median(v_fit.reshape(-1, 2), axis=0) + else: + m = np.asarray(mask_reference, dtype=bool) + self.u_ref = np.array( + ( + np.median(u_fit[m, 0]), + np.median(u_fit[m, 1]), + ), + dtype=float, + ) + self.v_ref = np.array( + ( + np.median(v_fit[m, 0]), + np.median(v_fit[m, 1]), + ), + dtype=float, + ) + Uref = np.stack((self.u_ref, self.v_ref), axis=1).astype(float) + det = float(np.linalg.det(Uref)) + if not np.isfinite(det) or abs(det) < 1e-12: + Uref_inv = np.linalg.pinv(Uref) + else: + Uref_inv = np.linalg.inv(Uref) + # init + self.strain_trans = Dataset4d.from_shape( + (scan_r, scan_c, 2, 2), + name="transformation matrix", + signal_units="fractional", + ) -def _apply_center_crop_limits(ax, shape: tuple[int, int], cropping_factor: float) -> None: + # Loop over probe positions + for r in range(scan_r): + for c in range(scan_c): + U = np.stack((u_fit[r, c, :], v_fit[r, c, :]), axis=1) + self.strain_trans.array[r, c, :, :] = U @ Uref_inv + + # get strains in orthogonal directions + self.strain_raw_err = Dataset2d.from_array( + self.strain_trans.array[:, :, 0, 0] - 1, + name="strain err", + signal_units="fractional", + ) + self.strain_raw_ecc = Dataset2d.from_array( + self.strain_trans.array[:, :, 1, 1] - 1, + name="strain ecc", + signal_units="fractional", + ) + self.strain_raw_erc = Dataset2d.from_array( + self.strain_trans.array[:, :, 1, 0]*0.5 + self.strain_trans.array[:, :, 0, 1]*0.5, + name="strain erc", + signal_units="fractional", + ) + self.strain_rotation = Dataset2d.from_array( + self.strain_trans.array[:, :, 1, 0]*-0.5 + self.strain_trans.array[:, :, 0, 1]*0.5, + name="strain rotation", + signal_units="fractional", + ) + + return self + + + def plot_strain( + self, + ref_u_v=(1.0, 0.0), + ref_angle_degrees=None, + strain_range_percent=(-3.0, 3.0), + rotation_range_degrees=(-2.0, 2.0), + plot_rotation=True, + cmap_strain="PiYG_r", + cmap_rotation="PiYG_r", + layout="horizontal", + figsize=(6, 6), + ): + import matplotlib.pyplot as plt + + if ref_angle_degrees is None: + ref_vec = self.u_ref * float(ref_u_v[0]) + self.v_ref * float(ref_u_v[1]) + ref_angle = float(np.arctan2(ref_vec[1], ref_vec[0])) + else: + ref_angle = float(np.deg2rad(ref_angle_degrees)) + + angle = ref_angle + np.deg2rad(self.metadata["q_to_r_rotation_ccw_deg"]) + print(np.round(np.rad2deg(angle),2)) + + c = float(np.cos(angle)) + s = float(np.sin(angle)) + + err = self.strain_raw_err.array + ecc = self.strain_raw_ecc.array + erc = self.strain_raw_erc.array + + euu = err * (c * c) + 2.0 * erc * (c * s) + ecc * (s * s) + evv = err * (s * s) - 2.0 * erc * (c * s) + ecc * (c * c) + euv = (ecc - err) * (c * s) + erc * (c * c - s * s) + + self.strain_euu = self.strain_raw_err.copy() + self.strain_evv = self.strain_raw_ecc.copy() + self.strain_euv = self.strain_raw_erc.copy() + self.strain_euu.array[...] = euu + self.strain_evv.array[...] = evv + self.strain_euv.array[...] = euv + + if layout == "horizontal": + if plot_rotation: + fig, ax = plt.subplots(1, 4, figsize=figsize) + + ax[0].imshow( + self.strain_euu.array * 100, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cmap_strain, + ) + ax[1].imshow( + self.strain_evv.array * 100, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cmap_strain, + ) + ax[2].imshow( + self.strain_euv.array * 100, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cmap_strain, + ) + ax[3].imshow( + np.rad2deg(self.strain_rotation.array), + vmin=rotation_range_degrees[0], + vmax=rotation_range_degrees[1], + cmap=cmap_rotation, + ) + return fig, ax + + fig, ax = plt.subplots(1, 3, figsize=figsize) + ax[0].imshow( + self.strain_euu.array * 100, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cmap_strain, + ) + ax[1].imshow( + self.strain_evv.array * 100, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cmap_strain, + ) + ax[2].imshow( + self.strain_euv.array * 100, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cmap_strain, + ) + return fig, ax + + raise ValueError("layout must be 'horizontal'") + + +def _nice_length_units(target: float) -> float: + target = float(target) + if not np.isfinite(target) or target <= 0: + return 0.0 + exp = np.floor(np.log10(target)) + base = target / (10.0**exp) + if base < 1.5: + nice = 1.0 + elif base < 3.5: + nice = 2.0 + elif base < 7.5: + nice = 5.0 + else: + nice = 10.0 + return float(nice * (10.0**exp)) + + +def _apply_center_crop_limits(ax: Any, shape: tuple[int, int], cropping_factor: float) -> None: cf = float(cropping_factor) if cf >= 1.0: return @@ -163,8 +642,8 @@ def _apply_center_crop_limits(ax, shape: tuple[int, int], cropping_factor: float raise ValueError("cropping_factor must be in (0, 1].") H, W = int(shape[0]), int(shape[1]) - r0 = (H - 1) / 2.0 - c0 = (W - 1) / 2.0 + r0 = float(H // 2) + c0 = float(W // 2) half_h = 0.5 * cf * H half_w = 0.5 * cf * W @@ -177,3 +656,232 @@ def _apply_center_crop_limits(ax, shape: tuple[int, int], cropping_factor: float ax.set_ylim(r0 - half_h, r0 + half_h) +def _flatten_axes(ax: Any) -> list[Any]: + if isinstance(ax, np.ndarray): + return list(ax.ravel()) + if isinstance(ax, (list, tuple)): + out: list[Any] = [] + for a in ax: + out.extend(_flatten_axes(a)) + return out + return [ax] + + +def _raw_vec_to_display(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: bool) -> NDArray: + v = np.asarray(vec_rc, dtype=float).reshape(2) + dr, dc = float(v[0]), float(v[1]) + + if transpose: + dr, dc = dc, dr + + theta = float(np.deg2rad(rotation_ccw_deg)) + ct = float(np.cos(theta)) + st = float(np.sin(theta)) + + dr2 = ct * dr - st * dc + dc2 = st * dr + ct * dc + return np.array((dr2, dc2), dtype=float) + + +def _display_vec_to_raw(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: bool) -> NDArray: + v = np.asarray(vec_rc, dtype=float).reshape(2) + dr, dc = float(v[0]), float(v[1]) + + theta = float(np.deg2rad(rotation_ccw_deg)) + ct = float(np.cos(theta)) + st = float(np.sin(theta)) + + dr2 = ct * dr + st * dc + dc2 = -st * dr + ct * dc + + if transpose: + dr2, dc2 = dc2, dr2 + + return np.array((dr2, dc2), dtype=float) + + +def _plot_lattice_vectors(ax: Any, center_rc: tuple[float, float], u_rc: NDArray, v_rc: NDArray) -> None: + r0, c0 = float(center_rc[0]), float(center_rc[1]) + + def _draw(vec: NDArray, label: str, color: tuple[float, float, float]) -> None: + dr, dc = float(vec[0]), float(vec[1]) + ax.plot([c0, c0 + dc], [r0, r0 + dr], linewidth=2.75, color=color) + ax.plot([c0 + dc], [r0 + dr], marker="o", markersize=6.0, color=color) + ax.text(c0 + dc, r0 + dr, f" {label}", color=color, fontsize=18, va="center") + + _draw(np.asarray(u_rc, dtype=float).reshape(2), "u", (1.0, 0.0, 0.0)) + _draw(np.asarray(v_rc, dtype=float).reshape(2), "v", (0.0, 0.7, 1.0)) + + +def _overlay_lattice_vectors( + *, + ax: Any, + shape: tuple[int, int], + u_rc: NDArray, + v_rc: NDArray, + rot_ccw_deg: float, + q_transpose: bool, +) -> None: + axs = _flatten_axes(ax) + if not axs: + return + + H, W = int(shape[0]), int(shape[1]) + center_rc = (float(H // 2), float(W // 2)) + + _plot_lattice_vectors(axs[0], center_rc, u_rc, v_rc) + + if len(axs) >= 2: + u_disp = _raw_vec_to_display(u_rc, rotation_ccw_deg=float(rot_ccw_deg), transpose=bool(q_transpose)) + v_disp = _raw_vec_to_display(v_rc, rotation_ccw_deg=float(rot_ccw_deg), transpose=bool(q_transpose)) + _plot_lattice_vectors(axs[1], center_rc, u_disp, v_disp) + + +def _parabolic_vertex_delta(v_m1: float, v_0: float, v_p1: float) -> float: + denom = (v_m1 - 2.0 * v_0 + v_p1) + if denom == 0 or not np.isfinite(denom): + return 0.0 + delta = 0.5 * (v_m1 - v_p1) / denom + if not np.isfinite(delta): + return 0.0 + return float(np.clip(delta, -1.0, 1.0)) + + +def _refine_peak_subpixel( + im: NDArray, + *, + r_guess: float, + c_guess: float, + radius_px: float = 2.0, + log_fit: bool = False, +) -> tuple[float, float]: + im = np.asarray(im, dtype=float) + H, W = im.shape + + r0 = int(np.clip(int(np.round(r_guess)), 0, H - 1)) + c0 = int(np.clip(int(np.round(c_guess)), 0, W - 1)) + rad = int(max(0, int(np.ceil(float(radius_px))))) + + r1 = max(0, r0 - rad) + r2 = min(H, r0 + rad + 1) + c1 = max(0, c0 - rad) + c2 = min(W, c0 + rad + 1) + + win = im[r1:r2, c1:c2] + if win.size == 0: + return float(r_guess), float(c_guess) + + ir, ic = np.unravel_index(int(np.argmax(win)), win.shape) + r_peak = r1 + int(ir) + c_peak = c1 + int(ic) + + if 0 < r_peak < H - 1: + col = im[r_peak - 1 : r_peak + 2, c_peak] + if log_fit: + col = np.log(np.clip(col, 1e-12, None)) + dr = _parabolic_vertex_delta(float(col[0]), float(col[1]), float(col[2])) + else: + dr = 0.0 + + if 0 < c_peak < W - 1: + row = im[r_peak, c_peak - 1 : c_peak + 2] + if log_fit: + row = np.log(np.clip(row, 1e-12, None)) + dc = _parabolic_vertex_delta(float(row[0]), float(row[1]), float(row[2])) + else: + dc = 0.0 + + return float(r_peak) + dr, float(c_peak) + dc + + +def _refine_peak_subpixel_dft( + im: NDArray, + *, + r0: float, + c0: float, + upsample: int, + log_fit: bool = False, +) -> tuple[float, float]: + if int(upsample) <= 1: + return float(r0), float(c0) + + im = np.asarray(im, dtype=float) + F = np.fft.fft2(im) + + up = int(upsample) + du = int(np.ceil(1.5 * up)) + + patch = dft_upsample(F, up=up, shift=(float(r0), float(c0)), device="cpu") + patch = np.asarray(patch, dtype=float) + + i0, j0 = np.unravel_index(int(np.argmax(patch)), patch.shape) + i0 = int(i0) + j0 = int(j0) + + if 0 < i0 < patch.shape[0] - 1: + col = patch[i0 - 1 : i0 + 2, j0] + if log_fit: + col = np.log(np.clip(col, 1e-12, None)) + di = _parabolic_vertex_delta(float(col[0]), float(col[1]), float(col[2])) + else: + di = 0.0 + + if 0 < j0 < patch.shape[1] - 1: + row = patch[i0, j0 - 1 : j0 + 2] + if log_fit: + row = np.log(np.clip(row, 1e-12, None)) + dj = _parabolic_vertex_delta(float(row[0]), float(row[1]), float(row[2])) + else: + dj = 0.0 + + dr = (float(i0) - float(du) + float(di)) / float(up) + dc = (float(j0) - float(du) + float(dj)) / float(up) + + return float(r0) + dr, float(c0) + dc + + +def _refine_lattice_vectors( + im: NDArray, + *, + u_rc: NDArray, + v_rc: NDArray, + radius_px: float = 2.0, + log_fit: bool = False, + refine_dft: bool = True, + upsample: int = 16, +) -> tuple[NDArray, NDArray]: + im = np.asarray(im, dtype=float) + if im.ndim != 2: + raise ValueError("im must be 2D.") + + H, W = im.shape + r_center = float(H // 2) + c_center = float(W // 2) + + def _refine(vec: NDArray) -> NDArray: + vec = np.asarray(vec, dtype=float).reshape(2) + r_guess = r_center + float(vec[0]) + c_guess = c_center + float(vec[1]) + + r1, c1 = _refine_peak_subpixel( + im, + r_guess=float(r_guess), + c_guess=float(c_guess), + radius_px=float(radius_px), + log_fit=bool(log_fit), + ) + + if refine_dft and int(upsample) > 1: + r2, c2 = _refine_peak_subpixel_dft( + im, + r0=float(r1), + c0=float(c1), + upsample=int(upsample), + log_fit=bool(log_fit), + ) + else: + r2, c2 = float(r1), float(c1) + + return np.array((r2 - r_center, c2 - c_center), dtype=float) + + return _refine(u_rc), _refine(v_rc) From 9b943923a2140f92dc1b51c6b7ce351511c7ceb1 Mon Sep 17 00:00:00 2001 From: cophus Date: Sat, 3 Jan 2026 12:39:39 -0800 Subject: [PATCH 007/113] faster strain calc, adding h5plugin to read arina data --- pyproject.toml | 1 + src/quantem/diffraction/strain.py | 55 ++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9223ae7c5..1c61b262c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,4 +69,5 @@ dev = [ "pre-commit>=4.2.0", "ruff>=0.11.5", "tomli>=2.2.1", + "hdf5plugin>=6.0.0", ] diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index b5549ff7e..bcfa981de 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -59,7 +59,7 @@ def from_data(cls, data: NDArray | Dataset4dstem, *, name: str = "strain_map") - def diffraction_mask( self, threshold = None, - edge_blend = 32.0, + edge_blend = 64.0, plot_mask = True, figsize = (8,4), ): @@ -103,6 +103,7 @@ def preprocess( mode: str = "linear", q_to_r_rotation_ccw_deg: float | None = None, q_transpose: bool | None = None, + skip = None, plot_transform: bool = True, cropping_factor: float = 0.25, **plot_kwargs: Any, @@ -178,18 +179,32 @@ def preprocess( self.metadata["q_to_r_rotation_ccw_deg"] = float(q_to_r_rotation_ccw_deg) self.metadata["q_transpose"] = bool(q_transpose) - if self.metadata["mode"] == "linear": - im = np.mean(np.abs(np.fft.fft2( - self.dataset.array * self.mask_diffraction[None,None,:,:] + \ - self.mask_diffraction_inv[None,None,:,:] - )), axis=(0, 1)) - elif self.metadata["mode"] == "log": - im = np.mean(np.abs(np.fft.fft2(np.log( - self.dataset.array * self.mask_diffraction[None,None,:,:] + \ - self.mask_diffraction_inv[None,None,:,:] - ))), axis=(0, 1)) + if skip is None: + if self.metadata["mode"] == "linear": + im = np.mean(np.abs(np.fft.fft2( + self.dataset.array * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + )), axis=(0, 1)) + elif self.metadata["mode"] == "log": + im = np.mean(np.abs(np.fft.fft2(np.log1p( + self.dataset.array * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + ))), axis=(0, 1)) + else: + raise ValueError("mode must be 'linear' or 'log'") else: - raise ValueError("mode must be 'linear' or 'log'") + if self.metadata["mode"] == "linear": + im = np.mean(np.abs(np.fft.fft2( + self.dataset.array[::skip,::skip] * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + )), axis=(0, 1)) + elif self.metadata["mode"] == "log": + im = np.mean(np.abs(np.fft.fft2(np.log1p( + self.dataset.array[::skip,::skip] * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + ))), axis=(0, 1)) + else: + raise ValueError("mode must be 'linear' or 'log'") im = np.fft.fftshift(im) @@ -239,8 +254,19 @@ def plot_transform( target_units = float(scalebar_fraction) * view_w_px * sampling sb_len = _nice_length_units(target_units) + # intensity scaling: compute from transform, apply same scaling to both panels + kr = (np.arange(self.transform.shape[0], dtype=float) - self.transform.shape[0] // 2)[:, None] + kc = (np.arange(self.transform.shape[1], dtype=float) - self.transform.shape[1] // 2)[None, :] + qmag = np.sqrt(kr * kr + kc * kc) + im0 = self.transform.array + tmp = im0 * qmag + i0 = np.unravel_index(int(np.nanargmax(tmp)), tmp.shape) + vmin = 0.0 + vmax = im0[i0] + defaults = dict( - vmax=1.0, + vmin=vmin, + vmax=vmax, title=("Original Transform", "Rotated Transform"), scalebar=ScalebarConfig( sampling=sampling, @@ -371,8 +397,7 @@ def fit_lattice_vectors( if mode == "linear": im = np.fft.fftshift(np.abs(np.fft.fft2(dp))) elif mode == "log": - int_range = np.max(dp) - np.min(dp) - im = np.fft.fftshift(np.abs(np.fft.fft2(np.log(dp + int_range*0.01)))) + im = np.fft.fftshift(np.abs(np.fft.fft2(np.log1p(dp)))) else: raise ValueError("metadata['mode'] must be 'linear' or 'log'") From dc4ee09d7941ca56b211d9e5f39099702c04df3d Mon Sep 17 00:00:00 2001 From: cophus Date: Sat, 3 Jan 2026 12:41:18 -0800 Subject: [PATCH 008/113] faster strain, adding h5plugin to read arina data --- pyproject.toml | 1 + src/quantem/diffraction/strain.py | 55 +- uv.lock | 3256 +++++++++++++++-------------- 3 files changed, 1670 insertions(+), 1642 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9223ae7c5..1c61b262c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,4 +69,5 @@ dev = [ "pre-commit>=4.2.0", "ruff>=0.11.5", "tomli>=2.2.1", + "hdf5plugin>=6.0.0", ] diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index b5549ff7e..bcfa981de 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -59,7 +59,7 @@ def from_data(cls, data: NDArray | Dataset4dstem, *, name: str = "strain_map") - def diffraction_mask( self, threshold = None, - edge_blend = 32.0, + edge_blend = 64.0, plot_mask = True, figsize = (8,4), ): @@ -103,6 +103,7 @@ def preprocess( mode: str = "linear", q_to_r_rotation_ccw_deg: float | None = None, q_transpose: bool | None = None, + skip = None, plot_transform: bool = True, cropping_factor: float = 0.25, **plot_kwargs: Any, @@ -178,18 +179,32 @@ def preprocess( self.metadata["q_to_r_rotation_ccw_deg"] = float(q_to_r_rotation_ccw_deg) self.metadata["q_transpose"] = bool(q_transpose) - if self.metadata["mode"] == "linear": - im = np.mean(np.abs(np.fft.fft2( - self.dataset.array * self.mask_diffraction[None,None,:,:] + \ - self.mask_diffraction_inv[None,None,:,:] - )), axis=(0, 1)) - elif self.metadata["mode"] == "log": - im = np.mean(np.abs(np.fft.fft2(np.log( - self.dataset.array * self.mask_diffraction[None,None,:,:] + \ - self.mask_diffraction_inv[None,None,:,:] - ))), axis=(0, 1)) + if skip is None: + if self.metadata["mode"] == "linear": + im = np.mean(np.abs(np.fft.fft2( + self.dataset.array * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + )), axis=(0, 1)) + elif self.metadata["mode"] == "log": + im = np.mean(np.abs(np.fft.fft2(np.log1p( + self.dataset.array * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + ))), axis=(0, 1)) + else: + raise ValueError("mode must be 'linear' or 'log'") else: - raise ValueError("mode must be 'linear' or 'log'") + if self.metadata["mode"] == "linear": + im = np.mean(np.abs(np.fft.fft2( + self.dataset.array[::skip,::skip] * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + )), axis=(0, 1)) + elif self.metadata["mode"] == "log": + im = np.mean(np.abs(np.fft.fft2(np.log1p( + self.dataset.array[::skip,::skip] * self.mask_diffraction[None,None,:,:] + \ + self.mask_diffraction_inv[None,None,:,:] + ))), axis=(0, 1)) + else: + raise ValueError("mode must be 'linear' or 'log'") im = np.fft.fftshift(im) @@ -239,8 +254,19 @@ def plot_transform( target_units = float(scalebar_fraction) * view_w_px * sampling sb_len = _nice_length_units(target_units) + # intensity scaling: compute from transform, apply same scaling to both panels + kr = (np.arange(self.transform.shape[0], dtype=float) - self.transform.shape[0] // 2)[:, None] + kc = (np.arange(self.transform.shape[1], dtype=float) - self.transform.shape[1] // 2)[None, :] + qmag = np.sqrt(kr * kr + kc * kc) + im0 = self.transform.array + tmp = im0 * qmag + i0 = np.unravel_index(int(np.nanargmax(tmp)), tmp.shape) + vmin = 0.0 + vmax = im0[i0] + defaults = dict( - vmax=1.0, + vmin=vmin, + vmax=vmax, title=("Original Transform", "Rotated Transform"), scalebar=ScalebarConfig( sampling=sampling, @@ -371,8 +397,7 @@ def fit_lattice_vectors( if mode == "linear": im = np.fft.fftshift(np.abs(np.fft.fft2(dp))) elif mode == "log": - int_range = np.max(dp) - np.min(dp) - im = np.fft.fftshift(np.abs(np.fft.fft2(np.log(dp + int_range*0.01)))) + im = np.fft.fftshift(np.abs(np.fft.fft2(np.log1p(dp)))) else: raise ValueError("metadata['mode'] must be 'linear' or 'log'") diff --git a/uv.lock b/uv.lock index 2960e9b30..ae9f2360f 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'", @@ -11,9 +11,9 @@ resolution-markers = [ name = "absl-py" version = "2.3.1" 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/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588 } 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/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811 }, ] [[package]] @@ -25,9 +25,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/a6/74c8cadc2882977d80ad756a13857857dbcf9bd405bc80b662eb10651282/alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e", size = 1988064, upload-time = "2025-11-14T20:35:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/a6/74c8cadc2882977d80ad756a13857857dbcf9bd405bc80b662eb10651282/alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e", size = 1988064 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, + { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554 }, ] [[package]] @@ -39,18 +39,18 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094 } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097 }, ] [[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]] @@ -60,9 +60,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]] @@ -72,28 +72,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]] @@ -104,45 +104,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.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380, upload-time = "2025-03-16T17:25:36.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069, upload-time = "2025-03-16T17:25:35.422Z" }, + { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069 }, ] [[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.17.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/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } 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/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, ] [[package]] @@ -153,9 +153,9 @@ dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822 } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392 }, ] [[package]] @@ -165,9 +165,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] @@ -179,9 +179,9 @@ css = [ name = "certifi" version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438 }, ] [[package]] @@ -191,149 +191,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.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, ] [[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]] @@ -343,18 +343,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]] @@ -366,18 +366,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]] @@ -387,9 +387,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]] @@ -399,18 +399,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]] @@ -420,155 +420,155 @@ 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 = "crc32c" version = "2.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/66/7e97aa77af7cf6afbff26e3651b564fe41932599bc2d3dce0b2f73d4829a/crc32c-2.8.tar.gz", hash = "sha256:578728964e59c47c356aeeedee6220e021e124b9d3e8631d95d9a5e5f06e261c", size = 48179, upload-time = "2025-10-17T06:20:13.61Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/0b/5e03b22d913698e9cc563f39b9f6bbd508606bf6b8e9122cd6bf196b87ea/crc32c-2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e560a97fbb96c9897cb1d9b5076ef12fc12e2e25622530a1afd0de4240f17e1f", size = 66329, upload-time = "2025-10-17T06:19:01.771Z" }, - { url = "https://files.pythonhosted.org/packages/6b/38/2fe0051ffe8c6a650c8b1ac0da31b8802d1dbe5fa40a84e4b6b6f5583db5/crc32c-2.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6762d276d90331a490ef7e71ffee53b9c0eb053bd75a272d786f3b08d3fe3671", size = 62988, upload-time = "2025-10-17T06:19:02.953Z" }, - { url = "https://files.pythonhosted.org/packages/3e/30/5837a71c014be83aba1469c58820d287fc836512a0cad6b8fdd43868accd/crc32c-2.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60670569f5ede91e39f48fb0cb4060e05b8d8704dd9e17ede930bf441b2f73ef", size = 61522, upload-time = "2025-10-17T06:19:03.796Z" }, - { url = "https://files.pythonhosted.org/packages/ca/29/63972fc1452778e2092ae998c50cbfc2fc93e3fa9798a0278650cd6169c5/crc32c-2.8-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:711743da6ccc70b3c6718c328947b0b6f34a1fe6a6c27cc6c1d69cc226bf70e9", size = 80200, upload-time = "2025-10-17T06:19:04.617Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3a/60eb49d7bdada4122b3ffd45b0df54bdc1b8dd092cda4b069a287bdfcff4/crc32c-2.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5eb4094a2054774f13b26f21bf56792bb44fa1fcee6c6ad099387a43ffbfb4fa", size = 81757, upload-time = "2025-10-17T06:19:05.496Z" }, - { url = "https://files.pythonhosted.org/packages/f5/63/6efc1b64429ef7d23bd58b75b7ac24d15df327e3ebbe9c247a0f7b1c2ed1/crc32c-2.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fff15bf2bd3e95780516baae935ed12be88deaa5ebe6143c53eb0d26a7bdc7b7", size = 80830, upload-time = "2025-10-17T06:19:06.621Z" }, - { url = "https://files.pythonhosted.org/packages/e1/eb/0ae9f436f8004f1c88f7429e659a7218a3879bd11a6b18ed1257aad7e98b/crc32c-2.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c0e11e3826668121fa53e0745635baf5e4f0ded437e8ff63ea56f38fc4f970a", size = 80095, upload-time = "2025-10-17T06:19:07.381Z" }, - { url = "https://files.pythonhosted.org/packages/9e/81/4afc9d468977a4cd94a2eb62908553345009a7c0d30e74463a15d4b48ec3/crc32c-2.8-cp311-cp311-win32.whl", hash = "sha256:38f915336715d1f1353ab07d7d786f8a789b119e273aea106ba55355dfc9101d", size = 64886, upload-time = "2025-10-17T06:19:08.497Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e8/94e839c9f7e767bf8479046a207afd440a08f5c59b52586e1af5e64fa4a0/crc32c-2.8-cp311-cp311-win_amd64.whl", hash = "sha256:60e0a765b1caab8d31b2ea80840639253906a9351d4b861551c8c8625ea20f86", size = 66639, upload-time = "2025-10-17T06:19:09.338Z" }, - { url = "https://files.pythonhosted.org/packages/b6/36/fd18ef23c42926b79c7003e16cb0f79043b5b179c633521343d3b499e996/crc32c-2.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:572ffb1b78cce3d88e8d4143e154d31044a44be42cb3f6fbbf77f1e7a941c5ab", size = 66379, upload-time = "2025-10-17T06:19:10.115Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b8/c584958e53f7798dd358f5bdb1bbfc97483134f053ee399d3eeb26cca075/crc32c-2.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cf827b3758ee0c4aacd21ceca0e2da83681f10295c38a10bfeb105f7d98f7a68", size = 63042, upload-time = "2025-10-17T06:19:10.946Z" }, - { url = "https://files.pythonhosted.org/packages/62/e6/6f2af0ec64a668a46c861e5bc778ea3ee42171fedfc5440f791f470fd783/crc32c-2.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:106fbd79013e06fa92bc3b51031694fcc1249811ed4364ef1554ee3dd2c7f5a2", size = 61528, upload-time = "2025-10-17T06:19:11.768Z" }, - { url = "https://files.pythonhosted.org/packages/17/8b/4a04bd80a024f1a23978f19ae99407783e06549e361ab56e9c08bba3c1d3/crc32c-2.8-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6dde035f91ffbfe23163e68605ee5a4bb8ceebd71ed54bb1fb1d0526cdd125a2", size = 80028, upload-time = "2025-10-17T06:19:12.554Z" }, - { url = "https://files.pythonhosted.org/packages/21/8f/01c7afdc76ac2007d0e6a98e7300b4470b170480f8188475b597d1f4b4c6/crc32c-2.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e41ebe7c2f0fdcd9f3a3fd206989a36b460b4d3f24816d53e5be6c7dba72c5e1", size = 81531, upload-time = "2025-10-17T06:19:13.406Z" }, - { url = "https://files.pythonhosted.org/packages/32/2b/8f78c5a8cc66486be5f51b6f038fc347c3ba748d3ea68be17a014283c331/crc32c-2.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecf66cf90266d9c15cea597d5cc86c01917cd1a238dc3c51420c7886fa750d7e", size = 80608, upload-time = "2025-10-17T06:19:14.223Z" }, - { url = "https://files.pythonhosted.org/packages/db/86/fad1a94cdeeeb6b6e2323c87f970186e74bfd6fbfbc247bf5c88ad0873d5/crc32c-2.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:59eee5f3a69ad0793d5fa9cdc9b9d743b0cd50edf7fccc0a3988a821fef0208c", size = 79886, upload-time = "2025-10-17T06:19:15.345Z" }, - { url = "https://files.pythonhosted.org/packages/d5/db/1a7cb6757a1e32376fa2dfce00c815ea4ee614a94f9bff8228e37420c183/crc32c-2.8-cp312-cp312-win32.whl", hash = "sha256:a73d03ce3604aa5d7a2698e9057a0eef69f529c46497b27ee1c38158e90ceb76", size = 64896, upload-time = "2025-10-17T06:19:16.457Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8e/2024de34399b2e401a37dcb54b224b56c747b0dc46de4966886827b4d370/crc32c-2.8-cp312-cp312-win_amd64.whl", hash = "sha256:56b3b7d015247962cf58186e06d18c3d75a1a63d709d3233509e1c50a2d36aa2", size = 66645, upload-time = "2025-10-17T06:19:17.235Z" }, - { url = "https://files.pythonhosted.org/packages/e8/d8/3ae227890b3be40955a7144106ef4dd97d6123a82c2a5310cdab58ca49d8/crc32c-2.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:36f1e03ee9e9c6938e67d3bcb60e36f260170aa5f37da1185e04ef37b56af395", size = 66380, upload-time = "2025-10-17T06:19:18.009Z" }, - { url = "https://files.pythonhosted.org/packages/bd/8b/178d3f987cd0e049b484615512d3f91f3d2caeeb8ff336bb5896ae317438/crc32c-2.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b2f3226b94b85a8dd9b3533601d7a63e9e3e8edf03a8a169830ee8303a199aeb", size = 63048, upload-time = "2025-10-17T06:19:18.853Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a1/48145ae2545ebc0169d3283ebe882da580ea4606bfb67cf4ca922ac3cfc3/crc32c-2.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e08628bc72d5b6bc8e0730e8f142194b610e780a98c58cb6698e665cb885a5b", size = 61530, upload-time = "2025-10-17T06:19:19.974Z" }, - { url = "https://files.pythonhosted.org/packages/06/4b/cf05ed9d934cc30e5ae22f97c8272face420a476090e736615d9a6b53de0/crc32c-2.8-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:086f64793c5ec856d1ab31a026d52ad2b895ac83d7a38fce557d74eb857f0a82", size = 80001, upload-time = "2025-10-17T06:19:20.784Z" }, - { url = "https://files.pythonhosted.org/packages/15/ab/4b04801739faf36345f6ba1920be5b1c70282fec52f8280afd3613fb13e2/crc32c-2.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcf72ee7e0135b3d941c34bb2c26c3fc6bc207106b49fd89aaafaeae223ae209", size = 81543, upload-time = "2025-10-17T06:19:21.557Z" }, - { url = "https://files.pythonhosted.org/packages/a9/1b/6e38dde5bfd2ea69b7f2ab6ec229fcd972a53d39e2db4efe75c0ac0382ce/crc32c-2.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a717dd9c3fd777d9bc6603717eae172887d402c4ab589d124ebd0184a83f89e", size = 80644, upload-time = "2025-10-17T06:19:22.325Z" }, - { url = "https://files.pythonhosted.org/packages/ce/45/012176ffee90059ae8ec7131019c71724ea472aa63e72c0c8edbd1fad1d7/crc32c-2.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0450bb845b3c3c7b9bdc0b4e95620ec9a40824abdc8c86d6285c919a90743c1a", size = 79919, upload-time = "2025-10-17T06:19:23.101Z" }, - { url = "https://files.pythonhosted.org/packages/f0/2b/f557629842f9dec2b3461cb3a0d854bb586ec45b814cea58b082c32f0dde/crc32c-2.8-cp313-cp313-win32.whl", hash = "sha256:765d220bfcbcffa6598ac11eb1e10af0ee4802b49fe126aa6bf79f8ddb9931d1", size = 64896, upload-time = "2025-10-17T06:19:23.88Z" }, - { url = "https://files.pythonhosted.org/packages/d0/db/fd0f698c15d1e21d47c64181a98290665a08fcbb3940cd559e9c15bda57e/crc32c-2.8-cp313-cp313-win_amd64.whl", hash = "sha256:171ff0260d112c62abcce29332986950a57bddee514e0a2418bfde493ea06bb3", size = 66646, upload-time = "2025-10-17T06:19:24.702Z" }, - { url = "https://files.pythonhosted.org/packages/db/b9/8e5d7054fe8e7eecab10fd0c8e7ffb01439417bdb6de1d66a81c38fc4a20/crc32c-2.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b977a32a3708d6f51703c8557008f190aaa434d7347431efb0e86fcbe78c2a50", size = 66203, upload-time = "2025-10-17T06:19:25.872Z" }, - { url = "https://files.pythonhosted.org/packages/55/5f/cc926c70057a63cc0c98a3c8a896eb15fc7e74d3034eadd53c94917c6cc3/crc32c-2.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7399b01db4adaf41da2fb36fe2408e75a8d82a179a9564ed7619412e427b26d6", size = 62956, upload-time = "2025-10-17T06:19:26.652Z" }, - { url = "https://files.pythonhosted.org/packages/a1/8a/0660c44a2dd2cb6ccbb529eb363b9280f5c766f1017bc8355ed8d695bd94/crc32c-2.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4379f73f9cdad31958a673d11a332ec725ca71572401ca865867229f5f15e853", size = 61442, upload-time = "2025-10-17T06:19:27.74Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5a/6108d2dfc0fe33522ce83ba07aed4b22014911b387afa228808a278e27cd/crc32c-2.8-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e68264555fab19bab08331550dab58573e351a63ed79c869d455edd3b0aa417", size = 79109, upload-time = "2025-10-17T06:19:28.535Z" }, - { url = "https://files.pythonhosted.org/packages/84/1e/c054f9e390090c197abf3d2936f4f9effaf0c6ee14569ae03d6ddf86958a/crc32c-2.8-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b48f2486727b8d0e7ccbae4a34cb0300498433d2a9d6b49cb13cb57c2e3f19cb", size = 80987, upload-time = "2025-10-17T06:19:29.305Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ad/1650e5c3341e4a485f800ea83116d72965030c5d48ccc168fcc685756e4d/crc32c-2.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ecf123348934a086df8c8fde7f9f2d716d523ca0707c5a1367b8bb00d8134823", size = 79994, upload-time = "2025-10-17T06:19:30.109Z" }, - { url = "https://files.pythonhosted.org/packages/d7/3b/f2ed924b177729cbb2ab30ca2902abff653c31d48c95e7b66717a9ca9fcc/crc32c-2.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e636ac60f76de538f7a2c0d0f3abf43104ee83a8f5e516f6345dc283ed1a4df7", size = 79046, upload-time = "2025-10-17T06:19:30.894Z" }, - { url = "https://files.pythonhosted.org/packages/4b/80/413b05ee6ace613208b31b3670c3135ee1cf451f0e72a9c839b4946acc04/crc32c-2.8-cp313-cp313t-win32.whl", hash = "sha256:8dd4a19505e0253892e1b2f1425cc3bd47f79ae5a04cb8800315d00aad7197f2", size = 64837, upload-time = "2025-10-17T06:19:32.03Z" }, - { url = "https://files.pythonhosted.org/packages/3b/1b/85eddb6ac5b38496c4e35c20298aae627970c88c3c624a22ab33e84f16c7/crc32c-2.8-cp313-cp313t-win_amd64.whl", hash = "sha256:4bb18e4bd98fb266596523ffc6be9c5b2387b2fa4e505ec56ca36336f49cb639", size = 66574, upload-time = "2025-10-17T06:19:33.143Z" }, - { url = "https://files.pythonhosted.org/packages/aa/df/50e9079b532ff53dbfc0e66eed781374bd455af02ed5df8b56ad538de4ff/crc32c-2.8-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3a3b2e4bcf7b3ee333050e7d3ff38e2ba46ea205f1d73d8949b248aaffe937ac", size = 66399, upload-time = "2025-10-17T06:19:34.279Z" }, - { url = "https://files.pythonhosted.org/packages/5a/2e/67e3b0bc3d30e46ea5d16365cc81203286387671e22f2307eb41f19abb9c/crc32c-2.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:445e559e66dff16be54f8a4ef95aa6b01db799a639956d995c5498ba513fccc2", size = 63044, upload-time = "2025-10-17T06:19:35.062Z" }, - { url = "https://files.pythonhosted.org/packages/36/ea/1723b17437e4344ed8d067456382ecb1f5b535d83fdc5aaebab676c6d273/crc32c-2.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bf3040919e17afa5782e01b1875d6a05f44b8f19c05f211d8b9f8a1deb8bbd9c", size = 61541, upload-time = "2025-10-17T06:19:36.204Z" }, - { url = "https://files.pythonhosted.org/packages/4c/6a/cbec8a235c5b46a01f319939b538958662159aec0ed3a74944e3a6de21f1/crc32c-2.8-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5607ab8221e1ffd411f64aa40dbb6850cf06dd2908c9debd05d371e1acf62ff3", size = 80139, upload-time = "2025-10-17T06:19:37.351Z" }, - { url = "https://files.pythonhosted.org/packages/21/31/d096722fe74b692d6e8206c27da1ea5f6b2a12ff92c54a62a6ba2f376254/crc32c-2.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f5db4f16816926986d3c94253314920689706ae13a9bf4888b47336c6735ce", size = 81736, upload-time = "2025-10-17T06:19:38.16Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a2/f75ef716ff7e3c22f385ba6ef30c5de80c19a21ebe699dc90824a1903275/crc32c-2.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70b0153c4d418b673309d3529334d117e1074c4a3b2d7f676e430d72c14de67b", size = 80795, upload-time = "2025-10-17T06:19:38.948Z" }, - { url = "https://files.pythonhosted.org/packages/d8/94/6d647a12d96ab087d9b8eacee3da073f981987827d57c7072f89ffc7b6cd/crc32c-2.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c8933531442042438753755a5c8a9034e4d88b01da9eb796f7e151b31a7256c", size = 80042, upload-time = "2025-10-17T06:19:39.725Z" }, - { url = "https://files.pythonhosted.org/packages/cd/dc/32b8896b40a0afee7a3c040536d0da5a73e68df2be9fadd21770fd158e16/crc32c-2.8-cp314-cp314-win32.whl", hash = "sha256:cdc83a3fe6c4e5df9457294cfd643de7d95bd4e9382c1dd6ed1e0f0f9169172c", size = 64914, upload-time = "2025-10-17T06:19:40.527Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b4/4308b27d307e8ecaf8dd1dcc63bbb0e47ae1826d93faa3e62d1ee00ee2d5/crc32c-2.8-cp314-cp314-win_amd64.whl", hash = "sha256:509e10035106df66770fe24b9eb8d9e32b6fb967df17744402fb67772d8b2bc7", size = 66723, upload-time = "2025-10-17T06:19:42.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/d5/a19d2489fa997a143bfbbf971a5c9a43f8b1ba9e775b1fb362d8fb15260c/crc32c-2.8-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:864359a39777a07b09b28eb31337c0cc603d5c1bf0fc328c3af736a8da624ec0", size = 66201, upload-time = "2025-10-17T06:19:43.273Z" }, - { url = "https://files.pythonhosted.org/packages/98/c2/5f82f22d2c1242cb6f6fe92aa9a42991ebea86de994b8f9974d9c1d128e2/crc32c-2.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:14511d7cfc5d9f5e1a6c6b64caa6225c2bdc1ed00d725e9a374a3e84073ce180", size = 62956, upload-time = "2025-10-17T06:19:44.099Z" }, - { url = "https://files.pythonhosted.org/packages/9b/61/3d43d33489cf974fb78bfb3500845770e139ae6d1d83473b660bd8f79a6c/crc32c-2.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:918b7999b52b5dcbcea34081e9a02d46917d571921a3f209956a9a429b2e06e5", size = 61443, upload-time = "2025-10-17T06:19:44.89Z" }, - { url = "https://files.pythonhosted.org/packages/52/6d/f306ce64a352a3002f76b0fc88a1373f4541f9d34fad3668688610bab14b/crc32c-2.8-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cc445da03fc012a5a03b71da1df1b40139729e6a5571fd4215ab40bfb39689c7", size = 79106, upload-time = "2025-10-17T06:19:45.688Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b7/1f74965dd7ea762954a69d172dfb3a706049c84ffa45d31401d010a4a126/crc32c-2.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e3dde2ec59a8a830511d72a086ead95c0b0b7f0d418f93ea106244c5e77e350", size = 80983, upload-time = "2025-10-17T06:19:46.792Z" }, - { url = "https://files.pythonhosted.org/packages/1b/50/af93f0d91ccd61833ce77374ebfbd16f5805f5c17d18c6470976d9866d76/crc32c-2.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:61d51681a08b6a2a2e771b7f0cd1947fb87cb28f38ed55a01cb7c40b2ac4cdd8", size = 80009, upload-time = "2025-10-17T06:19:47.619Z" }, - { url = "https://files.pythonhosted.org/packages/ee/fa/94f394beb68a88258af694dab2f1284f55a406b615d7900bdd6235283bc4/crc32c-2.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:67c0716c3b1a02d5235be649487b637eed21f2d070f2b3f63f709dcd2fefb4c7", size = 79066, upload-time = "2025-10-17T06:19:48.409Z" }, - { url = "https://files.pythonhosted.org/packages/91/c6/a6050e0c64fd73c67a97da96cb59f08b05111e00b958fb87ecdce99f17ac/crc32c-2.8-cp314-cp314t-win32.whl", hash = "sha256:2e8fe863fbbd8bdb6b414a2090f1b0f52106e76e9a9c96a413495dbe5ebe492a", size = 64869, upload-time = "2025-10-17T06:19:49.197Z" }, - { url = "https://files.pythonhosted.org/packages/08/1f/c7735034e401cb1ea14f996a224518e3a3fa9987cb13680e707328a7d779/crc32c-2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:20a9cfb897693eb6da19e52e2a7be2026fd4d9fc8ae318f086c0d71d5dd2d8e0", size = 66633, upload-time = "2025-10-17T06:19:50.003Z" }, - { url = "https://files.pythonhosted.org/packages/a7/1d/dd926c68eb8aac8b142a1a10b8eb62d95212c1cf81775644373fe7cceac2/crc32c-2.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5833f4071da7ea182c514ba17d1eee8aec3c5be927d798222fbfbbd0f5eea02c", size = 62345, upload-time = "2025-10-17T06:20:09.39Z" }, - { url = "https://files.pythonhosted.org/packages/51/be/803404e5abea2ef2c15042edca04bbb7f625044cca879e47f186b43887c2/crc32c-2.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1dc4da036126ac07b39dd9d03e93e585ec615a2ad28ff12757aef7de175295a8", size = 61229, upload-time = "2025-10-17T06:20:10.236Z" }, - { url = "https://files.pythonhosted.org/packages/fc/3a/00cc578cd27ed0b22c9be25cef2c24539d92df9fa80ebd67a3fc5419724c/crc32c-2.8-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:15905fa78344654e241371c47e6ed2411f9eeb2b8095311c68c88eccf541e8b4", size = 64108, upload-time = "2025-10-17T06:20:11.072Z" }, - { url = "https://files.pythonhosted.org/packages/6b/bc/0587ef99a1c7629f95dd0c9d4f3d894de383a0df85831eb16c48a6afdae4/crc32c-2.8-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c596f918688821f796434e89b431b1698396c38bf0b56de873621528fe3ecb1e", size = 64815, upload-time = "2025-10-17T06:20:11.919Z" }, - { url = "https://files.pythonhosted.org/packages/73/42/94f2b8b92eae9064fcfb8deef2b971514065bd606231f8857ff8ae02bebd/crc32c-2.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8d23c4fe01b3844cb6e091044bc1cebdef7d16472e058ce12d9fadf10d2614af", size = 66659, upload-time = "2025-10-17T06:20:12.766Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/e3/66/7e97aa77af7cf6afbff26e3651b564fe41932599bc2d3dce0b2f73d4829a/crc32c-2.8.tar.gz", hash = "sha256:578728964e59c47c356aeeedee6220e021e124b9d3e8631d95d9a5e5f06e261c", size = 48179 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0b/5e03b22d913698e9cc563f39b9f6bbd508606bf6b8e9122cd6bf196b87ea/crc32c-2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e560a97fbb96c9897cb1d9b5076ef12fc12e2e25622530a1afd0de4240f17e1f", size = 66329 }, + { url = "https://files.pythonhosted.org/packages/6b/38/2fe0051ffe8c6a650c8b1ac0da31b8802d1dbe5fa40a84e4b6b6f5583db5/crc32c-2.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6762d276d90331a490ef7e71ffee53b9c0eb053bd75a272d786f3b08d3fe3671", size = 62988 }, + { url = "https://files.pythonhosted.org/packages/3e/30/5837a71c014be83aba1469c58820d287fc836512a0cad6b8fdd43868accd/crc32c-2.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60670569f5ede91e39f48fb0cb4060e05b8d8704dd9e17ede930bf441b2f73ef", size = 61522 }, + { url = "https://files.pythonhosted.org/packages/ca/29/63972fc1452778e2092ae998c50cbfc2fc93e3fa9798a0278650cd6169c5/crc32c-2.8-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:711743da6ccc70b3c6718c328947b0b6f34a1fe6a6c27cc6c1d69cc226bf70e9", size = 80200 }, + { url = "https://files.pythonhosted.org/packages/cb/3a/60eb49d7bdada4122b3ffd45b0df54bdc1b8dd092cda4b069a287bdfcff4/crc32c-2.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5eb4094a2054774f13b26f21bf56792bb44fa1fcee6c6ad099387a43ffbfb4fa", size = 81757 }, + { url = "https://files.pythonhosted.org/packages/f5/63/6efc1b64429ef7d23bd58b75b7ac24d15df327e3ebbe9c247a0f7b1c2ed1/crc32c-2.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fff15bf2bd3e95780516baae935ed12be88deaa5ebe6143c53eb0d26a7bdc7b7", size = 80830 }, + { url = "https://files.pythonhosted.org/packages/e1/eb/0ae9f436f8004f1c88f7429e659a7218a3879bd11a6b18ed1257aad7e98b/crc32c-2.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c0e11e3826668121fa53e0745635baf5e4f0ded437e8ff63ea56f38fc4f970a", size = 80095 }, + { url = "https://files.pythonhosted.org/packages/9e/81/4afc9d468977a4cd94a2eb62908553345009a7c0d30e74463a15d4b48ec3/crc32c-2.8-cp311-cp311-win32.whl", hash = "sha256:38f915336715d1f1353ab07d7d786f8a789b119e273aea106ba55355dfc9101d", size = 64886 }, + { url = "https://files.pythonhosted.org/packages/d6/e8/94e839c9f7e767bf8479046a207afd440a08f5c59b52586e1af5e64fa4a0/crc32c-2.8-cp311-cp311-win_amd64.whl", hash = "sha256:60e0a765b1caab8d31b2ea80840639253906a9351d4b861551c8c8625ea20f86", size = 66639 }, + { url = "https://files.pythonhosted.org/packages/b6/36/fd18ef23c42926b79c7003e16cb0f79043b5b179c633521343d3b499e996/crc32c-2.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:572ffb1b78cce3d88e8d4143e154d31044a44be42cb3f6fbbf77f1e7a941c5ab", size = 66379 }, + { url = "https://files.pythonhosted.org/packages/7f/b8/c584958e53f7798dd358f5bdb1bbfc97483134f053ee399d3eeb26cca075/crc32c-2.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cf827b3758ee0c4aacd21ceca0e2da83681f10295c38a10bfeb105f7d98f7a68", size = 63042 }, + { url = "https://files.pythonhosted.org/packages/62/e6/6f2af0ec64a668a46c861e5bc778ea3ee42171fedfc5440f791f470fd783/crc32c-2.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:106fbd79013e06fa92bc3b51031694fcc1249811ed4364ef1554ee3dd2c7f5a2", size = 61528 }, + { url = "https://files.pythonhosted.org/packages/17/8b/4a04bd80a024f1a23978f19ae99407783e06549e361ab56e9c08bba3c1d3/crc32c-2.8-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6dde035f91ffbfe23163e68605ee5a4bb8ceebd71ed54bb1fb1d0526cdd125a2", size = 80028 }, + { url = "https://files.pythonhosted.org/packages/21/8f/01c7afdc76ac2007d0e6a98e7300b4470b170480f8188475b597d1f4b4c6/crc32c-2.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e41ebe7c2f0fdcd9f3a3fd206989a36b460b4d3f24816d53e5be6c7dba72c5e1", size = 81531 }, + { url = "https://files.pythonhosted.org/packages/32/2b/8f78c5a8cc66486be5f51b6f038fc347c3ba748d3ea68be17a014283c331/crc32c-2.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecf66cf90266d9c15cea597d5cc86c01917cd1a238dc3c51420c7886fa750d7e", size = 80608 }, + { url = "https://files.pythonhosted.org/packages/db/86/fad1a94cdeeeb6b6e2323c87f970186e74bfd6fbfbc247bf5c88ad0873d5/crc32c-2.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:59eee5f3a69ad0793d5fa9cdc9b9d743b0cd50edf7fccc0a3988a821fef0208c", size = 79886 }, + { url = "https://files.pythonhosted.org/packages/d5/db/1a7cb6757a1e32376fa2dfce00c815ea4ee614a94f9bff8228e37420c183/crc32c-2.8-cp312-cp312-win32.whl", hash = "sha256:a73d03ce3604aa5d7a2698e9057a0eef69f529c46497b27ee1c38158e90ceb76", size = 64896 }, + { url = "https://files.pythonhosted.org/packages/bf/8e/2024de34399b2e401a37dcb54b224b56c747b0dc46de4966886827b4d370/crc32c-2.8-cp312-cp312-win_amd64.whl", hash = "sha256:56b3b7d015247962cf58186e06d18c3d75a1a63d709d3233509e1c50a2d36aa2", size = 66645 }, + { url = "https://files.pythonhosted.org/packages/e8/d8/3ae227890b3be40955a7144106ef4dd97d6123a82c2a5310cdab58ca49d8/crc32c-2.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:36f1e03ee9e9c6938e67d3bcb60e36f260170aa5f37da1185e04ef37b56af395", size = 66380 }, + { url = "https://files.pythonhosted.org/packages/bd/8b/178d3f987cd0e049b484615512d3f91f3d2caeeb8ff336bb5896ae317438/crc32c-2.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b2f3226b94b85a8dd9b3533601d7a63e9e3e8edf03a8a169830ee8303a199aeb", size = 63048 }, + { url = "https://files.pythonhosted.org/packages/f2/a1/48145ae2545ebc0169d3283ebe882da580ea4606bfb67cf4ca922ac3cfc3/crc32c-2.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e08628bc72d5b6bc8e0730e8f142194b610e780a98c58cb6698e665cb885a5b", size = 61530 }, + { url = "https://files.pythonhosted.org/packages/06/4b/cf05ed9d934cc30e5ae22f97c8272face420a476090e736615d9a6b53de0/crc32c-2.8-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:086f64793c5ec856d1ab31a026d52ad2b895ac83d7a38fce557d74eb857f0a82", size = 80001 }, + { url = "https://files.pythonhosted.org/packages/15/ab/4b04801739faf36345f6ba1920be5b1c70282fec52f8280afd3613fb13e2/crc32c-2.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcf72ee7e0135b3d941c34bb2c26c3fc6bc207106b49fd89aaafaeae223ae209", size = 81543 }, + { url = "https://files.pythonhosted.org/packages/a9/1b/6e38dde5bfd2ea69b7f2ab6ec229fcd972a53d39e2db4efe75c0ac0382ce/crc32c-2.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a717dd9c3fd777d9bc6603717eae172887d402c4ab589d124ebd0184a83f89e", size = 80644 }, + { url = "https://files.pythonhosted.org/packages/ce/45/012176ffee90059ae8ec7131019c71724ea472aa63e72c0c8edbd1fad1d7/crc32c-2.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0450bb845b3c3c7b9bdc0b4e95620ec9a40824abdc8c86d6285c919a90743c1a", size = 79919 }, + { url = "https://files.pythonhosted.org/packages/f0/2b/f557629842f9dec2b3461cb3a0d854bb586ec45b814cea58b082c32f0dde/crc32c-2.8-cp313-cp313-win32.whl", hash = "sha256:765d220bfcbcffa6598ac11eb1e10af0ee4802b49fe126aa6bf79f8ddb9931d1", size = 64896 }, + { url = "https://files.pythonhosted.org/packages/d0/db/fd0f698c15d1e21d47c64181a98290665a08fcbb3940cd559e9c15bda57e/crc32c-2.8-cp313-cp313-win_amd64.whl", hash = "sha256:171ff0260d112c62abcce29332986950a57bddee514e0a2418bfde493ea06bb3", size = 66646 }, + { url = "https://files.pythonhosted.org/packages/db/b9/8e5d7054fe8e7eecab10fd0c8e7ffb01439417bdb6de1d66a81c38fc4a20/crc32c-2.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b977a32a3708d6f51703c8557008f190aaa434d7347431efb0e86fcbe78c2a50", size = 66203 }, + { url = "https://files.pythonhosted.org/packages/55/5f/cc926c70057a63cc0c98a3c8a896eb15fc7e74d3034eadd53c94917c6cc3/crc32c-2.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7399b01db4adaf41da2fb36fe2408e75a8d82a179a9564ed7619412e427b26d6", size = 62956 }, + { url = "https://files.pythonhosted.org/packages/a1/8a/0660c44a2dd2cb6ccbb529eb363b9280f5c766f1017bc8355ed8d695bd94/crc32c-2.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4379f73f9cdad31958a673d11a332ec725ca71572401ca865867229f5f15e853", size = 61442 }, + { url = "https://files.pythonhosted.org/packages/f5/5a/6108d2dfc0fe33522ce83ba07aed4b22014911b387afa228808a278e27cd/crc32c-2.8-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e68264555fab19bab08331550dab58573e351a63ed79c869d455edd3b0aa417", size = 79109 }, + { url = "https://files.pythonhosted.org/packages/84/1e/c054f9e390090c197abf3d2936f4f9effaf0c6ee14569ae03d6ddf86958a/crc32c-2.8-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b48f2486727b8d0e7ccbae4a34cb0300498433d2a9d6b49cb13cb57c2e3f19cb", size = 80987 }, + { url = "https://files.pythonhosted.org/packages/c8/ad/1650e5c3341e4a485f800ea83116d72965030c5d48ccc168fcc685756e4d/crc32c-2.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ecf123348934a086df8c8fde7f9f2d716d523ca0707c5a1367b8bb00d8134823", size = 79994 }, + { url = "https://files.pythonhosted.org/packages/d7/3b/f2ed924b177729cbb2ab30ca2902abff653c31d48c95e7b66717a9ca9fcc/crc32c-2.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e636ac60f76de538f7a2c0d0f3abf43104ee83a8f5e516f6345dc283ed1a4df7", size = 79046 }, + { url = "https://files.pythonhosted.org/packages/4b/80/413b05ee6ace613208b31b3670c3135ee1cf451f0e72a9c839b4946acc04/crc32c-2.8-cp313-cp313t-win32.whl", hash = "sha256:8dd4a19505e0253892e1b2f1425cc3bd47f79ae5a04cb8800315d00aad7197f2", size = 64837 }, + { url = "https://files.pythonhosted.org/packages/3b/1b/85eddb6ac5b38496c4e35c20298aae627970c88c3c624a22ab33e84f16c7/crc32c-2.8-cp313-cp313t-win_amd64.whl", hash = "sha256:4bb18e4bd98fb266596523ffc6be9c5b2387b2fa4e505ec56ca36336f49cb639", size = 66574 }, + { url = "https://files.pythonhosted.org/packages/aa/df/50e9079b532ff53dbfc0e66eed781374bd455af02ed5df8b56ad538de4ff/crc32c-2.8-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3a3b2e4bcf7b3ee333050e7d3ff38e2ba46ea205f1d73d8949b248aaffe937ac", size = 66399 }, + { url = "https://files.pythonhosted.org/packages/5a/2e/67e3b0bc3d30e46ea5d16365cc81203286387671e22f2307eb41f19abb9c/crc32c-2.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:445e559e66dff16be54f8a4ef95aa6b01db799a639956d995c5498ba513fccc2", size = 63044 }, + { url = "https://files.pythonhosted.org/packages/36/ea/1723b17437e4344ed8d067456382ecb1f5b535d83fdc5aaebab676c6d273/crc32c-2.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bf3040919e17afa5782e01b1875d6a05f44b8f19c05f211d8b9f8a1deb8bbd9c", size = 61541 }, + { url = "https://files.pythonhosted.org/packages/4c/6a/cbec8a235c5b46a01f319939b538958662159aec0ed3a74944e3a6de21f1/crc32c-2.8-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5607ab8221e1ffd411f64aa40dbb6850cf06dd2908c9debd05d371e1acf62ff3", size = 80139 }, + { url = "https://files.pythonhosted.org/packages/21/31/d096722fe74b692d6e8206c27da1ea5f6b2a12ff92c54a62a6ba2f376254/crc32c-2.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f5db4f16816926986d3c94253314920689706ae13a9bf4888b47336c6735ce", size = 81736 }, + { url = "https://files.pythonhosted.org/packages/f6/a2/f75ef716ff7e3c22f385ba6ef30c5de80c19a21ebe699dc90824a1903275/crc32c-2.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70b0153c4d418b673309d3529334d117e1074c4a3b2d7f676e430d72c14de67b", size = 80795 }, + { url = "https://files.pythonhosted.org/packages/d8/94/6d647a12d96ab087d9b8eacee3da073f981987827d57c7072f89ffc7b6cd/crc32c-2.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c8933531442042438753755a5c8a9034e4d88b01da9eb796f7e151b31a7256c", size = 80042 }, + { url = "https://files.pythonhosted.org/packages/cd/dc/32b8896b40a0afee7a3c040536d0da5a73e68df2be9fadd21770fd158e16/crc32c-2.8-cp314-cp314-win32.whl", hash = "sha256:cdc83a3fe6c4e5df9457294cfd643de7d95bd4e9382c1dd6ed1e0f0f9169172c", size = 64914 }, + { url = "https://files.pythonhosted.org/packages/f2/b4/4308b27d307e8ecaf8dd1dcc63bbb0e47ae1826d93faa3e62d1ee00ee2d5/crc32c-2.8-cp314-cp314-win_amd64.whl", hash = "sha256:509e10035106df66770fe24b9eb8d9e32b6fb967df17744402fb67772d8b2bc7", size = 66723 }, + { url = "https://files.pythonhosted.org/packages/90/d5/a19d2489fa997a143bfbbf971a5c9a43f8b1ba9e775b1fb362d8fb15260c/crc32c-2.8-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:864359a39777a07b09b28eb31337c0cc603d5c1bf0fc328c3af736a8da624ec0", size = 66201 }, + { url = "https://files.pythonhosted.org/packages/98/c2/5f82f22d2c1242cb6f6fe92aa9a42991ebea86de994b8f9974d9c1d128e2/crc32c-2.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:14511d7cfc5d9f5e1a6c6b64caa6225c2bdc1ed00d725e9a374a3e84073ce180", size = 62956 }, + { url = "https://files.pythonhosted.org/packages/9b/61/3d43d33489cf974fb78bfb3500845770e139ae6d1d83473b660bd8f79a6c/crc32c-2.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:918b7999b52b5dcbcea34081e9a02d46917d571921a3f209956a9a429b2e06e5", size = 61443 }, + { url = "https://files.pythonhosted.org/packages/52/6d/f306ce64a352a3002f76b0fc88a1373f4541f9d34fad3668688610bab14b/crc32c-2.8-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cc445da03fc012a5a03b71da1df1b40139729e6a5571fd4215ab40bfb39689c7", size = 79106 }, + { url = "https://files.pythonhosted.org/packages/a5/b7/1f74965dd7ea762954a69d172dfb3a706049c84ffa45d31401d010a4a126/crc32c-2.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e3dde2ec59a8a830511d72a086ead95c0b0b7f0d418f93ea106244c5e77e350", size = 80983 }, + { url = "https://files.pythonhosted.org/packages/1b/50/af93f0d91ccd61833ce77374ebfbd16f5805f5c17d18c6470976d9866d76/crc32c-2.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:61d51681a08b6a2a2e771b7f0cd1947fb87cb28f38ed55a01cb7c40b2ac4cdd8", size = 80009 }, + { url = "https://files.pythonhosted.org/packages/ee/fa/94f394beb68a88258af694dab2f1284f55a406b615d7900bdd6235283bc4/crc32c-2.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:67c0716c3b1a02d5235be649487b637eed21f2d070f2b3f63f709dcd2fefb4c7", size = 79066 }, + { url = "https://files.pythonhosted.org/packages/91/c6/a6050e0c64fd73c67a97da96cb59f08b05111e00b958fb87ecdce99f17ac/crc32c-2.8-cp314-cp314t-win32.whl", hash = "sha256:2e8fe863fbbd8bdb6b414a2090f1b0f52106e76e9a9c96a413495dbe5ebe492a", size = 64869 }, + { url = "https://files.pythonhosted.org/packages/08/1f/c7735034e401cb1ea14f996a224518e3a3fa9987cb13680e707328a7d779/crc32c-2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:20a9cfb897693eb6da19e52e2a7be2026fd4d9fc8ae318f086c0d71d5dd2d8e0", size = 66633 }, + { url = "https://files.pythonhosted.org/packages/a7/1d/dd926c68eb8aac8b142a1a10b8eb62d95212c1cf81775644373fe7cceac2/crc32c-2.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5833f4071da7ea182c514ba17d1eee8aec3c5be927d798222fbfbbd0f5eea02c", size = 62345 }, + { url = "https://files.pythonhosted.org/packages/51/be/803404e5abea2ef2c15042edca04bbb7f625044cca879e47f186b43887c2/crc32c-2.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1dc4da036126ac07b39dd9d03e93e585ec615a2ad28ff12757aef7de175295a8", size = 61229 }, + { url = "https://files.pythonhosted.org/packages/fc/3a/00cc578cd27ed0b22c9be25cef2c24539d92df9fa80ebd67a3fc5419724c/crc32c-2.8-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:15905fa78344654e241371c47e6ed2411f9eeb2b8095311c68c88eccf541e8b4", size = 64108 }, + { url = "https://files.pythonhosted.org/packages/6b/bc/0587ef99a1c7629f95dd0c9d4f3d894de383a0df85831eb16c48a6afdae4/crc32c-2.8-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c596f918688821f796434e89b431b1698396c38bf0b56de873621528fe3ecb1e", size = 64815 }, + { url = "https://files.pythonhosted.org/packages/73/42/94f2b8b92eae9064fcfb8deef2b971514065bd606231f8857ff8ae02bebd/crc32c-2.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8d23c4fe01b3844cb6e091044bc1cebdef7d16472e058ce12d9fadf10d2614af", size = 66659 }, ] [[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]] @@ -585,9 +585,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/33/eacaa72731f7fc64868caaf2d35060d50049eff889bd217263e68f76472f/dask-2025.11.0.tar.gz", hash = "sha256:23d59e624b80ee05b7cc8df858682cca58262c4c3b197ccf61da0f6543c8f7c3", size = 10984781, upload-time = "2025-11-06T16:56:51.535Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/33/eacaa72731f7fc64868caaf2d35060d50049eff889bd217263e68f76472f/dask-2025.11.0.tar.gz", hash = "sha256:23d59e624b80ee05b7cc8df858682cca58262c4c3b197ccf61da0f6543c8f7c3", size = 10984781 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/54/a46920229d12c3a6e9f0081d1bdaeffad23c1826353ace95714faee926e5/dask-2025.11.0-py3-none-any.whl", hash = "sha256:08c35a8146c05c93b34f83cf651009129c42ee71762da7ca452fb7308641c2b8", size = 1477108, upload-time = "2025-11-06T16:56:44.892Z" }, + { url = "https://files.pythonhosted.org/packages/1d/54/a46920229d12c3a6e9f0081d1bdaeffad23c1826353ace95714faee926e5/dask-2025.11.0-py3-none-any.whl", hash = "sha256:08c35a8146c05c93b34f83cf651009129c42ee71762da7ca452fb7308641c2b8", size = 1477108 }, ] [package.optional-dependencies] @@ -599,61 +599,61 @@ array = [ name = "debugpy" version = "1.8.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/53/3af72b5c159278c4a0cf4cffa518675a0e73bdb7d1cac0239b815502d2ce/debugpy-1.8.17-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:d3fce3f0e3de262a3b67e69916d001f3e767661c6e1ee42553009d445d1cd840", size = 2207154, upload-time = "2025-09-17T16:33:29.457Z" }, - { url = "https://files.pythonhosted.org/packages/8f/6d/204f407df45600e2245b4a39860ed4ba32552330a0b3f5f160ae4cc30072/debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f", size = 3170322, upload-time = "2025-09-17T16:33:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/f2/13/1b8f87d39cf83c6b713de2620c31205299e6065622e7dd37aff4808dd410/debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da", size = 5155078, upload-time = "2025-09-17T16:33:33.331Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl", hash = "sha256:b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4", size = 5179011, upload-time = "2025-09-17T16:33:35.711Z" }, - { url = "https://files.pythonhosted.org/packages/08/2b/9d8e65beb2751876c82e1aceb32f328c43ec872711fa80257c7674f45650/debugpy-1.8.17-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:f14467edef672195c6f6b8e27ce5005313cb5d03c9239059bc7182b60c176e2d", size = 2549522, upload-time = "2025-09-17T16:33:38.466Z" }, - { url = "https://files.pythonhosted.org/packages/b4/78/eb0d77f02971c05fca0eb7465b18058ba84bd957062f5eec82f941ac792a/debugpy-1.8.17-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:24693179ef9dfa20dca8605905a42b392be56d410c333af82f1c5dff807a64cc", size = 4309417, upload-time = "2025-09-17T16:33:41.299Z" }, - { url = "https://files.pythonhosted.org/packages/37/42/c40f1d8cc1fed1e75ea54298a382395b8b937d923fcf41ab0797a554f555/debugpy-1.8.17-cp312-cp312-win32.whl", hash = "sha256:6a4e9dacf2cbb60d2514ff7b04b4534b0139facbf2abdffe0639ddb6088e59cf", size = 5277130, upload-time = "2025-09-17T16:33:43.554Z" }, - { url = "https://files.pythonhosted.org/packages/72/22/84263b205baad32b81b36eac076de0cdbe09fe2d0637f5b32243dc7c925b/debugpy-1.8.17-cp312-cp312-win_amd64.whl", hash = "sha256:e8f8f61c518952fb15f74a302e068b48d9c4691768ade433e4adeea961993464", size = 5319053, upload-time = "2025-09-17T16:33:53.033Z" }, - { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386, upload-time = "2025-09-17T16:33:54.594Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100, upload-time = "2025-09-17T16:33:56.353Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002, upload-time = "2025-09-17T16:33:58.231Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047, upload-time = "2025-09-17T16:34:00.586Z" }, - { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899, upload-time = "2025-09-17T16:34:02.657Z" }, - { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254, upload-time = "2025-09-17T16:34:04.486Z" }, - { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203, upload-time = "2025-09-17T16:34:06.65Z" }, - { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493, upload-time = "2025-09-17T16:34:08.483Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" }, + { url = "https://files.pythonhosted.org/packages/d8/53/3af72b5c159278c4a0cf4cffa518675a0e73bdb7d1cac0239b815502d2ce/debugpy-1.8.17-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:d3fce3f0e3de262a3b67e69916d001f3e767661c6e1ee42553009d445d1cd840", size = 2207154 }, + { url = "https://files.pythonhosted.org/packages/8f/6d/204f407df45600e2245b4a39860ed4ba32552330a0b3f5f160ae4cc30072/debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f", size = 3170322 }, + { url = "https://files.pythonhosted.org/packages/f2/13/1b8f87d39cf83c6b713de2620c31205299e6065622e7dd37aff4808dd410/debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da", size = 5155078 }, + { url = "https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl", hash = "sha256:b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4", size = 5179011 }, + { url = "https://files.pythonhosted.org/packages/08/2b/9d8e65beb2751876c82e1aceb32f328c43ec872711fa80257c7674f45650/debugpy-1.8.17-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:f14467edef672195c6f6b8e27ce5005313cb5d03c9239059bc7182b60c176e2d", size = 2549522 }, + { url = "https://files.pythonhosted.org/packages/b4/78/eb0d77f02971c05fca0eb7465b18058ba84bd957062f5eec82f941ac792a/debugpy-1.8.17-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:24693179ef9dfa20dca8605905a42b392be56d410c333af82f1c5dff807a64cc", size = 4309417 }, + { url = "https://files.pythonhosted.org/packages/37/42/c40f1d8cc1fed1e75ea54298a382395b8b937d923fcf41ab0797a554f555/debugpy-1.8.17-cp312-cp312-win32.whl", hash = "sha256:6a4e9dacf2cbb60d2514ff7b04b4534b0139facbf2abdffe0639ddb6088e59cf", size = 5277130 }, + { url = "https://files.pythonhosted.org/packages/72/22/84263b205baad32b81b36eac076de0cdbe09fe2d0637f5b32243dc7c925b/debugpy-1.8.17-cp312-cp312-win_amd64.whl", hash = "sha256:e8f8f61c518952fb15f74a302e068b48d9c4691768ade433e4adeea961993464", size = 5319053 }, + { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386 }, + { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100 }, + { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002 }, + { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047 }, + { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899 }, + { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254 }, + { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203 }, + { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493 }, + { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210 }, ] [[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.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668 }, ] [[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]] @@ -663,36 +663,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.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922 } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054 }, ] [[package]] @@ -702,9 +702,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]] @@ -714,126 +714,126 @@ 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.60.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", size = 2831872, upload-time = "2025-09-29T21:11:20.329Z" }, - { url = "https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", size = 2356990, upload-time = "2025-09-29T21:11:22.754Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/1934b537c86fcf99f9761823f1fc37a98fbd54568e8e613f29a90fed95a9/fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", size = 5042189, upload-time = "2025-09-29T21:11:25.061Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", size = 4978683, upload-time = "2025-09-29T21:11:27.693Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c4/0fb2dfd1ecbe9a07954cc13414713ed1eab17b1c0214ef07fc93df234a47/fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", size = 5021372, upload-time = "2025-09-29T21:11:30.257Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d5/495fc7ae2fab20223cc87179a8f50f40f9a6f821f271ba8301ae12bb580f/fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa", size = 5132562, upload-time = "2025-09-29T21:11:32.737Z" }, - { url = "https://files.pythonhosted.org/packages/bc/fa/021dab618526323c744e0206b3f5c8596a2e7ae9aa38db5948a131123e83/fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", size = 2230288, upload-time = "2025-09-29T21:11:35.015Z" }, - { url = "https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", size = 2278184, upload-time = "2025-09-29T21:11:37.434Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953, upload-time = "2025-09-29T21:11:39.616Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706, upload-time = "2025-09-29T21:11:41.826Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716, upload-time = "2025-09-29T21:11:43.893Z" }, - { url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175, upload-time = "2025-09-29T21:11:46.439Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031, upload-time = "2025-09-29T21:11:48.977Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966, upload-time = "2025-09-29T21:11:54.344Z" }, - { url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750, upload-time = "2025-09-29T21:11:56.601Z" }, - { url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026, upload-time = "2025-09-29T21:11:58.852Z" }, - { url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777, upload-time = "2025-09-29T21:12:01.22Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080, upload-time = "2025-09-29T21:12:03.785Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082, upload-time = "2025-09-29T21:12:06.382Z" }, - { url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125, upload-time = "2025-09-29T21:12:09.314Z" }, - { url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454, upload-time = "2025-09-29T21:12:11.931Z" }, - { url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495, upload-time = "2025-09-29T21:12:15.241Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028, upload-time = "2025-09-29T21:12:17.96Z" }, - { url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200, upload-time = "2025-09-29T21:12:20.14Z" }, - { url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" }, - { url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" }, - { url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" }, - { url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" }, - { url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" }, - { url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" }, - { url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" }, - { url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" }, - { url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", size = 2831872 }, + { url = "https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", size = 2356990 }, + { url = "https://files.pythonhosted.org/packages/94/dd/1934b537c86fcf99f9761823f1fc37a98fbd54568e8e613f29a90fed95a9/fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", size = 5042189 }, + { url = "https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", size = 4978683 }, + { url = "https://files.pythonhosted.org/packages/cc/c4/0fb2dfd1ecbe9a07954cc13414713ed1eab17b1c0214ef07fc93df234a47/fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", size = 5021372 }, + { url = "https://files.pythonhosted.org/packages/0c/d5/495fc7ae2fab20223cc87179a8f50f40f9a6f821f271ba8301ae12bb580f/fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa", size = 5132562 }, + { url = "https://files.pythonhosted.org/packages/bc/fa/021dab618526323c744e0206b3f5c8596a2e7ae9aa38db5948a131123e83/fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", size = 2230288 }, + { url = "https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", size = 2278184 }, + { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953 }, + { url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706 }, + { url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716 }, + { url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175 }, + { url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031 }, + { url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966 }, + { url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750 }, + { url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026 }, + { url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777 }, + { url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080 }, + { url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082 }, + { url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125 }, + { url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454 }, + { url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495 }, + { url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028 }, + { url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200 }, + { url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830 }, + { url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524 }, + { url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490 }, + { url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184 }, + { url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218 }, + { url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324 }, + { url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861 }, + { url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934 }, + { url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340 }, + { url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073 }, + { url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758 }, + { url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598 }, + { url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603 }, + { url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184 }, + { url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241 }, + { url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760 }, + { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175 }, ] [[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 = "2025.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285, upload-time = "2025-10-30T14:58:44.036Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966, upload-time = "2025-10-30T14:58:42.53Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966 }, ] [[package]] name = "greenlet" version = "3.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, - { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, - { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, - { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, - { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, - { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, - { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, - { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, - { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, - { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, - { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, - { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, - { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, - { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, - { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, - { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, - { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, - { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, - { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, - { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, - { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, - { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, - { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, - { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, - { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, - { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, - { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, - { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, - { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, - { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305 }, + { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472 }, + { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646 }, + { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519 }, + { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707 }, + { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684 }, + { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647 }, + { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073 }, + { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385 }, + { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329 }, + { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100 }, + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079 }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997 }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185 }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926 }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839 }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586 }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281 }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142 }, + { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846 }, + { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814 }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899 }, + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814 }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073 }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191 }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516 }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169 }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497 }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662 }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210 }, + { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759 }, + { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288 }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685 }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586 }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346 }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218 }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659 }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355 }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512 }, + { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508 }, + { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760 }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425 }, ] [[package]] @@ -843,57 +843,57 @@ 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/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688 }, + { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315 }, + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750 }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003 }, + { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727 }, + { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799 }, + { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961 }, + { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462 }, ] [[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]] @@ -903,40 +903,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]] @@ -946,13 +946,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]] @@ -963,9 +963,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]] @@ -978,27 +978,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.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183 }, ] [[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]] @@ -1009,9 +1009,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]] @@ -1021,18 +1021,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641 } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 }, ] [[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]] @@ -1054,9 +1054,9 @@ 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/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579 } 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/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968 }, ] [[package]] @@ -1076,9 +1076,9 @@ dependencies = [ { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e", size = 4422115, upload-time = "2025-11-05T12:18:54.646Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e", size = 4422115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl", hash = "sha256:bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f", size = 618911, upload-time = "2025-11-05T12:18:52.484Z" }, + { url = "https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl", hash = "sha256:bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f", size = 618911 }, ] [[package]] @@ -1088,9 +1088,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]] @@ -1100,9 +1100,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]] @@ -1112,9 +1112,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]] @@ -1124,27 +1124,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.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191, upload-time = "2025-08-12T19:47:42.583Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191 } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119, upload-time = "2025-08-12T19:47:41.131Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119 }, ] [[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]] @@ -1157,9 +1157,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040 }, ] [package.optional-dependencies] @@ -1182,9 +1182,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]] @@ -1198,9 +1198,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, ] [[package]] @@ -1211,9 +1211,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]] @@ -1230,9 +1230,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]] @@ -1242,9 +1242,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]] @@ -1272,9 +1272,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]] @@ -1285,9 +1285,9 @@ dependencies = [ { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "terminado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" }, + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, ] [[package]] @@ -1309,18 +1309,18 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/e5/4fa382a796a6d8e2cd867816b64f1ff27f906e43a7a83ad9eb389e448cd8/jupyterlab-4.5.0.tar.gz", hash = "sha256:aec33d6d8f1225b495ee2cf20f0514f45e6df8e360bdd7ac9bace0b7ac5177ea", size = 23989880, upload-time = "2025-11-18T13:19:00.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/e5/4fa382a796a6d8e2cd867816b64f1ff27f906e43a7a83ad9eb389e448cd8/jupyterlab-4.5.0.tar.gz", hash = "sha256:aec33d6d8f1225b495ee2cf20f0514f45e6df8e360bdd7ac9bace0b7ac5177ea", size = 23989880 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/1e/5a4d5498eba382fee667ed797cf64ae5d1b13b04356df62f067f48bb0f61/jupyterlab-4.5.0-py3-none-any.whl", hash = "sha256:88e157c75c1afff64c7dc4b801ec471450b922a4eae4305211ddd40da8201c8a", size = 12380641, upload-time = "2025-11-18T13:18:56.252Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1e/5a4d5498eba382fee667ed797cf64ae5d1b13b04356df62f067f48bb0f61/jupyterlab-4.5.0-py3-none-any.whl", hash = "sha256:88e157c75c1afff64c7dc4b801ec471450b922a4eae4305211ddd40da8201c8a", size = 12380641 }, ] [[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]] @@ -1336,108 +1336,108 @@ 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 = "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]] @@ -1447,9 +1447,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]] @@ -1461,18 +1461,18 @@ dependencies = [ { 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/b8/39/6fc58ca81492db047149b4b8fd385aa1bfb8c28cd7cacb0c7eb0c44d842f/lightning_utilities-0.15.2.tar.gz", hash = "sha256:cdf12f530214a63dacefd713f180d1ecf5d165338101617b4742e8f22c032e24", size = 31090 } 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/de/73/3d757cb3fc16f0f9794dd289bcd0c4a031d9cf54d8137d6b984b2d02edf3/lightning_utilities-0.15.2-py3-none-any.whl", hash = "sha256:ad3ab1703775044bbf880dbf7ddaaac899396c96315f3aa1779cec9d618a9841", size = 29431 }, ] [[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]] @@ -1482,92 +1482,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" 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/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931 } 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/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678 }, ] [[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]] @@ -1585,53 +1585,53 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a", size = 8257507, upload-time = "2025-10-09T00:26:19.073Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6", size = 8119565, upload-time = "2025-10-09T00:26:21.023Z" }, - { url = "https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a", size = 8692668, upload-time = "2025-10-09T00:26:22.967Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e7/664d2b97016f46683a02d854d730cfcf54ff92c1dafa424beebef50f831d/matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1", size = 9521051, upload-time = "2025-10-09T00:26:25.041Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a3/37aef1404efa615f49b5758a5e0261c16dd88f389bc1861e722620e4a754/matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc", size = 9576878, upload-time = "2025-10-09T00:26:27.478Z" }, - { url = "https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e", size = 8115142, upload-time = "2025-10-09T00:26:29.774Z" }, - { url = "https://files.pythonhosted.org/packages/2e/39/63bca9d2b78455ed497fcf51a9c71df200a11048f48249038f06447fa947/matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9", size = 7992439, upload-time = "2025-10-09T00:26:40.32Z" }, - { url = "https://files.pythonhosted.org/packages/be/b3/09eb0f7796932826ec20c25b517d568627754f6c6462fca19e12c02f2e12/matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748", size = 8272389, upload-time = "2025-10-09T00:26:42.474Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/1ae80ddafb8652fd8046cb5c8460ecc8d4afccb89e2c6d6bec61e04e1eaf/matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f", size = 8128247, upload-time = "2025-10-09T00:26:44.77Z" }, - { url = "https://files.pythonhosted.org/packages/7d/18/95ae2e242d4a5c98bd6e90e36e128d71cf1c7e39b0874feaed3ef782e789/matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0", size = 8696996, upload-time = "2025-10-09T00:26:46.792Z" }, - { url = "https://files.pythonhosted.org/packages/7e/3d/5b559efc800bd05cb2033aa85f7e13af51958136a48327f7c261801ff90a/matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695", size = 9530153, upload-time = "2025-10-09T00:26:49.07Z" }, - { url = "https://files.pythonhosted.org/packages/88/57/eab4a719fd110312d3c220595d63a3c85ec2a39723f0f4e7fa7e6e3f74ba/matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65", size = 9593093, upload-time = "2025-10-09T00:26:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/31/3c/80816f027b3a4a28cd2a0a6ef7f89a2db22310e945cd886ec25bfb399221/matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee", size = 8122771, upload-time = "2025-10-09T00:26:53.296Z" }, - { url = "https://files.pythonhosted.org/packages/de/77/ef1fc78bfe99999b2675435cc52120887191c566b25017d78beaabef7f2d/matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8", size = 7992812, upload-time = "2025-10-09T00:26:54.882Z" }, - { url = "https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f", size = 8273212, upload-time = "2025-10-09T00:26:56.752Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c", size = 8128713, upload-time = "2025-10-09T00:26:59.001Z" }, - { url = "https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1", size = 8698527, upload-time = "2025-10-09T00:27:00.69Z" }, - { url = "https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632", size = 9529690, upload-time = "2025-10-09T00:27:02.664Z" }, - { url = "https://files.pythonhosted.org/packages/b8/95/b80fc2c1f269f21ff3d193ca697358e24408c33ce2b106a7438a45407b63/matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84", size = 9593732, upload-time = "2025-10-09T00:27:04.653Z" }, - { url = "https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815", size = 8122727, upload-time = "2025-10-09T00:27:06.814Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a6/2faaf48133b82cf3607759027f82b5c702aa99cdfcefb7f93d6ccf26a424/matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7", size = 7992958, upload-time = "2025-10-09T00:27:08.567Z" }, - { url = "https://files.pythonhosted.org/packages/4a/f0/b018fed0b599bd48d84c08794cb242227fe3341952da102ee9d9682db574/matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355", size = 8316849, upload-time = "2025-10-09T00:27:10.254Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b7/bb4f23856197659f275e11a2a164e36e65e9b48ea3e93c4ec25b4f163198/matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b", size = 8178225, upload-time = "2025-10-09T00:27:12.241Z" }, - { url = "https://files.pythonhosted.org/packages/62/56/0600609893ff277e6f3ab3c0cef4eafa6e61006c058e84286c467223d4d5/matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67", size = 8711708, upload-time = "2025-10-09T00:27:13.879Z" }, - { url = "https://files.pythonhosted.org/packages/d8/1a/6bfecb0cafe94d6658f2f1af22c43b76cf7a1c2f0dc34ef84cbb6809617e/matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67", size = 9541409, upload-time = "2025-10-09T00:27:15.684Z" }, - { url = "https://files.pythonhosted.org/packages/08/50/95122a407d7f2e446fd865e2388a232a23f2b81934960ea802f3171518e4/matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84", size = 9594054, upload-time = "2025-10-09T00:27:17.547Z" }, - { url = "https://files.pythonhosted.org/packages/13/76/75b194a43b81583478a81e78a07da8d9ca6ddf50dd0a2ccabf258059481d/matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2", size = 8200100, upload-time = "2025-10-09T00:27:20.039Z" }, - { url = "https://files.pythonhosted.org/packages/f5/9e/6aefebdc9f8235c12bdeeda44cc0383d89c1e41da2c400caf3ee2073a3ce/matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf", size = 8042131, upload-time = "2025-10-09T00:27:21.608Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4b/e5bc2c321b6a7e3a75638d937d19ea267c34bd5a90e12bee76c4d7c7a0d9/matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100", size = 8273787, upload-time = "2025-10-09T00:27:23.27Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/6efae459c56c2fbc404da154e13e3a6039129f3c942b0152624f1c621f05/matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f", size = 8131348, upload-time = "2025-10-09T00:27:24.926Z" }, - { url = "https://files.pythonhosted.org/packages/a6/5a/a4284d2958dee4116359cc05d7e19c057e64ece1b4ac986ab0f2f4d52d5a/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715", size = 9533949, upload-time = "2025-10-09T00:27:26.704Z" }, - { url = "https://files.pythonhosted.org/packages/de/ff/f3781b5057fa3786623ad8976fc9f7b0d02b2f28534751fd5a44240de4cf/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1", size = 9804247, upload-time = "2025-10-09T00:27:28.514Z" }, - { url = "https://files.pythonhosted.org/packages/47/5a/993a59facb8444efb0e197bf55f545ee449902dcee86a4dfc580c3b61314/matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722", size = 9595497, upload-time = "2025-10-09T00:27:30.418Z" }, - { url = "https://files.pythonhosted.org/packages/0d/a5/77c95aaa9bb32c345cbb49626ad8eb15550cba2e6d4c88081a6c2ac7b08d/matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866", size = 8252732, upload-time = "2025-10-09T00:27:32.332Z" }, - { url = "https://files.pythonhosted.org/packages/74/04/45d269b4268d222390d7817dae77b159651909669a34ee9fdee336db5883/matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb", size = 8124240, upload-time = "2025-10-09T00:27:33.94Z" }, - { url = "https://files.pythonhosted.org/packages/4b/c7/ca01c607bb827158b439208c153d6f14ddb9fb640768f06f7ca3488ae67b/matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1", size = 8316938, upload-time = "2025-10-09T00:27:35.534Z" }, - { url = "https://files.pythonhosted.org/packages/84/d2/5539e66e9f56d2fdec94bb8436f5e449683b4e199bcc897c44fbe3c99e28/matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4", size = 8178245, upload-time = "2025-10-09T00:27:37.334Z" }, - { url = "https://files.pythonhosted.org/packages/77/b5/e6ca22901fd3e4fe433a82e583436dd872f6c966fca7e63cf806b40356f8/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318", size = 9541411, upload-time = "2025-10-09T00:27:39.387Z" }, - { url = "https://files.pythonhosted.org/packages/9e/99/a4524db57cad8fee54b7237239a8f8360bfcfa3170d37c9e71c090c0f409/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca", size = 9803664, upload-time = "2025-10-09T00:27:41.492Z" }, - { url = "https://files.pythonhosted.org/packages/e6/a5/85e2edf76ea0ad4288d174926d9454ea85f3ce5390cc4e6fab196cbf250b/matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", size = 9594066, upload-time = "2025-10-09T00:27:43.694Z" }, - { url = "https://files.pythonhosted.org/packages/39/69/9684368a314f6d83fe5c5ad2a4121a3a8e03723d2e5c8ea17b66c1bad0e7/matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", size = 8342832, upload-time = "2025-10-09T00:27:45.543Z" }, - { url = "https://files.pythonhosted.org/packages/04/5f/e22e08da14bc1a0894184640d47819d2338b792732e20d292bf86e5ab785/matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", size = 8172585, upload-time = "2025-10-09T00:27:47.185Z" }, - { url = "https://files.pythonhosted.org/packages/58/8f/76d5dc21ac64a49e5498d7f0472c0781dae442dd266a67458baec38288ec/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0", size = 8252283, upload-time = "2025-10-09T00:27:54.739Z" }, - { url = "https://files.pythonhosted.org/packages/27/0d/9c5d4c2317feb31d819e38c9f947c942f42ebd4eb935fc6fd3518a11eaa7/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68", size = 8116733, upload-time = "2025-10-09T00:27:56.406Z" }, - { url = "https://files.pythonhosted.org/packages/9a/cc/3fe688ff1355010937713164caacf9ed443675ac48a997bab6ed23b3f7c0/matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", size = 8693919, upload-time = "2025-10-09T00:27:58.41Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a", size = 8257507 }, + { url = "https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6", size = 8119565 }, + { url = "https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a", size = 8692668 }, + { url = "https://files.pythonhosted.org/packages/e6/e7/664d2b97016f46683a02d854d730cfcf54ff92c1dafa424beebef50f831d/matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1", size = 9521051 }, + { url = "https://files.pythonhosted.org/packages/a8/a3/37aef1404efa615f49b5758a5e0261c16dd88f389bc1861e722620e4a754/matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc", size = 9576878 }, + { url = "https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e", size = 8115142 }, + { url = "https://files.pythonhosted.org/packages/2e/39/63bca9d2b78455ed497fcf51a9c71df200a11048f48249038f06447fa947/matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9", size = 7992439 }, + { url = "https://files.pythonhosted.org/packages/be/b3/09eb0f7796932826ec20c25b517d568627754f6c6462fca19e12c02f2e12/matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748", size = 8272389 }, + { url = "https://files.pythonhosted.org/packages/11/0b/1ae80ddafb8652fd8046cb5c8460ecc8d4afccb89e2c6d6bec61e04e1eaf/matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f", size = 8128247 }, + { url = "https://files.pythonhosted.org/packages/7d/18/95ae2e242d4a5c98bd6e90e36e128d71cf1c7e39b0874feaed3ef782e789/matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0", size = 8696996 }, + { url = "https://files.pythonhosted.org/packages/7e/3d/5b559efc800bd05cb2033aa85f7e13af51958136a48327f7c261801ff90a/matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695", size = 9530153 }, + { url = "https://files.pythonhosted.org/packages/88/57/eab4a719fd110312d3c220595d63a3c85ec2a39723f0f4e7fa7e6e3f74ba/matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65", size = 9593093 }, + { url = "https://files.pythonhosted.org/packages/31/3c/80816f027b3a4a28cd2a0a6ef7f89a2db22310e945cd886ec25bfb399221/matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee", size = 8122771 }, + { url = "https://files.pythonhosted.org/packages/de/77/ef1fc78bfe99999b2675435cc52120887191c566b25017d78beaabef7f2d/matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8", size = 7992812 }, + { url = "https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f", size = 8273212 }, + { url = "https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c", size = 8128713 }, + { url = "https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1", size = 8698527 }, + { url = "https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632", size = 9529690 }, + { url = "https://files.pythonhosted.org/packages/b8/95/b80fc2c1f269f21ff3d193ca697358e24408c33ce2b106a7438a45407b63/matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84", size = 9593732 }, + { url = "https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815", size = 8122727 }, + { url = "https://files.pythonhosted.org/packages/b3/a6/2faaf48133b82cf3607759027f82b5c702aa99cdfcefb7f93d6ccf26a424/matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7", size = 7992958 }, + { url = "https://files.pythonhosted.org/packages/4a/f0/b018fed0b599bd48d84c08794cb242227fe3341952da102ee9d9682db574/matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355", size = 8316849 }, + { url = "https://files.pythonhosted.org/packages/b0/b7/bb4f23856197659f275e11a2a164e36e65e9b48ea3e93c4ec25b4f163198/matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b", size = 8178225 }, + { url = "https://files.pythonhosted.org/packages/62/56/0600609893ff277e6f3ab3c0cef4eafa6e61006c058e84286c467223d4d5/matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67", size = 8711708 }, + { url = "https://files.pythonhosted.org/packages/d8/1a/6bfecb0cafe94d6658f2f1af22c43b76cf7a1c2f0dc34ef84cbb6809617e/matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67", size = 9541409 }, + { url = "https://files.pythonhosted.org/packages/08/50/95122a407d7f2e446fd865e2388a232a23f2b81934960ea802f3171518e4/matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84", size = 9594054 }, + { url = "https://files.pythonhosted.org/packages/13/76/75b194a43b81583478a81e78a07da8d9ca6ddf50dd0a2ccabf258059481d/matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2", size = 8200100 }, + { url = "https://files.pythonhosted.org/packages/f5/9e/6aefebdc9f8235c12bdeeda44cc0383d89c1e41da2c400caf3ee2073a3ce/matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf", size = 8042131 }, + { url = "https://files.pythonhosted.org/packages/0d/4b/e5bc2c321b6a7e3a75638d937d19ea267c34bd5a90e12bee76c4d7c7a0d9/matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100", size = 8273787 }, + { url = "https://files.pythonhosted.org/packages/86/ad/6efae459c56c2fbc404da154e13e3a6039129f3c942b0152624f1c621f05/matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f", size = 8131348 }, + { url = "https://files.pythonhosted.org/packages/a6/5a/a4284d2958dee4116359cc05d7e19c057e64ece1b4ac986ab0f2f4d52d5a/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715", size = 9533949 }, + { url = "https://files.pythonhosted.org/packages/de/ff/f3781b5057fa3786623ad8976fc9f7b0d02b2f28534751fd5a44240de4cf/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1", size = 9804247 }, + { url = "https://files.pythonhosted.org/packages/47/5a/993a59facb8444efb0e197bf55f545ee449902dcee86a4dfc580c3b61314/matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722", size = 9595497 }, + { url = "https://files.pythonhosted.org/packages/0d/a5/77c95aaa9bb32c345cbb49626ad8eb15550cba2e6d4c88081a6c2ac7b08d/matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866", size = 8252732 }, + { url = "https://files.pythonhosted.org/packages/74/04/45d269b4268d222390d7817dae77b159651909669a34ee9fdee336db5883/matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb", size = 8124240 }, + { url = "https://files.pythonhosted.org/packages/4b/c7/ca01c607bb827158b439208c153d6f14ddb9fb640768f06f7ca3488ae67b/matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1", size = 8316938 }, + { url = "https://files.pythonhosted.org/packages/84/d2/5539e66e9f56d2fdec94bb8436f5e449683b4e199bcc897c44fbe3c99e28/matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4", size = 8178245 }, + { url = "https://files.pythonhosted.org/packages/77/b5/e6ca22901fd3e4fe433a82e583436dd872f6c966fca7e63cf806b40356f8/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318", size = 9541411 }, + { url = "https://files.pythonhosted.org/packages/9e/99/a4524db57cad8fee54b7237239a8f8360bfcfa3170d37c9e71c090c0f409/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca", size = 9803664 }, + { url = "https://files.pythonhosted.org/packages/e6/a5/85e2edf76ea0ad4288d174926d9454ea85f3ce5390cc4e6fab196cbf250b/matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", size = 9594066 }, + { url = "https://files.pythonhosted.org/packages/39/69/9684368a314f6d83fe5c5ad2a4121a3a8e03723d2e5c8ea17b66c1bad0e7/matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", size = 8342832 }, + { url = "https://files.pythonhosted.org/packages/04/5f/e22e08da14bc1a0894184640d47819d2338b792732e20d292bf86e5ab785/matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", size = 8172585 }, + { url = "https://files.pythonhosted.org/packages/58/8f/76d5dc21ac64a49e5498d7f0472c0781dae442dd266a67458baec38288ec/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0", size = 8252283 }, + { url = "https://files.pythonhosted.org/packages/27/0d/9c5d4c2317feb31d819e38c9f947c942f42ebd4eb935fc6fd3518a11eaa7/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68", size = 8116733 }, + { url = "https://files.pythonhosted.org/packages/9a/cc/3fe688ff1355010937713164caacf9ed443675ac48a997bab6ed23b3f7c0/matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", size = 8693919 }, ] [[package]] @@ -1641,27 +1641,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.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/02/a7fb8b21d4d55ac93cdcde9d3638da5dd0ebdd3a4fed76c7725e10b81cbe/mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164", size = 94588, upload-time = "2025-08-29T07:20:43.594Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/02/a7fb8b21d4d55ac93cdcde9d3638da5dd0ebdd3a4fed76c7725e10b81cbe/mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164", size = 94588 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481 }, ] [[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]] @@ -1674,9 +1674,9 @@ dependencies = [ { name = "nbformat" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424 } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434 }, ] [[package]] @@ -1699,9 +1699,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/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715 } 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/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525 }, ] [[package]] @@ -1714,36 +1714,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.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406 }, ] [[package]] name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] [[package]] @@ -1753,9 +1753,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]] @@ -1766,23 +1766,23 @@ dependencies = [ { name = "numpy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/48/6188e359b90a9d8a1850f2bc888c023e66f4a8b2b496820babbea414f008/numcodecs-0.16.3.tar.gz", hash = "sha256:53d705865faaf0a7927c973af3777532001c8fbb653de119c1e844608614d799", size = 6275704, upload-time = "2025-09-18T18:54:57.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/cc/917a85972537498f2bbd7914047efc98babc8667587ceb9dcb228378978a/numcodecs-0.16.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:95c9f2a49bef10cf91ad614a761cba9bfe96656b60c12540e1080de5d909b4ca", size = 1642356, upload-time = "2025-09-18T18:54:36.402Z" }, - { url = "https://files.pythonhosted.org/packages/3b/6a/64c25a089e8537441fe67c09ecb7f3f7fb5d98cd04faf01f605d43aca41c/numcodecs-0.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2afe73d5ebaf9ca0cd5c83aad945da80d29a33d860a80d43a7248491d8813ff", size = 1169186, upload-time = "2025-09-18T18:54:37.838Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a0/0de627baeb43e2045a3d4b3de99bf8b69af329a33df1ed4cda468d70c1fb/numcodecs-0.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913f08194d82dcb37594e6705e6d4ae6ccd4b6571500b832fb3e4a155de1dfe8", size = 8341668, upload-time = "2025-09-18T18:54:39.444Z" }, - { url = "https://files.pythonhosted.org/packages/b6/0f/49d1f74a216149240c4b9403218111f11670bd11af0919fda357bb056bf2/numcodecs-0.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a7f1cae9eb18b85709af46570bf9c60056e7155c4c8f610e8080c68124d0e5", size = 8866611, upload-time = "2025-09-18T18:54:41.168Z" }, - { url = "https://files.pythonhosted.org/packages/aa/51/03aece765108fe247717105b5131856546e5428f22a56a14ffdebd017424/numcodecs-0.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:f7bb7f2c46eb7ec8a1c5f8d8fe1a72c222256dd6d6df5af9eaac7a6b905f3575", size = 806787, upload-time = "2025-09-18T18:54:42.78Z" }, - { url = "https://files.pythonhosted.org/packages/0d/78/e4b34803a3aa1d0769919695de4b133266c18c80c474d32ebc462fa1a9bd/numcodecs-0.16.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c77454d92941a335d148b0b822f5d4783103f392774d5d76283bbf7f21b49529", size = 1681108, upload-time = "2025-09-18T18:54:43.856Z" }, - { url = "https://files.pythonhosted.org/packages/25/cf/ca36f463b03a4097767d2a1c1b72f31810e8c6384e9449dd9b925203783c/numcodecs-0.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:270e7a33ee96bdf5c957acf25a2487002a233811a125a155c400c2f036b69c73", size = 1165589, upload-time = "2025-09-18T18:54:44.954Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ae/670260c3c4b5ed34a0674561355f3d4ce7fcbdf09a667e5bc841526d271c/numcodecs-0.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f43fa4a347d1dba775c4506a1c9b15b90144c258433b81f79f1c1b1a990db5", size = 8316365, upload-time = "2025-09-18T18:54:46.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/fa/94e022419c751a60ff0f53642ebae5ef81ed3cc3640f958588e3ad3dc18d/numcodecs-0.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44869ef564a50aa545215c6a0d42ba5bbc34e9715523fb2336ada3d1fb2b331d", size = 8846228, upload-time = "2025-09-18T18:54:47.858Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/f23733589f3e059bf8589508acd23ffeec230bdf179f138a54f5ab16e0a6/numcodecs-0.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:9aae6996172ba10c5f5111b2998709071b5aeba6b58b1ee0b26b61ed6aa7f2f4", size = 806260, upload-time = "2025-09-18T18:54:49.41Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d5/d3536d06ac1e5fb848a3186958204082b68b106364c9a3669652dd786731/numcodecs-0.16.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:947406b01c20f2ce7ce2e631e7f21b782e8a9d4b57b374a41c9e7b1341a8f3a2", size = 1677129, upload-time = "2025-09-18T18:54:50.5Z" }, - { url = "https://files.pythonhosted.org/packages/e1/fd/b0513a3428dc2b38ec85eea771703ae69c49f09b9650d6c44c9105c80073/numcodecs-0.16.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7cf50e351398a34b45817974c411527629e88937b7683695e276afd65da6ed6f", size = 1159058, upload-time = "2025-09-18T18:54:51.675Z" }, - { url = "https://files.pythonhosted.org/packages/98/05/b7c127283cfb154a97abb284363825401b69302d71a28608af66f73257cc/numcodecs-0.16.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7938502fcc060ed9543814f38ca67048b33d7bd2667756e36e6b1060455b17e", size = 8260987, upload-time = "2025-09-18T18:54:52.883Z" }, - { url = "https://files.pythonhosted.org/packages/ff/46/320d960aff884bc63abaaf846ffa3de4803e83e8070b6f84c5688464839c/numcodecs-0.16.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:010d628c95be1214536fb22c0df4ced58da954b404b1fcb25ddebf64e4a3f7f3", size = 8805295, upload-time = "2025-09-18T18:54:54.698Z" }, - { url = "https://files.pythonhosted.org/packages/31/ae/acc2e0f1f49ba32afa2174578f170673139248ef86f77e334f2619133867/numcodecs-0.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:e83115e3c32de798c7b7164503e06aae9f9746c1cef564d029616eb44bd6cd90", size = 803204, upload-time = "2025-09-18T18:54:56.192Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f6/48/6188e359b90a9d8a1850f2bc888c023e66f4a8b2b496820babbea414f008/numcodecs-0.16.3.tar.gz", hash = "sha256:53d705865faaf0a7927c973af3777532001c8fbb653de119c1e844608614d799", size = 6275704 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/cc/917a85972537498f2bbd7914047efc98babc8667587ceb9dcb228378978a/numcodecs-0.16.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:95c9f2a49bef10cf91ad614a761cba9bfe96656b60c12540e1080de5d909b4ca", size = 1642356 }, + { url = "https://files.pythonhosted.org/packages/3b/6a/64c25a089e8537441fe67c09ecb7f3f7fb5d98cd04faf01f605d43aca41c/numcodecs-0.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2afe73d5ebaf9ca0cd5c83aad945da80d29a33d860a80d43a7248491d8813ff", size = 1169186 }, + { url = "https://files.pythonhosted.org/packages/d8/a0/0de627baeb43e2045a3d4b3de99bf8b69af329a33df1ed4cda468d70c1fb/numcodecs-0.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913f08194d82dcb37594e6705e6d4ae6ccd4b6571500b832fb3e4a155de1dfe8", size = 8341668 }, + { url = "https://files.pythonhosted.org/packages/b6/0f/49d1f74a216149240c4b9403218111f11670bd11af0919fda357bb056bf2/numcodecs-0.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a7f1cae9eb18b85709af46570bf9c60056e7155c4c8f610e8080c68124d0e5", size = 8866611 }, + { url = "https://files.pythonhosted.org/packages/aa/51/03aece765108fe247717105b5131856546e5428f22a56a14ffdebd017424/numcodecs-0.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:f7bb7f2c46eb7ec8a1c5f8d8fe1a72c222256dd6d6df5af9eaac7a6b905f3575", size = 806787 }, + { url = "https://files.pythonhosted.org/packages/0d/78/e4b34803a3aa1d0769919695de4b133266c18c80c474d32ebc462fa1a9bd/numcodecs-0.16.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c77454d92941a335d148b0b822f5d4783103f392774d5d76283bbf7f21b49529", size = 1681108 }, + { url = "https://files.pythonhosted.org/packages/25/cf/ca36f463b03a4097767d2a1c1b72f31810e8c6384e9449dd9b925203783c/numcodecs-0.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:270e7a33ee96bdf5c957acf25a2487002a233811a125a155c400c2f036b69c73", size = 1165589 }, + { url = "https://files.pythonhosted.org/packages/ed/ae/670260c3c4b5ed34a0674561355f3d4ce7fcbdf09a667e5bc841526d271c/numcodecs-0.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f43fa4a347d1dba775c4506a1c9b15b90144c258433b81f79f1c1b1a990db5", size = 8316365 }, + { url = "https://files.pythonhosted.org/packages/bb/fa/94e022419c751a60ff0f53642ebae5ef81ed3cc3640f958588e3ad3dc18d/numcodecs-0.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44869ef564a50aa545215c6a0d42ba5bbc34e9715523fb2336ada3d1fb2b331d", size = 8846228 }, + { url = "https://files.pythonhosted.org/packages/71/60/f23733589f3e059bf8589508acd23ffeec230bdf179f138a54f5ab16e0a6/numcodecs-0.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:9aae6996172ba10c5f5111b2998709071b5aeba6b58b1ee0b26b61ed6aa7f2f4", size = 806260 }, + { url = "https://files.pythonhosted.org/packages/3c/d5/d3536d06ac1e5fb848a3186958204082b68b106364c9a3669652dd786731/numcodecs-0.16.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:947406b01c20f2ce7ce2e631e7f21b782e8a9d4b57b374a41c9e7b1341a8f3a2", size = 1677129 }, + { url = "https://files.pythonhosted.org/packages/e1/fd/b0513a3428dc2b38ec85eea771703ae69c49f09b9650d6c44c9105c80073/numcodecs-0.16.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7cf50e351398a34b45817974c411527629e88937b7683695e276afd65da6ed6f", size = 1159058 }, + { url = "https://files.pythonhosted.org/packages/98/05/b7c127283cfb154a97abb284363825401b69302d71a28608af66f73257cc/numcodecs-0.16.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7938502fcc060ed9543814f38ca67048b33d7bd2667756e36e6b1060455b17e", size = 8260987 }, + { url = "https://files.pythonhosted.org/packages/ff/46/320d960aff884bc63abaaf846ffa3de4803e83e8070b6f84c5688464839c/numcodecs-0.16.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:010d628c95be1214536fb22c0df4ced58da954b404b1fcb25ddebf64e4a3f7f3", size = 8805295 }, + { url = "https://files.pythonhosted.org/packages/31/ae/acc2e0f1f49ba32afa2174578f170673139248ef86f77e334f2619133867/numcodecs-0.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:e83115e3c32de798c7b7164503e06aae9f9746c1cef564d029616eb44bd6cd90", size = 803204 }, ] [package.optional-dependencies] @@ -1794,81 +1794,81 @@ crc32c = [ name = "numpy" version = "2.3.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, - { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, - { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, - { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, - { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, - { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, - { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, - { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, - { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, - { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, - { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, - { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641 }, + { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324 }, + { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872 }, + { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148 }, + { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282 }, + { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903 }, + { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672 }, + { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896 }, + { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608 }, + { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442 }, + { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555 }, + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873 }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838 }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378 }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559 }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702 }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086 }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985 }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976 }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274 }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922 }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667 }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251 }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652 }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172 }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990 }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902 }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430 }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551 }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275 }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637 }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090 }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710 }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292 }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897 }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391 }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275 }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855 }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359 }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374 }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587 }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940 }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341 }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507 }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706 }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507 }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049 }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603 }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696 }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350 }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190 }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749 }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432 }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388 }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651 }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503 }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612 }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042 }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502 }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962 }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054 }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613 }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147 }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806 }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760 }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459 }, + { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689 }, + { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053 }, + { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635 }, + { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770 }, + { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768 }, + { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263 }, + { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213 }, ] [[package]] @@ -1876,7 +1876,7 @@ name = "nvidia-cublas-cu12" version = "12.8.4.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { 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 }, ] [[package]] @@ -1884,7 +1884,7 @@ name = "nvidia-cuda-cupti-cu12" version = "12.8.90" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { 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 }, ] [[package]] @@ -1892,7 +1892,7 @@ name = "nvidia-cuda-nvrtc-cu12" version = "12.8.93" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { 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 }, ] [[package]] @@ -1900,7 +1900,7 @@ name = "nvidia-cuda-runtime-cu12" version = "12.8.90" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { 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 }, ] [[package]] @@ -1911,7 +1911,7 @@ dependencies = [ { name = "nvidia-cublas-cu12" }, ] 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/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467 }, ] [[package]] @@ -1922,7 +1922,7 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12" }, ] 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/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 }, ] [[package]] @@ -1930,7 +1930,7 @@ name = "nvidia-cufile-cu12" version = "1.13.1.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { 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 }, ] [[package]] @@ -1938,7 +1938,7 @@ name = "nvidia-curand-cu12" version = "10.3.9.90" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { 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 }, ] [[package]] @@ -1951,7 +1951,7 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12" }, ] 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/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905 }, ] [[package]] @@ -1962,7 +1962,7 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12" }, ] 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/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 }, ] [[package]] @@ -1970,7 +1970,7 @@ name = "nvidia-cusparselt-cu12" version = "0.7.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { 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 }, ] [[package]] @@ -1978,7 +1978,7 @@ name = "nvidia-nccl-cu12" version = "2.27.5" 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/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 }, ] [[package]] @@ -1986,7 +1986,7 @@ name = "nvidia-nvjitlink-cu12" version = "12.8.93" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, + { 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 }, ] [[package]] @@ -1994,7 +1994,7 @@ name = "nvidia-nvshmem-cu12" version = "3.3.20" 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/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 }, ] [[package]] @@ -2002,7 +2002,7 @@ name = "nvidia-nvtx-cu12" version = "12.8.90" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, + { 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 }, ] [[package]] @@ -2018,45 +2018,45 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/81/08f90f194eed78178064a9383432eca95611e2c5331e7b01e2418ce4b15a/optuna-4.6.0.tar.gz", hash = "sha256:89e38c2447c7f793a726617b8043f01e31f0bad54855040db17eb3b49404a369", size = 477444, upload-time = "2025-11-10T05:14:30.151Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/81/08f90f194eed78178064a9383432eca95611e2c5331e7b01e2418ce4b15a/optuna-4.6.0.tar.gz", hash = "sha256:89e38c2447c7f793a726617b8043f01e31f0bad54855040db17eb3b49404a369", size = 477444 } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/de/3d8455b08cb6312f8cc46aacdf16c71d4d881a1db4a4140fc5ef31108422/optuna-4.6.0-py3-none-any.whl", hash = "sha256:4c3a9facdef2b2dd7e3e2a8ae3697effa70fae4056fcf3425cfc6f5a40feb069", size = 404708, upload-time = "2025-11-10T05:14:28.6Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/3d8455b08cb6312f8cc46aacdf16c71d4d881a1db4a4140fc5ef31108422/optuna-4.6.0-py3-none-any.whl", hash = "sha256:4c3a9facdef2b2dd7e3e2a8ae3697effa70fae4056fcf3425cfc6f5a40feb069", size = 404708 }, ] [[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 = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } 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/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, ] [[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.5" 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/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205 } 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/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668 }, ] [[package]] @@ -2067,9 +2067,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]] @@ -2079,96 +2079,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.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, - { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, - { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, - { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, - { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, - { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, - { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, - { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, - { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, - { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, - { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" }, - { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" }, - { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" }, - { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" }, - { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" }, - { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" }, - { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" }, - { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" }, - { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" }, - { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" }, - { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" }, - { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" }, - { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" }, - { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" }, - { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" }, - { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" }, - { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" }, - { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" }, - { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" }, - { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" }, - { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" }, - { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" }, - { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" }, - { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" }, - { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" }, - { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" }, - { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" }, - { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" }, - { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" }, - { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" }, - { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" }, - { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" }, - { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" }, - { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" }, - { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" }, - { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, - { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, - { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, - { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798 }, + { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589 }, + { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472 }, + { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887 }, + { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964 }, + { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756 }, + { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075 }, + { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955 }, + { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440 }, + { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256 }, + { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025 }, + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377 }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343 }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981 }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399 }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740 }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201 }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334 }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162 }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769 }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107 }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012 }, + { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493 }, + { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461 }, + { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912 }, + { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132 }, + { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099 }, + { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808 }, + { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804 }, + { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553 }, + { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729 }, + { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789 }, + { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917 }, + { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391 }, + { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477 }, + { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918 }, + { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406 }, + { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218 }, + { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564 }, + { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260 }, + { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248 }, + { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043 }, + { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915 }, + { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998 }, + { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201 }, + { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165 }, + { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834 }, + { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531 }, + { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554 }, + { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812 }, + { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689 }, + { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186 }, + { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308 }, + { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222 }, + { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657 }, + { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482 }, + { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416 }, + { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584 }, + { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621 }, + { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916 }, + { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836 }, + { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092 }, + { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158 }, + { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882 }, + { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001 }, + { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146 }, + { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344 }, + { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864 }, + { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911 }, + { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045 }, + { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282 }, + { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630 }, + { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068 }, + { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994 }, + { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639 }, + { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839 }, + { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505 }, + { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654 }, + { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850 }, ] [[package]] @@ -2181,27 +2181,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.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632 } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651 }, ] [[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]] @@ -2215,18 +2215,18 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/49/7845c2d7bf6474efd8e27905b51b11e6ce411708c91e829b93f324de9929/pre_commit-4.4.0.tar.gz", hash = "sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15", size = 197501, upload-time = "2025-11-08T21:12:11.607Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/49/7845c2d7bf6474efd8e27905b51b11e6ce411708c91e829b93f324de9929/pre_commit-4.4.0.tar.gz", hash = "sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15", size = 197501 } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813", size = 226049, upload-time = "2025-11-08T21:12:10.228Z" }, + { url = "https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813", size = 226049 }, ] [[package]] name = "prometheus-client" version = "0.23.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145 }, ] [[package]] @@ -2236,95 +2236,95 @@ 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.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/03/a1440979a3f74f16cab3b75b0da1a1a7f922d56a8ddea96092391998edc0/protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b", size = 443432, upload-time = "2025-11-13T16:44:18.895Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/03/a1440979a3f74f16cab3b75b0da1a1a7f922d56a8ddea96092391998edc0/protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b", size = 443432 } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/f1/446a9bbd2c60772ca36556bac8bfde40eceb28d9cc7838755bc41e001d8f/protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b", size = 425593, upload-time = "2025-11-13T16:44:06.275Z" }, - { url = "https://files.pythonhosted.org/packages/a6/79/8780a378c650e3df849b73de8b13cf5412f521ca2ff9b78a45c247029440/protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed", size = 436883, upload-time = "2025-11-13T16:44:09.222Z" }, - { url = "https://files.pythonhosted.org/packages/cd/93/26213ff72b103ae55bb0d73e7fb91ea570ef407c3ab4fd2f1f27cac16044/protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490", size = 427522, upload-time = "2025-11-13T16:44:10.475Z" }, - { url = "https://files.pythonhosted.org/packages/c2/32/df4a35247923393aa6b887c3b3244a8c941c32a25681775f96e2b418f90e/protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178", size = 324445, upload-time = "2025-11-13T16:44:11.869Z" }, - { url = "https://files.pythonhosted.org/packages/8e/d0/d796e419e2ec93d2f3fa44888861c3f88f722cde02b7c3488fcc6a166820/protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53", size = 339161, upload-time = "2025-11-13T16:44:12.778Z" }, - { url = "https://files.pythonhosted.org/packages/1d/2a/3c5f05a4af06649547027d288747f68525755de692a26a7720dced3652c0/protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1", size = 323171, upload-time = "2025-11-13T16:44:14.035Z" }, - { url = "https://files.pythonhosted.org/packages/08/b4/46310463b4f6ceef310f8348786f3cff181cea671578e3d9743ba61a459e/protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa", size = 170477, upload-time = "2025-11-13T16:44:17.633Z" }, + { url = "https://files.pythonhosted.org/packages/06/f1/446a9bbd2c60772ca36556bac8bfde40eceb28d9cc7838755bc41e001d8f/protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b", size = 425593 }, + { url = "https://files.pythonhosted.org/packages/a6/79/8780a378c650e3df849b73de8b13cf5412f521ca2ff9b78a45c247029440/protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed", size = 436883 }, + { url = "https://files.pythonhosted.org/packages/cd/93/26213ff72b103ae55bb0d73e7fb91ea570ef407c3ab4fd2f1f27cac16044/protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490", size = 427522 }, + { url = "https://files.pythonhosted.org/packages/c2/32/df4a35247923393aa6b887c3b3244a8c941c32a25681775f96e2b418f90e/protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178", size = 324445 }, + { url = "https://files.pythonhosted.org/packages/8e/d0/d796e419e2ec93d2f3fa44888861c3f88f722cde02b7c3488fcc6a166820/protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53", size = 339161 }, + { url = "https://files.pythonhosted.org/packages/1d/2a/3c5f05a4af06649547027d288747f68525755de692a26a7720dced3652c0/protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1", size = 323171 }, + { url = "https://files.pythonhosted.org/packages/08/b4/46310463b4f6ceef310f8348786f3cff181cea671578e3d9743ba61a459e/protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa", size = 170477 }, ] [[package]] name = "psutil" version = "7.1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, - { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, - { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, - { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, - { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, - { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, - { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, - { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, - { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, - { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751 }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368 }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134 }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904 }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642 }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518 }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843 }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369 }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210 }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182 }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466 }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756 }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359 }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171 }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261 }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635 }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633 }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608 }, ] [[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 = "2.23" 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/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734 } 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/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140 }, ] [[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.2.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890 }, ] [[package]] @@ -2338,27 +2338,27 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668 }, ] [[package]] name = "python-box" version = "7.3.2" 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/29/f7/635eed8c500adf26208e86e985bbffb6ff039cd8950e3a4749ceca904218/python_box-7.3.2.tar.gz", hash = "sha256:028b9917129e67f311932d93347b8a4f1b500d7a5a2870ee3c035f4e7b19403b", size = 45771 } 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/1e/3f/133619c00d8a9d4f86efd8626c0e4ec356c8b8dabac66da18dac5cfaf70c/python_box-7.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32163b1cb151883de0da62b0cd3572610dc72ccf0762f2447baf1d2562e25bea", size = 1834122 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/d4/e2/6cdc8649381ae14def88c3e2e93d5b8b17a622a95896e0d1c92861270b7d/python_box-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:488f0fba9a6416c3334b602366dcd92825adb0811e07e03753dfcf0ed79cd6f7", size = 1232328 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/e4/6b/32484b2a3cd2fb5e5f56bfb53a4537d93a4d2014ccf7fc0c0017fa6f65e9/python_box-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9eead914b9fb7d98a1473f5027dcfe27d26b3a10ffa33b9ba22cf948a23cd280", size = 1211252 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/43/90/72367e03033c11a5e82676ee389b572bf136647ff4e3081557392b37e1ad/python_box-7.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e143295f74d47a9ab24562ead2375c9be10629599b57f2e86717d3fff60f82a9", size = 1206740 }, + { url = "https://files.pythonhosted.org/packages/37/13/8a990c6e2b6cc12700dce16f3cb383324e6d9a30f604eca22a2fdf84c923/python_box-7.3.2-py3-none-any.whl", hash = "sha256:fd7d74d5a848623f93b5221fd9fb00b8c00ff0e130fa87f396277aa188659c92", size = 29479 }, ] [[package]] @@ -2368,87 +2368,87 @@ 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.2" 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/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669 } 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/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23", size = 2050304 }, + { url = "https://files.pythonhosted.org/packages/02/4e/1098484e042c9485f56f16eb2b69b43b874bd526044ee401512234cf9e04/pywinpty-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:99fdd9b455f0ad6419aba6731a7a0d2f88ced83c3c94a80ff9533d95fa8d8a9e", size = 2050391 }, + { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057 }, + { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874 }, + { url = "https://files.pythonhosted.org/packages/ca/15/f12c6055e2d7a617d4d5820e8ac4ceaff849da4cb124640ef5116a230771/pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a", size = 2050386 }, + { url = "https://files.pythonhosted.org/packages/de/24/c6907c5bb06043df98ad6a0a0ff5db2e0affcecbc3b15c42404393a3f72a/pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767", size = 2049834 }, ] [[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]] @@ -2458,55 +2458,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]] @@ -2535,6 +2535,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "hdf5plugin" }, { name = "jupyterlab" }, { name = "packaging" }, { name = "pre-commit" }, @@ -2569,6 +2570,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "hdf5plugin", specifier = ">=6.0.0" }, { name = "jupyterlab", specifier = ">=4.4.0" }, { name = "packaging", specifier = ">=24.2" }, { name = "pre-commit", specifier = ">=4.2.0" }, @@ -2587,9 +2589,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]] @@ -2602,9 +2604,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]] @@ -2614,18 +2616,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]] @@ -2635,9 +2637,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]] @@ -2652,163 +2654,163 @@ dependencies = [ { name = "python-dateutil" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/4c/f1560842f8b9aba82973b670eb9a4e57cdfca0d998fd106140c6c74ae1e0/rosettasciio-0.10.0.tar.gz", hash = "sha256:0082ad325029fb815d1da8015f9069df90e979114fcba84bda94a63c2e63039b", size = 1186926, upload-time = "2025-07-27T18:12:15.59Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/db/1d65725ed9fbd1ed9ce89d0bf38aed0a98d5683db13e898f25a2d8b239ca/rosettasciio-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:718ec836f2cf884c584be86cd0da188419692d7c32915665738d46ad6b6e3a37", size = 795531, upload-time = "2025-07-27T18:11:37.418Z" }, - { url = "https://files.pythonhosted.org/packages/10/ef/5ffde08333c095978c231dd488e7f05d97ee5187cabe5670df83f96d8403/rosettasciio-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f75142a4bfa4b85384efdf1d69af21ec8d9a83e17dfd3e577166c3a87a796420", size = 789939, upload-time = "2025-07-27T18:11:38.875Z" }, - { url = "https://files.pythonhosted.org/packages/84/ba/bfea3c8aa25b313d01e955821130bf1b4e5cc1eb48ee33fd0ef7db0cbf37/rosettasciio-0.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21a5d7acf1501e979f8439995acc8edb2d24d42b9dc5ecfbe498c624cd84d4ee", size = 1255637, upload-time = "2025-07-27T18:11:40.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/54/dbc75dcd3ff0fd6f0d631d636d6d2410c6c4feac0fd8507d9d133fc4715d/rosettasciio-0.10.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b2ef07b84dfb8a9cb3dfa0cf4e071c6bee2a70cd173293c85522b791e7ceb07", size = 1258334, upload-time = "2025-07-27T18:11:41.872Z" }, - { url = "https://files.pythonhosted.org/packages/ef/77/e8b57c6fde7a01af588448ee7be43b7f29fdf265d00c646eb7b8196653a8/rosettasciio-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1fb511579431dd1c7b7aed6626f469ac2b77ab45f6728c834c3d054913ac71f", size = 787920, upload-time = "2025-07-27T18:11:43.096Z" }, - { url = "https://files.pythonhosted.org/packages/e6/4f/94b8166640de28b7f2c8a98664288bfc674b22a5ba49d9953bb2af1dd932/rosettasciio-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3441354fc7cb1d92c5e0bf8eccdcd26a5abc429299625a820087f95daed4cfa2", size = 796549, upload-time = "2025-07-27T18:11:44.407Z" }, - { url = "https://files.pythonhosted.org/packages/f1/48/af8159fd1b9dfeb82a47c84587a81d1d98974f05f85e764952862a5206de/rosettasciio-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96e08cd6a373dc794edc268da4c644b3fd93927e2324c5a3721c3dd71d3b592c", size = 789810, upload-time = "2025-07-27T18:11:46.039Z" }, - { url = "https://files.pythonhosted.org/packages/99/9d/5cf650f4c39c649e3c09ff885b6a471aa3a0c57e52e1a538f88156083ca8/rosettasciio-0.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e056f143c5c7e41ca55ee5dc59b5efaa2f14ace6f6f8039d5102f156385f2c9", size = 1257517, upload-time = "2025-07-27T18:11:47.409Z" }, - { url = "https://files.pythonhosted.org/packages/2d/49/d0484376a7724aa8bbc3628d6e4331f0593dcbee3efd3ba193c81cc380f6/rosettasciio-0.10.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:465811041da11db7259f8fde1c72fb6838aaa14678c000feed8719c4f8d2dfe0", size = 1263282, upload-time = "2025-07-27T18:11:49.011Z" }, - { url = "https://files.pythonhosted.org/packages/94/99/a70aeef2b316de92a315c8b6f5d3153fc1b36bbaedc96ad3caf70b45606c/rosettasciio-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:fffc27d62c14e63818d8928d55458fe660f0153c406b4294e3cd4cbd63e1bea7", size = 788436, upload-time = "2025-07-27T18:11:51.052Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5f/ef4db517d63a657a9c1d626d65e9c843850fd8da8a364cfb674abe1e6fc4/rosettasciio-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8627f64c027361face099eaad76ea02eb7cfce4f7c66174826f36e236204e970", size = 795544, upload-time = "2025-07-27T18:11:52.664Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/d1590ce68aed0cf1c8e76cd52c1077a5a92f4e14c97d2a7456871296899c/rosettasciio-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c5ca0b541c5744b9b9559f5e002b5f2362d4cb09a002b2650a9d358025477261", size = 788949, upload-time = "2025-07-27T18:11:53.906Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/680259cc71741d9fa0c418230653177cf9bea4f554014a26354ab8079380/rosettasciio-0.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccf69c1d3e141bca2feca2275158a796f27299888a8ac7943eceec9779f1b811", size = 1249455, upload-time = "2025-07-27T18:11:55.127Z" }, - { url = "https://files.pythonhosted.org/packages/24/4f/8b668c9312ab4d2c87a342e4d947a2c0c10cadda44391bff1154df5a623e/rosettasciio-0.10.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a8929f285c2cfdebb5ae180dfe80e90ada916e37063e83d8ea7840be7851ddb", size = 1259454, upload-time = "2025-07-27T18:11:56.502Z" }, - { url = "https://files.pythonhosted.org/packages/ba/5f/07ba8f617b121431b0d8f45f946bd9602256fb1f1d43c275f21fecfe9f27/rosettasciio-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c802e0292537cb0f4878eca631d227183562a7e13c2449fe1e6f23b4eae808ff", size = 788185, upload-time = "2025-07-27T18:11:57.776Z" }, - { url = "https://files.pythonhosted.org/packages/40/2f/4515d05d896b13410c379eb21a6090c61fd654956fae0f54fce108a7379a/rosettasciio-0.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a8ab4c344987364c479232001d2e072f00a855fe3d385cbcbac68641a0c2f802", size = 800276, upload-time = "2025-07-27T18:11:59.083Z" }, - { url = "https://files.pythonhosted.org/packages/57/d7/2bd8a60a7758d5a894ae59192846bb72a68bd773b24daed9ea8c737c41bf/rosettasciio-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e8bbf513d81e05242b6ee5d35d58ba06232431990adaff6473bd942518cf2b82", size = 795978, upload-time = "2025-07-27T18:12:00.34Z" }, - { url = "https://files.pythonhosted.org/packages/35/54/dae525b83a98b27fbabe2152045d990b4baa858522024196c5e47d06bbc1/rosettasciio-0.10.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b34b629ef130aec286062753b44d4e4d73caf1a4d9aba3530ec3801bd49755c", size = 1265997, upload-time = "2025-07-27T18:12:01.634Z" }, - { url = "https://files.pythonhosted.org/packages/06/ae/a5969d089adc71df61451af8665df7e30a34957a0ebdc2614c449d2c37cf/rosettasciio-0.10.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:365750bd177c7cff56f88b9f8cf9b59d2c99b52e7d38db142282996e25e552c2", size = 1255782, upload-time = "2025-07-27T18:12:03.883Z" }, - { url = "https://files.pythonhosted.org/packages/d2/38/7e9d8eb167419d9e8b34433b698532b88c4b06ed38cd6723f7e702260f9f/rosettasciio-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a16dbadb662290be0d4ea0a2496109cff4484c155fd12eb6b57345e64d9015d9", size = 798624, upload-time = "2025-07-27T18:12:05.255Z" }, - { url = "https://files.pythonhosted.org/packages/92/a6/a4cff62b7346daca9dfdd1d08c1aed5e0c27283845465b6b5c797fb41713/rosettasciio-0.10.0-py3-none-any.whl", hash = "sha256:a4e353365972865e915b7a689d2f9ba38a6f94aec8cd19c2e35513523b718b1f", size = 550949, upload-time = "2025-07-27T18:12:13.987Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cc/4c/f1560842f8b9aba82973b670eb9a4e57cdfca0d998fd106140c6c74ae1e0/rosettasciio-0.10.0.tar.gz", hash = "sha256:0082ad325029fb815d1da8015f9069df90e979114fcba84bda94a63c2e63039b", size = 1186926 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/db/1d65725ed9fbd1ed9ce89d0bf38aed0a98d5683db13e898f25a2d8b239ca/rosettasciio-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:718ec836f2cf884c584be86cd0da188419692d7c32915665738d46ad6b6e3a37", size = 795531 }, + { url = "https://files.pythonhosted.org/packages/10/ef/5ffde08333c095978c231dd488e7f05d97ee5187cabe5670df83f96d8403/rosettasciio-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f75142a4bfa4b85384efdf1d69af21ec8d9a83e17dfd3e577166c3a87a796420", size = 789939 }, + { url = "https://files.pythonhosted.org/packages/84/ba/bfea3c8aa25b313d01e955821130bf1b4e5cc1eb48ee33fd0ef7db0cbf37/rosettasciio-0.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21a5d7acf1501e979f8439995acc8edb2d24d42b9dc5ecfbe498c624cd84d4ee", size = 1255637 }, + { url = "https://files.pythonhosted.org/packages/6e/54/dbc75dcd3ff0fd6f0d631d636d6d2410c6c4feac0fd8507d9d133fc4715d/rosettasciio-0.10.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b2ef07b84dfb8a9cb3dfa0cf4e071c6bee2a70cd173293c85522b791e7ceb07", size = 1258334 }, + { url = "https://files.pythonhosted.org/packages/ef/77/e8b57c6fde7a01af588448ee7be43b7f29fdf265d00c646eb7b8196653a8/rosettasciio-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1fb511579431dd1c7b7aed6626f469ac2b77ab45f6728c834c3d054913ac71f", size = 787920 }, + { url = "https://files.pythonhosted.org/packages/e6/4f/94b8166640de28b7f2c8a98664288bfc674b22a5ba49d9953bb2af1dd932/rosettasciio-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3441354fc7cb1d92c5e0bf8eccdcd26a5abc429299625a820087f95daed4cfa2", size = 796549 }, + { url = "https://files.pythonhosted.org/packages/f1/48/af8159fd1b9dfeb82a47c84587a81d1d98974f05f85e764952862a5206de/rosettasciio-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96e08cd6a373dc794edc268da4c644b3fd93927e2324c5a3721c3dd71d3b592c", size = 789810 }, + { url = "https://files.pythonhosted.org/packages/99/9d/5cf650f4c39c649e3c09ff885b6a471aa3a0c57e52e1a538f88156083ca8/rosettasciio-0.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e056f143c5c7e41ca55ee5dc59b5efaa2f14ace6f6f8039d5102f156385f2c9", size = 1257517 }, + { url = "https://files.pythonhosted.org/packages/2d/49/d0484376a7724aa8bbc3628d6e4331f0593dcbee3efd3ba193c81cc380f6/rosettasciio-0.10.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:465811041da11db7259f8fde1c72fb6838aaa14678c000feed8719c4f8d2dfe0", size = 1263282 }, + { url = "https://files.pythonhosted.org/packages/94/99/a70aeef2b316de92a315c8b6f5d3153fc1b36bbaedc96ad3caf70b45606c/rosettasciio-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:fffc27d62c14e63818d8928d55458fe660f0153c406b4294e3cd4cbd63e1bea7", size = 788436 }, + { url = "https://files.pythonhosted.org/packages/bc/5f/ef4db517d63a657a9c1d626d65e9c843850fd8da8a364cfb674abe1e6fc4/rosettasciio-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8627f64c027361face099eaad76ea02eb7cfce4f7c66174826f36e236204e970", size = 795544 }, + { url = "https://files.pythonhosted.org/packages/90/45/d1590ce68aed0cf1c8e76cd52c1077a5a92f4e14c97d2a7456871296899c/rosettasciio-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c5ca0b541c5744b9b9559f5e002b5f2362d4cb09a002b2650a9d358025477261", size = 788949 }, + { url = "https://files.pythonhosted.org/packages/b0/6f/680259cc71741d9fa0c418230653177cf9bea4f554014a26354ab8079380/rosettasciio-0.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccf69c1d3e141bca2feca2275158a796f27299888a8ac7943eceec9779f1b811", size = 1249455 }, + { url = "https://files.pythonhosted.org/packages/24/4f/8b668c9312ab4d2c87a342e4d947a2c0c10cadda44391bff1154df5a623e/rosettasciio-0.10.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a8929f285c2cfdebb5ae180dfe80e90ada916e37063e83d8ea7840be7851ddb", size = 1259454 }, + { url = "https://files.pythonhosted.org/packages/ba/5f/07ba8f617b121431b0d8f45f946bd9602256fb1f1d43c275f21fecfe9f27/rosettasciio-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c802e0292537cb0f4878eca631d227183562a7e13c2449fe1e6f23b4eae808ff", size = 788185 }, + { url = "https://files.pythonhosted.org/packages/40/2f/4515d05d896b13410c379eb21a6090c61fd654956fae0f54fce108a7379a/rosettasciio-0.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a8ab4c344987364c479232001d2e072f00a855fe3d385cbcbac68641a0c2f802", size = 800276 }, + { url = "https://files.pythonhosted.org/packages/57/d7/2bd8a60a7758d5a894ae59192846bb72a68bd773b24daed9ea8c737c41bf/rosettasciio-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e8bbf513d81e05242b6ee5d35d58ba06232431990adaff6473bd942518cf2b82", size = 795978 }, + { url = "https://files.pythonhosted.org/packages/35/54/dae525b83a98b27fbabe2152045d990b4baa858522024196c5e47d06bbc1/rosettasciio-0.10.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b34b629ef130aec286062753b44d4e4d73caf1a4d9aba3530ec3801bd49755c", size = 1265997 }, + { url = "https://files.pythonhosted.org/packages/06/ae/a5969d089adc71df61451af8665df7e30a34957a0ebdc2614c449d2c37cf/rosettasciio-0.10.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:365750bd177c7cff56f88b9f8cf9b59d2c99b52e7d38db142282996e25e552c2", size = 1255782 }, + { url = "https://files.pythonhosted.org/packages/d2/38/7e9d8eb167419d9e8b34433b698532b88c4b06ed38cd6723f7e702260f9f/rosettasciio-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a16dbadb662290be0d4ea0a2496109cff4484c155fd12eb6b57345e64d9015d9", size = 798624 }, + { url = "https://files.pythonhosted.org/packages/92/a6/a4cff62b7346daca9dfdd1d08c1aed5e0c27283845465b6b5c797fb41713/rosettasciio-0.10.0-py3-none-any.whl", hash = "sha256:a4e353365972865e915b7a689d2f9ba38a6f94aec8cd19c2e35513523b718b1f", size = 550949 }, ] [[package]] name = "rpds-py" version = "0.29.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/33/23b3b3419b6a3e0f559c7c0d2ca8fc1b9448382b25245033788785921332/rpds_py-0.29.0.tar.gz", hash = "sha256:fe55fe686908f50154d1dc599232016e50c243b438c3b7432f24e2895b0e5359", size = 69359, upload-time = "2025-11-16T14:50:39.532Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/ab/7fb95163a53ab122c74a7c42d2d2f012819af2cf3deb43fb0d5acf45cc1a/rpds_py-0.29.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b9c764a11fd637e0322a488560533112837f5334ffeb48b1be20f6d98a7b437", size = 372344, upload-time = "2025-11-16T14:47:57.279Z" }, - { url = "https://files.pythonhosted.org/packages/b3/45/f3c30084c03b0d0f918cb4c5ae2c20b0a148b51ba2b3f6456765b629bedd/rpds_py-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fd2164d73812026ce970d44c3ebd51e019d2a26a4425a5dcbdfa93a34abc383", size = 363041, upload-time = "2025-11-16T14:47:58.908Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e9/4d044a1662608c47a87cbb37b999d4d5af54c6d6ebdda93a4d8bbf8b2a10/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a097b7f7f7274164566ae90a221fd725363c0e9d243e2e9ed43d195ccc5495c", size = 391775, upload-time = "2025-11-16T14:48:00.197Z" }, - { url = "https://files.pythonhosted.org/packages/50/c9/7616d3ace4e6731aeb6e3cd85123e03aec58e439044e214b9c5c60fd8eb1/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cdc0490374e31cedefefaa1520d5fe38e82fde8748cbc926e7284574c714d6b", size = 405624, upload-time = "2025-11-16T14:48:01.496Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e2/6d7d6941ca0843609fd2d72c966a438d6f22617baf22d46c3d2156c31350/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89ca2e673ddd5bde9b386da9a0aac0cab0e76f40c8f0aaf0d6311b6bbf2aa311", size = 527894, upload-time = "2025-11-16T14:48:03.167Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f7/aee14dc2db61bb2ae1e3068f134ca9da5f28c586120889a70ff504bb026f/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5d9da3ff5af1ca1249b1adb8ef0573b94c76e6ae880ba1852f033bf429d4588", size = 412720, upload-time = "2025-11-16T14:48:04.413Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e2/2293f236e887c0360c2723d90c00d48dee296406994d6271faf1712e94ec/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8238d1d310283e87376c12f658b61e1ee23a14c0e54c7c0ce953efdbdc72deed", size = 392945, upload-time = "2025-11-16T14:48:06.252Z" }, - { url = "https://files.pythonhosted.org/packages/14/cd/ceea6147acd3bd1fd028d1975228f08ff19d62098078d5ec3eed49703797/rpds_py-0.29.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2d6fb2ad1c36f91c4646989811e84b1ea5e0c3cf9690b826b6e32b7965853a63", size = 406385, upload-time = "2025-11-16T14:48:07.575Z" }, - { url = "https://files.pythonhosted.org/packages/52/36/fe4dead19e45eb77a0524acfdbf51e6cda597b26fc5b6dddbff55fbbb1a5/rpds_py-0.29.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:534dc9df211387547267ccdb42253aa30527482acb38dd9b21c5c115d66a96d2", size = 423943, upload-time = "2025-11-16T14:48:10.175Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7b/4551510803b582fa4abbc8645441a2d15aa0c962c3b21ebb380b7e74f6a1/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d456e64724a075441e4ed648d7f154dc62e9aabff29bcdf723d0c00e9e1d352f", size = 574204, upload-time = "2025-11-16T14:48:11.499Z" }, - { url = "https://files.pythonhosted.org/packages/64/ba/071ccdd7b171e727a6ae079f02c26f75790b41555f12ca8f1151336d2124/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a738f2da2f565989401bd6fd0b15990a4d1523c6d7fe83f300b7e7d17212feca", size = 600587, upload-time = "2025-11-16T14:48:12.822Z" }, - { url = "https://files.pythonhosted.org/packages/03/09/96983d48c8cf5a1e03c7d9cc1f4b48266adfb858ae48c7c2ce978dbba349/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a110e14508fd26fd2e472bb541f37c209409876ba601cf57e739e87d8a53cf95", size = 562287, upload-time = "2025-11-16T14:48:14.108Z" }, - { url = "https://files.pythonhosted.org/packages/40/f0/8c01aaedc0fa92156f0391f39ea93b5952bc0ec56b897763858f95da8168/rpds_py-0.29.0-cp311-cp311-win32.whl", hash = "sha256:923248a56dd8d158389a28934f6f69ebf89f218ef96a6b216a9be6861804d3f4", size = 221394, upload-time = "2025-11-16T14:48:15.374Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a5/a8b21c54c7d234efdc83dc034a4d7cd9668e3613b6316876a29b49dece71/rpds_py-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:539eb77eb043afcc45314d1be09ea6d6cafb3addc73e0547c171c6d636957f60", size = 235713, upload-time = "2025-11-16T14:48:16.636Z" }, - { url = "https://files.pythonhosted.org/packages/a7/1f/df3c56219523947b1be402fa12e6323fe6d61d883cf35d6cb5d5bb6db9d9/rpds_py-0.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:bdb67151ea81fcf02d8f494703fb728d4d34d24556cbff5f417d74f6f5792e7c", size = 229157, upload-time = "2025-11-16T14:48:17.891Z" }, - { url = "https://files.pythonhosted.org/packages/3c/50/bc0e6e736d94e420df79be4deb5c9476b63165c87bb8f19ef75d100d21b3/rpds_py-0.29.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0891cfd8db43e085c0ab93ab7e9b0c8fee84780d436d3b266b113e51e79f954", size = 376000, upload-time = "2025-11-16T14:48:19.141Z" }, - { url = "https://files.pythonhosted.org/packages/3e/3a/46676277160f014ae95f24de53bed0e3b7ea66c235e7de0b9df7bd5d68ba/rpds_py-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3897924d3f9a0361472d884051f9a2460358f9a45b1d85a39a158d2f8f1ad71c", size = 360575, upload-time = "2025-11-16T14:48:20.443Z" }, - { url = "https://files.pythonhosted.org/packages/75/ba/411d414ed99ea1afdd185bbabeeaac00624bd1e4b22840b5e9967ade6337/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21deb8e0d1571508c6491ce5ea5e25669b1dd4adf1c9d64b6314842f708b5d", size = 392159, upload-time = "2025-11-16T14:48:22.12Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b1/e18aa3a331f705467a48d0296778dc1fea9d7f6cf675bd261f9a846c7e90/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9efe71687d6427737a0a2de9ca1c0a216510e6cd08925c44162be23ed7bed2d5", size = 410602, upload-time = "2025-11-16T14:48:23.563Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6c/04f27f0c9f2299274c76612ac9d2c36c5048bb2c6c2e52c38c60bf3868d9/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40f65470919dc189c833e86b2c4bd21bd355f98436a2cef9e0a9a92aebc8e57e", size = 515808, upload-time = "2025-11-16T14:48:24.949Z" }, - { url = "https://files.pythonhosted.org/packages/83/56/a8412aa464fb151f8bc0d91fb0bb888adc9039bd41c1c6ba8d94990d8cf8/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:def48ff59f181130f1a2cb7c517d16328efac3ec03951cca40c1dc2049747e83", size = 416015, upload-time = "2025-11-16T14:48:26.782Z" }, - { url = "https://files.pythonhosted.org/packages/04/4c/f9b8a05faca3d9e0a6397c90d13acb9307c9792b2bff621430c58b1d6e76/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7bd570be92695d89285a4b373006930715b78d96449f686af422debb4d3949", size = 395325, upload-time = "2025-11-16T14:48:28.055Z" }, - { url = "https://files.pythonhosted.org/packages/34/60/869f3bfbf8ed7b54f1ad9a5543e0fdffdd40b5a8f587fe300ee7b4f19340/rpds_py-0.29.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:5a572911cd053137bbff8e3a52d31c5d2dba51d3a67ad902629c70185f3f2181", size = 410160, upload-time = "2025-11-16T14:48:29.338Z" }, - { url = "https://files.pythonhosted.org/packages/91/aa/e5b496334e3aba4fe4c8a80187b89f3c1294c5c36f2a926da74338fa5a73/rpds_py-0.29.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d583d4403bcbf10cffc3ab5cee23d7643fcc960dff85973fd3c2d6c86e8dbb0c", size = 425309, upload-time = "2025-11-16T14:48:30.691Z" }, - { url = "https://files.pythonhosted.org/packages/85/68/4e24a34189751ceb6d66b28f18159922828dd84155876551f7ca5b25f14f/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:070befbb868f257d24c3bb350dbd6e2f645e83731f31264b19d7231dd5c396c7", size = 574644, upload-time = "2025-11-16T14:48:31.964Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/474a005ea4ea9c3b4f17b6108b6b13cebfc98ebaff11d6e1b193204b3a93/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fc935f6b20b0c9f919a8ff024739174522abd331978f750a74bb68abd117bd19", size = 601605, upload-time = "2025-11-16T14:48:33.252Z" }, - { url = "https://files.pythonhosted.org/packages/f4/b1/c56f6a9ab8c5f6bb5c65c4b5f8229167a3a525245b0773f2c0896686b64e/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c5a8ecaa44ce2d8d9d20a68a2483a74c07f05d72e94a4dff88906c8807e77b0", size = 564593, upload-time = "2025-11-16T14:48:34.643Z" }, - { url = "https://files.pythonhosted.org/packages/b3/13/0494cecce4848f68501e0a229432620b4b57022388b071eeff95f3e1e75b/rpds_py-0.29.0-cp312-cp312-win32.whl", hash = "sha256:ba5e1aeaf8dd6d8f6caba1f5539cddda87d511331714b7b5fc908b6cfc3636b7", size = 223853, upload-time = "2025-11-16T14:48:36.419Z" }, - { url = "https://files.pythonhosted.org/packages/1f/6a/51e9aeb444a00cdc520b032a28b07e5f8dc7bc328b57760c53e7f96997b4/rpds_py-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:b5f6134faf54b3cb83375db0f113506f8b7770785be1f95a631e7e2892101977", size = 239895, upload-time = "2025-11-16T14:48:37.956Z" }, - { url = "https://files.pythonhosted.org/packages/d1/d4/8bce56cdad1ab873e3f27cb31c6a51d8f384d66b022b820525b879f8bed1/rpds_py-0.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:b016eddf00dca7944721bf0cd85b6af7f6c4efaf83ee0b37c4133bd39757a8c7", size = 230321, upload-time = "2025-11-16T14:48:39.71Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d9/c5de60d9d371bbb186c3e9bf75f4fc5665e11117a25a06a6b2e0afb7380e/rpds_py-0.29.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1585648d0760b88292eecab5181f5651111a69d90eff35d6b78aa32998886a61", size = 375710, upload-time = "2025-11-16T14:48:41.063Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b3/0860cdd012291dc21272895ce107f1e98e335509ba986dd83d72658b82b9/rpds_py-0.29.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:521807963971a23996ddaf764c682b3e46459b3c58ccd79fefbe16718db43154", size = 360582, upload-time = "2025-11-16T14:48:42.423Z" }, - { url = "https://files.pythonhosted.org/packages/92/8a/a18c2f4a61b3407e56175f6aab6deacdf9d360191a3d6f38566e1eaf7266/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a8896986efaa243ab713c69e6491a4138410f0fe36f2f4c71e18bd5501e8014", size = 391172, upload-time = "2025-11-16T14:48:43.75Z" }, - { url = "https://files.pythonhosted.org/packages/fd/49/e93354258508c50abc15cdcd5fcf7ac4117f67bb6233ad7859f75e7372a0/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d24564a700ef41480a984c5ebed62b74e6ce5860429b98b1fede76049e953e6", size = 409586, upload-time = "2025-11-16T14:48:45.498Z" }, - { url = "https://files.pythonhosted.org/packages/5a/8d/a27860dae1c19a6bdc901f90c81f0d581df1943355802961a57cdb5b6cd1/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6596b93c010d386ae46c9fba9bfc9fc5965fa8228edeac51576299182c2e31c", size = 516339, upload-time = "2025-11-16T14:48:47.308Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ad/a75e603161e79b7110c647163d130872b271c6b28712c803c65d492100f7/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5cc58aac218826d054c7da7f95821eba94125d88be673ff44267bb89d12a5866", size = 416201, upload-time = "2025-11-16T14:48:48.615Z" }, - { url = "https://files.pythonhosted.org/packages/b9/42/555b4ee17508beafac135c8b450816ace5a96194ce97fefc49d58e5652ea/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de73e40ebc04dd5d9556f50180395322193a78ec247e637e741c1b954810f295", size = 395095, upload-time = "2025-11-16T14:48:50.027Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f0/c90b671b9031e800ec45112be42ea9f027f94f9ac25faaac8770596a16a1/rpds_py-0.29.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:295ce5ac7f0cf69a651ea75c8f76d02a31f98e5698e82a50a5f4d4982fbbae3b", size = 410077, upload-time = "2025-11-16T14:48:51.515Z" }, - { url = "https://files.pythonhosted.org/packages/3d/80/9af8b640b81fe21e6f718e9dec36c0b5f670332747243130a5490f292245/rpds_py-0.29.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ea59b23ea931d494459c8338056fe7d93458c0bf3ecc061cd03916505369d55", size = 424548, upload-time = "2025-11-16T14:48:53.237Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0b/b5647446e991736e6a495ef510e6710df91e880575a586e763baeb0aa770/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f49d41559cebd608042fdcf54ba597a4a7555b49ad5c1c0c03e0af82692661cd", size = 573661, upload-time = "2025-11-16T14:48:54.769Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b3/1b1c9576839ff583d1428efbf59f9ee70498d8ce6c0b328ac02f1e470879/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:05a2bd42768ea988294ca328206efbcc66e220d2d9b7836ee5712c07ad6340ea", size = 600937, upload-time = "2025-11-16T14:48:56.247Z" }, - { url = "https://files.pythonhosted.org/packages/6c/7b/b6cfca2f9fee4c4494ce54f7fb1b9f578867495a9aa9fc0d44f5f735c8e0/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33ca7bdfedd83339ca55da3a5e1527ee5870d4b8369456b5777b197756f3ca22", size = 564496, upload-time = "2025-11-16T14:48:57.691Z" }, - { url = "https://files.pythonhosted.org/packages/b9/fb/ba29ec7f0f06eb801bac5a23057a9ff7670623b5e8013bd59bec4aa09de8/rpds_py-0.29.0-cp313-cp313-win32.whl", hash = "sha256:20c51ae86a0bb9accc9ad4e6cdeec58d5ebb7f1b09dd4466331fc65e1766aae7", size = 223126, upload-time = "2025-11-16T14:48:59.058Z" }, - { url = "https://files.pythonhosted.org/packages/3c/6b/0229d3bed4ddaa409e6d90b0ae967ed4380e4bdd0dad6e59b92c17d42457/rpds_py-0.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:6410e66f02803600edb0b1889541f4b5cc298a5ccda0ad789cc50ef23b54813e", size = 239771, upload-time = "2025-11-16T14:49:00.872Z" }, - { url = "https://files.pythonhosted.org/packages/e4/38/d2868f058b164f8efd89754d85d7b1c08b454f5c07ac2e6cc2e9bd4bd05b/rpds_py-0.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:56838e1cd9174dc23c5691ee29f1d1be9eab357f27efef6bded1328b23e1ced2", size = 229994, upload-time = "2025-11-16T14:49:02.673Z" }, - { url = "https://files.pythonhosted.org/packages/52/91/5de91c5ec7d41759beec9b251630824dbb8e32d20c3756da1a9a9d309709/rpds_py-0.29.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:37d94eadf764d16b9a04307f2ab1d7af6dc28774bbe0535c9323101e14877b4c", size = 365886, upload-time = "2025-11-16T14:49:04.133Z" }, - { url = "https://files.pythonhosted.org/packages/85/7c/415d8c1b016d5f47ecec5145d9d6d21002d39dce8761b30f6c88810b455a/rpds_py-0.29.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d472cf73efe5726a067dce63eebe8215b14beabea7c12606fd9994267b3cfe2b", size = 355262, upload-time = "2025-11-16T14:49:05.543Z" }, - { url = "https://files.pythonhosted.org/packages/3d/14/bf83e2daa4f980e4dc848aed9299792a8b84af95e12541d9e7562f84a6ef/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72fdfd5ff8992e4636621826371e3ac5f3e3b8323e9d0e48378e9c13c3dac9d0", size = 384826, upload-time = "2025-11-16T14:49:07.301Z" }, - { url = "https://files.pythonhosted.org/packages/33/b8/53330c50a810ae22b4fbba5e6cf961b68b9d72d9bd6780a7c0a79b070857/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2549d833abdf8275c901313b9e8ff8fba57e50f6a495035a2a4e30621a2f7cc4", size = 394234, upload-time = "2025-11-16T14:49:08.782Z" }, - { url = "https://files.pythonhosted.org/packages/cc/32/01e2e9645cef0e584f518cfde4567563e57db2257244632b603f61b40e50/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4448dad428f28a6a767c3e3b80cde3446a22a0efbddaa2360f4bb4dc836d0688", size = 520008, upload-time = "2025-11-16T14:49:10.253Z" }, - { url = "https://files.pythonhosted.org/packages/98/c3/0d1b95a81affae2b10f950782e33a1fd2edd6ce2a479966cac98c9a66f57/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:115f48170fd4296a33938d8c11f697f5f26e0472e43d28f35624764173a60e4d", size = 409569, upload-time = "2025-11-16T14:49:12.478Z" }, - { url = "https://files.pythonhosted.org/packages/fa/60/aa3b8678f3f009f675b99174fa2754302a7fbfe749162e8043d111de2d88/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e5bb73ffc029820f4348e9b66b3027493ae00bca6629129cd433fd7a76308ee", size = 385188, upload-time = "2025-11-16T14:49:13.88Z" }, - { url = "https://files.pythonhosted.org/packages/92/02/5546c1c8aa89c18d40c1fcffdcc957ba730dee53fb7c3ca3a46f114761d2/rpds_py-0.29.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b1581fcde18fcdf42ea2403a16a6b646f8eb1e58d7f90a0ce693da441f76942e", size = 398587, upload-time = "2025-11-16T14:49:15.339Z" }, - { url = "https://files.pythonhosted.org/packages/6c/e0/ad6eeaf47e236eba052fa34c4073078b9e092bd44da6bbb35aaae9580669/rpds_py-0.29.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16e9da2bda9eb17ea318b4c335ec9ac1818e88922cbe03a5743ea0da9ecf74fb", size = 416641, upload-time = "2025-11-16T14:49:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/1a/93/0acedfd50ad9cdd3879c615a6dc8c5f1ce78d2fdf8b87727468bb5bb4077/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:28fd300326dd21198f311534bdb6d7e989dd09b3418b3a91d54a0f384c700967", size = 566683, upload-time = "2025-11-16T14:49:18.342Z" }, - { url = "https://files.pythonhosted.org/packages/62/53/8c64e0f340a9e801459fc6456821abc15b3582cb5dc3932d48705a9d9ac7/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2aba991e041d031c7939e1358f583ae405a7bf04804ca806b97a5c0e0af1ea5e", size = 592730, upload-time = "2025-11-16T14:49:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/85/ef/3109b6584f8c4b0d2490747c916df833c127ecfa82be04d9a40a376f2090/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f437026dbbc3f08c99cc41a5b2570c6e1a1ddbe48ab19a9b814254128d4ea7a", size = 557361, upload-time = "2025-11-16T14:49:21.574Z" }, - { url = "https://files.pythonhosted.org/packages/ff/3b/61586475e82d57f01da2c16edb9115a618afe00ce86fe1b58936880b15af/rpds_py-0.29.0-cp313-cp313t-win32.whl", hash = "sha256:6e97846e9800a5d0fe7be4d008f0c93d0feeb2700da7b1f7528dabafb31dfadb", size = 211227, upload-time = "2025-11-16T14:49:23.03Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3a/12dc43f13594a54ea0c9d7e9d43002116557330e3ad45bc56097ddf266e2/rpds_py-0.29.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f49196aec7c4b406495f60e6f947ad71f317a765f956d74bbd83996b9edc0352", size = 225248, upload-time = "2025-11-16T14:49:24.841Z" }, - { url = "https://files.pythonhosted.org/packages/89/b1/0b1474e7899371d9540d3bbb2a499a3427ae1fc39c998563fe9035a1073b/rpds_py-0.29.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:394d27e4453d3b4d82bb85665dc1fcf4b0badc30fc84282defed71643b50e1a1", size = 363731, upload-time = "2025-11-16T14:49:26.683Z" }, - { url = "https://files.pythonhosted.org/packages/28/12/3b7cf2068d0a334ed1d7b385a9c3c8509f4c2bcba3d4648ea71369de0881/rpds_py-0.29.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55d827b2ae95425d3be9bc9a5838b6c29d664924f98146557f7715e331d06df8", size = 354343, upload-time = "2025-11-16T14:49:28.24Z" }, - { url = "https://files.pythonhosted.org/packages/eb/73/5afcf8924bc02a749416eda64e17ac9c9b28f825f4737385295a0e99b0c1/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc31a07ed352e5462d3ee1b22e89285f4ce97d5266f6d1169da1142e78045626", size = 385406, upload-time = "2025-11-16T14:49:29.943Z" }, - { url = "https://files.pythonhosted.org/packages/c8/37/5db736730662508535221737a21563591b6f43c77f2e388951c42f143242/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4695dd224212f6105db7ea62197144230b808d6b2bba52238906a2762f1d1e7", size = 396162, upload-time = "2025-11-16T14:49:31.833Z" }, - { url = "https://files.pythonhosted.org/packages/70/0d/491c1017d14f62ce7bac07c32768d209a50ec567d76d9f383b4cfad19b80/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcae1770b401167f8b9e1e3f566562e6966ffa9ce63639916248a9e25fa8a244", size = 517719, upload-time = "2025-11-16T14:49:33.804Z" }, - { url = "https://files.pythonhosted.org/packages/d7/25/b11132afcb17cd5d82db173f0c8dab270ffdfaba43e5ce7a591837ae9649/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90f30d15f45048448b8da21c41703b31c61119c06c216a1bf8c245812a0f0c17", size = 409498, upload-time = "2025-11-16T14:49:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7d/e6543cedfb2e6403a1845710a5ab0e0ccf8fc288e0b5af9a70bfe2c12053/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a91e0ab77bdc0004b43261a4b8cd6d6b451e8d443754cfda830002b5745b32", size = 382743, upload-time = "2025-11-16T14:49:36.704Z" }, - { url = "https://files.pythonhosted.org/packages/75/11/a4ebc9f654293ae9fefb83b2b6be7f3253e85ea42a5db2f77d50ad19aaeb/rpds_py-0.29.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:4aa195e5804d32c682e453b34474f411ca108e4291c6a0f824ebdc30a91c973c", size = 400317, upload-time = "2025-11-16T14:49:39.132Z" }, - { url = "https://files.pythonhosted.org/packages/52/18/97677a60a81c7f0e5f64e51fb3f8271c5c8fcabf3a2df18e97af53d7c2bf/rpds_py-0.29.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7971bdb7bf4ee0f7e6f67fa4c7fbc6019d9850cc977d126904392d363f6f8318", size = 416979, upload-time = "2025-11-16T14:49:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/f0/69/28ab391a9968f6c746b2a2db181eaa4d16afaa859fedc9c2f682d19f7e18/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8ae33ad9ce580c7a47452c3b3f7d8a9095ef6208e0a0c7e4e2384f9fc5bf8212", size = 567288, upload-time = "2025-11-16T14:49:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d3/0c7afdcdb830eee94f5611b64e71354ffe6ac8df82d00c2faf2bfffd1d4e/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c661132ab2fb4eeede2ef69670fd60da5235209874d001a98f1542f31f2a8a94", size = 593157, upload-time = "2025-11-16T14:49:43.782Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ac/a0fcbc2feed4241cf26d32268c195eb88ddd4bd862adfc9d4b25edfba535/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb78b3a0d31ac1bde132c67015a809948db751cb4e92cdb3f0b242e430b6ed0d", size = 554741, upload-time = "2025-11-16T14:49:45.557Z" }, - { url = "https://files.pythonhosted.org/packages/0f/f1/fcc24137c470df8588674a677f33719d5800ec053aaacd1de8a5d5d84d9e/rpds_py-0.29.0-cp314-cp314-win32.whl", hash = "sha256:f475f103488312e9bd4000bc890a95955a07b2d0b6e8884aef4be56132adbbf1", size = 215508, upload-time = "2025-11-16T14:49:47.562Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c7/1d169b2045512eac019918fc1021ea07c30e84a4343f9f344e3e0aa8c788/rpds_py-0.29.0-cp314-cp314-win_amd64.whl", hash = "sha256:b9cf2359a4fca87cfb6801fae83a76aedf66ee1254a7a151f1341632acf67f1b", size = 228125, upload-time = "2025-11-16T14:49:49.064Z" }, - { url = "https://files.pythonhosted.org/packages/be/36/0cec88aaba70ec4a6e381c444b0d916738497d27f0c30406e3d9fcbd3bc2/rpds_py-0.29.0-cp314-cp314-win_arm64.whl", hash = "sha256:9ba8028597e824854f0f1733d8b964e914ae3003b22a10c2c664cb6927e0feb9", size = 221992, upload-time = "2025-11-16T14:49:50.777Z" }, - { url = "https://files.pythonhosted.org/packages/b1/fa/a2e524631717c9c0eb5d90d30f648cfba6b731047821c994acacb618406c/rpds_py-0.29.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e71136fd0612556b35c575dc2726ae04a1669e6a6c378f2240312cf5d1a2ab10", size = 366425, upload-time = "2025-11-16T14:49:52.691Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a4/6d43ebe0746ff694a30233f63f454aed1677bd50ab7a59ff6b2bb5ac61f2/rpds_py-0.29.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:76fe96632d53f3bf0ea31ede2f53bbe3540cc2736d4aec3b3801b0458499ef3a", size = 355282, upload-time = "2025-11-16T14:49:54.292Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a7/52fd8270e0320b09eaf295766ae81dd175f65394687906709b3e75c71d06/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9459a33f077130dbb2c7c3cea72ee9932271fb3126404ba2a2661e4fe9eb7b79", size = 384968, upload-time = "2025-11-16T14:49:55.857Z" }, - { url = "https://files.pythonhosted.org/packages/f4/7d/e6bc526b7a14e1ef80579a52c1d4ad39260a058a51d66c6039035d14db9d/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9546cfdd5d45e562cc0444b6dddc191e625c62e866bf567a2c69487c7ad28a", size = 394714, upload-time = "2025-11-16T14:49:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3f/f0ade3954e7db95c791e7eaf978aa7e08a756d2046e8bdd04d08146ed188/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12597d11d97b8f7e376c88929a6e17acb980e234547c92992f9f7c058f1a7310", size = 520136, upload-time = "2025-11-16T14:49:59.162Z" }, - { url = "https://files.pythonhosted.org/packages/87/b3/07122ead1b97009715ab9d4082be6d9bd9546099b2b03fae37c3116f72be/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28de03cf48b8a9e6ec10318f2197b83946ed91e2891f651a109611be4106ac4b", size = 409250, upload-time = "2025-11-16T14:50:00.698Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c6/dcbee61fd1dc892aedcb1b489ba661313101aa82ec84b1a015d4c63ebfda/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd7951c964069039acc9d67a8ff1f0a7f34845ae180ca542b17dc1456b1f1808", size = 384940, upload-time = "2025-11-16T14:50:02.312Z" }, - { url = "https://files.pythonhosted.org/packages/47/11/914ecb6f3574cf9bf8b38aced4063e0f787d6e1eb30b181a7efbc6c1da9a/rpds_py-0.29.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:c07d107b7316088f1ac0177a7661ca0c6670d443f6fe72e836069025e6266761", size = 399392, upload-time = "2025-11-16T14:50:03.829Z" }, - { url = "https://files.pythonhosted.org/packages/f5/fd/2f4bd9433f58f816434bb934313584caa47dbc6f03ce5484df8ac8980561/rpds_py-0.29.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de2345af363d25696969befc0c1688a6cb5e8b1d32b515ef84fc245c6cddba3", size = 416796, upload-time = "2025-11-16T14:50:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/79/a5/449f0281af33efa29d5c71014399d74842342ae908d8cd38260320167692/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:00e56b12d2199ca96068057e1ae7f9998ab6e99cda82431afafd32f3ec98cca9", size = 566843, upload-time = "2025-11-16T14:50:07.243Z" }, - { url = "https://files.pythonhosted.org/packages/ab/32/0a6a1ccee2e37fcb1b7ba9afde762b77182dbb57937352a729c6cd3cf2bb/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3919a3bbecee589300ed25000b6944174e07cd20db70552159207b3f4bbb45b8", size = 593956, upload-time = "2025-11-16T14:50:09.029Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3d/eb820f95dce4306f07a495ede02fb61bef36ea201d9137d4fcd5ab94ec1e/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7fa2ccc312bbd91e43aa5e0869e46bc03278a3dddb8d58833150a18b0f0283a", size = 557288, upload-time = "2025-11-16T14:50:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f8/b8ff786f40470462a252918e0836e0db903c28e88e3eec66bc4a7856ee5d/rpds_py-0.29.0-cp314-cp314t-win32.whl", hash = "sha256:97c817863ffc397f1e6a6e9d2d89fe5408c0a9922dac0329672fb0f35c867ea5", size = 211382, upload-time = "2025-11-16T14:50:12.827Z" }, - { url = "https://files.pythonhosted.org/packages/c9/7f/1a65ae870bc9d0576aebb0c501ea5dccf1ae2178fe2821042150ebd2e707/rpds_py-0.29.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2023473f444752f0f82a58dfcbee040d0a1b3d1b3c2ec40e884bd25db6d117d2", size = 225919, upload-time = "2025-11-16T14:50:14.734Z" }, - { url = "https://files.pythonhosted.org/packages/f2/ac/b97e80bf107159e5b9ba9c91df1ab95f69e5e41b435f27bdd737f0d583ac/rpds_py-0.29.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:acd82a9e39082dc5f4492d15a6b6c8599aa21db5c35aaf7d6889aea16502c07d", size = 373963, upload-time = "2025-11-16T14:50:16.205Z" }, - { url = "https://files.pythonhosted.org/packages/40/5a/55e72962d5d29bd912f40c594e68880d3c7a52774b0f75542775f9250712/rpds_py-0.29.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:715b67eac317bf1c7657508170a3e011a1ea6ccb1c9d5f296e20ba14196be6b3", size = 364644, upload-time = "2025-11-16T14:50:18.22Z" }, - { url = "https://files.pythonhosted.org/packages/99/2a/6b6524d0191b7fc1351c3c0840baac42250515afb48ae40c7ed15499a6a2/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b1b87a237cb2dba4db18bcfaaa44ba4cd5936b91121b62292ff21df577fc43", size = 393847, upload-time = "2025-11-16T14:50:20.012Z" }, - { url = "https://files.pythonhosted.org/packages/1c/b8/c5692a7df577b3c0c7faed7ac01ee3c608b81750fc5d89f84529229b6873/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c3c3e8101bb06e337c88eb0c0ede3187131f19d97d43ea0e1c5407ea74c0cbf", size = 407281, upload-time = "2025-11-16T14:50:21.64Z" }, - { url = "https://files.pythonhosted.org/packages/f0/57/0546c6f84031b7ea08b76646a8e33e45607cc6bd879ff1917dc077bb881e/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8e54d6e61f3ecd3abe032065ce83ea63417a24f437e4a3d73d2f85ce7b7cfe", size = 529213, upload-time = "2025-11-16T14:50:23.219Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c1/01dd5f444233605555bc11fe5fed6a5c18f379f02013870c176c8e630a23/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fbd4e9aebf110473a420dea85a238b254cf8a15acb04b22a5a6b5ce8925b760", size = 413808, upload-time = "2025-11-16T14:50:25.262Z" }, - { url = "https://files.pythonhosted.org/packages/aa/0a/60f98b06156ea2a7af849fb148e00fbcfdb540909a5174a5ed10c93745c7/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fdf53d36e6c72819993e35d1ebeeb8e8fc688d0c6c2b391b55e335b3afba5a", size = 394600, upload-time = "2025-11-16T14:50:26.956Z" }, - { url = "https://files.pythonhosted.org/packages/37/f1/dc9312fc9bec040ece08396429f2bd9e0977924ba7a11c5ad7056428465e/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:ea7173df5d86f625f8dde6d5929629ad811ed8decda3b60ae603903839ac9ac0", size = 408634, upload-time = "2025-11-16T14:50:28.989Z" }, - { url = "https://files.pythonhosted.org/packages/ed/41/65024c9fd40c89bb7d604cf73beda4cbdbcebe92d8765345dd65855b6449/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:76054d540061eda273274f3d13a21a4abdde90e13eaefdc205db37c05230efce", size = 426064, upload-time = "2025-11-16T14:50:30.674Z" }, - { url = "https://files.pythonhosted.org/packages/a2/e0/cf95478881fc88ca2fdbf56381d7df36567cccc39a05394beac72182cd62/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9f84c549746a5be3bc7415830747a3a0312573afc9f95785eb35228bb17742ec", size = 575871, upload-time = "2025-11-16T14:50:33.428Z" }, - { url = "https://files.pythonhosted.org/packages/ea/c0/df88097e64339a0218b57bd5f9ca49898e4c394db756c67fccc64add850a/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:0ea962671af5cb9a260489e311fa22b2e97103e3f9f0caaea6f81390af96a9ed", size = 601702, upload-time = "2025-11-16T14:50:36.051Z" }, - { url = "https://files.pythonhosted.org/packages/87/f4/09ffb3ebd0cbb9e2c7c9b84d252557ecf434cd71584ee1e32f66013824df/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f7728653900035fb7b8d06e1e5900545d8088efc9d5d4545782da7df03ec803f", size = 564054, upload-time = "2025-11-16T14:50:37.733Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/98/33/23b3b3419b6a3e0f559c7c0d2ca8fc1b9448382b25245033788785921332/rpds_py-0.29.0.tar.gz", hash = "sha256:fe55fe686908f50154d1dc599232016e50c243b438c3b7432f24e2895b0e5359", size = 69359 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/ab/7fb95163a53ab122c74a7c42d2d2f012819af2cf3deb43fb0d5acf45cc1a/rpds_py-0.29.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b9c764a11fd637e0322a488560533112837f5334ffeb48b1be20f6d98a7b437", size = 372344 }, + { url = "https://files.pythonhosted.org/packages/b3/45/f3c30084c03b0d0f918cb4c5ae2c20b0a148b51ba2b3f6456765b629bedd/rpds_py-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fd2164d73812026ce970d44c3ebd51e019d2a26a4425a5dcbdfa93a34abc383", size = 363041 }, + { url = "https://files.pythonhosted.org/packages/e3/e9/4d044a1662608c47a87cbb37b999d4d5af54c6d6ebdda93a4d8bbf8b2a10/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a097b7f7f7274164566ae90a221fd725363c0e9d243e2e9ed43d195ccc5495c", size = 391775 }, + { url = "https://files.pythonhosted.org/packages/50/c9/7616d3ace4e6731aeb6e3cd85123e03aec58e439044e214b9c5c60fd8eb1/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cdc0490374e31cedefefaa1520d5fe38e82fde8748cbc926e7284574c714d6b", size = 405624 }, + { url = "https://files.pythonhosted.org/packages/c2/e2/6d7d6941ca0843609fd2d72c966a438d6f22617baf22d46c3d2156c31350/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89ca2e673ddd5bde9b386da9a0aac0cab0e76f40c8f0aaf0d6311b6bbf2aa311", size = 527894 }, + { url = "https://files.pythonhosted.org/packages/8d/f7/aee14dc2db61bb2ae1e3068f134ca9da5f28c586120889a70ff504bb026f/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5d9da3ff5af1ca1249b1adb8ef0573b94c76e6ae880ba1852f033bf429d4588", size = 412720 }, + { url = "https://files.pythonhosted.org/packages/2f/e2/2293f236e887c0360c2723d90c00d48dee296406994d6271faf1712e94ec/rpds_py-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8238d1d310283e87376c12f658b61e1ee23a14c0e54c7c0ce953efdbdc72deed", size = 392945 }, + { url = "https://files.pythonhosted.org/packages/14/cd/ceea6147acd3bd1fd028d1975228f08ff19d62098078d5ec3eed49703797/rpds_py-0.29.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2d6fb2ad1c36f91c4646989811e84b1ea5e0c3cf9690b826b6e32b7965853a63", size = 406385 }, + { url = "https://files.pythonhosted.org/packages/52/36/fe4dead19e45eb77a0524acfdbf51e6cda597b26fc5b6dddbff55fbbb1a5/rpds_py-0.29.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:534dc9df211387547267ccdb42253aa30527482acb38dd9b21c5c115d66a96d2", size = 423943 }, + { url = "https://files.pythonhosted.org/packages/a1/7b/4551510803b582fa4abbc8645441a2d15aa0c962c3b21ebb380b7e74f6a1/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d456e64724a075441e4ed648d7f154dc62e9aabff29bcdf723d0c00e9e1d352f", size = 574204 }, + { url = "https://files.pythonhosted.org/packages/64/ba/071ccdd7b171e727a6ae079f02c26f75790b41555f12ca8f1151336d2124/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a738f2da2f565989401bd6fd0b15990a4d1523c6d7fe83f300b7e7d17212feca", size = 600587 }, + { url = "https://files.pythonhosted.org/packages/03/09/96983d48c8cf5a1e03c7d9cc1f4b48266adfb858ae48c7c2ce978dbba349/rpds_py-0.29.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a110e14508fd26fd2e472bb541f37c209409876ba601cf57e739e87d8a53cf95", size = 562287 }, + { url = "https://files.pythonhosted.org/packages/40/f0/8c01aaedc0fa92156f0391f39ea93b5952bc0ec56b897763858f95da8168/rpds_py-0.29.0-cp311-cp311-win32.whl", hash = "sha256:923248a56dd8d158389a28934f6f69ebf89f218ef96a6b216a9be6861804d3f4", size = 221394 }, + { url = "https://files.pythonhosted.org/packages/7e/a5/a8b21c54c7d234efdc83dc034a4d7cd9668e3613b6316876a29b49dece71/rpds_py-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:539eb77eb043afcc45314d1be09ea6d6cafb3addc73e0547c171c6d636957f60", size = 235713 }, + { url = "https://files.pythonhosted.org/packages/a7/1f/df3c56219523947b1be402fa12e6323fe6d61d883cf35d6cb5d5bb6db9d9/rpds_py-0.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:bdb67151ea81fcf02d8f494703fb728d4d34d24556cbff5f417d74f6f5792e7c", size = 229157 }, + { url = "https://files.pythonhosted.org/packages/3c/50/bc0e6e736d94e420df79be4deb5c9476b63165c87bb8f19ef75d100d21b3/rpds_py-0.29.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0891cfd8db43e085c0ab93ab7e9b0c8fee84780d436d3b266b113e51e79f954", size = 376000 }, + { url = "https://files.pythonhosted.org/packages/3e/3a/46676277160f014ae95f24de53bed0e3b7ea66c235e7de0b9df7bd5d68ba/rpds_py-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3897924d3f9a0361472d884051f9a2460358f9a45b1d85a39a158d2f8f1ad71c", size = 360575 }, + { url = "https://files.pythonhosted.org/packages/75/ba/411d414ed99ea1afdd185bbabeeaac00624bd1e4b22840b5e9967ade6337/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21deb8e0d1571508c6491ce5ea5e25669b1dd4adf1c9d64b6314842f708b5d", size = 392159 }, + { url = "https://files.pythonhosted.org/packages/8f/b1/e18aa3a331f705467a48d0296778dc1fea9d7f6cf675bd261f9a846c7e90/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9efe71687d6427737a0a2de9ca1c0a216510e6cd08925c44162be23ed7bed2d5", size = 410602 }, + { url = "https://files.pythonhosted.org/packages/2f/6c/04f27f0c9f2299274c76612ac9d2c36c5048bb2c6c2e52c38c60bf3868d9/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40f65470919dc189c833e86b2c4bd21bd355f98436a2cef9e0a9a92aebc8e57e", size = 515808 }, + { url = "https://files.pythonhosted.org/packages/83/56/a8412aa464fb151f8bc0d91fb0bb888adc9039bd41c1c6ba8d94990d8cf8/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:def48ff59f181130f1a2cb7c517d16328efac3ec03951cca40c1dc2049747e83", size = 416015 }, + { url = "https://files.pythonhosted.org/packages/04/4c/f9b8a05faca3d9e0a6397c90d13acb9307c9792b2bff621430c58b1d6e76/rpds_py-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7bd570be92695d89285a4b373006930715b78d96449f686af422debb4d3949", size = 395325 }, + { url = "https://files.pythonhosted.org/packages/34/60/869f3bfbf8ed7b54f1ad9a5543e0fdffdd40b5a8f587fe300ee7b4f19340/rpds_py-0.29.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:5a572911cd053137bbff8e3a52d31c5d2dba51d3a67ad902629c70185f3f2181", size = 410160 }, + { url = "https://files.pythonhosted.org/packages/91/aa/e5b496334e3aba4fe4c8a80187b89f3c1294c5c36f2a926da74338fa5a73/rpds_py-0.29.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d583d4403bcbf10cffc3ab5cee23d7643fcc960dff85973fd3c2d6c86e8dbb0c", size = 425309 }, + { url = "https://files.pythonhosted.org/packages/85/68/4e24a34189751ceb6d66b28f18159922828dd84155876551f7ca5b25f14f/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:070befbb868f257d24c3bb350dbd6e2f645e83731f31264b19d7231dd5c396c7", size = 574644 }, + { url = "https://files.pythonhosted.org/packages/8c/cf/474a005ea4ea9c3b4f17b6108b6b13cebfc98ebaff11d6e1b193204b3a93/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fc935f6b20b0c9f919a8ff024739174522abd331978f750a74bb68abd117bd19", size = 601605 }, + { url = "https://files.pythonhosted.org/packages/f4/b1/c56f6a9ab8c5f6bb5c65c4b5f8229167a3a525245b0773f2c0896686b64e/rpds_py-0.29.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c5a8ecaa44ce2d8d9d20a68a2483a74c07f05d72e94a4dff88906c8807e77b0", size = 564593 }, + { url = "https://files.pythonhosted.org/packages/b3/13/0494cecce4848f68501e0a229432620b4b57022388b071eeff95f3e1e75b/rpds_py-0.29.0-cp312-cp312-win32.whl", hash = "sha256:ba5e1aeaf8dd6d8f6caba1f5539cddda87d511331714b7b5fc908b6cfc3636b7", size = 223853 }, + { url = "https://files.pythonhosted.org/packages/1f/6a/51e9aeb444a00cdc520b032a28b07e5f8dc7bc328b57760c53e7f96997b4/rpds_py-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:b5f6134faf54b3cb83375db0f113506f8b7770785be1f95a631e7e2892101977", size = 239895 }, + { url = "https://files.pythonhosted.org/packages/d1/d4/8bce56cdad1ab873e3f27cb31c6a51d8f384d66b022b820525b879f8bed1/rpds_py-0.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:b016eddf00dca7944721bf0cd85b6af7f6c4efaf83ee0b37c4133bd39757a8c7", size = 230321 }, + { url = "https://files.pythonhosted.org/packages/fd/d9/c5de60d9d371bbb186c3e9bf75f4fc5665e11117a25a06a6b2e0afb7380e/rpds_py-0.29.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1585648d0760b88292eecab5181f5651111a69d90eff35d6b78aa32998886a61", size = 375710 }, + { url = "https://files.pythonhosted.org/packages/b3/b3/0860cdd012291dc21272895ce107f1e98e335509ba986dd83d72658b82b9/rpds_py-0.29.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:521807963971a23996ddaf764c682b3e46459b3c58ccd79fefbe16718db43154", size = 360582 }, + { url = "https://files.pythonhosted.org/packages/92/8a/a18c2f4a61b3407e56175f6aab6deacdf9d360191a3d6f38566e1eaf7266/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a8896986efaa243ab713c69e6491a4138410f0fe36f2f4c71e18bd5501e8014", size = 391172 }, + { url = "https://files.pythonhosted.org/packages/fd/49/e93354258508c50abc15cdcd5fcf7ac4117f67bb6233ad7859f75e7372a0/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d24564a700ef41480a984c5ebed62b74e6ce5860429b98b1fede76049e953e6", size = 409586 }, + { url = "https://files.pythonhosted.org/packages/5a/8d/a27860dae1c19a6bdc901f90c81f0d581df1943355802961a57cdb5b6cd1/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6596b93c010d386ae46c9fba9bfc9fc5965fa8228edeac51576299182c2e31c", size = 516339 }, + { url = "https://files.pythonhosted.org/packages/fc/ad/a75e603161e79b7110c647163d130872b271c6b28712c803c65d492100f7/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5cc58aac218826d054c7da7f95821eba94125d88be673ff44267bb89d12a5866", size = 416201 }, + { url = "https://files.pythonhosted.org/packages/b9/42/555b4ee17508beafac135c8b450816ace5a96194ce97fefc49d58e5652ea/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de73e40ebc04dd5d9556f50180395322193a78ec247e637e741c1b954810f295", size = 395095 }, + { url = "https://files.pythonhosted.org/packages/cd/f0/c90b671b9031e800ec45112be42ea9f027f94f9ac25faaac8770596a16a1/rpds_py-0.29.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:295ce5ac7f0cf69a651ea75c8f76d02a31f98e5698e82a50a5f4d4982fbbae3b", size = 410077 }, + { url = "https://files.pythonhosted.org/packages/3d/80/9af8b640b81fe21e6f718e9dec36c0b5f670332747243130a5490f292245/rpds_py-0.29.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ea59b23ea931d494459c8338056fe7d93458c0bf3ecc061cd03916505369d55", size = 424548 }, + { url = "https://files.pythonhosted.org/packages/e4/0b/b5647446e991736e6a495ef510e6710df91e880575a586e763baeb0aa770/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f49d41559cebd608042fdcf54ba597a4a7555b49ad5c1c0c03e0af82692661cd", size = 573661 }, + { url = "https://files.pythonhosted.org/packages/f7/b3/1b1c9576839ff583d1428efbf59f9ee70498d8ce6c0b328ac02f1e470879/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:05a2bd42768ea988294ca328206efbcc66e220d2d9b7836ee5712c07ad6340ea", size = 600937 }, + { url = "https://files.pythonhosted.org/packages/6c/7b/b6cfca2f9fee4c4494ce54f7fb1b9f578867495a9aa9fc0d44f5f735c8e0/rpds_py-0.29.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33ca7bdfedd83339ca55da3a5e1527ee5870d4b8369456b5777b197756f3ca22", size = 564496 }, + { url = "https://files.pythonhosted.org/packages/b9/fb/ba29ec7f0f06eb801bac5a23057a9ff7670623b5e8013bd59bec4aa09de8/rpds_py-0.29.0-cp313-cp313-win32.whl", hash = "sha256:20c51ae86a0bb9accc9ad4e6cdeec58d5ebb7f1b09dd4466331fc65e1766aae7", size = 223126 }, + { url = "https://files.pythonhosted.org/packages/3c/6b/0229d3bed4ddaa409e6d90b0ae967ed4380e4bdd0dad6e59b92c17d42457/rpds_py-0.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:6410e66f02803600edb0b1889541f4b5cc298a5ccda0ad789cc50ef23b54813e", size = 239771 }, + { url = "https://files.pythonhosted.org/packages/e4/38/d2868f058b164f8efd89754d85d7b1c08b454f5c07ac2e6cc2e9bd4bd05b/rpds_py-0.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:56838e1cd9174dc23c5691ee29f1d1be9eab357f27efef6bded1328b23e1ced2", size = 229994 }, + { url = "https://files.pythonhosted.org/packages/52/91/5de91c5ec7d41759beec9b251630824dbb8e32d20c3756da1a9a9d309709/rpds_py-0.29.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:37d94eadf764d16b9a04307f2ab1d7af6dc28774bbe0535c9323101e14877b4c", size = 365886 }, + { url = "https://files.pythonhosted.org/packages/85/7c/415d8c1b016d5f47ecec5145d9d6d21002d39dce8761b30f6c88810b455a/rpds_py-0.29.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d472cf73efe5726a067dce63eebe8215b14beabea7c12606fd9994267b3cfe2b", size = 355262 }, + { url = "https://files.pythonhosted.org/packages/3d/14/bf83e2daa4f980e4dc848aed9299792a8b84af95e12541d9e7562f84a6ef/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72fdfd5ff8992e4636621826371e3ac5f3e3b8323e9d0e48378e9c13c3dac9d0", size = 384826 }, + { url = "https://files.pythonhosted.org/packages/33/b8/53330c50a810ae22b4fbba5e6cf961b68b9d72d9bd6780a7c0a79b070857/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2549d833abdf8275c901313b9e8ff8fba57e50f6a495035a2a4e30621a2f7cc4", size = 394234 }, + { url = "https://files.pythonhosted.org/packages/cc/32/01e2e9645cef0e584f518cfde4567563e57db2257244632b603f61b40e50/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4448dad428f28a6a767c3e3b80cde3446a22a0efbddaa2360f4bb4dc836d0688", size = 520008 }, + { url = "https://files.pythonhosted.org/packages/98/c3/0d1b95a81affae2b10f950782e33a1fd2edd6ce2a479966cac98c9a66f57/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:115f48170fd4296a33938d8c11f697f5f26e0472e43d28f35624764173a60e4d", size = 409569 }, + { url = "https://files.pythonhosted.org/packages/fa/60/aa3b8678f3f009f675b99174fa2754302a7fbfe749162e8043d111de2d88/rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e5bb73ffc029820f4348e9b66b3027493ae00bca6629129cd433fd7a76308ee", size = 385188 }, + { url = "https://files.pythonhosted.org/packages/92/02/5546c1c8aa89c18d40c1fcffdcc957ba730dee53fb7c3ca3a46f114761d2/rpds_py-0.29.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b1581fcde18fcdf42ea2403a16a6b646f8eb1e58d7f90a0ce693da441f76942e", size = 398587 }, + { url = "https://files.pythonhosted.org/packages/6c/e0/ad6eeaf47e236eba052fa34c4073078b9e092bd44da6bbb35aaae9580669/rpds_py-0.29.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16e9da2bda9eb17ea318b4c335ec9ac1818e88922cbe03a5743ea0da9ecf74fb", size = 416641 }, + { url = "https://files.pythonhosted.org/packages/1a/93/0acedfd50ad9cdd3879c615a6dc8c5f1ce78d2fdf8b87727468bb5bb4077/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:28fd300326dd21198f311534bdb6d7e989dd09b3418b3a91d54a0f384c700967", size = 566683 }, + { url = "https://files.pythonhosted.org/packages/62/53/8c64e0f340a9e801459fc6456821abc15b3582cb5dc3932d48705a9d9ac7/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2aba991e041d031c7939e1358f583ae405a7bf04804ca806b97a5c0e0af1ea5e", size = 592730 }, + { url = "https://files.pythonhosted.org/packages/85/ef/3109b6584f8c4b0d2490747c916df833c127ecfa82be04d9a40a376f2090/rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f437026dbbc3f08c99cc41a5b2570c6e1a1ddbe48ab19a9b814254128d4ea7a", size = 557361 }, + { url = "https://files.pythonhosted.org/packages/ff/3b/61586475e82d57f01da2c16edb9115a618afe00ce86fe1b58936880b15af/rpds_py-0.29.0-cp313-cp313t-win32.whl", hash = "sha256:6e97846e9800a5d0fe7be4d008f0c93d0feeb2700da7b1f7528dabafb31dfadb", size = 211227 }, + { url = "https://files.pythonhosted.org/packages/3b/3a/12dc43f13594a54ea0c9d7e9d43002116557330e3ad45bc56097ddf266e2/rpds_py-0.29.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f49196aec7c4b406495f60e6f947ad71f317a765f956d74bbd83996b9edc0352", size = 225248 }, + { url = "https://files.pythonhosted.org/packages/89/b1/0b1474e7899371d9540d3bbb2a499a3427ae1fc39c998563fe9035a1073b/rpds_py-0.29.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:394d27e4453d3b4d82bb85665dc1fcf4b0badc30fc84282defed71643b50e1a1", size = 363731 }, + { url = "https://files.pythonhosted.org/packages/28/12/3b7cf2068d0a334ed1d7b385a9c3c8509f4c2bcba3d4648ea71369de0881/rpds_py-0.29.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55d827b2ae95425d3be9bc9a5838b6c29d664924f98146557f7715e331d06df8", size = 354343 }, + { url = "https://files.pythonhosted.org/packages/eb/73/5afcf8924bc02a749416eda64e17ac9c9b28f825f4737385295a0e99b0c1/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc31a07ed352e5462d3ee1b22e89285f4ce97d5266f6d1169da1142e78045626", size = 385406 }, + { url = "https://files.pythonhosted.org/packages/c8/37/5db736730662508535221737a21563591b6f43c77f2e388951c42f143242/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4695dd224212f6105db7ea62197144230b808d6b2bba52238906a2762f1d1e7", size = 396162 }, + { url = "https://files.pythonhosted.org/packages/70/0d/491c1017d14f62ce7bac07c32768d209a50ec567d76d9f383b4cfad19b80/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcae1770b401167f8b9e1e3f566562e6966ffa9ce63639916248a9e25fa8a244", size = 517719 }, + { url = "https://files.pythonhosted.org/packages/d7/25/b11132afcb17cd5d82db173f0c8dab270ffdfaba43e5ce7a591837ae9649/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90f30d15f45048448b8da21c41703b31c61119c06c216a1bf8c245812a0f0c17", size = 409498 }, + { url = "https://files.pythonhosted.org/packages/0f/7d/e6543cedfb2e6403a1845710a5ab0e0ccf8fc288e0b5af9a70bfe2c12053/rpds_py-0.29.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a91e0ab77bdc0004b43261a4b8cd6d6b451e8d443754cfda830002b5745b32", size = 382743 }, + { url = "https://files.pythonhosted.org/packages/75/11/a4ebc9f654293ae9fefb83b2b6be7f3253e85ea42a5db2f77d50ad19aaeb/rpds_py-0.29.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:4aa195e5804d32c682e453b34474f411ca108e4291c6a0f824ebdc30a91c973c", size = 400317 }, + { url = "https://files.pythonhosted.org/packages/52/18/97677a60a81c7f0e5f64e51fb3f8271c5c8fcabf3a2df18e97af53d7c2bf/rpds_py-0.29.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7971bdb7bf4ee0f7e6f67fa4c7fbc6019d9850cc977d126904392d363f6f8318", size = 416979 }, + { url = "https://files.pythonhosted.org/packages/f0/69/28ab391a9968f6c746b2a2db181eaa4d16afaa859fedc9c2f682d19f7e18/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8ae33ad9ce580c7a47452c3b3f7d8a9095ef6208e0a0c7e4e2384f9fc5bf8212", size = 567288 }, + { url = "https://files.pythonhosted.org/packages/3b/d3/0c7afdcdb830eee94f5611b64e71354ffe6ac8df82d00c2faf2bfffd1d4e/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c661132ab2fb4eeede2ef69670fd60da5235209874d001a98f1542f31f2a8a94", size = 593157 }, + { url = "https://files.pythonhosted.org/packages/e2/ac/a0fcbc2feed4241cf26d32268c195eb88ddd4bd862adfc9d4b25edfba535/rpds_py-0.29.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb78b3a0d31ac1bde132c67015a809948db751cb4e92cdb3f0b242e430b6ed0d", size = 554741 }, + { url = "https://files.pythonhosted.org/packages/0f/f1/fcc24137c470df8588674a677f33719d5800ec053aaacd1de8a5d5d84d9e/rpds_py-0.29.0-cp314-cp314-win32.whl", hash = "sha256:f475f103488312e9bd4000bc890a95955a07b2d0b6e8884aef4be56132adbbf1", size = 215508 }, + { url = "https://files.pythonhosted.org/packages/7b/c7/1d169b2045512eac019918fc1021ea07c30e84a4343f9f344e3e0aa8c788/rpds_py-0.29.0-cp314-cp314-win_amd64.whl", hash = "sha256:b9cf2359a4fca87cfb6801fae83a76aedf66ee1254a7a151f1341632acf67f1b", size = 228125 }, + { url = "https://files.pythonhosted.org/packages/be/36/0cec88aaba70ec4a6e381c444b0d916738497d27f0c30406e3d9fcbd3bc2/rpds_py-0.29.0-cp314-cp314-win_arm64.whl", hash = "sha256:9ba8028597e824854f0f1733d8b964e914ae3003b22a10c2c664cb6927e0feb9", size = 221992 }, + { url = "https://files.pythonhosted.org/packages/b1/fa/a2e524631717c9c0eb5d90d30f648cfba6b731047821c994acacb618406c/rpds_py-0.29.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e71136fd0612556b35c575dc2726ae04a1669e6a6c378f2240312cf5d1a2ab10", size = 366425 }, + { url = "https://files.pythonhosted.org/packages/a2/a4/6d43ebe0746ff694a30233f63f454aed1677bd50ab7a59ff6b2bb5ac61f2/rpds_py-0.29.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:76fe96632d53f3bf0ea31ede2f53bbe3540cc2736d4aec3b3801b0458499ef3a", size = 355282 }, + { url = "https://files.pythonhosted.org/packages/fa/a7/52fd8270e0320b09eaf295766ae81dd175f65394687906709b3e75c71d06/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9459a33f077130dbb2c7c3cea72ee9932271fb3126404ba2a2661e4fe9eb7b79", size = 384968 }, + { url = "https://files.pythonhosted.org/packages/f4/7d/e6bc526b7a14e1ef80579a52c1d4ad39260a058a51d66c6039035d14db9d/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9546cfdd5d45e562cc0444b6dddc191e625c62e866bf567a2c69487c7ad28a", size = 394714 }, + { url = "https://files.pythonhosted.org/packages/c0/3f/f0ade3954e7db95c791e7eaf978aa7e08a756d2046e8bdd04d08146ed188/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12597d11d97b8f7e376c88929a6e17acb980e234547c92992f9f7c058f1a7310", size = 520136 }, + { url = "https://files.pythonhosted.org/packages/87/b3/07122ead1b97009715ab9d4082be6d9bd9546099b2b03fae37c3116f72be/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28de03cf48b8a9e6ec10318f2197b83946ed91e2891f651a109611be4106ac4b", size = 409250 }, + { url = "https://files.pythonhosted.org/packages/c9/c6/dcbee61fd1dc892aedcb1b489ba661313101aa82ec84b1a015d4c63ebfda/rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd7951c964069039acc9d67a8ff1f0a7f34845ae180ca542b17dc1456b1f1808", size = 384940 }, + { url = "https://files.pythonhosted.org/packages/47/11/914ecb6f3574cf9bf8b38aced4063e0f787d6e1eb30b181a7efbc6c1da9a/rpds_py-0.29.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:c07d107b7316088f1ac0177a7661ca0c6670d443f6fe72e836069025e6266761", size = 399392 }, + { url = "https://files.pythonhosted.org/packages/f5/fd/2f4bd9433f58f816434bb934313584caa47dbc6f03ce5484df8ac8980561/rpds_py-0.29.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de2345af363d25696969befc0c1688a6cb5e8b1d32b515ef84fc245c6cddba3", size = 416796 }, + { url = "https://files.pythonhosted.org/packages/79/a5/449f0281af33efa29d5c71014399d74842342ae908d8cd38260320167692/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:00e56b12d2199ca96068057e1ae7f9998ab6e99cda82431afafd32f3ec98cca9", size = 566843 }, + { url = "https://files.pythonhosted.org/packages/ab/32/0a6a1ccee2e37fcb1b7ba9afde762b77182dbb57937352a729c6cd3cf2bb/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3919a3bbecee589300ed25000b6944174e07cd20db70552159207b3f4bbb45b8", size = 593956 }, + { url = "https://files.pythonhosted.org/packages/4a/3d/eb820f95dce4306f07a495ede02fb61bef36ea201d9137d4fcd5ab94ec1e/rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7fa2ccc312bbd91e43aa5e0869e46bc03278a3dddb8d58833150a18b0f0283a", size = 557288 }, + { url = "https://files.pythonhosted.org/packages/e9/f8/b8ff786f40470462a252918e0836e0db903c28e88e3eec66bc4a7856ee5d/rpds_py-0.29.0-cp314-cp314t-win32.whl", hash = "sha256:97c817863ffc397f1e6a6e9d2d89fe5408c0a9922dac0329672fb0f35c867ea5", size = 211382 }, + { url = "https://files.pythonhosted.org/packages/c9/7f/1a65ae870bc9d0576aebb0c501ea5dccf1ae2178fe2821042150ebd2e707/rpds_py-0.29.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2023473f444752f0f82a58dfcbee040d0a1b3d1b3c2ec40e884bd25db6d117d2", size = 225919 }, + { url = "https://files.pythonhosted.org/packages/f2/ac/b97e80bf107159e5b9ba9c91df1ab95f69e5e41b435f27bdd737f0d583ac/rpds_py-0.29.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:acd82a9e39082dc5f4492d15a6b6c8599aa21db5c35aaf7d6889aea16502c07d", size = 373963 }, + { url = "https://files.pythonhosted.org/packages/40/5a/55e72962d5d29bd912f40c594e68880d3c7a52774b0f75542775f9250712/rpds_py-0.29.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:715b67eac317bf1c7657508170a3e011a1ea6ccb1c9d5f296e20ba14196be6b3", size = 364644 }, + { url = "https://files.pythonhosted.org/packages/99/2a/6b6524d0191b7fc1351c3c0840baac42250515afb48ae40c7ed15499a6a2/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b1b87a237cb2dba4db18bcfaaa44ba4cd5936b91121b62292ff21df577fc43", size = 393847 }, + { url = "https://files.pythonhosted.org/packages/1c/b8/c5692a7df577b3c0c7faed7ac01ee3c608b81750fc5d89f84529229b6873/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c3c3e8101bb06e337c88eb0c0ede3187131f19d97d43ea0e1c5407ea74c0cbf", size = 407281 }, + { url = "https://files.pythonhosted.org/packages/f0/57/0546c6f84031b7ea08b76646a8e33e45607cc6bd879ff1917dc077bb881e/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8e54d6e61f3ecd3abe032065ce83ea63417a24f437e4a3d73d2f85ce7b7cfe", size = 529213 }, + { url = "https://files.pythonhosted.org/packages/fa/c1/01dd5f444233605555bc11fe5fed6a5c18f379f02013870c176c8e630a23/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fbd4e9aebf110473a420dea85a238b254cf8a15acb04b22a5a6b5ce8925b760", size = 413808 }, + { url = "https://files.pythonhosted.org/packages/aa/0a/60f98b06156ea2a7af849fb148e00fbcfdb540909a5174a5ed10c93745c7/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fdf53d36e6c72819993e35d1ebeeb8e8fc688d0c6c2b391b55e335b3afba5a", size = 394600 }, + { url = "https://files.pythonhosted.org/packages/37/f1/dc9312fc9bec040ece08396429f2bd9e0977924ba7a11c5ad7056428465e/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:ea7173df5d86f625f8dde6d5929629ad811ed8decda3b60ae603903839ac9ac0", size = 408634 }, + { url = "https://files.pythonhosted.org/packages/ed/41/65024c9fd40c89bb7d604cf73beda4cbdbcebe92d8765345dd65855b6449/rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:76054d540061eda273274f3d13a21a4abdde90e13eaefdc205db37c05230efce", size = 426064 }, + { url = "https://files.pythonhosted.org/packages/a2/e0/cf95478881fc88ca2fdbf56381d7df36567cccc39a05394beac72182cd62/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9f84c549746a5be3bc7415830747a3a0312573afc9f95785eb35228bb17742ec", size = 575871 }, + { url = "https://files.pythonhosted.org/packages/ea/c0/df88097e64339a0218b57bd5f9ca49898e4c394db756c67fccc64add850a/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:0ea962671af5cb9a260489e311fa22b2e97103e3f9f0caaea6f81390af96a9ed", size = 601702 }, + { url = "https://files.pythonhosted.org/packages/87/f4/09ffb3ebd0cbb9e2c7c9b84d252557ecf434cd71584ee1e32f66013824df/rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f7728653900035fb7b8d06e1e5900545d8088efc9d5d4545782da7df03ec803f", size = 564054 }, ] [[package]] name = "ruff" version = "0.14.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944, upload-time = "2025-11-13T19:58:51.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630, upload-time = "2025-11-13T19:57:54.894Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925, upload-time = "2025-11-13T19:57:59.181Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040, upload-time = "2025-11-13T19:58:02.056Z" }, - { url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755, upload-time = "2025-11-13T19:58:05.172Z" }, - { url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641, upload-time = "2025-11-13T19:58:08.345Z" }, - { url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854, upload-time = "2025-11-13T19:58:11.419Z" }, - { url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088, upload-time = "2025-11-13T19:58:14.551Z" }, - { url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717, upload-time = "2025-11-13T19:58:17.518Z" }, - { url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812, upload-time = "2025-11-13T19:58:20.533Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656, upload-time = "2025-11-13T19:58:23.345Z" }, - { url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922, upload-time = "2025-11-13T19:58:26.537Z" }, - { url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501, upload-time = "2025-11-13T19:58:29.822Z" }, - { url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319, upload-time = "2025-11-13T19:58:32.923Z" }, - { url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209, upload-time = "2025-11-13T19:58:35.952Z" }, - { url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709, upload-time = "2025-11-13T19:58:39.002Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808, upload-time = "2025-11-13T19:58:42.779Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546, upload-time = "2025-11-13T19:58:45.691Z" }, - { url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331, upload-time = "2025-11-13T19:58:48.434Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630 }, + { url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925 }, + { url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040 }, + { url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755 }, + { url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641 }, + { url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854 }, + { url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088 }, + { url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717 }, + { url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812 }, + { url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656 }, + { url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922 }, + { url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501 }, + { url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319 }, + { url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209 }, + { url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709 }, + { url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808 }, + { url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546 }, + { url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331 }, ] [[package]] @@ -2825,24 +2827,24 @@ dependencies = [ { name = "scipy" }, { name = "tifffile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, - { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, - { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, - { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, - { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" }, - { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" }, - { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, - { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, - { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, - { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, - { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, - { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, + { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057 }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335 }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783 }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376 }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698 }, + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000 }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893 }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389 }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435 }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474 }, + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841 }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862 }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785 }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119 }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116 }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801 }, ] [[package]] @@ -2852,113 +2854,113 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97", size = 36630881, upload-time = "2025-10-28T17:31:47.104Z" }, - { url = "https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511", size = 28941012, upload-time = "2025-10-28T17:31:53.411Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a8/0e7a9a6872a923505dbdf6bb93451edcac120363131c19013044a1e7cb0c/scipy-1.16.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bea0a62734d20d67608660f69dcda23e7f90fb4ca20974ab80b6ed40df87a005", size = 20931935, upload-time = "2025-10-28T17:31:57.361Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c7/020fb72bd79ad798e4dbe53938543ecb96b3a9ac3fe274b7189e23e27353/scipy-1.16.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2a207a6ce9c24f1951241f4693ede2d393f59c07abc159b2cb2be980820e01fb", size = 23534466, upload-time = "2025-10-28T17:32:01.875Z" }, - { url = "https://files.pythonhosted.org/packages/be/a0/668c4609ce6dbf2f948e167836ccaf897f95fb63fa231c87da7558a374cd/scipy-1.16.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:532fb5ad6a87e9e9cd9c959b106b73145a03f04c7d57ea3e6f6bb60b86ab0876", size = 33593618, upload-time = "2025-10-28T17:32:06.902Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2", size = 35899798, upload-time = "2025-10-28T17:32:12.665Z" }, - { url = "https://files.pythonhosted.org/packages/79/e8/d0f33590364cdbd67f28ce79368b373889faa4ee959588beddf6daef9abe/scipy-1.16.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7180967113560cca57418a7bc719e30366b47959dd845a93206fbed693c867e", size = 36226154, upload-time = "2025-10-28T17:32:17.961Z" }, - { url = "https://files.pythonhosted.org/packages/39/c1/1903de608c0c924a1749c590064e65810f8046e437aba6be365abc4f7557/scipy-1.16.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:deb3841c925eeddb6afc1e4e4a45e418d19ec7b87c5df177695224078e8ec733", size = 38878540, upload-time = "2025-10-28T17:32:23.907Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78", size = 38722107, upload-time = "2025-10-28T17:32:29.921Z" }, - { url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272, upload-time = "2025-10-28T17:32:34.577Z" }, - { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" }, - { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" }, - { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" }, - { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" }, - { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" }, - { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" }, - { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" }, - { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" }, - { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856, upload-time = "2025-10-28T17:33:31.375Z" }, - { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306, upload-time = "2025-10-28T17:33:36.516Z" }, - { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371, upload-time = "2025-10-28T17:33:42.094Z" }, - { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877, upload-time = "2025-10-28T17:33:48.483Z" }, - { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103, upload-time = "2025-10-28T17:33:56.495Z" }, - { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297, upload-time = "2025-10-28T17:34:04.722Z" }, - { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756, upload-time = "2025-10-28T17:34:13.482Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566, upload-time = "2025-10-28T17:34:22.384Z" }, - { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877, upload-time = "2025-10-28T17:35:51.076Z" }, - { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366, upload-time = "2025-10-28T17:35:59.014Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931, upload-time = "2025-10-28T17:34:31.451Z" }, - { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081, upload-time = "2025-10-28T17:34:39.087Z" }, - { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244, upload-time = "2025-10-28T17:34:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753, upload-time = "2025-10-28T17:34:51.793Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912, upload-time = "2025-10-28T17:34:59.8Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371, upload-time = "2025-10-28T17:35:08.173Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477, upload-time = "2025-10-28T17:35:16.7Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" }, - { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" }, - { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" }, - { url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469, upload-time = "2025-10-28T17:36:08.741Z" }, - { url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043, upload-time = "2025-10-28T17:36:16.599Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952, upload-time = "2025-10-28T17:36:22.966Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512, upload-time = "2025-10-28T17:36:29.731Z" }, - { url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639, upload-time = "2025-10-28T17:36:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729, upload-time = "2025-10-28T17:36:46.547Z" }, - { url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251, upload-time = "2025-10-28T17:36:55.161Z" }, - { url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681, upload-time = "2025-10-28T17:37:04.1Z" }, - { url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423, upload-time = "2025-10-28T17:38:20.005Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027, upload-time = "2025-10-28T17:38:24.966Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379, upload-time = "2025-10-28T17:37:14.061Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052, upload-time = "2025-10-28T17:37:21.709Z" }, - { url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183, upload-time = "2025-10-28T17:37:29.559Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174, upload-time = "2025-10-28T17:37:36.306Z" }, - { url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852, upload-time = "2025-10-28T17:37:42.228Z" }, - { url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595, upload-time = "2025-10-28T17:37:48.102Z" }, - { url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269, upload-time = "2025-10-28T17:37:53.72Z" }, - { url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779, upload-time = "2025-10-28T17:37:59.393Z" }, - { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128, upload-time = "2025-10-28T17:38:05.259Z" }, - { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97", size = 36630881 }, + { url = "https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511", size = 28941012 }, + { url = "https://files.pythonhosted.org/packages/a8/a8/0e7a9a6872a923505dbdf6bb93451edcac120363131c19013044a1e7cb0c/scipy-1.16.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bea0a62734d20d67608660f69dcda23e7f90fb4ca20974ab80b6ed40df87a005", size = 20931935 }, + { url = "https://files.pythonhosted.org/packages/bd/c7/020fb72bd79ad798e4dbe53938543ecb96b3a9ac3fe274b7189e23e27353/scipy-1.16.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2a207a6ce9c24f1951241f4693ede2d393f59c07abc159b2cb2be980820e01fb", size = 23534466 }, + { url = "https://files.pythonhosted.org/packages/be/a0/668c4609ce6dbf2f948e167836ccaf897f95fb63fa231c87da7558a374cd/scipy-1.16.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:532fb5ad6a87e9e9cd9c959b106b73145a03f04c7d57ea3e6f6bb60b86ab0876", size = 33593618 }, + { url = "https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2", size = 35899798 }, + { url = "https://files.pythonhosted.org/packages/79/e8/d0f33590364cdbd67f28ce79368b373889faa4ee959588beddf6daef9abe/scipy-1.16.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7180967113560cca57418a7bc719e30366b47959dd845a93206fbed693c867e", size = 36226154 }, + { url = "https://files.pythonhosted.org/packages/39/c1/1903de608c0c924a1749c590064e65810f8046e437aba6be365abc4f7557/scipy-1.16.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:deb3841c925eeddb6afc1e4e4a45e418d19ec7b87c5df177695224078e8ec733", size = 38878540 }, + { url = "https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78", size = 38722107 }, + { url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272 }, + { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043 }, + { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986 }, + { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814 }, + { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795 }, + { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476 }, + { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692 }, + { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345 }, + { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975 }, + { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926 }, + { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014 }, + { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856 }, + { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306 }, + { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371 }, + { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877 }, + { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103 }, + { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756 }, + { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566 }, + { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877 }, + { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366 }, + { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931 }, + { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081 }, + { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244 }, + { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753 }, + { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912 }, + { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371 }, + { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477 }, + { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678 }, + { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178 }, + { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246 }, + { url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469 }, + { url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043 }, + { url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952 }, + { url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512 }, + { url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639 }, + { url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729 }, + { url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251 }, + { url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681 }, + { url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423 }, + { url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027 }, + { url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379 }, + { url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052 }, + { url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183 }, + { url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174 }, + { url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852 }, + { url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595 }, + { url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269 }, + { url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779 }, + { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128 }, + { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127 }, ] [[package]] name = "send2trash" version = "1.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, ] [[package]] name = "setuptools" version = "80.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } 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/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, ] [[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 = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] [[package]] name = "soupsieve" version = "2.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472 } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679 }, ] [[package]] @@ -2969,33 +2971,33 @@ 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/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/81/15d7c161c9ddf0900b076b55345872ed04ff1ed6a0666e5e94ab44b0163c/sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd", size = 2140517, upload-time = "2025-10-10T15:36:15.64Z" }, - { url = "https://files.pythonhosted.org/packages/d4/d5/4abd13b245c7d91bdf131d4916fd9e96a584dac74215f8b5bc945206a974/sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa", size = 2130738, upload-time = "2025-10-10T15:36:16.91Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3c/8418969879c26522019c1025171cefbb2a8586b6789ea13254ac602986c0/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e", size = 3304145, upload-time = "2025-10-10T15:34:19.569Z" }, - { url = "https://files.pythonhosted.org/packages/94/2d/fdb9246d9d32518bda5d90f4b65030b9bf403a935cfe4c36a474846517cb/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e", size = 3304511, upload-time = "2025-10-10T15:47:05.088Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fb/40f2ad1da97d5c83f6c1269664678293d3fe28e90ad17a1093b735420549/sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399", size = 3235161, upload-time = "2025-10-10T15:34:21.193Z" }, - { url = "https://files.pythonhosted.org/packages/95/cb/7cf4078b46752dca917d18cf31910d4eff6076e5b513c2d66100c4293d83/sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b", size = 3261426, upload-time = "2025-10-10T15:47:07.196Z" }, - { url = "https://files.pythonhosted.org/packages/f8/3b/55c09b285cb2d55bdfa711e778bdffdd0dc3ffa052b0af41f1c5d6e582fa/sqlalchemy-2.0.44-cp311-cp311-win32.whl", hash = "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3", size = 2105392, upload-time = "2025-10-10T15:38:20.051Z" }, - { url = "https://files.pythonhosted.org/packages/c7/23/907193c2f4d680aedbfbdf7bf24c13925e3c7c292e813326c1b84a0b878e/sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl", hash = "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5", size = 2130293, upload-time = "2025-10-10T15:38:21.601Z" }, - { url = "https://files.pythonhosted.org/packages/62/c4/59c7c9b068e6813c898b771204aad36683c96318ed12d4233e1b18762164/sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250", size = 2139675, upload-time = "2025-10-10T16:03:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ae/eeb0920537a6f9c5a3708e4a5fc55af25900216bdb4847ec29cfddf3bf3a/sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29", size = 2127726, upload-time = "2025-10-10T16:03:35.934Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d5/2ebbabe0379418eda8041c06b0b551f213576bfe4c2f09d77c06c07c8cc5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44", size = 3327603, upload-time = "2025-10-10T15:35:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/5aa65852dadc24b7d8ae75b7efb8d19303ed6ac93482e60c44a585930ea5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1", size = 3337842, upload-time = "2025-10-10T15:43:45.431Z" }, - { url = "https://files.pythonhosted.org/packages/41/92/648f1afd3f20b71e880ca797a960f638d39d243e233a7082c93093c22378/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7", size = 3264558, upload-time = "2025-10-10T15:35:29.93Z" }, - { url = "https://files.pythonhosted.org/packages/40/cf/e27d7ee61a10f74b17740918e23cbc5bc62011b48282170dc4c66da8ec0f/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d", size = 3301570, upload-time = "2025-10-10T15:43:48.407Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3d/3116a9a7b63e780fb402799b6da227435be878b6846b192f076d2f838654/sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4", size = 2103447, upload-time = "2025-10-10T15:03:21.678Z" }, - { url = "https://files.pythonhosted.org/packages/25/83/24690e9dfc241e6ab062df82cc0df7f4231c79ba98b273fa496fb3dd78ed/sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e", size = 2130912, upload-time = "2025-10-10T15:03:24.656Z" }, - { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" }, - { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" }, - { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" }, - { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" }, - { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" }, - { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" }, - { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" }, - { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/81/15d7c161c9ddf0900b076b55345872ed04ff1ed6a0666e5e94ab44b0163c/sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd", size = 2140517 }, + { url = "https://files.pythonhosted.org/packages/d4/d5/4abd13b245c7d91bdf131d4916fd9e96a584dac74215f8b5bc945206a974/sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa", size = 2130738 }, + { url = "https://files.pythonhosted.org/packages/cb/3c/8418969879c26522019c1025171cefbb2a8586b6789ea13254ac602986c0/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e", size = 3304145 }, + { url = "https://files.pythonhosted.org/packages/94/2d/fdb9246d9d32518bda5d90f4b65030b9bf403a935cfe4c36a474846517cb/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e", size = 3304511 }, + { url = "https://files.pythonhosted.org/packages/7d/fb/40f2ad1da97d5c83f6c1269664678293d3fe28e90ad17a1093b735420549/sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399", size = 3235161 }, + { url = "https://files.pythonhosted.org/packages/95/cb/7cf4078b46752dca917d18cf31910d4eff6076e5b513c2d66100c4293d83/sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b", size = 3261426 }, + { url = "https://files.pythonhosted.org/packages/f8/3b/55c09b285cb2d55bdfa711e778bdffdd0dc3ffa052b0af41f1c5d6e582fa/sqlalchemy-2.0.44-cp311-cp311-win32.whl", hash = "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3", size = 2105392 }, + { url = "https://files.pythonhosted.org/packages/c7/23/907193c2f4d680aedbfbdf7bf24c13925e3c7c292e813326c1b84a0b878e/sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl", hash = "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5", size = 2130293 }, + { url = "https://files.pythonhosted.org/packages/62/c4/59c7c9b068e6813c898b771204aad36683c96318ed12d4233e1b18762164/sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250", size = 2139675 }, + { url = "https://files.pythonhosted.org/packages/d6/ae/eeb0920537a6f9c5a3708e4a5fc55af25900216bdb4847ec29cfddf3bf3a/sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29", size = 2127726 }, + { url = "https://files.pythonhosted.org/packages/d8/d5/2ebbabe0379418eda8041c06b0b551f213576bfe4c2f09d77c06c07c8cc5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44", size = 3327603 }, + { url = "https://files.pythonhosted.org/packages/45/e5/5aa65852dadc24b7d8ae75b7efb8d19303ed6ac93482e60c44a585930ea5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1", size = 3337842 }, + { url = "https://files.pythonhosted.org/packages/41/92/648f1afd3f20b71e880ca797a960f638d39d243e233a7082c93093c22378/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7", size = 3264558 }, + { url = "https://files.pythonhosted.org/packages/40/cf/e27d7ee61a10f74b17740918e23cbc5bc62011b48282170dc4c66da8ec0f/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d", size = 3301570 }, + { url = "https://files.pythonhosted.org/packages/3b/3d/3116a9a7b63e780fb402799b6da227435be878b6846b192f076d2f838654/sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4", size = 2103447 }, + { url = "https://files.pythonhosted.org/packages/25/83/24690e9dfc241e6ab062df82cc0df7f4231c79ba98b273fa496fb3dd78ed/sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e", size = 2130912 }, + { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479 }, + { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212 }, + { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353 }, + { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222 }, + { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614 }, + { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248 }, + { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275 }, + { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901 }, + { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718 }, ] [[package]] @@ -3007,9 +3009,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]] @@ -3019,9 +3021,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]] @@ -3041,7 +3043,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]] @@ -3049,9 +3051,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]] @@ -3063,9 +3065,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]] @@ -3075,9 +3077,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/b5/0d8f3d395f07d25ec4cafcdfc8cab234b2cc6bf2465e9d7660633983fe8f/tifffile-2025.10.16.tar.gz", hash = "sha256:425179ec7837ac0e07bc95d2ea5bea9b179ce854967c12ba07fc3f093e58efc1", size = 371848, upload-time = "2025-10-16T22:56:09.043Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/b5/0d8f3d395f07d25ec4cafcdfc8cab234b2cc6bf2465e9d7660633983fe8f/tifffile-2025.10.16.tar.gz", hash = "sha256:425179ec7837ac0e07bc95d2ea5bea9b179ce854967c12ba07fc3f093e58efc1", size = 371848 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/5e/56c751afab61336cf0e7aa671b134255a30f15f59cd9e04f59c598a37ff5/tifffile-2025.10.16-py3-none-any.whl", hash = "sha256:41463d979c1c262b0a5cdef2a7f95f0388a072ad82d899458b154a48609d759c", size = 231162, upload-time = "2025-10-16T22:56:07.214Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5e/56c751afab61336cf0e7aa671b134255a30f15f59cd9e04f59c598a37ff5/tifffile-2025.10.16-py3-none-any.whl", hash = "sha256:41463d979c1c262b0a5cdef2a7f95f0388a072ad82d899458b154a48609d759c", size = 231162 }, ] [[package]] @@ -3087,67 +3089,67 @@ 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.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236 }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084 }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832 }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052 }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555 }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128 }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445 }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165 }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891 }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796 }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121 }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070 }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859 }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296 }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124 }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698 }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819 }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766 }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771 }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586 }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792 }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909 }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946 }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705 }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244 }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637 }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925 }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045 }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835 }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109 }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930 }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964 }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065 }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088 }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193 }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488 }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669 }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709 }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563 }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756 }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408 }, ] [[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]] @@ -3180,30 +3182,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/15/db/c064112ac0089af3d2f7a2b5bfbabf4aa407a78b74f87889e524b91c5402/torch-2.9.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:62b3fd888277946918cba4478cf849303da5359f0fb4e3bfb86b0533ba2eaf8d", size = 104220430 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/47/cc/7a2949e38dfe3244c4df21f0e1c27bce8aedd6c604a587dd44fc21017cb4/torch-2.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:0d06b30a9207b7c3516a9e0102114024755a07045f0c1d2f2a56b1819ac06bcb", size = 110973074 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/b1/1a/64f5769025db846a82567fa5b7d21dba4558a7234ee631712ee4771c436c/torch-2.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:81a285002d7b8cfd3fdf1b98aa8df138d41f1a8334fd9ea37511517cedf43083", size = 110940568 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/a6/47/c7843d69d6de8938c1cbb1eba426b1d48ddf375f101473d3e31a5fc52b74/torch-2.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:545844cc16b3f91e08ce3b40e9c2d77012dd33a48d505aed34b7740ed627a1b2", size = 110944162 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/1f/9f/6986b83a53b4d043e36f3f898b798ab51f7f20fdf1a9b01a2720f445043d/torch-2.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2e1c42c0ae92bf803a4b2409fdfed85e30f9027a66887f5e7dcdbc014c7531db", size = 111176995 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/63/5d/e8d4e009e52b6b2cf1684bde2a6be157b96fb873732542fb2a9a99e85a83/torch-2.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:d187566a2cdc726fc80138c3cdb260970fab1c27e99f85452721f7759bbd554d", size = 110934845 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/db/2b/f7818f6ec88758dfd21da46b6cd46af9d1b3433e53ddbb19ad1e0da17f9b/torch-2.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c88d3299ddeb2b35dcc31753305612db485ab6f1823e37fb29451c8b2732b87e", size = 111163659 }, ] [[package]] @@ -3216,9 +3218,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/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]] @@ -3231,49 +3233,49 @@ 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/e7/69/30f5f03752aa1a7c23931d2519b31e557f3f10af5089d787cddf3b903ecf/torchvision-0.24.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:056c525dc875f18fe8e9c27079ada166a7b2755cea5a2199b0bc7f1f8364e600", size = 1891436 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/fa/bb/cfc6a6f6ccc84a534ed1fdf029ae5716dd6ff04e57ed9dc2dab38bf652d5/torchvision-0.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:a9308cdd37d8a42e14a3e7fd9d271830c7fecb150dd929b642f3c1460514599a", size = 4037588 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/69/98/16e583f59f86cd59949f59d52bfa8fc286f86341a229a9d15cbe7a694f0c/torchvision-0.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:4aa6cb806eb8541e92c9b313e96192c6b826e9eb0042720e2fa250d021079952", size = 4302006 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/5c/ec/54a96ae9ab6a0dd66d4bba27771f892e36478a9c3489fa56e51c70abcc4d/torchvision-0.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:16274823b93048e0a29d83415166a2e9e0bf4e1b432668357b657612a4802864", size = 4319808 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/4f/2b/ba02e4261369c3798310483028495cf507e6cb3f394f42e4796981ecf3a7/torchvision-0.24.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d83e16d70ea85d2f196d678bfb702c36be7a655b003abed84e465988b6128938", size = 4251604 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/6d/ba/8fae3525b233e109317ce6a9c1de922ab2881737b029a7e88021f81e068f/torchvision-0.24.1-cp314-cp314-win_amd64.whl", hash = "sha256:18f9cb60e64b37b551cd605a3d62c15730c086362b40682d23e24b616a697d41", size = 4234459 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/d6/ab/e2bcc7c2f13d882a58f8b30ff86f794210b075736587ea50f8c545834f8a/torchvision-0.24.1-cp314-cp314t-win_amd64.whl", hash = "sha256:480b271d6edff83ac2e8d69bbb4cf2073f93366516a50d48f140ccfceedb002e", size = 4335190 }, ] [[package]] name = "tornado" version = "6.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, - { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, - { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, - { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, - { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, - { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, - { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, - { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563 }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729 }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295 }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644 }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878 }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549 }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973 }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954 }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023 }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427 }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456 }, ] [[package]] @@ -3283,18 +3285,18 @@ 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/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } 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/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, ] [[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]] @@ -3302,48 +3304,48 @@ name = "triton" version = "3.5.1" 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/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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, ] [[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.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, ] [[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.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, ] [[package]] @@ -3355,45 +3357,45 @@ dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799 } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095 }, ] [[package]] name = "wcwidth" version = "0.2.14" 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/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293 } 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/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286 }, ] [[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]] @@ -3403,9 +3405,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, ] [[package]] @@ -3419,16 +3421,16 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/67/14be68a7bad15eecda09b1e81fca2420f7533645fe187bf4d6104c1aad52/zarr-3.1.3.tar.gz", hash = "sha256:01342f3e26a02ed5670db608a5576fbdb8d76acb5c280bd2d0082454b1ba6f79", size = 349125, upload-time = "2025-09-18T19:32:41.688Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/67/14be68a7bad15eecda09b1e81fca2420f7533645fe187bf4d6104c1aad52/zarr-3.1.3.tar.gz", hash = "sha256:01342f3e26a02ed5670db608a5576fbdb8d76acb5c280bd2d0082454b1ba6f79", size = 349125 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/71/9de7229515a53d1cc5705ca9c411530f711a2242f962214d9dbfe2741aa4/zarr-3.1.3-py3-none-any.whl", hash = "sha256:45f67f87f65f14fa453f99dd8110a5936b7ac69f3a21981d33e90407c80c302a", size = 276427, upload-time = "2025-09-18T19:32:40.042Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/9de7229515a53d1cc5705ca9c411530f711a2242f962214d9dbfe2741aa4/zarr-3.1.3-py3-none-any.whl", hash = "sha256:45f67f87f65f14fa453f99dd8110a5936b7ac69f3a21981d33e90407c80c302a", size = 276427 }, ] [[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 0852c49218e183244af0d5bd5779c805b8fbad25 Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 18 Jan 2026 16:52:56 -0800 Subject: [PATCH 009/113] temp removing function for merge --- src/quantem/core/utils/imaging_utils.py | 63 ------------------------- 1 file changed, 63 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 8a07b3c17..5dced5bcb 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -548,66 +548,3 @@ def fourier_cropping( return result - -def rotate_image( - im, - rotation_deg: float, - origin: tuple[float, float] | None = None, - clockwise: bool = True, - interpolation: str = "bilinear", - mode: str = "constant", - cval: float = 0.0, -): - """Rotate an array about a pixel origin using bilinear/bicubic interpolation.""" - im = np.asarray(im) - if im.ndim < 2: - raise ValueError("im must have at least 2 dimensions") - - H, W = im.shape[-2], im.shape[-1] - if origin is None: - r0 = float(H // 2) - c0 = float(W // 2) - else: - r0 = float(origin[0]) - c0 = float(origin[1]) - - interp = str(interpolation).lower() - if interp in {"bilinear", "linear"}: - order = 1 - elif interp in {"bicubic", "cubic"}: - order = 3 - else: - raise ValueError("interpolation must be 'bilinear' or 'bicubic'") - - theta = float(np.deg2rad(rotation_deg)) - if not clockwise: - theta = -theta - - ct = float(np.cos(theta)) - st = float(np.sin(theta)) - - r_out, c_out = np.meshgrid( - np.arange(H, dtype=np.float64), - np.arange(W, dtype=np.float64), - indexing="ij", - ) - - c_rel = c_out - c0 - r_rel = r_out - r0 - - c_in = ct * c_rel + st * r_rel + c0 - r_in = -st * c_rel + ct * r_rel + r0 - - coords = np.vstack((r_in.ravel(), c_in.ravel())) - - if im.ndim == 2: - out = map_coordinates(im, coords, order=order, mode=mode, cval=cval) - return out.reshape(H, W) - - prefix = im.shape[:-2] - n = int(np.prod(prefix)) if prefix else 1 - im_flat = im.reshape(n, H, W) - out_flat = np.empty((n, H * W), dtype=np.result_type(im_flat.dtype, np.float64)) - for i in range(n): - out_flat[i] = map_coordinates(im_flat[i], coords, order=order, mode=mode, cval=cval) - return out_flat.reshape(*prefix, H, W) From 53ac2d5c473e8361393f15be30d0ac0d94a12203 Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 18 Jan 2026 16:53:47 -0800 Subject: [PATCH 010/113] adding rotate_image back --- src/quantem/core/utils/imaging_utils.py | 65 +++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 9be6c9c45..ba5fc3e9c 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -1049,3 +1049,68 @@ def unwrap_phase_2d_torch( raise ValueError( f'`method` must be one of {{"reliability-sorting", "poisson"}}, got {method!r}' ) + + + +def rotate_image( + im, + rotation_deg: float, + origin: tuple[float, float] | None = None, + clockwise: bool = True, + interpolation: str = "bilinear", + mode: str = "constant", + cval: float = 0.0, +): + """Rotate an array about a pixel origin using bilinear/bicubic interpolation.""" + im = np.asarray(im) + if im.ndim < 2: + raise ValueError("im must have at least 2 dimensions") + + H, W = im.shape[-2], im.shape[-1] + if origin is None: + r0 = float(H // 2) + c0 = float(W // 2) + else: + r0 = float(origin[0]) + c0 = float(origin[1]) + + interp = str(interpolation).lower() + if interp in {"bilinear", "linear"}: + order = 1 + elif interp in {"bicubic", "cubic"}: + order = 3 + else: + raise ValueError("interpolation must be 'bilinear' or 'bicubic'") + + theta = float(np.deg2rad(rotation_deg)) + if not clockwise: + theta = -theta + + ct = float(np.cos(theta)) + st = float(np.sin(theta)) + + r_out, c_out = np.meshgrid( + np.arange(H, dtype=np.float64), + np.arange(W, dtype=np.float64), + indexing="ij", + ) + + c_rel = c_out - c0 + r_rel = r_out - r0 + + c_in = ct * c_rel + st * r_rel + c0 + r_in = -st * c_rel + ct * r_rel + r0 + + coords = np.vstack((r_in.ravel(), c_in.ravel())) + + if im.ndim == 2: + out = map_coordinates(im, coords, order=order, mode=mode, cval=cval) + return out.reshape(H, W) + + prefix = im.shape[:-2] + n = int(np.prod(prefix)) if prefix else 1 + im_flat = im.reshape(n, H, W) + out_flat = np.empty((n, H * W), dtype=np.result_type(im_flat.dtype, np.float64)) + for i in range(n): + out_flat[i] = map_coordinates(im_flat[i], coords, order=order, mode=mode, cval=cval) + return out_flat.reshape(*prefix, H, W) From 22804b2289b9195900a4ef89e87c064b4489c9e4 Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 18 Jan 2026 20:23:34 -0800 Subject: [PATCH 011/113] strain mapping updates --- src/quantem/diffraction/__init__.py | 2 +- src/quantem/diffraction/strain.py | 953 ++++++++++++++++++---------- 2 files changed, 608 insertions(+), 347 deletions(-) diff --git a/src/quantem/diffraction/__init__.py b/src/quantem/diffraction/__init__.py index 6d0e4202a..77bc5f829 100644 --- a/src/quantem/diffraction/__init__.py +++ b/src/quantem/diffraction/__init__.py @@ -1,2 +1,2 @@ from quantem.diffraction.polar import RDF as RDF -from quantem.diffraction.strain import StrainMap as StrainMap +from quantem.diffraction.strain import StrainMapAutocorrelation as StrainMapAutocorrelation diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index bcfa981de..fa92c8474 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -18,7 +18,7 @@ from quantem.core.visualization import ScalebarConfig, show_2d -class StrainMap(AutoSerialize): +class StrainMapAutocorrelation(AutoSerialize): _token = object() def __init__( @@ -28,7 +28,9 @@ def __init__( _token: object | None = None, ): if _token is not self._token: - raise RuntimeError("Use StrainMap.from_data() to instantiate this class.") + raise RuntimeError( + "Use StrainMapAutocorrelation.from_dataset() or StrainMapAutocorrelation.from_array() to instantiate this class." + ) super().__init__() self.dataset = dataset self.input_data = input_data @@ -36,106 +38,129 @@ def __init__( self.metadata: dict[str, Any] = {} self.transform: Dataset2d | None = None self.transform_rotated: Dataset2d | None = None + self.u: NDArray | None = None self.v: NDArray | None = None + + self.u_fit: Dataset3d | None = None + self.v_fit: Dataset3d | None = None + self.u_peak_fit: Dataset3d | None = None + self.v_peak_fit: Dataset3d | None = None + self.mask_diffraction = np.ones(self.dataset.array.shape[2:]) self.mask_diffraction_inv = np.zeros(self.dataset.array.shape[2:]) @classmethod - def from_data(cls, data: NDArray | Dataset4dstem, *, name: str = "strain_map") -> "StrainMap": - if isinstance(data, Dataset4dstem): - return cls(dataset=data, input_data=data, _token=cls._token) + def from_dataset(cls, dataset: Dataset4dstem, *, name: str | None = None) -> "StrainMapAutocorrelation": + if not isinstance(dataset, Dataset4dstem): + raise TypeError("StrainMapAutocorrelation.from_dataset expects a Dataset4dstem instance.") + if name is not None: + dataset.name = name + return cls(dataset=dataset, input_data=dataset, _token=cls._token) - arr = ensure_valid_array(data) + @classmethod + def from_array(cls, array: NDArray, *, name: str = "strain_map_autocorrelation") -> "StrainMapAutocorrelation": + arr = ensure_valid_array(array) if arr.ndim != 4: raise ValueError( - "StrainMap.from_data expects a 4D array with shape (scan_r, scan_c, dp_r, dp_c)." + "StrainMapAutocorrelation.from_array expects a 4D array with shape (scan_r, scan_c, dp_r, dp_c)." ) - ds4 = Dataset4dstem.from_array(arr, name=name) - return cls(dataset=ds4, input_data=data, _token=cls._token) - + return cls(dataset=ds4, input_data=array, _token=cls._token) def diffraction_mask( self, - threshold = None, - edge_blend = 64.0, - plot_mask = True, - figsize = (8,4), + threshold=None, + edge_blend=64.0, + plot_mask=True, + figsize=(8, 4), ): - dp_mean = np.mean(self.dataset.array,axis=(0,1)) + dp_mean = np.mean(self.dataset.array, axis=(0, 1)) mask_init = dp_mean < threshold - mask_init[:,0] = True - mask_init[0,:] = True - mask_init[:,-1] = True - mask_init[-1,:] = True + mask_init[:, 0] = True + mask_init[0, :] = True + mask_init[:, -1] = True + mask_init[-1, :] = True self.mask_diffraction = np.sin( np.clip( distance_transform_edt(np.logical_not(mask_init)) / edge_blend, 0.0, 1.0, - )*np.pi/2, - )**2 - # int_edge = np.sum(dp_mean*self.mask_diffraction) / np.sum(self.mask_diffraction) - int_edge = np.min(dp_mean[self.mask_diffraction>0.99]) + ) + * np.pi + / 2, + ) ** 2 + int_edge = np.min(dp_mean[self.mask_diffraction > 0.99]) self.mask_diffraction_inv = (1 - self.mask_diffraction) * int_edge if plot_mask: - fig,ax = plt.subplots(1,2,figsize=figsize) + fig, ax = plt.subplots(1, 2, figsize=figsize) ax[0].imshow( - np.log(np.maximum(dp_mean,np.min(dp_mean[dp_mean>0]))), - cmap = 'gray', + np.log(np.maximum(dp_mean, np.min(dp_mean[dp_mean > 0]))), + cmap="gray", ) ax[1].imshow( np.log( - dp_mean*self.mask_diffraction + \ - self.mask_diffraction_inv, + dp_mean * self.mask_diffraction + self.mask_diffraction_inv, ), - cmap = 'gray', + cmap="gray", ) - - return self + return self def preprocess( self, mode: str = "linear", q_to_r_rotation_ccw_deg: float | None = None, q_transpose: bool | None = None, - skip = None, + skip=None, plot_transform: bool = True, cropping_factor: float = 0.25, + gamma: float = 0.5, **plot_kwargs: Any, - ) -> "StrainMap": + ) -> "StrainMapAutocorrelation": + mode_in = mode.strip().lower() + if mode_in in {"linear", "patterson", "paterson", "acf", "autocorrelation"}: + mode_norm = "linear" + elif mode_in in {"log", "cepstrum", "cepstral"}: + mode_norm = "log" + elif mode_in in {"gamma", "power", "sqrt"}: + mode_norm = "gamma" + else: + raise ValueError( + "mode must be 'linear', 'log', or 'gamma' (aliases: 'patterson'->'linear', 'cepstrum'/'cepstral'->'log')." + ) - self.metadata["mode"] = mode + self.metadata["mode"] = mode_norm + if mode_norm == "gamma": + self.metadata["gamma"] = gamma - qrow_unit = str(self.dataset.units[2]) - qcol_unit = str(self.dataset.units[3]) + qrow_unit = self.dataset.units[2] + qcol_unit = self.dataset.units[3] if qrow_unit in {"A", "Å"}: - qrow_sampling_ang = float(self.dataset.sampling[2]) + qrow_sampling_ang = self.dataset.sampling[2] elif qrow_unit == "mrad": - wavelength = float(electron_wavelength_angstrom(float(self.dataset.metadata["energy"]))) - qrow_sampling_ang = float(self.dataset.sampling[2]) / 1000.0 / wavelength + wavelength = electron_wavelength_angstrom(self.dataset.metadata["energy"]) + qrow_sampling_ang = self.dataset.sampling[2] / 1000.0 / wavelength else: qrow_sampling_ang = 1.0 qrow_unit = "pixels" if qcol_unit in {"A", "Å"}: - qcol_sampling_ang = float(self.dataset.sampling[3]) + qcol_sampling_ang = self.dataset.sampling[3] elif qcol_unit == "mrad": - wavelength = float(electron_wavelength_angstrom(float(self.dataset.metadata["energy"]))) - qcol_sampling_ang = float(self.dataset.sampling[3]) / 1000.0 / wavelength + wavelength = electron_wavelength_angstrom(self.dataset.metadata["energy"]) + qcol_sampling_ang = self.dataset.sampling[3] / 1000.0 / wavelength else: qcol_sampling_ang = 1.0 qcol_unit = "pixels" self.metadata["sampling_real"] = np.array( ( - 1.0 / (qrow_sampling_ang * float(self.dataset.shape[2])), - 1.0 / (qcol_sampling_ang * float(self.dataset.shape[3])), + 1.0 / (qrow_sampling_ang * self.dataset.shape[2]), + 1.0 / (qcol_sampling_ang * self.dataset.shape[3]), ), dtype=float, ) @@ -145,67 +170,53 @@ def preprocess( else: self.metadata["real_units"] = r"$\mathrm{\AA}$" - if q_to_r_rotation_ccw_deg is None or q_transpose is None: - parent_rot = self.dataset.metadata.get("q_to_r_rotation_ccw_deg", None) - parent_tr = self.dataset.metadata.get("q_transpose", None) - if q_to_r_rotation_ccw_deg is None and parent_rot is not None: - q_to_r_rotation_ccw_deg = float(parent_rot) - if q_transpose is None and parent_tr is not None: - q_transpose = bool(parent_tr) - if (parent_rot is not None or parent_tr is not None) and ( - q_to_r_rotation_ccw_deg is not None or q_transpose is not None - ): - import warnings - - warnings.warn( - f"StrainMap.preprocess: using parent Dataset4dstem metadata " - f"(q_to_r_rotation_ccw_deg={float(q_to_r_rotation_ccw_deg or 0.0)}, " - f"q_transpose={bool(q_transpose or False)}).", - UserWarning, - ) + parent_rot = self.dataset.metadata.get("q_to_r_rotation_ccw_deg", None) + parent_tr = self.dataset.metadata.get("q_transpose", None) - if q_to_r_rotation_ccw_deg is None or q_transpose is None: + used_parent = False + if q_to_r_rotation_ccw_deg is None and parent_rot is not None: + q_to_r_rotation_ccw_deg = parent_rot + used_parent = True + if q_transpose is None and parent_tr is not None: + q_transpose = parent_tr + used_parent = True + + if used_parent: import warnings - q_to_r_rotation_ccw_deg = ( - 0.0 if q_to_r_rotation_ccw_deg is None else float(q_to_r_rotation_ccw_deg) + warnings.warn( + "StrainMapAutocorrelation.preprocess: using parent Dataset4dstem metadata " + f"(q_to_r_rotation_ccw_deg={q_to_r_rotation_ccw_deg or 0.0}, " + f"q_transpose={q_transpose or False}).", + UserWarning, ) - q_transpose = False if q_transpose is None else bool(q_transpose) + + if q_to_r_rotation_ccw_deg is None or q_transpose is None: + import warnings + + q_to_r_rotation_ccw_deg = 0.0 if q_to_r_rotation_ccw_deg is None else q_to_r_rotation_ccw_deg + q_transpose = False if q_transpose is None else q_transpose warnings.warn( - "StrainMap.preprocess: setting q_to_r_rotation_ccw_deg=0.0 and q_transpose=False.", + "StrainMapPatterson.preprocess: setting q_to_r_rotation_ccw_deg=0.0 and q_transpose=False.", UserWarning, ) - self.metadata["q_to_r_rotation_ccw_deg"] = float(q_to_r_rotation_ccw_deg) - self.metadata["q_transpose"] = bool(q_transpose) - - if skip is None: - if self.metadata["mode"] == "linear": - im = np.mean(np.abs(np.fft.fft2( - self.dataset.array * self.mask_diffraction[None,None,:,:] + \ - self.mask_diffraction_inv[None,None,:,:] - )), axis=(0, 1)) - elif self.metadata["mode"] == "log": - im = np.mean(np.abs(np.fft.fft2(np.log1p( - self.dataset.array * self.mask_diffraction[None,None,:,:] + \ - self.mask_diffraction_inv[None,None,:,:] - ))), axis=(0, 1)) - else: - raise ValueError("mode must be 'linear' or 'log'") + self.metadata["q_to_r_rotation_ccw_deg"] = q_to_r_rotation_ccw_deg + self.metadata["q_transpose"] = q_transpose + + arr = self.dataset.array if skip is None else self.dataset.array[::skip, ::skip] + dp = arr * self.mask_diffraction[None, None, :, :] + self.mask_diffraction_inv[None, None, :, :] + + if mode_norm == "linear": + dp_proc = dp + elif mode_norm == "log": + dp_proc = np.log1p(dp) + elif mode_norm == "gamma": + dp_proc = np.power(np.clip(dp, 0.0, None), self.metadata["gamma"]) else: - if self.metadata["mode"] == "linear": - im = np.mean(np.abs(np.fft.fft2( - self.dataset.array[::skip,::skip] * self.mask_diffraction[None,None,:,:] + \ - self.mask_diffraction_inv[None,None,:,:] - )), axis=(0, 1)) - elif self.metadata["mode"] == "log": - im = np.mean(np.abs(np.fft.fft2(np.log1p( - self.dataset.array[::skip,::skip] * self.mask_diffraction[None,None,:,:] + \ - self.mask_diffraction_inv[None,None,:,:] - ))), axis=(0, 1)) - else: - raise ValueError("mode must be 'linear' or 'log'") + raise RuntimeError("Unreachable: normalized mode mapping failed.") + im = np.mean(np.abs(np.fft.fft2(dp_proc)), axis=(0, 1)) im = np.fft.fftshift(im) self.transform = Dataset2d.from_array( @@ -217,13 +228,13 @@ def preprocess( ) im_plot = self.transform.array - if bool(self.metadata["q_transpose"]): + if self.metadata["q_transpose"]: im_plot = im_plot.T self.transform_rotated = Dataset2d.from_array( rotate_image( im_plot, - float(self.metadata["q_to_r_rotation_ccw_deg"]), + self.metadata["q_to_r_rotation_ccw_deg"], clockwise=False, ), origin=(im.shape[0] // 2, im.shape[1] // 2), @@ -246,21 +257,20 @@ def plot_transform( if self.transform is None or self.transform_rotated is None: raise ValueError("Run preprocess() first to compute transform images.") - sampling = float(np.mean(self.metadata["sampling_real"])) - units = str(self.metadata.get("real_units", r"$\mathrm{\AA}$")) + sampling = np.mean(self.metadata["sampling_real"]) + units = self.metadata.get("real_units", r"$\mathrm{\AA}$") - W = int(self.transform.shape[1]) - view_w_px = float(W) * float(cropping_factor) - target_units = float(scalebar_fraction) * view_w_px * sampling + W = self.transform.shape[1] + view_w_px = W * cropping_factor + target_units = scalebar_fraction * view_w_px * sampling sb_len = _nice_length_units(target_units) - # intensity scaling: compute from transform, apply same scaling to both panels kr = (np.arange(self.transform.shape[0], dtype=float) - self.transform.shape[0] // 2)[:, None] kc = (np.arange(self.transform.shape[1], dtype=float) - self.transform.shape[1] // 2)[None, :] qmag = np.sqrt(kr * kr + kc * kc) im0 = self.transform.array tmp = im0 * qmag - i0 = np.unravel_index(int(np.nanargmax(tmp)), tmp.shape) + i0 = np.unravel_index(np.nanargmax(tmp), tmp.shape) vmin = 0.0 vmax = im0[i0] @@ -283,53 +293,57 @@ def plot_transform( return fig, ax - def choose_lattice_vector( self, *, u: tuple[float, float] | NDArray, v: tuple[float, float] | NDArray, define_in_rotated: bool = False, - refine_subpixel: bool = True, - refine_subpixel_dft: bool = False, + refine_gaussian: bool = True, + refine_dft: bool = False, refine_radius_px: float = 2.0, - refine_log: bool = False, upsample: int = 16, + gaussian_maxfev: int = 100, plot: bool = True, cropping_factor: float = 0.25, **plot_kwargs: Any, - ) -> "StrainMap": + ) -> "StrainMapAutocorrelation": if self.transform is None or self.transform_rotated is None: raise ValueError("Run preprocess() first to compute transform images.") u_rc = np.asarray(u, dtype=float).reshape(2) v_rc = np.asarray(v, dtype=float).reshape(2) - rot_ccw = float(self.metadata.get("q_to_r_rotation_ccw_deg", 0.0)) - q_transpose = bool(self.metadata.get("q_transpose", False)) + rot_ccw = self.metadata["q_to_r_rotation_ccw_deg"] + q_transpose = self.metadata["q_transpose"] if define_in_rotated: u_rc = _display_vec_to_raw(u_rc, rotation_ccw_deg=rot_ccw, transpose=q_transpose) v_rc = _display_vec_to_raw(v_rc, rotation_ccw_deg=rot_ccw, transpose=q_transpose) - if refine_subpixel_dft: - refine_subpixel = True - - if refine_subpixel: - u_rc, v_rc = _refine_lattice_vectors( - self.transform.array, - u_rc=u_rc, - v_rc=v_rc, - radius_px=float(refine_radius_px), - log_fit=bool(refine_log), - refine_dft=bool(refine_subpixel_dft), - upsample=int(upsample), - ) + u_fit_abs, v_fit_abs = _refine_lattice_vectors( + self.transform.array, + u_rc=u_rc, + v_rc=v_rc, + radius_px=refine_radius_px, + refine_gaussian=refine_gaussian, + refine_dft=refine_dft, + upsample=upsample, + maxfev=gaussian_maxfev, + ) + + H, W = self.transform.array.shape + center = np.array((H // 2, W // 2), dtype=float) - self.u = u_rc - self.v = v_rc - self.metadata["lattice_u_rc"] = self.u.copy() - self.metadata["lattice_v_rc"] = self.v.copy() + self.u = u_fit_abs[:2] - center + self.v = v_fit_abs[:2] - center + + self.metadata["choose_define_in_rotated"] = define_in_rotated + self.metadata["choose_refine_gaussian"] = refine_gaussian + self.metadata["choose_refine_dft"] = refine_dft + self.metadata["choose_refine_radius_px"] = refine_radius_px + self.metadata["choose_upsample"] = upsample + self.metadata["choose_gaussian_maxfev"] = gaussian_maxfev if plot: fig, ax = self.plot_transform(cropping_factor=cropping_factor, **plot_kwargs) @@ -345,38 +359,46 @@ def choose_lattice_vector( return self - def fit_lattice_vectors( self, - refine_subpixel: bool = True, - refine_subpixel_dft: bool = False, + refine_gaussian: bool = True, + refine_dft: bool = False, refine_radius_px: float = 2.0, upsample: int = 16, - refine_log: bool = False, + gaussian_maxfev: int = 100, progressbar: bool = True, - ) -> "StrainMap": - from quantem.core.datastructures.dataset3d import Dataset3d - + ) -> "StrainMapAutocorrelation": if self.u is None or self.v is None: raise ValueError("Run choose_lattice_vector() first to set initial lattice vectors (self.u, self.v).") - if refine_subpixel_dft: - refine_subpixel = True - scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] + + self.u_peak_fit = Dataset3d.from_shape( + (scan_r, scan_c, 5), + name="u_peak_fit", + signal_units="mixed", + ) + self.v_peak_fit = Dataset3d.from_shape( + (scan_r, scan_c, 5), + name="v_peak_fit", + signal_units="mixed", + ) + self.u_fit = Dataset3d.from_shape( (scan_r, scan_c, 2), - name="u_fits", + name="u_fit", signal_units="pixels", ) self.v_fit = Dataset3d.from_shape( (scan_r, scan_c, 2), - name="v_fits", + name="v_fit", signal_units="pixels", ) - mode = str(self.metadata.get("mode", "linear")).lower() + mode = self.metadata.get("mode", "linear").lower() + if mode == "gamma": + g = self.metadata["gamma"] it = np.ndindex(scan_r, scan_c) if progressbar: @@ -390,97 +412,150 @@ def fit_lattice_vectors( u0 = np.asarray(self.u, dtype=float).reshape(2) v0 = np.asarray(self.v, dtype=float).reshape(2) + dp_shape = self.dataset.array.shape[2:] + r_center = dp_shape[0] // 2 + c_center = dp_shape[1] // 2 + for r, c in it: - dp = self.dataset.array[r, c]*self.mask_diffraction + \ - self.mask_diffraction_inv + dp = self.dataset.array[r, c] * self.mask_diffraction + self.mask_diffraction_inv if mode == "linear": im = np.fft.fftshift(np.abs(np.fft.fft2(dp))) elif mode == "log": im = np.fft.fftshift(np.abs(np.fft.fft2(np.log1p(dp)))) + elif mode == "gamma": + im = np.fft.fftshift(np.abs(np.fft.fft2(np.power(np.clip(dp, 0.0, None), g)))) else: - raise ValueError("metadata['mode'] must be 'linear' or 'log'") - - if refine_subpixel: - u_rc, v_rc = _refine_lattice_vectors( - im, - u_rc=u0, - v_rc=v0, - radius_px=float(refine_radius_px), - log_fit=bool(refine_log), - refine_dft=bool(refine_subpixel_dft), - upsample=int(upsample), - ) - else: - u_rc = u0 - v_rc = v0 + raise ValueError("metadata['mode'] must be 'linear', 'log', or 'gamma'") + + u_fit_abs, v_fit_abs = _refine_lattice_vectors( + im, + u_rc=u0, + v_rc=v0, + radius_px=refine_radius_px, + refine_gaussian=refine_gaussian, + refine_dft=refine_dft, + upsample=upsample, + maxfev=gaussian_maxfev, + ) - self.u_fit.array[r, c, :] = u_rc - self.v_fit.array[r, c, :] = v_rc + self.u_peak_fit.array[r, c, :] = u_fit_abs + self.v_peak_fit.array[r, c, :] = v_fit_abs - self.metadata["fit_refine_subpixel"] = bool(refine_subpixel) - self.metadata["fit_refine_subpixel_dft"] = bool(refine_subpixel_dft) - self.metadata["fit_refine_radius_px"] = float(refine_radius_px) - self.metadata["fit_refine_log"] = bool(refine_log) - self.metadata["fit_upsample"] = int(upsample) + self.u_fit.array[r, c, 0] = u_fit_abs[0] - r_center + self.u_fit.array[r, c, 1] = u_fit_abs[1] - c_center + self.v_fit.array[r, c, 0] = v_fit_abs[0] - r_center + self.v_fit.array[r, c, 1] = v_fit_abs[1] - c_center - return self + self.metadata["fit_refine_gaussian"] = refine_gaussian + self.metadata["fit_refine_dft"] = refine_dft + self.metadata["fit_refine_radius_px"] = refine_radius_px + self.metadata["fit_upsample"] = upsample + self.metadata["fit_gaussian_maxfev"] = gaussian_maxfev + return self def plot_lattice_vectors( self, subtract_mean: bool = True, - scalebar: bool = False, - **plot_kwargs: Any, + max_shift: float = 1.0, + cmap: str = "PiYG_r", + axsize: tuple[float, float] | None = None, + figsize: tuple[float, float] | None = None, + **imshow_kwargs: Any, ): - if getattr(self, "u_fit", None) is None or getattr(self, "v_fit", None) is None: + if self.u_fit is None or self.v_fit is None: raise ValueError("Run fit_lattice_vectors() first to compute u_fit and v_fit.") + if self.u is None or self.v is None: + raise ValueError("Run choose_lattice_vector() first to set self.u and self.v.") - im0 = self.u_fit.array[:,:,0] - im1 = self.u_fit.array[:,:,1] - im2 = self.v_fit.array[:,:,0] - im3 = self.v_fit.array[:,:,1] + im0 = self.u_fit.array[:, :, 0] + im1 = self.u_fit.array[:, :, 1] + im2 = self.v_fit.array[:, :, 0] + im3 = self.v_fit.array[:, :, 1] + + du0 = im0 - self.u[0] + du1 = im1 - self.u[1] + dv0 = im2 - self.v[0] + dv1 = im3 - self.v[1] + + max_shift2 = max_shift * max_shift + mu = (du0 * du0 + du1 * du1) <= max_shift2 + mv = (dv0 * dv0 + dv1 * dv1) <= max_shift2 if subtract_mean: - im0 = im0 - float(np.nanmean(im0)) - im1 = im1 - float(np.nanmean(im1)) - im2 = im2 - float(np.nanmean(im2)) - im3 = im3 - float(np.nanmean(im3)) + if np.any(mu): + im0 = im0 - np.mean(im0[mu]) + im1 = im1 - np.mean(im1[mu]) + else: + im0 = im0 - np.mean(im0) + im1 = im1 - np.mean(im1) + + if np.any(mv): + im2 = im2 - np.mean(im2[mv]) + im3 = im3 - np.mean(im3[mv]) + else: + im2 = im2 - np.mean(im2) + im3 = im3 - np.mean(im3) + + vals = [] + if np.any(mu): + vals.append(np.abs(im0[mu])) + vals.append(np.abs(im1[mu])) + if np.any(mv): + vals.append(np.abs(im2[mv])) + vals.append(np.abs(im3[mv])) + + if vals: + vlim = np.max(np.concatenate(vals)) + else: + vlim = np.max(np.abs(np.stack([im0, im1, im2, im3], axis=0))) - vlim = float(np.nanmax(np.abs(np.stack([im0, im1, im2, im3], axis=0)))) vmin = -vlim vmax = vlim - defaults: dict[str, Any] = dict( - title=("u_r", "u_c", "v_r", "v_c"), - vmin=vmin, - vmax=vmax, - ) + cm = plt.get_cmap(cmap).copy() + cm.set_bad(color="black") - if scalebar: - s0 = float(self.dataset.sampling[0]) if len(self.dataset.sampling) > 0 else 1.0 - s1 = float(self.dataset.sampling[1]) if len(self.dataset.sampling) > 1 else s0 - sampling_scan = float(np.mean([s0, s1])) - units_scan = str(self.dataset.units[0]) if len(self.dataset.units) > 0 else "pixels" - defaults["scalebar"] = ScalebarConfig(sampling=sampling_scan, units=units_scan) + m0 = np.ma.array(im0, mask=~mu) + m1 = np.ma.array(im1, mask=~mu) + m2 = np.ma.array(im2, mask=~mv) + m3 = np.ma.array(im3, mask=~mv) - defaults.update(plot_kwargs) + if axsize is None and figsize is None: + axsize = (4.0, 4.0) + if figsize is None: + figsize = (axsize[0] * 4.0, axsize[1]) - fig, ax = show_2d([im0, im1, im2, im3], **defaults) - return fig, ax + fig, ax = plt.subplots(1, 4, figsize=figsize) + + ax[0].imshow(m0, cmap=cm, vmin=vmin, vmax=vmax, **imshow_kwargs) + ax[1].imshow(m1, cmap=cm, vmin=vmin, vmax=vmax, **imshow_kwargs) + ax[2].imshow(m2, cmap=cm, vmin=vmin, vmax=vmax, **imshow_kwargs) + ax[3].imshow(m3, cmap=cm, vmin=vmin, vmax=vmax, **imshow_kwargs) + + ax[0].set_title("u_r") + ax[1].set_title("u_c") + ax[2].set_title("v_r") + ax[3].set_title("v_c") + for a in ax: + a.set_xticks([]) + a.set_yticks([]) + + return fig, ax def fit_strain( self, - mask_reference = None, - plot_strain = True, + mask_reference=None, + plot_strain=True, ): if self.u_fit is None or self.v_fit is None: raise ValueError("Run fit_lattice_vectors() first to compute u_fit and v_fit.") u_fit = self.u_fit.array v_fit = self.v_fit.array - scan_r, scan_c = int(u_fit.shape[0]), int(u_fit.shape[1]) + scan_r, scan_c = u_fit.shape[0], u_fit.shape[1] if mask_reference is None: self.u_ref = np.median(u_fit.reshape(-1, 2), axis=0) @@ -503,26 +578,23 @@ def fit_strain( ) Uref = np.stack((self.u_ref, self.v_ref), axis=1).astype(float) - det = float(np.linalg.det(Uref)) + det = np.linalg.det(Uref) if not np.isfinite(det) or abs(det) < 1e-12: Uref_inv = np.linalg.pinv(Uref) else: Uref_inv = np.linalg.inv(Uref) - # init self.strain_trans = Dataset4d.from_shape( (scan_r, scan_c, 2, 2), name="transformation matrix", signal_units="fractional", ) - # Loop over probe positions for r in range(scan_r): for c in range(scan_c): U = np.stack((u_fit[r, c, :], v_fit[r, c, :]), axis=1) self.strain_trans.array[r, c, :, :] = U @ Uref_inv - # get strains in orthogonal directions self.strain_raw_err = Dataset2d.from_array( self.strain_trans.array[:, :, 0, 0] - 1, name="strain err", @@ -534,12 +606,12 @@ def fit_strain( signal_units="fractional", ) self.strain_raw_erc = Dataset2d.from_array( - self.strain_trans.array[:, :, 1, 0]*0.5 + self.strain_trans.array[:, :, 0, 1]*0.5, + self.strain_trans.array[:, :, 1, 0] * 0.5 + self.strain_trans.array[:, :, 0, 1] * 0.5, name="strain erc", signal_units="fractional", ) self.strain_rotation = Dataset2d.from_array( - self.strain_trans.array[:, :, 1, 0]*-0.5 + self.strain_trans.array[:, :, 0, 1]*0.5, + self.strain_trans.array[:, :, 1, 0] * -0.5 + self.strain_trans.array[:, :, 0, 1] * 0.5, name="strain rotation", signal_units="fractional", ) @@ -555,23 +627,26 @@ def plot_strain( rotation_range_degrees=(-2.0, 2.0), plot_rotation=True, cmap_strain="PiYG_r", - cmap_rotation="PiYG_r", + cmap_rotation=None, layout="horizontal", figsize=(6, 6), + max_shift: tuple[float, float] | None = None, + amp_range: tuple[float, float] | None = None, ): import matplotlib.pyplot as plt + if cmap_rotation is None: + cmap_rotation = cmap_strain + if ref_angle_degrees is None: - ref_vec = self.u_ref * float(ref_u_v[0]) + self.v_ref * float(ref_u_v[1]) - ref_angle = float(np.arctan2(ref_vec[1], ref_vec[0])) + ref_vec = self.u_ref * ref_u_v[0] + self.v_ref * ref_u_v[1] + ref_angle = np.arctan2(ref_vec[1], ref_vec[0]) else: - ref_angle = float(np.deg2rad(ref_angle_degrees)) + ref_angle = np.deg2rad(ref_angle_degrees) angle = ref_angle + np.deg2rad(self.metadata["q_to_r_rotation_ccw_deg"]) - print(np.round(np.rad2deg(angle),2)) - - c = float(np.cos(angle)) - s = float(np.sin(angle)) + c = np.cos(angle) + s = np.sin(angle) err = self.strain_raw_err.array ecc = self.strain_raw_ecc.array @@ -588,62 +663,148 @@ def plot_strain( self.strain_evv.array[...] = evv self.strain_euv.array[...] = euv - if layout == "horizontal": - if plot_rotation: - fig, ax = plt.subplots(1, 4, figsize=figsize) - - ax[0].imshow( - self.strain_euu.array * 100, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cmap_strain, - ) - ax[1].imshow( - self.strain_evv.array * 100, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cmap_strain, - ) - ax[2].imshow( - self.strain_euv.array * 100, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cmap_strain, - ) - ax[3].imshow( - np.rad2deg(self.strain_rotation.array), - vmin=rotation_range_degrees[0], - vmax=rotation_range_degrees[1], - cmap=cmap_rotation, - ) - return fig, ax - - fig, ax = plt.subplots(1, 3, figsize=figsize) - ax[0].imshow( - self.strain_euu.array * 100, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cmap_strain, - ) - ax[1].imshow( - self.strain_evv.array * 100, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cmap_strain, - ) - ax[2].imshow( - self.strain_euv.array * 100, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cmap_strain, + alpha = None + if max_shift is not None: + if self.u_fit is None or self.v_fit is None or self.u is None or self.v is None: + raise ValueError("max_shift masking requires u_fit, v_fit, u, v to be available.") + + ur = self.u_fit.array[:, :, 0] + uc = self.u_fit.array[:, :, 1] + vr = self.v_fit.array[:, :, 0] + vc = self.v_fit.array[:, :, 1] + + du0 = ur - self.u[0] + du1 = uc - self.u[1] + dv0 = vr - self.v[0] + dv1 = vc - self.v[1] + + su = du0 * du0 + du1 * du1 + sv = dv0 * dv0 + dv1 * dv1 + sdist2 = 0.5 * (su + sv) + + smin, smax = max_shift + mask = np.clip((sdist2 - smin) / (smax - smin), 0.0, 1.0) + alpha = 1.0 - mask + + if amp_range is not None: + if self.u_peak_fit is None or self.v_peak_fit is None: + raise ValueError("amp_range masking requires u_peak_fit and v_peak_fit to be available.") + a = 0.5 * (self.u_peak_fit.array[:, :, 2] + self.v_peak_fit.array[:, :, 2]) + amin, amax = amp_range + a_mask = np.clip((a - amin) / (amax - amin), 0.0, 1.0) + alpha = a_mask if alpha is None else alpha * a_mask + + if alpha is not None: + alpha = np.asarray(alpha, dtype=float) + good = alpha > 0 + alpha_im = np.where(good, alpha, 1.0) + else: + good = None + alpha_im = None + + if layout != "horizontal": + raise ValueError("layout must be 'horizontal'") + + ncols = 4 if plot_rotation else 3 + fig, ax = plt.subplots(1, ncols, figsize=figsize) + + cm_strain = plt.get_cmap(cmap_strain).copy() + cm_strain.set_bad(color="black") + cm_rot = plt.get_cmap(cmap_rotation).copy() + cm_rot.set_bad(color="black") + + euu_pct = self.strain_euu.array * 100 + evv_pct = self.strain_evv.array * 100 + euv_pct = self.strain_euv.array * 100 + rot_deg = np.rad2deg(self.strain_rotation.array) + + if good is not None and np.any(good): + euu_m = np.ma.array(euu_pct, mask=~good) + evv_m = np.ma.array(evv_pct, mask=~good) + euv_m = np.ma.array(euv_pct, mask=~good) + rot_m = np.ma.array(rot_deg, mask=~good) + else: + euu_m = euu_pct + evv_m = evv_pct + euv_m = euv_pct + rot_m = rot_deg + + title_fs = 16 + im0 = ax[0].imshow( + euu_m, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cm_strain, + alpha=alpha_im, + ) + ax[1].imshow( + evv_m, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cm_strain, + alpha=alpha_im, + ) + ax[2].imshow( + euv_m, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cm_strain, + alpha=alpha_im, + ) + + ax[0].set_title(r"$\epsilon_{uu}$", fontsize=title_fs) + ax[1].set_title(r"$\epsilon_{vv}$", fontsize=title_fs) + ax[2].set_title(r"$\epsilon_{uv}$", fontsize=title_fs) + + if plot_rotation: + im3 = ax[3].imshow( + rot_m, + vmin=rotation_range_degrees[0], + vmax=rotation_range_degrees[1], + cmap=cm_rot, + alpha=alpha_im, ) - return fig, ax + ax[3].set_title("Rotation", fontsize=title_fs) - raise ValueError("layout must be 'horizontal'") + for a in ax: + a.set_xticks([]) + a.set_yticks([]) + a.set_facecolor("black") + + fig.subplots_adjust(left=0.02, right=0.98, top=0.90, bottom=0.16, wspace=0.03) + + b0 = ax[0].get_position() + b2 = ax[2].get_position() + left = b0.x0 + right = b2.x1 + width = right - left + + b3 = ax[3].get_position() if plot_rotation else None + + cb_height = 0.04 + cb_pad = 0.03 + y = b0.y0 - cb_pad - cb_height + + cax1 = fig.add_axes([left, y, width, cb_height]) + cbar1 = fig.colorbar(im0, cax=cax1, orientation="horizontal") + cbar1.set_label("Strain (%)", fontsize=title_fs) + cbar1.ax.tick_params(labelsize=12) + + if plot_rotation: + left_r = b3.x0 + width_r = b3.x1 - b3.x0 + cax2 = fig.add_axes([left_r, y, width_r, cb_height]) + cbar2 = fig.colorbar(im3, cax=cax2, orientation="horizontal") + cbar2.set_label("Rotation (deg)", fontsize=title_fs) + cbar2.ax.tick_params(labelsize=12) + + for a in ax: + a.set_aspect("equal") + + return fig, ax def _nice_length_units(target: float) -> float: - target = float(target) if not np.isfinite(target) or target <= 0: return 0.0 exp = np.floor(np.log10(target)) @@ -656,21 +817,20 @@ def _nice_length_units(target: float) -> float: nice = 5.0 else: nice = 10.0 - return float(nice * (10.0**exp)) + return nice * (10.0**exp) def _apply_center_crop_limits(ax: Any, shape: tuple[int, int], cropping_factor: float) -> None: - cf = float(cropping_factor) - if cf >= 1.0: + if cropping_factor >= 1.0: return - if not (0.0 < cf <= 1.0): + if not (0.0 < cropping_factor <= 1.0): raise ValueError("cropping_factor must be in (0, 1].") - H, W = int(shape[0]), int(shape[1]) - r0 = float(H // 2) - c0 = float(W // 2) - half_h = 0.5 * cf * H - half_w = 0.5 * cf * W + H, W = shape + r0 = H // 2 + c0 = W // 2 + half_h = 0.5 * cropping_factor * H + half_w = 0.5 * cropping_factor * W ax.set_xlim(c0 - half_w, c0 + half_w) @@ -694,14 +854,14 @@ def _flatten_axes(ax: Any) -> list[Any]: def _raw_vec_to_display(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: bool) -> NDArray: v = np.asarray(vec_rc, dtype=float).reshape(2) - dr, dc = float(v[0]), float(v[1]) + dr, dc = v[0], v[1] if transpose: dr, dc = dc, dr - theta = float(np.deg2rad(rotation_ccw_deg)) - ct = float(np.cos(theta)) - st = float(np.sin(theta)) + theta = np.deg2rad(rotation_ccw_deg) + ct = np.cos(theta) + st = np.sin(theta) dr2 = ct * dr - st * dc dc2 = st * dr + ct * dc @@ -710,11 +870,11 @@ def _raw_vec_to_display(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: def _display_vec_to_raw(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: bool) -> NDArray: v = np.asarray(vec_rc, dtype=float).reshape(2) - dr, dc = float(v[0]), float(v[1]) + dr, dc = v[0], v[1] - theta = float(np.deg2rad(rotation_ccw_deg)) - ct = float(np.cos(theta)) - st = float(np.sin(theta)) + theta = np.deg2rad(rotation_ccw_deg) + ct = np.cos(theta) + st = np.sin(theta) dr2 = ct * dr + st * dc dc2 = -st * dr + ct * dc @@ -725,17 +885,17 @@ def _display_vec_to_raw(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: return np.array((dr2, dc2), dtype=float) -def _plot_lattice_vectors(ax: Any, center_rc: tuple[float, float], u_rc: NDArray, v_rc: NDArray) -> None: - r0, c0 = float(center_rc[0]), float(center_rc[1]) +# def _plot_lattice_vectors(ax: Any, center_rc: tuple[float, float], u_rc: NDArray, v_rc: NDArray) -> None: +# r0, c0 = center_rc - def _draw(vec: NDArray, label: str, color: tuple[float, float, float]) -> None: - dr, dc = float(vec[0]), float(vec[1]) - ax.plot([c0, c0 + dc], [r0, r0 + dr], linewidth=2.75, color=color) - ax.plot([c0 + dc], [r0 + dr], marker="o", markersize=6.0, color=color) - ax.text(c0 + dc, r0 + dr, f" {label}", color=color, fontsize=18, va="center") +# def _draw(vec: NDArray, label: str, color: tuple[float, float, float]) -> None: +# dr, dc = vec[0], vec[1] +# ax.plot([c0, c0 + dc], [r0, r0 + dr], linewidth=2.75, color=color) +# ax.plot([c0 + dc], [r0 + dr], marker="o", markersize=6.0, color=color) +# ax.text(c0 + dc, r0 + dr, f" {label}", color=color, fontsize=18, va="center") - _draw(np.asarray(u_rc, dtype=float).reshape(2), "u", (1.0, 0.0, 0.0)) - _draw(np.asarray(v_rc, dtype=float).reshape(2), "v", (0.0, 0.7, 1.0)) +# _draw(np.asarray(u_rc, dtype=float).reshape(2), "u", (1.0, 0.0, 0.0)) +# _draw(np.asarray(v_rc, dtype=float).reshape(2), "v", (0.0, 0.7, 1.0)) def _overlay_lattice_vectors( @@ -751,25 +911,25 @@ def _overlay_lattice_vectors( if not axs: return - H, W = int(shape[0]), int(shape[1]) - center_rc = (float(H // 2), float(W // 2)) + H, W = shape + center_rc = (H // 2, W // 2) _plot_lattice_vectors(axs[0], center_rc, u_rc, v_rc) if len(axs) >= 2: - u_disp = _raw_vec_to_display(u_rc, rotation_ccw_deg=float(rot_ccw_deg), transpose=bool(q_transpose)) - v_disp = _raw_vec_to_display(v_rc, rotation_ccw_deg=float(rot_ccw_deg), transpose=bool(q_transpose)) + u_disp = _raw_vec_to_display(u_rc, rotation_ccw_deg=rot_ccw_deg, transpose=q_transpose) + v_disp = _raw_vec_to_display(v_rc, rotation_ccw_deg=rot_ccw_deg, transpose=q_transpose) _plot_lattice_vectors(axs[1], center_rc, u_disp, v_disp) def _parabolic_vertex_delta(v_m1: float, v_0: float, v_p1: float) -> float: - denom = (v_m1 - 2.0 * v_0 + v_p1) + denom = v_m1 - 2.0 * v_0 + v_p1 if denom == 0 or not np.isfinite(denom): return 0.0 delta = 0.5 * (v_m1 - v_p1) / denom if not np.isfinite(delta): return 0.0 - return float(np.clip(delta, -1.0, 1.0)) + return np.clip(delta, -1.0, 1.0) def _refine_peak_subpixel( @@ -778,14 +938,13 @@ def _refine_peak_subpixel( r_guess: float, c_guess: float, radius_px: float = 2.0, - log_fit: bool = False, ) -> tuple[float, float]: im = np.asarray(im, dtype=float) H, W = im.shape r0 = int(np.clip(int(np.round(r_guess)), 0, H - 1)) c0 = int(np.clip(int(np.round(c_guess)), 0, W - 1)) - rad = int(max(0, int(np.ceil(float(radius_px))))) + rad = int(max(0, int(np.ceil(radius_px)))) r1 = max(0, r0 - rad) r2 = min(H, r0 + rad + 1) @@ -794,29 +953,25 @@ def _refine_peak_subpixel( win = im[r1:r2, c1:c2] if win.size == 0: - return float(r_guess), float(c_guess) + return r_guess, c_guess - ir, ic = np.unravel_index(int(np.argmax(win)), win.shape) - r_peak = r1 + int(ir) - c_peak = c1 + int(ic) + ir, ic = np.unravel_index(np.argmax(win), win.shape) + r_peak = r1 + ir + c_peak = c1 + ic if 0 < r_peak < H - 1: col = im[r_peak - 1 : r_peak + 2, c_peak] - if log_fit: - col = np.log(np.clip(col, 1e-12, None)) - dr = _parabolic_vertex_delta(float(col[0]), float(col[1]), float(col[2])) + dr = _parabolic_vertex_delta(col[0], col[1], col[2]) else: dr = 0.0 if 0 < c_peak < W - 1: row = im[r_peak, c_peak - 1 : c_peak + 2] - if log_fit: - row = np.log(np.clip(row, 1e-12, None)) - dc = _parabolic_vertex_delta(float(row[0]), float(row[1]), float(row[2])) + dc = _parabolic_vertex_delta(row[0], row[1], row[2]) else: dc = 0.0 - return float(r_peak) + dr, float(c_peak) + dc + return r_peak + dr, c_peak + dc def _refine_peak_subpixel_dft( @@ -825,44 +980,37 @@ def _refine_peak_subpixel_dft( r0: float, c0: float, upsample: int, - log_fit: bool = False, ) -> tuple[float, float]: - if int(upsample) <= 1: - return float(r0), float(c0) + if upsample <= 1: + return r0, c0 im = np.asarray(im, dtype=float) F = np.fft.fft2(im) - up = int(upsample) + up = upsample du = int(np.ceil(1.5 * up)) - patch = dft_upsample(F, up=up, shift=(float(r0), float(c0)), device="cpu") + patch = dft_upsample(F, up=up, shift=(r0, c0), device="cpu") patch = np.asarray(patch, dtype=float) - i0, j0 = np.unravel_index(int(np.argmax(patch)), patch.shape) - i0 = int(i0) - j0 = int(j0) + i0, j0 = np.unravel_index(np.argmax(patch), patch.shape) if 0 < i0 < patch.shape[0] - 1: col = patch[i0 - 1 : i0 + 2, j0] - if log_fit: - col = np.log(np.clip(col, 1e-12, None)) - di = _parabolic_vertex_delta(float(col[0]), float(col[1]), float(col[2])) + di = _parabolic_vertex_delta(col[0], col[1], col[2]) else: di = 0.0 if 0 < j0 < patch.shape[1] - 1: row = patch[i0, j0 - 1 : j0 + 2] - if log_fit: - row = np.log(np.clip(row, 1e-12, None)) - dj = _parabolic_vertex_delta(float(row[0]), float(row[1]), float(row[2])) + dj = _parabolic_vertex_delta(row[0], row[1], row[2]) else: dj = 0.0 - dr = (float(i0) - float(du) + float(di)) / float(up) - dc = (float(j0) - float(du) + float(dj)) / float(up) + dr = (i0 - du + di) / up + dc = (j0 - du + dj) / up - return float(r0) + dr, float(c0) + dc + return r0 + dr, c0 + dc def _refine_lattice_vectors( @@ -871,42 +1019,155 @@ def _refine_lattice_vectors( u_rc: NDArray, v_rc: NDArray, radius_px: float = 2.0, - log_fit: bool = False, - refine_dft: bool = True, + refine_gaussian: bool = True, + refine_dft: bool = False, upsample: int = 16, + maxfev: int = 100, ) -> tuple[NDArray, NDArray]: + from scipy.optimize import curve_fit + im = np.asarray(im, dtype=float) if im.ndim != 2: raise ValueError("im must be 2D.") H, W = im.shape - r_center = float(H // 2) - c_center = float(W // 2) + r_center = H // 2 + c_center = W // 2 + + def _parabolic_peak_rc_amp(*, r_guess: float, c_guess: float) -> tuple[float, float, float]: + r0 = int(np.clip(int(np.round(r_guess)), 0, H - 1)) + c0 = int(np.clip(int(np.round(c_guess)), 0, W - 1)) + win = im[ + max(0, r0 - 1) : min(H, r0 + 2), + max(0, c0 - 1) : min(W, c0 + 2), + ] + if win.size == 0: + return r_guess, c_guess, 0.0 + + ir, ic = np.unravel_index(np.argmax(win), win.shape) + r_peak = max(0, r0 - 1) + ir + c_peak = max(0, c0 - 1) + ic + + r_ref = r_peak + c_ref = c_peak + + if 0 < r_peak < H - 1: + col = im[r_peak - 1 : r_peak + 2, c_peak] + dr = _parabolic_vertex_delta(col[0], col[1], col[2]) + else: + dr = 0.0 - def _refine(vec: NDArray) -> NDArray: + if 0 < c_peak < W - 1: + row = im[r_peak, c_peak - 1 : c_peak + 2] + dc = _parabolic_vertex_delta(row[0], row[1], row[2]) + else: + dc = 0.0 + + r_sub = r_ref + dr + c_sub = c_ref + dc + r_int = int(np.clip(int(np.round(r_sub)), 0, H - 1)) + c_int = int(np.clip(int(np.round(c_sub)), 0, W - 1)) + amp = im[r_int, c_int] + + return r_sub, c_sub, amp + + def _fit_gaussian_isotropic( + *, + r0: float, + c0: float, + radius_px: float, + maxfev: int, + ) -> tuple[float, float, float, float, float]: + rad = int(max(1, int(np.ceil(radius_px)))) + r0i = int(np.clip(int(np.round(r0)), 0, H - 1)) + c0i = int(np.clip(int(np.round(c0)), 0, W - 1)) + + r1 = max(0, r0i - rad) + r2 = min(H, r0i + rad + 1) + c1 = max(0, c0i - rad) + c2 = min(W, c0i + rad + 1) + + win = im[r1:r2, c1:c2] + if win.size == 0: + return r0, c0, 0.0, 0.0, 0.0 + + ir, ic = np.unravel_index(np.argmax(win), win.shape) + r_peak = r1 + ir + c_peak = c1 + ic + + bg0 = np.median(win) + amp0 = win[ir, ic] - bg0 + sig0 = max(0.75, radius_px / 2.0) + + rr = np.arange(r1, r2, dtype=float)[:, None] + cc = np.arange(c1, c2, dtype=float)[None, :] + RR = np.broadcast_to(rr, win.shape) + CC = np.broadcast_to(cc, win.shape) + + def _g2( + coords: tuple[NDArray, NDArray], + row: float, + col: float, + amp: float, + sigma: float, + background: float, + ) -> NDArray: + r, c = coords + sig = np.maximum(sigma, 1e-12) + return background + amp * np.exp(-((r - row) ** 2 + (c - col) ** 2) / (2.0 * sig * sig)) + + p0 = (r_peak, c_peak, max(0.0, amp0), sig0, bg0) + + rlo = r1 - 0.5 + rhi = (r2 - 1) + 0.5 + clo = c1 - 0.5 + chi = (c2 - 1) + 0.5 + + bounds_lo = (rlo, clo, 0.0, 0.25, -np.inf) + bounds_hi = (rhi, chi, np.inf, radius_px * 4.0, np.inf) + + try: + popt, _ = curve_fit( + _g2, + (RR.ravel(), CC.ravel()), + win.ravel(), + p0=p0, + bounds=(bounds_lo, bounds_hi), + maxfev=maxfev, + ) + row, col, amp, sig, bg = popt + if not (np.isfinite(row) and np.isfinite(col) and np.isfinite(amp) and np.isfinite(sig) and np.isfinite(bg)): + return r0, c0, p0[2], 0.0, 0.0 + return row, col, amp, sig, bg + except Exception: + return r0, c0, p0[2], 0.0, 0.0 + + def _refine_one(vec: NDArray) -> NDArray: vec = np.asarray(vec, dtype=float).reshape(2) - r_guess = r_center + float(vec[0]) - c_guess = c_center + float(vec[1]) + r_guess = r_center + vec[0] + c_guess = c_center + vec[1] - r1, c1 = _refine_peak_subpixel( - im, - r_guess=float(r_guess), - c_guess=float(c_guess), - radius_px=float(radius_px), - log_fit=bool(log_fit), - ) + r_par, c_par, amp_par = _parabolic_peak_rc_amp(r_guess=r_guess, c_guess=c_guess) - if refine_dft and int(upsample) > 1: - r2, c2 = _refine_peak_subpixel_dft( - im, - r0=float(r1), - c0=float(c1), - upsample=int(upsample), - log_fit=bool(log_fit), + if refine_gaussian: + r_fit, c_fit, amp, sig, bg = _fit_gaussian_isotropic( + r0=r_par, + c0=c_par, + radius_px=radius_px, + maxfev=maxfev, ) else: - r2, c2 = float(r1), float(c1) + r_fit, c_fit, amp, sig, bg = r_par, c_par, amp_par, 0.0, 0.0 + + if refine_dft and upsample > 1: + r_dft, c_dft = _refine_peak_subpixel_dft( + im, + r0=r_fit, + c0=c_fit, + upsample=upsample, + ) + r_fit, c_fit = r_dft, c_dft - return np.array((r2 - r_center, c2 - c_center), dtype=float) + return np.array((r_fit, c_fit, amp, sig, bg), dtype=float) - return _refine(u_rc), _refine(v_rc) + return _refine_one(u_rc), _refine_one(v_rc) From a154aa6229e1dae67fc809bef4d7d562a4cea4f9 Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 19 Jan 2026 11:45:06 -0800 Subject: [PATCH 012/113] renaming parent .py file --- src/quantem/diffraction/__init__.py | 2 +- .../{strain.py => strain_autocorrelation.py} | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) rename src/quantem/diffraction/{strain.py => strain_autocorrelation.py} (98%) diff --git a/src/quantem/diffraction/__init__.py b/src/quantem/diffraction/__init__.py index 77bc5f829..450b9b2f8 100644 --- a/src/quantem/diffraction/__init__.py +++ b/src/quantem/diffraction/__init__.py @@ -1,2 +1,2 @@ from quantem.diffraction.polar import RDF as RDF -from quantem.diffraction.strain import StrainMapAutocorrelation as StrainMapAutocorrelation +from quantem.diffraction.strain_autocorrelation import StrainMapAutocorrelation as StrainMapAutocorrelation diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain_autocorrelation.py similarity index 98% rename from src/quantem/diffraction/strain.py rename to src/quantem/diffraction/strain_autocorrelation.py index fa92c8474..a084b32dd 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -626,7 +626,7 @@ def plot_strain( strain_range_percent=(-3.0, 3.0), rotation_range_degrees=(-2.0, 2.0), plot_rotation=True, - cmap_strain="PiYG_r", + cmap_strain="RdBu_r", cmap_rotation=None, layout="horizontal", figsize=(6, 6), @@ -885,17 +885,17 @@ def _display_vec_to_raw(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: return np.array((dr2, dc2), dtype=float) -# def _plot_lattice_vectors(ax: Any, center_rc: tuple[float, float], u_rc: NDArray, v_rc: NDArray) -> None: -# r0, c0 = center_rc +def _plot_lattice_vectors(ax: Any, center_rc: tuple[float, float], u_rc: NDArray, v_rc: NDArray) -> None: + r0, c0 = center_rc -# def _draw(vec: NDArray, label: str, color: tuple[float, float, float]) -> None: -# dr, dc = vec[0], vec[1] -# ax.plot([c0, c0 + dc], [r0, r0 + dr], linewidth=2.75, color=color) -# ax.plot([c0 + dc], [r0 + dr], marker="o", markersize=6.0, color=color) -# ax.text(c0 + dc, r0 + dr, f" {label}", color=color, fontsize=18, va="center") + def _draw(vec: NDArray, label: str, color: tuple[float, float, float]) -> None: + dr, dc = vec[0], vec[1] + ax.plot([c0, c0 + dc], [r0, r0 + dr], linewidth=2.75, color=color) + ax.plot([c0 + dc], [r0 + dr], marker="o", markersize=6.0, color=color) + ax.text(c0 + dc, r0 + dr, f" {label}", color=color, fontsize=18, va="center") -# _draw(np.asarray(u_rc, dtype=float).reshape(2), "u", (1.0, 0.0, 0.0)) -# _draw(np.asarray(v_rc, dtype=float).reshape(2), "v", (0.0, 0.7, 1.0)) + _draw(np.asarray(u_rc, dtype=float).reshape(2), "u", (1.0, 0.0, 0.0)) + _draw(np.asarray(v_rc, dtype=float).reshape(2), "v", (0.0, 0.7, 1.0)) def _overlay_lattice_vectors( From d3d3aa5952888ad4441c40b35128426fec30ab95 Mon Sep 17 00:00:00 2001 From: cophus Date: Wed, 28 Jan 2026 20:18:07 -0800 Subject: [PATCH 013/113] initial class --- src/quantem/diffraction/__init__.py | 1 + src/quantem/diffraction/maped.py | 87 +++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/quantem/diffraction/maped.py diff --git a/src/quantem/diffraction/__init__.py b/src/quantem/diffraction/__init__.py index 450b9b2f8..2a79312b0 100644 --- a/src/quantem/diffraction/__init__.py +++ b/src/quantem/diffraction/__init__.py @@ -1,2 +1,3 @@ from quantem.diffraction.polar import RDF as RDF from quantem.diffraction.strain_autocorrelation import StrainMapAutocorrelation as StrainMapAutocorrelation +from quantem.diffraction.maped import MAPED as MAPED diff --git a/src/quantem/diffraction/maped.py b/src/quantem/diffraction/maped.py new file mode 100644 index 000000000..f301454ef --- /dev/null +++ b/src/quantem/diffraction/maped.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +from typing import Any, Sequence + +import numpy as np + +from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from quantem.core.io.serialize import AutoSerialize +from quantem.core.visualization import show_2d + + +class MAPED(AutoSerialize): + _token = object() + + def __init__(self, datasets: list[Dataset4dstem], _token: object | None = None): + if _token is not self._token: + raise RuntimeError("Use MAPED.from_datasets() to instantiate this class.") + AutoSerialize.__init__(self) + self.datasets = datasets + self.metadata: dict[str, Any] = {} + + @classmethod + def from_datasets(cls, datasets: Sequence[Dataset4dstem]) -> "MAPED": + if not isinstance(datasets, Sequence) or isinstance(datasets, (str, bytes)): + raise TypeError("MAPED.from_datasets expects a sequence of Dataset4dstem instances.") + ds_list: list[Dataset4dstem] = [] + for d in datasets: + if not isinstance(d, Dataset4dstem): + raise TypeError("MAPED.from_datasets expects a sequence of Dataset4dstem instances.") + ds_list.append(d) + if len(ds_list) == 0: + raise ValueError("MAPED.from_datasets expects a non-empty sequence of Dataset4dstem instances.") + return cls(datasets=ds_list, _token=cls._token) + + def preprocess( + self, + *, + plot_summary: bool = True, + scale: float | Sequence[float] | None = None, + **plot_kwargs: Any, + ) -> "MAPED": + n = len(self.datasets) + if scale is None: + self.scales = np.ones(n, dtype=float) + elif isinstance(scale, (int, float, np.floating)): + self.scales = np.full(n, float(scale), dtype=float) + else: + self.scales = np.asarray(list(scale), dtype=float) + if self.scales.shape != (n,): + raise ValueError("scale must be a scalar or a sequence with the same length as datasets.") + if np.any(self.scales == 0): + raise ValueError("scale entries must be nonzero.") + + self.dp_mean = [] + self.im_bf = [] + + for d in self.datasets: + if hasattr(d, "get_dp_mean"): + try: + d.get_dp_mean() + except TypeError: + try: + d.get_dp_mean(returnval=False) + except Exception: + pass + + dp = getattr(d, "dp_mean", None) + if dp is None: + dp_arr = np.mean(np.asarray(d.array), axis=(0, 1)) + else: + dp_arr = np.asarray(dp.array if hasattr(dp, "array") else dp) + + im_bf_arr = np.mean(np.asarray(d.array), axis=(2, 3)) + + self.dp_mean.append(np.asarray(dp_arr)) + self.im_bf.append(np.asarray(im_bf_arr)) + + if plot_summary: + show_2d( + [ + [self.im_bf[i] / self.scales[i] for i in range(n)], + [self.dp_mean[i] for i in range(n)], + ], + **plot_kwargs, + ) + + return self From ed4478945cc5a73ed5d502a7c90bdb47bc1e89f5 Mon Sep 17 00:00:00 2001 From: cophus Date: Sat, 31 Jan 2026 16:19:06 -0800 Subject: [PATCH 014/113] fixing DFT upsampling / image correlation, adding tests --- src/quantem/core/utils/imaging_utils.py | 365 +++++++----------------- tests/core/utils/test_imaging_utils.py | 75 +++++ 2 files changed, 178 insertions(+), 262 deletions(-) create mode 100644 tests/core/utils/test_imaging_utils.py diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index ba5fc3e9c..805ca5b06 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -12,11 +12,17 @@ from quantem.core.utils.utils import generate_batches +def _parabolic_peak(v) -> float: + denom = 4.0 * v[1] - 2.0 * v[2] - 2.0 * v[0] + if denom == 0: + return 0.0 + return float((v[2] - v[0]) / denom) + + def dft_upsample( F: NDArray, up: int, shift: Tuple[float, float], - device: str = "cpu", ): """ Matrix multiplication DFT, from: @@ -25,27 +31,53 @@ def dft_upsample( image registration algorithms," Opt. Lett. 33, 156-158 (2008). http://www.sciencedirect.com/science/article/pii/S0045790612000778 """ - if device == "gpu": - import cupy as cp # type: ignore + M, N = F.shape + pixel_radius = 1.5 + num_row = int(math.ceil(pixel_radius * up)) + num_col = num_row - xp = cp - else: - xp = np + col_freq = np.fft.ifftshift(np.arange(N)) - math.floor(N / 2) + row_freq = np.fft.ifftshift(np.arange(M)) - math.floor(M / 2) - M, N = F.shape - du = np.ceil(1.5 * up).astype(int) - row = np.arange(-du, du + 1) - col = np.arange(-du, du + 1) - r_shift = shift[0] - M // 2 - c_shift = shift[1] - N // 2 - - kern_row = np.exp( - -2j * np.pi / (M * up) * np.outer(row, xp.fft.ifftshift(xp.arange(M)) - M // 2 + r_shift) - ) - kern_col = np.exp( - -2j * np.pi / (N * up) * np.outer(xp.fft.ifftshift(xp.arange(N)) - N // 2 + c_shift, col) - ) - return xp.real(kern_row @ F @ kern_col) + row_coords = np.arange(num_row, dtype=float) - float(shift[0]) + col_coords = np.arange(num_col, dtype=float) - float(shift[1]) + + factor_row = -2j * math.pi / (M * float(up)) + factor_col = -2j * math.pi / (N * float(up)) + + row_kern = np.exp(factor_row * (row_coords[:, None] * row_freq[None, :])).astype(F.dtype) + col_kern = np.exp(factor_col * (col_freq[:, None] * col_coords[None, :])).astype(F.dtype) + + return (row_kern @ F @ col_kern).real + + +def _upsampled_correlation_numpy( + imageCorr: NDArray, + upsampleFactor: int, + xyShift: NDArray, +) -> NDArray: + xyShift = np.round(xyShift * float(upsampleFactor)) / float(upsampleFactor) + globalShift = math.floor(math.ceil(upsampleFactor * 1.5) / 2.0) + upsampleCenter = float(globalShift) - (float(upsampleFactor) * xyShift) + + im_up = dft_upsample(np.conj(imageCorr), upsampleFactor, (float(upsampleCenter[0]), float(upsampleCenter[1]))) + imageCorrUpsample = np.conj(im_up) + + flat_idx = int(np.argmax(imageCorrUpsample.real)) + r = flat_idx // imageCorrUpsample.shape[1] + c = flat_idx % imageCorrUpsample.shape[1] + + dx = 0.0 + dy = 0.0 + patch = imageCorrUpsample.real[r - 1 : r + 2, c - 1 : c + 2] + if patch.shape == (3, 3): + dx = _parabolic_peak(patch[:, 1]) + dy = _parabolic_peak(patch[1, :]) + + xySubShift = np.array([float(r), float(c)], dtype=float) - float(globalShift) + xyShift = xyShift + (xySubShift + np.array([dx, dy], dtype=float)) / float(upsampleFactor) + + return xyShift def cross_correlation_shift( @@ -56,7 +88,6 @@ def cross_correlation_shift( return_shifted_image: bool = False, fft_input: bool = False, fft_output: bool = False, - device: str = "cpu", ): """ Estimate subpixel shift between two 2D images using Fourier cross-correlation. @@ -68,98 +99,78 @@ def cross_correlation_shift( im : ndarray Image to align or its FFT if fft_input=True upsample_factor : int - Subpixel upsampling factor (must be > 1 for subpixel accuracy) - fft_input : bool - If True, assumes im_ref and im are already in Fourier space + Subpixel upsampling factor (torch-equivalent behavior): + - <= 2 : half-pixel refinement (parabolic, then rounded to nearest 0.5 px) + - > 2 : additional DFT upsample refinement + max_shift : float or None + Optional radial cutoff in pixel-shift units (keeps only shifts with |shift| <= max_shift) return_shifted_image : bool If True, return the shifted version of `im` aligned to `im_ref` - device : str - 'cpu' or 'gpu' (requires CuPy) + fft_input : bool + If True, assumes im_ref and im are already in Fourier space + fft_output : bool + If True and return_shifted_image=True, return the shifted image in Fourier space Returns ------- shifts : tuple of float (row_shift, col_shift) to align `im` to `im_ref` image_shifted : ndarray (optional) - Shifted image in real space, only returned if return_shifted_image=True + Shifted image in real space (or Fourier space if fft_output=True) """ - if device == "gpu": - import cupy as cp # type: ignore - - xp = cp - else: - xp = np + F_ref = np.asarray(im_ref) if fft_input else np.fft.fft2(np.asarray(im_ref)) + F_im = np.asarray(im) if fft_input else np.fft.fft2(np.asarray(im)) - # Fourier transforms - F_ref = im_ref if fft_input else xp.fft.fft2(im_ref) - F_im = im if fft_input else xp.fft.fft2(im) + cc = F_ref * np.conj(F_im) + cc_real = np.fft.ifft2(cc).real - # Correlation - cc = F_ref * xp.conj(F_im) - cc_real = xp.real(xp.fft.ifft2(cc)) + M, N = cc_real.shape if max_shift is not None: - x = np.fft.fftfreq(cc.shape[0], 1 / cc.shape[0]) - y = np.fft.fftfreq(cc.shape[1], 1 / cc.shape[1]) - mask = x[:, None] ** 2 + y[None, :] ** 2 >= max_shift**2 - cc_real[mask] = 0.0 + x = np.fft.fftfreq(M) * M + y = np.fft.fftfreq(N) * N + mask = x[:, None] ** 2 + y[None, :] ** 2 > float(max_shift) ** 2 + cc_real = cc_real.copy() + cc_real[mask] = -np.inf - # Coarse peak - peak = xp.unravel_index(xp.argmax(cc_real), cc_real.shape) - x0, y0 = peak + flat_idx = int(np.argmax(cc_real)) + x0 = flat_idx // N + y0 = flat_idx % N - # Parabolic refinement - x_inds = xp.mod(x0 + xp.arange(-1, 2), cc.shape[0]).astype(int) - y_inds = xp.mod(y0 + xp.arange(-1, 2), cc.shape[1]).astype(int) + x_inds = [((x0 + dx) % M) for dx in (-1, 0, 1)] + y_inds = [((y0 + dy) % N) for dy in (-1, 0, 1)] vx = cc_real[x_inds, y0] vy = cc_real[x0, y_inds] - def parabolic_peak(v): - return (v[2] - v[0]) / (4 * v[1] - 2 * v[2] - 2 * v[0]) + dx = _parabolic_peak(vx) + dy = _parabolic_peak(vy) - dx = parabolic_peak(vx) - dy = parabolic_peak(vy) + x0 = np.round((float(x0) + float(dx)) * 2.0) / 2.0 + y0 = np.round((float(y0) + float(dy)) * 2.0) / 2.0 - x0 = (x0 + dx) % cc.shape[0] - y0 = (y0 + dy) % cc.shape[1] - - if upsample_factor <= 1: - shifts = (x0, y0) - else: - # Local DFT upsampling + xy_shift = np.array([x0, y0], dtype=float) - local = dft_upsample(cc, upsample_factor, (x0, y0), device=device) - peak = np.unravel_index(xp.argmax(local), local.shape) - - try: - lx, ly = peak - icc = local[lx - 1 : lx + 2, ly - 1 : ly + 2] - if icc.shape == (3, 3): - dxf = parabolic_peak(icc[:, 1]) - dyf = parabolic_peak(icc[1, :]) - else: - raise ValueError("Subarray too close to edge") - except (IndexError, ValueError): - dxf = dyf = 0.0 - - shifts = np.array([x0, y0]) + (np.array(peak) - upsample_factor) / upsample_factor - shifts += np.array([dxf, dyf]) / upsample_factor + if upsample_factor > 2: + xy_shift = _upsampled_correlation_numpy(cc, int(upsample_factor), xy_shift) - shifts = (shifts + 0.5 * np.array(cc.shape)) % cc.shape - 0.5 * np.array(cc.shape) + shifts = np.empty(2, dtype=float) + shifts[0] = ((xy_shift[0] + M / 2) % M) - M / 2 + shifts[1] = ((xy_shift[1] + N / 2) % N) - N / 2 + shifts = (float(shifts[0]), float(shifts[1])) if not return_shifted_image: return shifts - # Fourier shift image (F_im assumed to be FFT) - kx = xp.fft.fftfreq(F_im.shape[0])[:, None] - ky = xp.fft.fftfreq(F_im.shape[1])[None, :] - phase_ramp = xp.exp(-2j * np.pi * (kx * shifts[0] + ky * shifts[1])) + kx = np.fft.fftfreq(F_im.shape[0])[:, None] + ky = np.fft.fftfreq(F_im.shape[1])[None, :] + phase_ramp = np.exp(-2j * np.pi * (kx * shifts[0] + ky * shifts[1])) F_im_shifted = F_im * phase_ramp + if fft_output: image_shifted = F_im_shifted else: - image_shifted = xp.real(xp.fft.ifft2(F_im_shifted)) + image_shifted = np.fft.ifft2(F_im_shifted).real return shifts, image_shifted @@ -176,7 +187,6 @@ def cross_correlation_shift_torch( xy_shift = align_images_fourier_torch(G1, G2, upsample_factor) - # convert to centered signed shifts as original code M, N = im_ref.shape dx = ((xy_shift[0] + M / 2) % M) - M / 2 dy = ((xy_shift[1] + N / 2) % N) - N / 2 @@ -198,12 +208,10 @@ def align_images_fourier_torch( cc = G1 * G2.conj() cc_real = torch.fft.ifft2(cc).real - # local max (integer) flat_idx = torch.argmax(cc_real) x0 = (flat_idx // cc_real.shape[1]).to(torch.long).item() y0 = (flat_idx % cc_real.shape[1]).to(torch.long).item() - # half pixel shifts: pick ±1 indices with wrap (mod) M, N = cc_real.shape x_inds = [((x0 + dx) % M) for dx in (-1, 0, 1)] y_inds = [((y0 + dy) % N) for dy in (-1, 0, 1)] @@ -211,14 +219,11 @@ def align_images_fourier_torch( vx = cc_real[x_inds, y0] vy = cc_real[x0, y_inds] - # parabolic half-pixel refine - # dx = (vx[2] - vx[0]) / (4*vx[1] - 2*vx[2] - 2*vx[0]) denom_x = 4.0 * vx[1] - 2.0 * vx[2] - 2.0 * vx[0] denom_y = 4.0 * vy[1] - 2.0 * vy[2] - 2.0 * vy[0] dx = (vx[2] - vx[0]) / denom_x if denom_x != 0 else torch.tensor(0.0, device=device) dy = (vy[2] - vy[0]) / denom_y if denom_y != 0 else torch.tensor(0.0, device=device) - # round to nearest half-pixel x0 = torch.round((x0 + dx) * 2.0) / 2.0 y0 = torch.round((y0 + dy) * 2.0) / 2.0 @@ -243,7 +248,6 @@ def upsampled_correlation_torch( xyShift: 2-element tensor (x,y) in image coords; must be half-pixel precision as described. Returns refined xyShift (tensor length 2). """ - assert upsampleFactor > 2 xyShift = torch.round(xyShift * float(upsampleFactor)) / float(upsampleFactor) @@ -254,28 +258,25 @@ def upsampled_correlation_torch( im_up = dftUpsample_torch(conj_input, upsampleFactor, upsampleCenter) imageCorrUpsample = im_up.conj() - # find maximum - # flatten argmax -> unravel to 2D flat_idx = torch.argmax(imageCorrUpsample.real) - # unravel_index xySubShift0 = (flat_idx // imageCorrUpsample.shape[1]).to(torch.long) xySubShift1 = (flat_idx % imageCorrUpsample.shape[1]).to(torch.long) xySubShift = torch.tensor([xySubShift0.item(), xySubShift1.item()]) - # parabolic subpixel refinement dx = 0.0 dy = 0.0 try: - # extract 3x3 patch around found peak r = xySubShift[0].item() c = xySubShift[1].item() patch = imageCorrUpsample.real[r - 1 : r + 2, c - 1 : c + 2] - # if patch is incomplete (near edge) this will raise / have wrong shape -> except if patch.shape == (3, 3): icc = patch - # dx corresponds to row direction (vertical axis) as in original code: - dx = (icc[2, 1] - icc[0, 1]) / (4.0 * icc[1, 1] - 2.0 * icc[2, 1] - 2.0 * icc[0, 1]) - dy = (icc[1, 2] - icc[1, 0]) / (4.0 * icc[1, 1] - 2.0 * icc[1, 2] - 2.0 * icc[1, 0]) + dx = (icc[2, 1] - icc[0, 1]) / ( + 4.0 * icc[1, 1] - 2.0 * icc[2, 1] - 2.0 * icc[0, 1] + ) + dy = (icc[1, 2] - icc[1, 0]) / ( + 4.0 * icc[1, 1] - 2.0 * icc[1, 2] - 2.0 * icc[1, 0] + ) dx = dx.item() dy = dy.item() else: @@ -283,7 +284,6 @@ def upsampled_correlation_torch( except Exception: dx, dy = 0.0, 0.0 - # convert xySubShift to zero-centered by subtracting globalShift xySubShift = xySubShift.to(dtype=torch.get_default_dtype()) xySubShift = xySubShift - globalShift.to(xySubShift.dtype) @@ -312,13 +312,9 @@ def dftUpsample_torch( numRow = int(math.ceil(pixelRadius * upsampleFactor)) numCol = numRow - # prepare the vectors exactly like the numpy version - # col: frequency indices (centered) for N col_freq = torch.fft.ifftshift(torch.arange(N, device=device)) - math.floor(N / 2) - # row: frequency indices (centered) for M row_freq = torch.fft.ifftshift(torch.arange(M, device=device)) - math.floor(M / 2) - # small upsample grid coordinates (integer positions in the UPSAMPLED GRID) col_coords = torch.arange(numCol, device=device, dtype=torch.get_default_dtype()) - float( xyShift[1] ) @@ -326,25 +322,18 @@ def dftUpsample_torch( xyShift[0] ) - # build kernels: note factor signs and denominators match original numpy code - # colKern: shape (N, numCol) factor_col = -2j * math.pi / (N * float(upsampleFactor)) - # outer(col_freq, col_coords) -> shape (N, numCol) colKern = torch.exp(factor_col * (col_freq.unsqueeze(1) * col_coords.unsqueeze(0))).to( imageCorr.dtype ) - # rowKern: shape (numRow, M) factor_row = -2j * math.pi / (M * float(upsampleFactor)) - # outer(row_coords, row_freq) -> shape (numRow, M) rowKern = torch.exp(factor_row * (row_coords.unsqueeze(1) * row_freq.unsqueeze(0))).to( imageCorr.dtype ) - # perform the small-matrix DFT: (numRow, M) @ (M, N) @ (N, numCol) -> (numRow, numCol) imageUpsample = rowKern @ imageCorr @ colKern - # original code took xp.real(...) before returning return imageUpsample.real @@ -362,32 +351,6 @@ def bilinear_kde( ) -> NDArray | tuple[NDArray, NDArray]: """ Compute a bilinear kernel density estimate (KDE) with smooth threshold masking. - - Parameters - ---------- - xa : NDArray - Vertical (row) coordinates of input points. - ya : NDArray - Horizontal (col) coordinates of input points. - values : NDArray - Weights for each (xa, ya) point. - output_shape : tuple of int - Output image shape (rows, cols). - kde_sigma : float - Standard deviation of Gaussian KDE smoothing. - pad_value : float, default = 1.0 - Value to return when KDE support is too low. - threshold : float, default = 1e-3 - Minimum counts_KDE value for trusting the output signal. - lowpass_filter : bool, optional - If True, apply sinc-based inverse filtering to deconvolve the kernel. - max_batch_size : int or None, optional - Max number of points to process in one batch. - - Returns - ------- - NDArray - The estimated KDE image with threshold-masked output. """ rows, cols = output_shape xF = np.floor(xa.ravel()).astype(int) @@ -417,14 +380,12 @@ def bilinear_kde( inds_1D, weights=weights * w[start:end], minlength=rows * cols ) - # Reshape to 2D and apply Gaussian KDE pix_count = pix_count.reshape(output_shape) pix_output = pix_output.reshape(output_shape) pix_count = gaussian_filter(pix_count, kde_sigma) pix_output = gaussian_filter(pix_output, kde_sigma) - # Final image weight = np.minimum(pix_count / threshold, 1.0) image = pad_value * (1.0 - weight) + weight * (pix_output / np.maximum(pix_count, 1e-8)) @@ -456,23 +417,7 @@ def bilinear_array_interpolation( ) -> NDArray: """ Bilinear sampling of values from an array and pixel positions. - - Parameters - ---------- - image: np.ndarray - Image array to sample from - xa: np.ndarray - Vertical interpolation sampling positions of image array in pixels - ya: np.ndarray - Horizontal interpolation sampling positions of image array in pixels - - Returns - ------- - values: np.ndarray - Bilinear interpolation values of array at (xa,ya) positions - """ - xF = np.floor(xa.ravel()).astype("int") yF = np.floor(ya.ravel()).astype("int") dx = xa.ravel() - xF @@ -498,10 +443,7 @@ def bilinear_array_interpolation( values[start:end] += raveled_image[inds_1D] * weights - values = np.reshape( - values, - xa.shape, - ) + values = np.reshape(values, xa.shape) return values @@ -513,20 +455,7 @@ def fourier_cropping( """ Crops a corner-centered FFT array to retain only the lowest frequencies, equivalent to a center crop on the fftshifted version. - - Parameters: - ----------- - corner_centered_array : ndarray - 2D array (typically result of np.fft.fft2) with corner-centered DC - crop_shape : tuple of int - (height, width) of the desired cropped array (could be odd or even depending on arr.shape) - - Returns: - -------- - cropped : ndarray - Cropped array containing only the lowest frequencies, still corner-centered. """ - H, W = corner_centered_array.shape crop_h, crop_w = crop_shape @@ -537,13 +466,9 @@ def fourier_cropping( result = np.zeros(crop_shape, dtype=corner_centered_array.dtype) - # Top-left result[:h1, :w1] = corner_centered_array[:h1, :w1] - # Top-right result[:h1, -w2:] = corner_centered_array[:h1, -w2:] - # Bottom-left result[-h2:, :w1] = corner_centered_array[-h2:, :w1] - # Bottom-right result[-h2:, -w2:] = corner_centered_array[-h2:, -w2:] return result @@ -557,22 +482,6 @@ def compute_fsc_from_halfsets( """ Compute radially averaged Fourier Shell Correlation (FSC) from two half-set reconstructions. - - Parameters - ---------- - halfset_recons : list[torch.Tensor] - Two statistically-independent reconstructions, using half the dataset. - sampling: tuple[float,float] - Reconstruction sampling in Angstroms. - epsilon: float, optional - Small number to avoid dividing by zero - - Returns - ------- - q_bins: NDarray - Spatial frequency bins - fsc : NDarray - Fourier shell correlation as function of spatial frequency """ r1, r2 = halfset_recons @@ -602,12 +511,10 @@ def compute_fsc_from_halfsets( w0 = 1.0 - d_ind w1 = d_ind - # Flatten arrays cross = cross.reshape(-1) p1 = p1.reshape(-1) p2 = p2.reshape(-1) - # Accumulate cross_b = torch.bincount(inds_f, weights=cross * w0, minlength=num_bins) + torch.bincount( inds_f + 1, weights=cross * w1, minlength=num_bins ) @@ -637,45 +544,14 @@ def compute_spectral_snr_from_halfsets( ): """ Compute spectral SNR from two half-set reconstructions using symmetric/antisymmetric decomposition. - - The method decomposes the Fourier transforms into: - - Symmetric: (F₁ + F₂)/2 → signal + correlated noise - - Antisymmetric: (F₁ - F₂)/2 → uncorrelated noise only - - SSNR(q) = sqrt(signal_power / noise_power) - - where: - - signal_power = (|symmetric|² - |antisymmetric|²)₊ - - noise_power = |antisymmetric|² - - Parameters - ---------- - halfset_recons : list[torch.Tensor] - Two statistically-independent reconstructions, using half the dataset. - sampling: tuple[float,float] - Reconstruction sampling in Angstroms. - total_dose: float - Total _normalized_ electron dose, e.g. in DirectPtychography this is ~self.num_bf - epsilon: float, optional - Small number to avoid dividing by zero - - Returns - ------- - q_bins: NDarray - Spatial frequency bins - ssnr : NDarray - Radially averaged spectral SNR as function of spatial frequency """ - # Compute Fourier transforms halfset_1, halfset_2 = halfset_recons F1 = torch.fft.fft2(halfset_1) F2 = torch.fft.fft2(halfset_2) - # Symmetric and antisymmetric decomposition symmetric = (F1 + F2) / 2 antisymmetric = (F1 - F2) / 2 - # Power spectra noise_power = antisymmetric.abs() total_power = symmetric.abs() signal_power = (total_power - noise_power).clamp_min(0) @@ -699,11 +575,9 @@ def compute_spectral_snr_from_halfsets( w0 = 1.0 - d_ind w1 = d_ind - # Flatten arrays signal = signal_power.reshape(-1) noise = noise_power.reshape(-1) - # Accumulate signal_b = torch.bincount(inds_f, weights=signal * w0, minlength=num_bins) + torch.bincount( inds_f + 1, weights=signal * w1, minlength=num_bins ) @@ -726,20 +600,6 @@ def radially_average_fourier_array( ): """ Radially average a corner-centered Fourier array. - - Parameters - ---------- - corner_centered_array : list[torch.Tensor] - Fourier array to average radially. - sampling: tuple[float,float] - Reconstruction sampling in Angstroms. - - Returns - ------- - q_bins: NDarray - Spatial frequency bins - array_1d : NDarray - Radially averaged Fourier array as function of spatial frequency """ device = corner_centered_array.device nx, ny = corner_centered_array.shape @@ -760,10 +620,8 @@ def radially_average_fourier_array( w0 = 1.0 - d_ind w1 = d_ind - # Flatten arrays array = corner_centered_array.reshape(-1) - # Accumulate array_b = torch.bincount(inds_f, weights=array * w0, minlength=num_bins) + torch.bincount( inds_f + 1, weights=array * w1, minlength=num_bins ) @@ -842,9 +700,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] - torch.stack([i1, i2, rel, inc], dim=1) - ) + edges.append(torch.stack([i1, i2, rel, inc], dim=1)) if wrap_around: add_edges(idx.flatten(), torch.roll(idx, -1, 1).flatten()) @@ -856,7 +712,6 @@ def add_edges(i1, i2): edges = torch.cat(edges, dim=0) edges = edges[edges[:, 2].argsort()] - # return integer tensors only (CPU) return ( edges[:, 0].long(), edges[:, 1].long(), @@ -885,7 +740,6 @@ def union(self, x, y, inc_xy): if rx == ry: return - # phase(y) + oy + inc = phase(x) + ox delta = ox - oy - inc_xy if self.rank[rx] < self.rank[ry]: @@ -963,18 +817,6 @@ def _unwrap_phase_2d_torch_poisson( ): """ Least-squares / Poisson phase unwrapping with optional mask. - - Parameters - ---------- - phi_wrapped : (H, W) tensor - Wrapped phase in (-pi, pi], any device - mask : (H, W) bool tensor, optional - True = valid pixel - - Returns - ------- - phi_unwrapped : (H, W) tensor - Unwrapped phase (same device as input) """ device = phi_wrapped.device dtype = phi_wrapped.dtype @@ -1014,10 +856,10 @@ def _unwrap_phase_2d_torch_poisson( denom = kx**2 + ky**2 + regularization_lambda else: denom = kx**2 + ky**2 - denom[0, 0] = 1.0 # avoid divide by zero + denom[0, 0] = 1.0 phi_hat = -div_hat / denom - phi_hat[0, 0] = 0.0 # fix piston + phi_hat[0, 0] = 0.0 phi = torch.fft.ifftn(phi_hat).real @@ -1051,7 +893,6 @@ def unwrap_phase_2d_torch( ) - def rotate_image( im, rotation_deg: float, diff --git a/tests/core/utils/test_imaging_utils.py b/tests/core/utils/test_imaging_utils.py new file mode 100644 index 000000000..691756ddd --- /dev/null +++ b/tests/core/utils/test_imaging_utils.py @@ -0,0 +1,75 @@ +""" +Tests for imaging utilities in quantem.core.utils.imaging_utils +""" + +import numpy as np +import pytest + +torch = pytest.importorskip("torch") + +from quantem.core.utils.imaging_utils import cross_correlation_shift, cross_correlation_shift_torch + + +@pytest.fixture +def spot_image(): + from scipy.ndimage import gaussian_filter + + im = np.zeros((64, 64), dtype=np.float64) + im[32, 32] = 1.0 + im = gaussian_filter(im, 2.0) + im /= np.max(im) + return im + + +def _fourier_shift_numpy(im: np.ndarray, shift_rc: tuple[float, float]) -> np.ndarray: + dr, dc = shift_rc + kr = np.fft.fftfreq(im.shape[0])[:, None] + kc = np.fft.fftfreq(im.shape[1])[None, :] + F = np.fft.fft2(im) + phase = np.exp(-2j * np.pi * (kr * dr + kc * dc)) + return np.fft.ifft2(F * phase).real + + +def _wrap_shift_rc(shift_rc: tuple[float, float], shape: tuple[int, int]) -> tuple[float, float]: + dr, dc = shift_rc + M, N = shape + dr = ((dr + M / 2) % M) - M / 2 + dc = ((dc + N / 2) % N) - N / 2 + return float(dr), float(dc) + + +@pytest.mark.parametrize( + "shift_true, upsample_factor, atol", + [ + ((5.0, -3.0), 1000, 1e-3), + ((-7.123, 1.789), 1000, 1e-3), + ], +) +def test_cross_correlation_shift_numpy_matches_expected(spot_image, shift_true, upsample_factor, atol): + im_ref = spot_image + im = _fourier_shift_numpy(im_ref, shift_true) + expected = _wrap_shift_rc((-shift_true[0], -shift_true[1]), im_ref.shape) + + meas = cross_correlation_shift(im_ref, im, upsample_factor=upsample_factor) + assert meas[0] == pytest.approx(expected[0], abs=atol) + assert meas[1] == pytest.approx(expected[1], abs=atol) + + +@pytest.mark.parametrize( + "shift_true, upsample_factor, atol", + [ + ((5.0, -3.0), 1000, 1e-3), + ((-7.123, 1.789), 1000, 1e-3), + ], +) +def test_cross_correlation_shift_torch_matches_expected(spot_image, shift_true, upsample_factor, atol): + im_ref = spot_image + im = _fourier_shift_numpy(im_ref, shift_true) + expected = _wrap_shift_rc((-shift_true[0], -shift_true[1]), im_ref.shape) + + t_ref = torch.from_numpy(im_ref) + t_im = torch.from_numpy(im) + meas = cross_correlation_shift_torch(t_ref, t_im, upsample_factor=upsample_factor).cpu().numpy() + + assert float(meas[0]) == pytest.approx(expected[0], abs=atol) + assert float(meas[1]) == pytest.approx(expected[1], abs=atol) From f249c6c2b99b0f65db6bcc7dc7c3b6be3f34d78f Mon Sep 17 00:00:00 2001 From: cophus Date: Sat, 31 Jan 2026 16:19:17 -0800 Subject: [PATCH 015/113] initial maped class commit --- src/quantem/diffraction/maped.py | 169 ++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 5 deletions(-) diff --git a/src/quantem/diffraction/maped.py b/src/quantem/diffraction/maped.py index f301454ef..ca14c92fe 100644 --- a/src/quantem/diffraction/maped.py +++ b/src/quantem/diffraction/maped.py @@ -3,6 +3,7 @@ from typing import Any, Sequence import numpy as np +from scipy.signal.windows import tukey from quantem.core.datastructures.dataset4dstem import Dataset4dstem from quantem.core.io.serialize import AutoSerialize @@ -34,7 +35,6 @@ def from_datasets(cls, datasets: Sequence[Dataset4dstem]) -> "MAPED": def preprocess( self, - *, plot_summary: bool = True, scale: float | Sequence[float] | None = None, **plot_kwargs: Any, @@ -76,12 +76,171 @@ def preprocess( self.im_bf.append(np.asarray(im_bf_arr)) if plot_summary: + tiles = [[(self.im_bf[i] / self.scales[i]), self.dp_mean[i]] for i in range(n)] + titles = [[f"{i} - Mean Bright Field", f"{i} - Mean Diffraction Pattern"] for i in range(n)] show_2d( - [ - [self.im_bf[i] / self.scales[i] for i in range(n)], - [self.dp_mean[i] for i in range(n)], - ], + tiles, + titles=titles, **plot_kwargs, ) return self + + + def diffraction_find_origin( + self, + origins=None, + sigma=None, + plot_origins: bool = True, + plot_indices=None, + ): + """ + Choose or automatically find the origin in diffraction space. + + Parameters + ---------- + origins + Optional manual origins. Can be: + - a single (row, col) tuple, applied to all datasets + - a list of (row, col) tuples of length n (one per dataset) + - a list of (row, col) tuples shorter than n, used for plot/inspection only (will error if not broadcastable) + sigma + Optional low-pass smoothing sigma (pixels) applied to each mean DP prior to peak finding. + plot_origins + If True, plot mean diffraction patterns with overlaid origin markers. + plot_indices + Optional indices to plot. If None, plots all datasets. + + Stores + ------ + self.diffraction_origins : np.ndarray + Array of shape (n, 2) with integer (row, col) origins. + """ + import numpy as _np + + try: + from scipy.ndimage import gaussian_filter as _gaussian_filter + except Exception: # pragma: no cover + _gaussian_filter = None + + n = len(self.datasets) + if not hasattr(self, "dp_mean"): + raise RuntimeError("Run preprocess() first so self.dp_mean exists.") + + if plot_indices is None: + plot_indices_list = list(range(n)) + else: + plot_indices_list = list(plot_indices) + for i in plot_indices_list: + if i < 0 or i >= n: + raise IndexError("plot_indices contains an out-of-range index.") + + if origins is None: + origins_arr = _np.zeros((n, 2), dtype=int) + for i in range(n): + dp = _np.asarray(self.dp_mean[i]) + if sigma is not None and float(sigma) > 0: + if _gaussian_filter is None: + raise ImportError("scipy is required for sigma smoothing (gaussian_filter).") + dp_use = _gaussian_filter(dp.astype(float, copy=False), float(sigma)) + else: + dp_use = dp + ind = int(_np.argmax(dp_use)) + r, c = _np.unravel_index(ind, dp_use.shape) + origins_arr[i, 0] = int(r) + origins_arr[i, 1] = int(c) + else: + if isinstance(origins, tuple) and len(origins) == 2: + origins_arr = _np.tile(_np.asarray(origins, dtype=int)[None, :], (n, 1)) + else: + origins_list = list(origins) + if len(origins_list) != n: + raise ValueError("origins must be a single (row,col) tuple or a list of length n.") + origins_arr = _np.asarray(origins_list, dtype=int) + if origins_arr.shape != (n, 2): + raise ValueError("origins must have shape (n, 2) after conversion.") + + self.diffraction_origins = origins_arr + + if plot_origins: + dp_tiles = [[_np.asarray(self.dp_mean[i]) for i in plot_indices_list]] + titles = [[f"{i} - Mean Diffraction Pattern" for i in plot_indices_list]] + fig, axs = show_2d(dp_tiles, titles=titles, returnfig=True, **{}) + if not isinstance(axs, (list, _np.ndarray)): + axs = [axs] + axs_flat = _np.ravel(axs) + for j, i in enumerate(plot_indices_list): + ax = axs_flat[j] + r, c = self.diffraction_origins[i] + ax.plot([c], [r], marker="+", color="red", markersize=16, markeredgewidth=2) + return fig, axs + + return self + + + def diffraction_align( + self, + edge_blend = 8.0, + padding = None, + weight_scale = 1/8, + plot_aligned = True, + linewidth = 2, + **kwargs, + ): + """ + Refine the diffraction space origins, set padding, align images + + """ + + # window function + from scipy.signal.windows import tukey + w = tukey(self.dp_mean[0].shape[0], alpha=2.0*edge_blend/self.dp_mean[0].shape[0])[:,None] * \ + tukey(self.dp_mean[0].shape[1], alpha=2.0*edge_blend/self.dp_mean[0].shape[1])[None,:] + + # coordinates + r = np.fft.fftfreq(self.dp_mean[0].shape[0],1/self.dp_mean[0].shape[0])[:,None] + c = np.fft.fftfreq(self.dp_mean[0].shape[1],1/self.dp_mean[0].shape[1])[None,:] + + # init + shifts = np.zeros((len(self.dp_mean),2)) + + # correlation alignment + G_ref = np.fft.fft2(w * self.dp_mean[0]) + xy0 = self.diffraction_origins[0] + for ind in range(1,2): + G = np.conj(np.fft.fft2(w * self.dp_mean[ind])) + xy = self.diffraction_origins[ind] + + dr2 = (r - xy0[0] + xy[0])**2 \ + + (c - xy0[1] + xy[1])**2 + im_weight = np.clip(1 - np.sqrt(dr2)/np.mean(self.dp_mean[0].shape)/weight_scale, 0.0, 1.0) + im_weight = np.sin(im_weight*np.pi/2)**2 + + im_corr = np.real(np.fft.ifft2(G_ref * G)) * im_weight + + + + if plot_aligned: + show_2d( + np.fft.fftshift(im_corr), + norm = { + 'upper_quantile':1.0, + }, + **kwargs, + ) + + + def real_space_align( + self + ): + pass + + + + def merge_datasets( + self + ): + pass + + + \ No newline at end of file From 494dc248cb3ecb28ce9d112da6fdb24bf49db234 Mon Sep 17 00:00:00 2001 From: cophus Date: Sat, 31 Jan 2026 17:20:00 -0800 Subject: [PATCH 016/113] Updating with weighted correlation --- src/quantem/core/utils/imaging_utils.py | 130 ++++++++++++++++++++++++ src/quantem/diffraction/maped.py | 21 +++- tests/core/utils/test_imaging_utils.py | 56 +++++++++- 3 files changed, 204 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 805ca5b06..e9c22b097 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -337,6 +337,136 @@ def dftUpsample_torch( return imageUpsample.real +def weighted_cross_correlation_shift( + im_ref=None, + im=None, + *, + cc=None, + weight_real=None, + upsample_factor: int = 1, + max_shift=None, + fft_input: bool = False, + return_shifted: bool = False, + shifted_output: str = "real", +): + """ + Weighted peak selection + DFT subpixel refinement for Fourier cross-correlation. + + You can provide either: + - im_ref and im (real-space images, or Fourier-domain if fft_input=True), OR + - cc (the Fourier-domain cross-spectrum), where cc = F_ref * conj(F_im) + + The weight is applied ONLY in real-space correlation to choose the peak location, + but the subpixel refinement uses the true (unweighted) cross-spectrum `cc`. + + Parameters + ---------- + im_ref, im : ndarray or None + Input images (real space) or their FFTs (if fft_input=True). + cc : ndarray or None + Fourier-domain cross-spectrum cc = F_ref * conj(F_im). If provided, im_ref/im are ignored. + weight_real : ndarray or None + Real-space weight image (same shape as correlation). Used only for peak selection. + If None, peak selection is unweighted. + upsample_factor : int + <= 2: half-pixel refinement (parabolic then rounded to nearest 0.5 px) + > 2 : additional DFT upsample refinement via _upsampled_correlation_numpy + max_shift : float or None + Optional radial cutoff (in pixels) applied to the (weighted) real correlation during peak pick. + fft_input : bool + If True, im_ref and im are already Fourier-domain arrays. + return_shifted : bool + If True, also return shifted version of `im` (or its FFT) aligned to `im_ref`. + Requires im to be provided (or fft_input=True with im as FFT). If only cc is provided, + shifted output is unavailable. + shifted_output : {"real","fft"} + Output type for the shifted image. + + Returns + ------- + shift_rc : tuple[float, float] + (d_row, d_col) shift to apply to `im` to align it to `im_ref`. + shifted : ndarray (optional) + Shifted image (real) or FFT (corner-centered) depending on shifted_output. + """ + import numpy as np + + from quantem.core.utils.imaging_utils import _parabolic_peak, _upsampled_correlation_numpy + + if cc is None: + if im_ref is None or im is None: + raise ValueError("Provide either `cc` or both `im_ref` and `im`.") + F_ref = np.asarray(im_ref) if fft_input else np.fft.fft2(np.asarray(im_ref)) + F_im = np.asarray(im) if fft_input else np.fft.fft2(np.asarray(im)) + cc = F_ref * np.conj(F_im) + else: + cc = np.asarray(cc) + F_im = None + + cc_real = np.fft.ifft2(cc).real + M, N = cc_real.shape + + if weight_real is not None: + w = np.asarray(weight_real) + if w.shape != cc_real.shape: + raise ValueError(f"weight_real.shape={w.shape} must match correlation shape {cc_real.shape}.") + cc_pick = cc_real * w + else: + cc_pick = cc_real + + if max_shift is not None: + x = np.fft.fftfreq(M) * M + y = np.fft.fftfreq(N) * N + mask = x[:, None] ** 2 + y[None, :] ** 2 > float(max_shift) ** 2 + cc_pick = cc_pick.copy() + cc_pick[mask] = -np.inf + + flat_idx = int(np.argmax(cc_pick)) + x0 = flat_idx // N + y0 = flat_idx % N + + x_inds = [((x0 + dx) % M) for dx in (-1, 0, 1)] + y_inds = [((y0 + dy) % N) for dy in (-1, 0, 1)] + vx = cc_pick[x_inds, y0] + vy = cc_pick[x0, y_inds] + + dx = _parabolic_peak(vx) + dy = _parabolic_peak(vy) + + x0 = np.round((float(x0) + float(dx)) * 2.0) / 2.0 + y0 = np.round((float(y0) + float(dy)) * 2.0) / 2.0 + xy_shift = np.array([x0, y0], dtype=float) + + if upsample_factor > 2: + xy_shift = _upsampled_correlation_numpy(cc, int(upsample_factor), xy_shift) + + dr = ((xy_shift[0] + M / 2) % M) - M / 2 + dc = ((xy_shift[1] + N / 2) % N) - N / 2 + shift_rc = (float(dr), float(dc)) + + if not return_shifted: + return shift_rc + + if im is None: + raise ValueError("return_shifted=True requires `im` (or its FFT via fft_input=True).") + + if F_im is None: + F_im = np.asarray(im) if fft_input else np.fft.fft2(np.asarray(im)) + + kr = np.fft.fftfreq(M)[:, None] + kc = np.fft.fftfreq(N)[None, :] + phase_ramp = np.exp(-2j * np.pi * (kr * shift_rc[0] + kc * shift_rc[1])) + F_im_shifted = F_im * phase_ramp + + out_mode = str(shifted_output).lower() + if out_mode in {"fft", "fourier"}: + return shift_rc, F_im_shifted + if out_mode in {"real", "image"}: + return shift_rc, np.fft.ifft2(F_im_shifted).real + + raise ValueError("shifted_output must be 'real' or 'fft'.") + + def bilinear_kde( xa: NDArray, ya: NDArray, diff --git a/src/quantem/diffraction/maped.py b/src/quantem/diffraction/maped.py index ca14c92fe..a6b21d5bf 100644 --- a/src/quantem/diffraction/maped.py +++ b/src/quantem/diffraction/maped.py @@ -8,6 +8,10 @@ from quantem.core.datastructures.dataset4dstem import Dataset4dstem from quantem.core.io.serialize import AutoSerialize from quantem.core.visualization import show_2d +# from quantem.core.utils.imaging_utils import cross_correlation_shift +# from quantem.core.utils.imaging_utils import dft_upsample +# from quantem.core.utils.imaging_utils import correlation_peak_shift_from_real +from quantem.core.utils.imaging_utils import cross_correlation_shift class MAPED(AutoSerialize): @@ -216,8 +220,23 @@ def diffraction_align( im_weight = np.clip(1 - np.sqrt(dr2)/np.mean(self.dp_mean[0].shape)/weight_scale, 0.0, 1.0) im_weight = np.sin(im_weight*np.pi/2)**2 - im_corr = np.real(np.fft.ifft2(G_ref * G)) * im_weight + # im_corr = np.real(np.fft.ifft2(G_ref * G)) * im_weight + # cc_weighted = + cc = G_ref * G + cc_weighted = np.fft.fft2(np.fft.ifft2(cc)) * im_weight + + + shift_rc = cross_correlation_shift( + cc_weighted, + np.ones_like(cc_weighted), # identity: cc = cc_weighted * conj(1) = cc_weighted + fft_input=True, # treat inputs as Fourier-domain + upsample_factor=100, # or whatever you want + ) + + shift = correlation_peak_shift_from_real(im_corr, upsample_factor=100) + + print(shift) if plot_aligned: diff --git a/tests/core/utils/test_imaging_utils.py b/tests/core/utils/test_imaging_utils.py index 691756ddd..bc3989d68 100644 --- a/tests/core/utils/test_imaging_utils.py +++ b/tests/core/utils/test_imaging_utils.py @@ -3,16 +3,16 @@ """ import numpy as np +from scipy.ndimage import gaussian_filter import pytest torch = pytest.importorskip("torch") -from quantem.core.utils.imaging_utils import cross_correlation_shift, cross_correlation_shift_torch +from quantem.core.utils.imaging_utils import cross_correlation_shift, cross_correlation_shift_torch, weighted_cross_correlation_shift @pytest.fixture def spot_image(): - from scipy.ndimage import gaussian_filter im = np.zeros((64, 64), dtype=np.float64) im[32, 32] = 1.0 @@ -73,3 +73,55 @@ def test_cross_correlation_shift_torch_matches_expected(spot_image, shift_true, assert float(meas[0]) == pytest.approx(expected[0], abs=atol) assert float(meas[1]) == pytest.approx(expected[1], abs=atol) + +import numpy as np +import pytest + +from quantem.core.utils.imaging_utils import weighted_cross_correlation_shift + + +@pytest.fixture +def peak_grid_images(): + im_ref = np.zeros((80, 80), dtype=float) + im = np.zeros_like(im_ref) + + r_ref = np.array([17, 27, 37, 47], dtype=int) + r_im = np.array([27, 37, 47, 57], dtype=int) # shifted +10 rows + c = np.array([17, 27, 37, 47], dtype=int) + + for rr in r_ref: + for cc in c: + im_ref[rr, cc] = 1.0 + + for rr in r_im: + for cc in c: + im[rr, cc] = 1.0 + + im_ref[37,27] = 3.0 + im[27,27] = 3.0 + + im_ref = gaussian_filter(im_ref,1.0) + im = gaussian_filter(im,1.0) + + # Smooth wrapped radial weight centered at 0 shift + M, N = im_ref.shape + fr = np.fft.fftfreq(M) * M + fc = np.fft.fftfreq(N) * N + dr2 = fr[:, None] ** 2 + fc[None, :] ** 2 + + sigma = 3.0 + weight = np.exp(dr2 / (-2.0*sigma**2)) + + return im_ref, im, weight + + +def test_weighted_cross_correlation_shift_unweighted_prefers_full_overlap(peak_grid_images): + im_ref, im, weight = peak_grid_images + shift = weighted_cross_correlation_shift(im_ref, im, upsample_factor=1000) + assert np.allclose(shift, (-10.0, 0.0), atol=1e-3) + + +def test_weighted_cross_correlation_shift_weighted_prefers_near_zero(peak_grid_images): + im_ref, im, weight = peak_grid_images + shift = weighted_cross_correlation_shift(im_ref, im, weight_real=weight, upsample_factor=1000) + assert np.allclose(shift, (0.0, 0.0), atol=1e-3) From 38a3fc21c6f34d697102947008b3c82a84e923e6 Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 1 Feb 2026 16:53:38 -0800 Subject: [PATCH 017/113] maped output --- src/quantem/core/utils/imaging_utils.py | 54 +-- src/quantem/diffraction/maped.py | 541 ++++++++++++++++++++++-- 2 files changed, 521 insertions(+), 74 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index e9c22b097..f6e5b6601 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -346,53 +346,27 @@ def weighted_cross_correlation_shift( upsample_factor: int = 1, max_shift=None, fft_input: bool = False, - return_shifted: bool = False, - shifted_output: str = "real", + fft_output: bool = False, + return_shifted_image: bool = False, ): """ Weighted peak selection + DFT subpixel refinement for Fourier cross-correlation. - You can provide either: + Provide either: - im_ref and im (real-space images, or Fourier-domain if fft_input=True), OR - cc (the Fourier-domain cross-spectrum), where cc = F_ref * conj(F_im) The weight is applied ONLY in real-space correlation to choose the peak location, but the subpixel refinement uses the true (unweighted) cross-spectrum `cc`. - Parameters - ---------- - im_ref, im : ndarray or None - Input images (real space) or their FFTs (if fft_input=True). - cc : ndarray or None - Fourier-domain cross-spectrum cc = F_ref * conj(F_im). If provided, im_ref/im are ignored. - weight_real : ndarray or None - Real-space weight image (same shape as correlation). Used only for peak selection. - If None, peak selection is unweighted. - upsample_factor : int - <= 2: half-pixel refinement (parabolic then rounded to nearest 0.5 px) - > 2 : additional DFT upsample refinement via _upsampled_correlation_numpy - max_shift : float or None - Optional radial cutoff (in pixels) applied to the (weighted) real correlation during peak pick. - fft_input : bool - If True, im_ref and im are already Fourier-domain arrays. - return_shifted : bool - If True, also return shifted version of `im` (or its FFT) aligned to `im_ref`. - Requires im to be provided (or fft_input=True with im as FFT). If only cc is provided, - shifted output is unavailable. - shifted_output : {"real","fft"} - Output type for the shifted image. - Returns ------- shift_rc : tuple[float, float] (d_row, d_col) shift to apply to `im` to align it to `im_ref`. shifted : ndarray (optional) - Shifted image (real) or FFT (corner-centered) depending on shifted_output. + If return_shifted=True: shifted image. If fft_output=True returns FFT (corner-centered), + else returns real-space image. """ - import numpy as np - - from quantem.core.utils.imaging_utils import _parabolic_peak, _upsampled_correlation_numpy - if cc is None: if im_ref is None or im is None: raise ValueError("Provide either `cc` or both `im_ref` and `im`.") @@ -415,9 +389,9 @@ def weighted_cross_correlation_shift( cc_pick = cc_real if max_shift is not None: - x = np.fft.fftfreq(M) * M - y = np.fft.fftfreq(N) * N - mask = x[:, None] ** 2 + y[None, :] ** 2 > float(max_shift) ** 2 + fr = np.fft.fftfreq(M) * M + fc = np.fft.fftfreq(N) * N + mask = fr[:, None] ** 2 + fc[None, :] ** 2 > float(max_shift) ** 2 cc_pick = cc_pick.copy() cc_pick[mask] = -np.inf @@ -444,11 +418,11 @@ def weighted_cross_correlation_shift( dc = ((xy_shift[1] + N / 2) % N) - N / 2 shift_rc = (float(dr), float(dc)) - if not return_shifted: + if not return_shifted_image: return shift_rc if im is None: - raise ValueError("return_shifted=True requires `im` (or its FFT via fft_input=True).") + raise ValueError("return_shifted_image=True requires `im` (or its FFT via fft_input=True).") if F_im is None: F_im = np.asarray(im) if fft_input else np.fft.fft2(np.asarray(im)) @@ -458,13 +432,9 @@ def weighted_cross_correlation_shift( phase_ramp = np.exp(-2j * np.pi * (kr * shift_rc[0] + kc * shift_rc[1])) F_im_shifted = F_im * phase_ramp - out_mode = str(shifted_output).lower() - if out_mode in {"fft", "fourier"}: + if fft_output: return shift_rc, F_im_shifted - if out_mode in {"real", "image"}: - return shift_rc, np.fft.ifft2(F_im_shifted).real - - raise ValueError("shifted_output must be 'real' or 'fft'.") + return shift_rc, np.fft.ifft2(F_im_shifted).real def bilinear_kde( diff --git a/src/quantem/diffraction/maped.py b/src/quantem/diffraction/maped.py index a6b21d5bf..afb1fc0c1 100644 --- a/src/quantem/diffraction/maped.py +++ b/src/quantem/diffraction/maped.py @@ -8,10 +8,7 @@ from quantem.core.datastructures.dataset4dstem import Dataset4dstem from quantem.core.io.serialize import AutoSerialize from quantem.core.visualization import show_2d -# from quantem.core.utils.imaging_utils import cross_correlation_shift -# from quantem.core.utils.imaging_utils import dft_upsample -# from quantem.core.utils.imaging_utils import correlation_peak_shift_from_real -from quantem.core.utils.imaging_utils import cross_correlation_shift +from quantem.core.utils.imaging_utils import weighted_cross_correlation_shift class MAPED(AutoSerialize): @@ -184,8 +181,10 @@ def diffraction_find_origin( def diffraction_align( self, - edge_blend = 8.0, + edge_blend = 16.0, padding = None, + pad_val = 'min', + upsample_factor = 100, weight_scale = 1/8, plot_aligned = True, linewidth = 2, @@ -206,13 +205,13 @@ def diffraction_align( c = np.fft.fftfreq(self.dp_mean[0].shape[1],1/self.dp_mean[0].shape[1])[None,:] # init - shifts = np.zeros((len(self.dp_mean),2)) + self.diffraction_shifts = np.zeros((len(self.dp_mean),2)) # correlation alignment G_ref = np.fft.fft2(w * self.dp_mean[0]) xy0 = self.diffraction_origins[0] - for ind in range(1,2): - G = np.conj(np.fft.fft2(w * self.dp_mean[ind])) + for ind in range(1,len(self.dp_mean)): + G = np.fft.fft2(w * self.dp_mean[ind]) xy = self.diffraction_origins[ind] dr2 = (r - xy0[0] + xy[0])**2 \ @@ -220,46 +219,524 @@ def diffraction_align( im_weight = np.clip(1 - np.sqrt(dr2)/np.mean(self.dp_mean[0].shape)/weight_scale, 0.0, 1.0) im_weight = np.sin(im_weight*np.pi/2)**2 - # im_corr = np.real(np.fft.ifft2(G_ref * G)) * im_weight - # cc_weighted = - - cc = G_ref * G - cc_weighted = np.fft.fft2(np.fft.ifft2(cc)) * im_weight - - - shift_rc = cross_correlation_shift( - cc_weighted, - np.ones_like(cc_weighted), # identity: cc = cc_weighted * conj(1) = cc_weighted - fft_input=True, # treat inputs as Fourier-domain - upsample_factor=100, # or whatever you want + shift, G_shift = weighted_cross_correlation_shift( + im_ref=G_ref, + im=G, + weight_real=im_weight*0+1.0, + upsample_factor = upsample_factor, + fft_input = True, + fft_output = True, + return_shifted_image = True, ) + self.diffraction_shifts[ind,:] = shift - shift = correlation_peak_shift_from_real(im_corr, upsample_factor=100) + # update reference + G_ref = G_ref*(ind/(ind+1)) + G_shift/(ind+1) - print(shift) + # Center shifts + self.diffraction_shifts -= np.mean(self.diffraction_shifts,axis=0)[None,:] + # Generate output image if plot_aligned: + im_aligned = shift_images( + images = self.dp_mean, + shifts_rc = self.diffraction_shifts, + edge_blend = edge_blend, + padding = padding, + pad_val = pad_val, + ) show_2d( - np.fft.fftshift(im_corr), - norm = { - 'upper_quantile':1.0, - }, + im_aligned, **kwargs, ) def real_space_align( - self + self, + num_images=None, + num_iter: int = 3, + edge_blend: float = 1.0, + padding=None, + pad_val: str | float = "median", + upsample_factor: int = 100, + max_shift=None, + shift_method: str = "bilinear", + edge_filter: bool = True, + edge_sigma: float = 2.0, + hanning_filter: bool = False, + plot_aligned: bool = True, + **kwargs, ): - pass + import numpy as np + from scipy.ndimage import gaussian_filter, shift as ndi_shift + from scipy.signal import convolve2d + from scipy.signal.windows import tukey + + from quantem.core.utils.imaging_utils import weighted_cross_correlation_shift + from quantem.core.visualization import show_2d + + if not hasattr(self, "im_bf"): + raise RuntimeError("Run preprocess() first so self.im_bf exists.") + if len(self.im_bf) == 0: + raise RuntimeError("No images found in self.im_bf.") + + H, W = self.im_bf[0].shape + for im in self.im_bf: + if im.shape != (H, W): + raise ValueError("all self.im_bf images must have the same shape") + + n_total = len(self.im_bf) + if num_images is None: + n = n_total + else: + n = int(num_images) + if n <= 0: + raise ValueError("num_images must be positive") + n = min(n, n_total) + + if int(num_iter) < 1: + raise ValueError("num_iter must be >= 1") + + if max_shift is not None: + pad_cc = int(np.ceil(float(max_shift))) + 4 + else: + pad_cc = int(np.ceil(float(edge_blend))) + 4 + + Hp = H + 2 * pad_cc + Wp = W + 2 * pad_cc + r0 = pad_cc + c0 = pad_cc + + w_h = np.ones((H, W), dtype=float) + if hanning_filter: + w_h = np.hanning(H)[:, None] * np.hanning(W)[None, :] + w_h_pad = np.zeros((Hp, Wp), dtype=float) + w_h_pad[r0 : r0 + H, c0 : c0 + W] = w_h + w_h_sum = float(np.sum(w_h_pad)) + if w_h_sum <= 0: + raise RuntimeError("hanning window sum is zero") + + wx = None + if edge_filter: + wx = np.array( + [ + [-1.0, -2.0, -1.0], + [ 0.0, 0.0, 0.0], + [ 1.0, 2.0, 1.0], + ], + dtype=float, + ) + + base_pad = np.zeros((n, Hp, Wp), dtype=float) + for i in range(n): + im0 = np.asarray(self.im_bf[i], dtype=float) + + if edge_filter: + gx = convolve2d(im0, wx, mode="same", boundary="symm") + gy = convolve2d(im0, wx.T, mode="same", boundary="symm") + gx = gaussian_filter(gx, float(edge_sigma), mode="nearest") + gy = gaussian_filter(gy, float(edge_sigma), mode="nearest") + im_use = np.sqrt(gx * gx + gy * gy) + else: + im_use = im0 + + base_pad[i, r0 : r0 + H, c0 : c0 + W] = im_use + + shifts = np.zeros((n, 2), dtype=float) + + for _ in range(int(num_iter)): + G_list = np.empty((n, Hp, Wp), dtype=np.complex128) + + for i in range(n): + im_a = ndi_shift( + base_pad[i], + shift=(float(shifts[i, 0]), float(shifts[i, 1])), + order=1, + mode="constant", + cval=0.0, + prefilter=False, + ) + im_mean = float(np.sum(im_a * w_h_pad) / w_h_sum) + im_win = (im_a - im_mean) * w_h_pad + G_list[i] = np.fft.fft2(im_win) + + G_ref = np.mean(G_list, axis=0) + + for i in range(1, n): + drc = weighted_cross_correlation_shift( + im_ref=G_ref, + im=G_list[i], + weight_real=None, + upsample_factor=int(upsample_factor), + max_shift=max_shift, + fft_input=True, + fft_output=False, + return_shifted_image=False, + ) + shifts[i, 0] += float(drc[0]) + shifts[i, 1] += float(drc[1]) + + shifts -= shifts[0][None, :] + + shifts -= np.mean(shifts, axis=0)[None, :] + + self.real_space_shifts = np.zeros((n_total, 2), dtype=float) + self.real_space_shifts[:n, :] = shifts + + if plot_aligned: + im_aligned = shift_images( + images=self.im_bf[:n], + shifts_rc=self.real_space_shifts[:n, :], + edge_blend=float(edge_blend), + padding=padding, + pad_val=pad_val, + shift_method=str(shift_method), + ) + show_2d(im_aligned, **kwargs) + + return self - def merge_datasets( - self + self, + real_space_padding=0, + real_space_edge_blend=1.0, + diffraction_padding=0, + diffraction_edge_blend=0.0, + diffraction_pad_val="min", + shift_method: str = "bilinear", + plot_result: bool = True, + **kwargs, ): - pass + import numpy as np + from scipy.ndimage import shift as ndi_shift + from scipy.signal.windows import tukey + from tqdm import tqdm + + if not hasattr(self, "real_space_shifts"): + raise RuntimeError("Run real_space_align() first so self.real_space_shifts exists.") + if not hasattr(self, "diffraction_shifts"): + raise RuntimeError("Run diffraction_align() first so self.diffraction_shifts exists.") + + n = len(self.datasets) + if n == 0: + raise RuntimeError("No datasets found in self.datasets.") + + arrays = [np.asarray(d.array, dtype=float) for d in self.datasets] + shape0 = arrays[0].shape + if len(shape0) != 4: + raise ValueError("Expected Dataset4dstem arrays with shape (R, C, H, W).") + Rs, Cs, H, W = shape0 + for a in arrays: + if a.shape != (Rs, Cs, H, W): + raise ValueError("All datasets must have the same shape (R, C, H, W).") + + rs_shifts = np.asarray(self.real_space_shifts, dtype=float) + dp_shifts = np.asarray(self.diffraction_shifts, dtype=float) + if rs_shifts.shape != (n, 2): + raise ValueError("self.real_space_shifts must have shape (n, 2).") + if dp_shifts.shape != (n, 2): + raise ValueError("self.diffraction_shifts must have shape (n, 2).") + + real_space_padding = int(real_space_padding) + if real_space_padding < 0: + raise ValueError("real_space_padding must be >= 0.") + + method = str(shift_method).strip().lower() + if method not in {"bilinear", "fourier"}: + raise ValueError("shift_method must be 'bilinear' or 'fourier'.") + + # Real-space taper window (used to weight contributions) + alpha_r = min(1.0, 2.0 * float(real_space_edge_blend) / float(Rs)) if real_space_edge_blend > 0 else 0.0 + alpha_c = min(1.0, 2.0 * float(real_space_edge_blend) / float(Cs)) if real_space_edge_blend > 0 else 0.0 + w_rs = tukey(Rs, alpha=alpha_r)[:, None] * tukey(Cs, alpha=alpha_c)[None, :] + w_rs = w_rs.astype(float, copy=False) + + # Diffraction padding (must be large enough to prevent wrap for Fourier shifts) + diffraction_padding = int(diffraction_padding) + if diffraction_padding < 0: + raise ValueError("diffraction_padding must be >= 0.") + max_abs_dp = float(np.max(np.abs(dp_shifts))) if dp_shifts.size else 0.0 + pad_dp_min = int(np.ceil(max_abs_dp)) + 2 + pad_dp = max(diffraction_padding, pad_dp_min) + + Hp = H + 2 * pad_dp + Wp = W + 2 * pad_dp + rp0 = pad_dp + cp0 = pad_dp + + # Diffraction taper window + alpha_hr = min(1.0, 2.0 * float(diffraction_edge_blend) / float(H)) if diffraction_edge_blend > 0 else 0.0 + alpha_hc = min(1.0, 2.0 * float(diffraction_edge_blend) / float(W)) if diffraction_edge_blend > 0 else 0.0 + w_dp = tukey(H, alpha=alpha_hr)[:, None] * tukey(W, alpha=alpha_hc)[None, :] + w_dp = w_dp.astype(float, copy=False) + + # Padded diffraction window (unshifted) + w_dp_pad = np.zeros((Hp, Wp)) + w_dp_pad[rp0 : rp0 + H, cp0 : cp0 + W] = w_dp + + # Pad value in diffraction space (computed from dp_means for speed) + if isinstance(diffraction_pad_val, str): + s = diffraction_pad_val.strip().lower() + dp_means = [np.mean(a, axis=(0, 1)) for a in arrays] + v = np.stack(dp_means, axis=0).reshape(-1) + if s == "min": + pad_val_dp = float(np.min(v)) + elif s == "max": + pad_val_dp = float(np.max(v)) + elif s == "mean": + pad_val_dp = float(np.mean(v)) + elif s == "median": + pad_val_dp = float(np.median(v)) + else: + raise ValueError("diffraction_pad_val must be a float or one of {'min','max','mean','median'}.") + else: + pad_val_dp = float(diffraction_pad_val) + + # Precompute diffraction window shifts per dataset + if method == "fourier": + kr = np.fft.fftfreq(Hp)[:, None] + kc = np.fft.fftfreq(Wp)[None, :] + ramps = [ + np.exp(-2j * np.pi * (kr * float(dp_shifts[i, 0]) + kc * float(dp_shifts[i, 1]))) + for i in range(n) + ] + wdp_shifted = np.empty((n, Hp, Wp)) + Fw0 = np.fft.fft2(w_dp_pad) + for i in range(n): + wtmp = np.fft.ifft2(Fw0 * ramps[i]).real + wtmp = np.clip(wtmp, 0.0, 1.0) + wdp_shifted[i] = wtmp + else: + wdp_shifted = np.empty((n, Hp, Wp)) + for i in range(n): + wtmp = ndi_shift( + w_dp_pad, + shift=(float(dp_shifts[i, 0]), float(dp_shifts[i, 1])), + order=1, + mode="constant", + cval=0.0, + prefilter=False, + ) + wtmp = np.clip(wtmp, 0.0, 1.0) + wdp_shifted[i] = wtmp + + # Edge blend weight for pad value (diffraction space) + edge_w_dp = 1.0 - np.clip(np.max(wdp_shifted, axis=0), 0.0, 1.0) + + Rout = Rs + 2 * real_space_padding + Cout = Cs + 2 * real_space_padding + merged = np.zeros((Rout, Cout, Hp, Wp)) + + dp_local = np.empty((H, W)) + dp_pad = np.zeros((Hp, Wp)) + dp_shifted_tmp = np.empty((Hp, Wp)) + num_tmp = np.zeros((Hp, Wp)) + den_tmp = np.zeros((Hp, Wp)) + out_tmp = np.empty((Hp, Wp)) + + for ro in tqdm(range(Rout), desc="Merging (rows)"): + r_base = float(ro - real_space_padding) + for co in range(Cout): + c_base = float(co - real_space_padding) + + num_tmp.fill(0.0) + den_tmp.fill(0.0) + max_wi = 0.0 + + for i in range(n): + r_in = r_base - float(rs_shifts[i, 0]) + c_in = c_base - float(rs_shifts[i, 1]) + + r0 = int(np.floor(r_in)) + c0 = int(np.floor(c_in)) + if r0 < 0 or r0 >= Rs - 1 or c0 < 0 or c0 >= Cs - 1: + continue + + dr = float(r_in - r0) + dc = float(c_in - c0) + + w00 = (1.0 - dr) * (1.0 - dc) + w10 = dr * (1.0 - dc) + w01 = (1.0 - dr) * dc + w11 = dr * dc + + wi = ( + w00 * w_rs[r0, c0] + + w10 * w_rs[r0 + 1, c0] + + w01 * w_rs[r0, c0 + 1] + + w11 * w_rs[r0 + 1, c0 + 1] + ) + if wi <= 0.0: + continue + if wi > max_wi: + max_wi = wi + + a = arrays[i] + dp_local[:] = ( + w00 * a[r0, c0] + + w10 * a[r0 + 1, c0] + + w01 * a[r0, c0 + 1] + + w11 * a[r0 + 1, c0 + 1] + ) + + dp_pad.fill(0.0) + dp_pad[rp0 : rp0 + H, cp0 : cp0 + W] = dp_local * w_dp + + if method == "fourier": + dp_shifted_tmp[:] = np.fft.ifft2(np.fft.fft2(dp_pad) * ramps[i]).real + else: + dp_shifted_tmp[:] = ndi_shift( + dp_pad, + shift=(float(dp_shifts[i, 0]), float(dp_shifts[i, 1])), + order=1, + mode="constant", + cval=0.0, + prefilter=False, + ) + + num_tmp += wi * dp_shifted_tmp + den_tmp += wi * wdp_shifted[i] + + if max_wi <= 0.0: + merged[ro, co] = 0.0 + continue + + num = num_tmp + edge_w_dp * pad_val_dp + den = den_tmp + edge_w_dp + + np.divide(num, den, out=out_tmp, where=(den > 0.0)) + out_tmp[den <= 0.0] = pad_val_dp + merged[ro, co] = out_tmp + + dataset_merged = Dataset4dstem.from_array(merged) + + self.im_bf_merged = np.mean(merged, axis=(2, 3)) + self.dp_mean_merged = np.mean(merged, axis=(0, 1)) + + dataset_merged.im_bf_merged = self.im_bf_merged + dataset_merged.dp_mean_merged = self.dp_mean_merged + dataset_merged.metadata["im_bf_merged"] = self.im_bf_merged + dataset_merged.metadata["dp_mean_merged"] = self.dp_mean_merged + dataset_merged.metadata["real_space_shifts_rc"] = rs_shifts.copy() + dataset_merged.metadata["diffraction_shifts_rc"] = dp_shifts.copy() + + if plot_result: + show_2d( + [[self.im_bf_merged, self.dp_mean_merged]], + titles=[["Merged Bright Field", "Merged Mean Diffraction Pattern"]], + **kwargs, + ) + + return dataset_merged + + +def shift_images( + images, + shifts_rc, + edge_blend: float = 8.0, + padding=None, + pad_val=0.0, + shift_method: str = "bilinear", +): + import numpy as np + from scipy.ndimage import shift as ndi_shift + from scipy.signal.windows import tukey + + images = [np.asarray(im, dtype=float) for im in images] + if len(images) == 0: + raise ValueError("images must be non-empty") + + H, W = images[0].shape + for im in images: + if im.shape != (H, W): + raise ValueError("all images must have the same shape") + + shifts_rc = np.asarray(shifts_rc, dtype=float) + if shifts_rc.shape != (len(images), 2): + raise ValueError("shifts_rc must have shape (len(images), 2)") + + if isinstance(pad_val, str): + s = pad_val.strip().lower() + v = np.stack(images, axis=0).reshape(-1) + if s == "min": + pad_val = float(np.min(v)) + elif s == "max": + pad_val = float(np.max(v)) + elif s == "mean": + pad_val = float(np.mean(v)) + elif s == "median": + pad_val = float(np.median(v)) + else: + raise ValueError("pad_val must be a float or one of {'min','max','mean','median'}") + else: + pad_val = float(pad_val) + + if padding is None: + max_shift = float(np.max(np.abs(shifts_rc))) if shifts_rc.size else 0.0 + padding = int(np.ceil(max_shift + float(edge_blend))) + 2 + padding = int(padding) + + alpha_r = min(1.0, 2.0 * float(edge_blend) / float(H)) if edge_blend > 0 else 0.0 + alpha_c = min(1.0, 2.0 * float(edge_blend) / float(W)) if edge_blend > 0 else 0.0 + w = tukey(H, alpha=alpha_r)[:, None] * tukey(W, alpha=alpha_c)[None, :] + w = w.astype(float, copy=False) + + Hp = H + 2 * padding + Wp = W + 2 * padding + + stack_w = np.zeros((len(images), Hp, Wp), dtype=float) + stack = np.zeros_like(stack_w) + + r0 = padding + c0 = padding + stack_w[:, r0 : r0 + H, c0 : c0 + W] = w[None, :, :] + for ind, im in enumerate(images): + stack[ind, r0 : r0 + H, c0 : c0 + W] = im * w + + method = str(shift_method).strip().lower() + if method not in {"bilinear", "fourier"}: + raise ValueError("shift_method must be 'bilinear' or 'fourier'") + + if method == "fourier": + kr = np.fft.fftfreq(Hp)[:, None] + kc = np.fft.fftfreq(Wp)[None, :] + for ind in range(len(images)): + dr, dc = float(shifts_rc[ind, 0]), float(shifts_rc[ind, 1]) + ramp = np.exp(-2j * np.pi * (kr * dr + kc * dc)) + + F = np.fft.fft2(stack[ind]) + stack[ind] = np.fft.ifft2(F * ramp).real + + Fw = np.fft.fft2(stack_w[ind]) + stack_w[ind] = np.fft.ifft2(Fw * ramp).real + stack_w[ind] = np.clip(stack_w[ind], 0.0, 1.0) + else: + for ind in range(len(images)): + stack[ind] = ndi_shift( + stack[ind], + shift=(float(shifts_rc[ind, 0]), float(shifts_rc[ind, 1])), + order=1, + mode="constant", + cval=0.0, + prefilter=False, + ) + stack_w[ind] = ndi_shift( + stack_w[ind], + shift=(float(shifts_rc[ind, 0]), float(shifts_rc[ind, 1])), + order=1, + mode="constant", + cval=0.0, + prefilter=False, + ) + stack_w[ind] = np.clip(stack_w[ind], 0.0, 1.0) + + # edge_w = 1.0 - np.clip(np.max(stack_w, axis=0), 0.0, 1.0) + edge_w = len(images) - np.sum(stack_w, axis=0) + num = np.sum(stack, axis=0) + edge_w * pad_val + den = np.sum(stack_w, axis=0) + edge_w + out = num / den - \ No newline at end of file + return out From 1e14d70ea12b9e4fb61b71d3ecdd90e66ebe9b6f Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 1 Feb 2026 17:13:41 -0800 Subject: [PATCH 018/113] datatype control for merged data --- src/quantem/diffraction/maped.py | 190 +++++++++++++++++++------------ 1 file changed, 118 insertions(+), 72 deletions(-) diff --git a/src/quantem/diffraction/maped.py b/src/quantem/diffraction/maped.py index afb1fc0c1..9df542f31 100644 --- a/src/quantem/diffraction/maped.py +++ b/src/quantem/diffraction/maped.py @@ -406,9 +406,13 @@ def merge_datasets( diffraction_edge_blend=0.0, diffraction_pad_val="min", shift_method: str = "bilinear", + dtype=None, + scale_output: bool = False, plot_result: bool = True, **kwargs, ): + import warnings + import numpy as np from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey @@ -419,18 +423,15 @@ def merge_datasets( if not hasattr(self, "diffraction_shifts"): raise RuntimeError("Run diffraction_align() first so self.diffraction_shifts exists.") - n = len(self.datasets) + arrays = [np.asarray(d.array) for d in self.datasets] + n = len(arrays) if n == 0: raise RuntimeError("No datasets found in self.datasets.") - arrays = [np.asarray(d.array, dtype=float) for d in self.datasets] - shape0 = arrays[0].shape - if len(shape0) != 4: - raise ValueError("Expected Dataset4dstem arrays with shape (R, C, H, W).") - Rs, Cs, H, W = shape0 + Rs, Cs, H, W = arrays[0].shape for a in arrays: if a.shape != (Rs, Cs, H, W): - raise ValueError("All datasets must have the same shape (R, C, H, W).") + raise ValueError("All dataset arrays must have the same shape (Rs, Cs, H, W).") rs_shifts = np.asarray(self.real_space_shifts, dtype=float) dp_shifts = np.asarray(self.diffraction_shifts, dtype=float) @@ -439,48 +440,48 @@ def merge_datasets( if dp_shifts.shape != (n, 2): raise ValueError("self.diffraction_shifts must have shape (n, 2).") + if dtype is None: + dtype_out = np.asarray(arrays[0]).dtype + warnings.warn(f"dtype=None; using parent dtype {dtype_out}.", RuntimeWarning) + else: + dtype_out = np.dtype(dtype) + real_space_padding = int(real_space_padding) - if real_space_padding < 0: - raise ValueError("real_space_padding must be >= 0.") + diffraction_padding = int(diffraction_padding) + + Rout = Rs + 2 * real_space_padding + Cout = Cs + 2 * real_space_padding + + Hp = H + 2 * diffraction_padding + Wp = W + 2 * diffraction_padding + rp0 = diffraction_padding + cp0 = diffraction_padding method = str(shift_method).strip().lower() if method not in {"bilinear", "fourier"}: raise ValueError("shift_method must be 'bilinear' or 'fourier'.") - # Real-space taper window (used to weight contributions) - alpha_r = min(1.0, 2.0 * float(real_space_edge_blend) / float(Rs)) if real_space_edge_blend > 0 else 0.0 - alpha_c = min(1.0, 2.0 * float(real_space_edge_blend) / float(Cs)) if real_space_edge_blend > 0 else 0.0 - w_rs = tukey(Rs, alpha=alpha_r)[:, None] * tukey(Cs, alpha=alpha_c)[None, :] + if real_space_edge_blend and float(real_space_edge_blend) > 0: + alpha_r = min(1.0, 2.0 * float(real_space_edge_blend) / float(Rs)) + alpha_c = min(1.0, 2.0 * float(real_space_edge_blend) / float(Cs)) + w_rs = tukey(Rs, alpha=alpha_r)[:, None] * tukey(Cs, alpha=alpha_c)[None, :] + else: + w_rs = np.ones((Rs, Cs), dtype=float) w_rs = w_rs.astype(float, copy=False) - # Diffraction padding (must be large enough to prevent wrap for Fourier shifts) - diffraction_padding = int(diffraction_padding) - if diffraction_padding < 0: - raise ValueError("diffraction_padding must be >= 0.") - max_abs_dp = float(np.max(np.abs(dp_shifts))) if dp_shifts.size else 0.0 - pad_dp_min = int(np.ceil(max_abs_dp)) + 2 - pad_dp = max(diffraction_padding, pad_dp_min) - - Hp = H + 2 * pad_dp - Wp = W + 2 * pad_dp - rp0 = pad_dp - cp0 = pad_dp - - # Diffraction taper window - alpha_hr = min(1.0, 2.0 * float(diffraction_edge_blend) / float(H)) if diffraction_edge_blend > 0 else 0.0 - alpha_hc = min(1.0, 2.0 * float(diffraction_edge_blend) / float(W)) if diffraction_edge_blend > 0 else 0.0 - w_dp = tukey(H, alpha=alpha_hr)[:, None] * tukey(W, alpha=alpha_hc)[None, :] + if diffraction_edge_blend and float(diffraction_edge_blend) > 0: + alpha_dr = min(1.0, 2.0 * float(diffraction_edge_blend) / float(H)) + alpha_dc = min(1.0, 2.0 * float(diffraction_edge_blend) / float(W)) + w_dp = tukey(H, alpha=alpha_dr)[:, None] * tukey(W, alpha=alpha_dc)[None, :] + else: + w_dp = np.ones((H, W), dtype=float) w_dp = w_dp.astype(float, copy=False) - # Padded diffraction window (unshifted) - w_dp_pad = np.zeros((Hp, Wp)) - w_dp_pad[rp0 : rp0 + H, cp0 : cp0 + W] = w_dp + dp_means = [np.mean(a, axis=(0, 1), dtype=np.float64) for a in arrays] + v = np.stack(dp_means, axis=0).reshape(-1) - # Pad value in diffraction space (computed from dp_means for speed) if isinstance(diffraction_pad_val, str): s = diffraction_pad_val.strip().lower() - dp_means = [np.mean(a, axis=(0, 1)) for a in arrays] - v = np.stack(dp_means, axis=0).reshape(-1) if s == "min": pad_val_dp = float(np.min(v)) elif s == "max": @@ -494,47 +495,44 @@ def merge_datasets( else: pad_val_dp = float(diffraction_pad_val) - # Precompute diffraction window shifts per dataset + wdp_pad = np.zeros((Hp, Wp), dtype=float) + wdp_pad[rp0 : rp0 + H, cp0 : cp0 + W] = w_dp + + wdp_shifted = np.zeros((n, Hp, Wp), dtype=float) if method == "fourier": kr = np.fft.fftfreq(Hp)[:, None] kc = np.fft.fftfreq(Wp)[None, :] - ramps = [ - np.exp(-2j * np.pi * (kr * float(dp_shifts[i, 0]) + kc * float(dp_shifts[i, 1]))) - for i in range(n) - ] - wdp_shifted = np.empty((n, Hp, Wp)) - Fw0 = np.fft.fft2(w_dp_pad) + ramps = [] + Fw = np.fft.fft2(wdp_pad) for i in range(n): - wtmp = np.fft.ifft2(Fw0 * ramps[i]).real - wtmp = np.clip(wtmp, 0.0, 1.0) - wdp_shifted[i] = wtmp + dr, dc = float(dp_shifts[i, 0]), float(dp_shifts[i, 1]) + ramp = np.exp(-2j * np.pi * (kr * dr + kc * dc)) + ramps.append(ramp) + w_i = np.fft.ifft2(Fw * ramp).real + wdp_shifted[i] = np.clip(w_i, 0.0, 1.0) else: - wdp_shifted = np.empty((n, Hp, Wp)) for i in range(n): - wtmp = ndi_shift( - w_dp_pad, + w_i = ndi_shift( + wdp_pad, shift=(float(dp_shifts[i, 0]), float(dp_shifts[i, 1])), order=1, mode="constant", cval=0.0, prefilter=False, ) - wtmp = np.clip(wtmp, 0.0, 1.0) - wdp_shifted[i] = wtmp + wdp_shifted[i] = np.clip(w_i, 0.0, 1.0) + ramps = None - # Edge blend weight for pad value (diffraction space) - edge_w_dp = 1.0 - np.clip(np.max(wdp_shifted, axis=0), 0.0, 1.0) + edge_w_dp = 1.0 - np.max(wdp_shifted, axis=0) + edge_w_dp = np.clip(edge_w_dp, 0.0, 1.0) - Rout = Rs + 2 * real_space_padding - Cout = Cs + 2 * real_space_padding - merged = np.zeros((Rout, Cout, Hp, Wp)) + merged = np.zeros((Rout, Cout, Hp, Wp), dtype=np.float64) - dp_local = np.empty((H, W)) - dp_pad = np.zeros((Hp, Wp)) - dp_shifted_tmp = np.empty((Hp, Wp)) - num_tmp = np.zeros((Hp, Wp)) - den_tmp = np.zeros((Hp, Wp)) - out_tmp = np.empty((Hp, Wp)) + dp_local = np.zeros((H, W), dtype=np.float64) + dp_pad = np.zeros((Hp, Wp), dtype=np.float64) + dp_shifted_tmp = np.zeros((Hp, Wp), dtype=np.float64) + num_tmp = np.zeros((Hp, Wp), dtype=np.float64) + den_tmp = np.zeros((Hp, Wp), dtype=np.float64) for ro in tqdm(range(Rout), desc="Merging (rows)"): r_base = float(ro - real_space_padding) @@ -606,21 +604,69 @@ def merge_datasets( num = num_tmp + edge_w_dp * pad_val_dp den = den_tmp + edge_w_dp - np.divide(num, den, out=out_tmp, where=(den > 0.0)) - out_tmp[den <= 0.0] = pad_val_dp - merged[ro, co] = out_tmp + out = np.empty_like(num) + np.divide(num, den, out=out, where=den != 0.0) + out[den == 0.0] = 0.0 + merged[ro, co] = out + + self.im_bf_merged = np.mean(merged, axis=(2, 3), dtype=np.float64) + self.dp_mean_merged = np.mean(merged, axis=(0, 1), dtype=np.float64) + + if np.issubdtype(dtype_out, np.integer): + info = np.iinfo(dtype_out) + dmin = float(info.min) + dmax = float(info.max) + + merged_f = merged # float64 + + if scale_output: + peak = float(np.max(merged_f)) + if peak <= 0.0: + scale = 1.0 + merged_scaled = merged_f + else: + scale = dmax / peak + merged_scaled = merged_f * scale + + if np.issubdtype(dtype_out, np.unsignedinteger): + if float(np.min(merged_scaled)) < 0.0: + warnings.warn( + f"scale_output=True with unsigned dtype {dtype_out}: " + "negative values present; they will be clipped to 0.", + RuntimeWarning, + ) + lo, hi = 0.0, dmax + else: + lo, hi = dmin, dmax + + if float(np.min(merged_scaled)) < lo or float(np.max(merged_scaled)) > hi: + warnings.warn( + f"Output overflow for dtype {dtype_out} after scaling: " + f"data range [{float(np.min(merged_scaled))}, {float(np.max(merged_scaled))}] exceeds " + f"[{lo}, {hi}]. Values will be clipped.", + RuntimeWarning, + ) + + merged_out = np.rint(np.clip(merged_scaled, lo, hi)).astype(dtype_out) + + else: + below = float(np.min(merged_f)) + above = float(np.max(merged_f)) + if below < dmin or above > dmax: + warnings.warn( + f"Output overflow for dtype {dtype_out}: data range [{below}, {above}] exceeds " + f"[{dmin}, {dmax}]. Values will be clipped.", + RuntimeWarning, + ) + merged_out = np.rint(np.clip(merged_f, dmin, dmax)).astype(dtype_out) + else: + merged_out = merged.astype(dtype_out, copy=False) - dataset_merged = Dataset4dstem.from_array(merged) - self.im_bf_merged = np.mean(merged, axis=(2, 3)) - self.dp_mean_merged = np.mean(merged, axis=(0, 1)) + dataset_merged = Dataset4dstem.from_array(array=merged_out) dataset_merged.im_bf_merged = self.im_bf_merged dataset_merged.dp_mean_merged = self.dp_mean_merged - dataset_merged.metadata["im_bf_merged"] = self.im_bf_merged - dataset_merged.metadata["dp_mean_merged"] = self.dp_mean_merged - dataset_merged.metadata["real_space_shifts_rc"] = rs_shifts.copy() - dataset_merged.metadata["diffraction_shifts_rc"] = dp_shifts.copy() if plot_result: show_2d( From 3196aca903ca46bc427706394cd9afc8a66d3a5e Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 1 Feb 2026 17:40:36 -0800 Subject: [PATCH 019/113] adding docstrings --- src/quantem/diffraction/maped.py | 452 +++++++++++++++++++------------ 1 file changed, 278 insertions(+), 174 deletions(-) diff --git a/src/quantem/diffraction/maped.py b/src/quantem/diffraction/maped.py index 9df542f31..3b5154c0f 100644 --- a/src/quantem/diffraction/maped.py +++ b/src/quantem/diffraction/maped.py @@ -1,28 +1,55 @@ from __future__ import annotations +import warnings from typing import Any, Sequence import numpy as np +from scipy.ndimage import gaussian_filter, shift as ndi_shift +from scipy.signal import convolve2d from scipy.signal.windows import tukey +from tqdm import tqdm from quantem.core.datastructures.dataset4dstem import Dataset4dstem from quantem.core.io.serialize import AutoSerialize -from quantem.core.visualization import show_2d from quantem.core.utils.imaging_utils import weighted_cross_correlation_shift +from quantem.core.visualization import show_2d class MAPED(AutoSerialize): + """ + Merge-Averaged Precession Electron Diffraction (MAPED) helper. + + This class manages a set of 4D-STEM datasets and provides utilities to: + - compute mean BF and mean DP summaries, + - choose/find diffraction origins, + - align diffraction space and real space, + - merge datasets into a single composite Dataset4dstem. + """ + _token = object() def __init__(self, datasets: list[Dataset4dstem], _token: object | None = None): if _token is not self._token: raise RuntimeError("Use MAPED.from_datasets() to instantiate this class.") - AutoSerialize.__init__(self) + super().__init__() self.datasets = datasets self.metadata: dict[str, Any] = {} @classmethod - def from_datasets(cls, datasets: Sequence[Dataset4dstem]) -> "MAPED": + def from_datasets(cls, datasets: Sequence[Dataset4dstem]) -> MAPED: + """ + Construct a MAPED instance from a non-empty sequence of Dataset4dstem. + + Parameters + ---------- + datasets + Sequence of Dataset4dstem instances. + + Returns + ------- + MAPED + New MAPED instance. + """ if not isinstance(datasets, Sequence) or isinstance(datasets, (str, bytes)): raise TypeError("MAPED.from_datasets expects a sequence of Dataset4dstem instances.") ds_list: list[Dataset4dstem] = [] @@ -30,7 +57,7 @@ def from_datasets(cls, datasets: Sequence[Dataset4dstem]) -> "MAPED": if not isinstance(d, Dataset4dstem): raise TypeError("MAPED.from_datasets expects a sequence of Dataset4dstem instances.") ds_list.append(d) - if len(ds_list) == 0: + if not ds_list: raise ValueError("MAPED.from_datasets expects a non-empty sequence of Dataset4dstem instances.") return cls(datasets=ds_list, _token=cls._token) @@ -39,7 +66,19 @@ def preprocess( plot_summary: bool = True, scale: float | Sequence[float] | None = None, **plot_kwargs: Any, - ) -> "MAPED": + ) -> MAPED: + """ + Compute dataset summary images. + + Stores + ------ + self.scales : np.ndarray + Per-dataset scaling factors (n,). + self.dp_mean : list[np.ndarray] + Mean diffraction patterns (H, W), one per dataset. + self.im_bf : list[np.ndarray] + Mean bright-field images (R, C), one per dataset. + """ n = len(self.datasets) if scale is None: self.scales = np.ones(n, dtype=float) @@ -52,8 +91,8 @@ def preprocess( if np.any(self.scales == 0): raise ValueError("scale entries must be nonzero.") - self.dp_mean = [] - self.im_bf = [] + self.dp_mean: list[np.ndarray] = [] + self.im_bf: list[np.ndarray] = [] for d in self.datasets: if hasattr(d, "get_dp_mean"): @@ -67,11 +106,13 @@ def preprocess( dp = getattr(d, "dp_mean", None) if dp is None: - dp_arr = np.mean(np.asarray(d.array), axis=(0, 1)) + arr = np.asarray(d.array) + dp_arr = np.mean(arr, axis=(0, 1)) else: dp_arr = np.asarray(dp.array if hasattr(dp, "array") else dp) - im_bf_arr = np.mean(np.asarray(d.array), axis=(2, 3)) + arr = np.asarray(d.array) + im_bf_arr = np.mean(arr, axis=(2, 3)) self.dp_mean.append(np.asarray(dp_arr)) self.im_bf.append(np.asarray(im_bf_arr)) @@ -79,22 +120,18 @@ def preprocess( if plot_summary: tiles = [[(self.im_bf[i] / self.scales[i]), self.dp_mean[i]] for i in range(n)] titles = [[f"{i} - Mean Bright Field", f"{i} - Mean Diffraction Pattern"] for i in range(n)] - show_2d( - tiles, - titles=titles, - **plot_kwargs, - ) + show_2d(tiles, title=titles, **plot_kwargs) return self - - def diffraction_find_origin( + def diffraction_origin( self, origins=None, sigma=None, plot_origins: bool = True, plot_indices=None, - ): + **plot_kwargs: Any, + ) -> MAPED: """ Choose or automatically find the origin in diffraction space. @@ -104,26 +141,20 @@ def diffraction_find_origin( Optional manual origins. Can be: - a single (row, col) tuple, applied to all datasets - a list of (row, col) tuples of length n (one per dataset) - - a list of (row, col) tuples shorter than n, used for plot/inspection only (will error if not broadcastable) sigma Optional low-pass smoothing sigma (pixels) applied to each mean DP prior to peak finding. plot_origins If True, plot mean diffraction patterns with overlaid origin markers. plot_indices Optional indices to plot. If None, plots all datasets. + **plot_kwargs + Passed to show_2d. Stores ------ self.diffraction_origins : np.ndarray Array of shape (n, 2) with integer (row, col) origins. """ - import numpy as _np - - try: - from scipy.ndimage import gaussian_filter as _gaussian_filter - except Exception: # pragma: no cover - _gaussian_filter = None - n = len(self.datasets) if not hasattr(self, "dp_mean"): raise RuntimeError("Run preprocess() first so self.dp_mean exists.") @@ -137,119 +168,133 @@ def diffraction_find_origin( raise IndexError("plot_indices contains an out-of-range index.") if origins is None: - origins_arr = _np.zeros((n, 2), dtype=int) + origins_arr = np.zeros((n, 2), dtype=int) for i in range(n): - dp = _np.asarray(self.dp_mean[i]) + dp = np.asarray(self.dp_mean[i]) if sigma is not None and float(sigma) > 0: - if _gaussian_filter is None: - raise ImportError("scipy is required for sigma smoothing (gaussian_filter).") - dp_use = _gaussian_filter(dp.astype(float, copy=False), float(sigma)) + dp_use = gaussian_filter(dp.astype(float, copy=False), float(sigma), mode="nearest") else: dp_use = dp - ind = int(_np.argmax(dp_use)) - r, c = _np.unravel_index(ind, dp_use.shape) + r, c = np.unravel_index(int(np.argmax(dp_use)), dp_use.shape) origins_arr[i, 0] = int(r) origins_arr[i, 1] = int(c) else: if isinstance(origins, tuple) and len(origins) == 2: - origins_arr = _np.tile(_np.asarray(origins, dtype=int)[None, :], (n, 1)) + origins_arr = np.tile(np.asarray(origins, dtype=int)[None, :], (n, 1)) else: origins_list = list(origins) if len(origins_list) != n: raise ValueError("origins must be a single (row,col) tuple or a list of length n.") - origins_arr = _np.asarray(origins_list, dtype=int) + origins_arr = np.asarray(origins_list, dtype=int) if origins_arr.shape != (n, 2): raise ValueError("origins must have shape (n, 2) after conversion.") self.diffraction_origins = origins_arr if plot_origins: - dp_tiles = [[_np.asarray(self.dp_mean[i]) for i in plot_indices_list]] - titles = [[f"{i} - Mean Diffraction Pattern" for i in plot_indices_list]] - fig, axs = show_2d(dp_tiles, titles=titles, returnfig=True, **{}) - if not isinstance(axs, (list, _np.ndarray)): - axs = [axs] - axs_flat = _np.ravel(axs) + arrays = [np.asarray(self.dp_mean[i]) for i in plot_indices_list] + titles = [f"{i} - Mean Diffraction Pattern" for i in plot_indices_list] + fig, ax = show_2d(arrays, title=titles, returnfig=True, **plot_kwargs) + axs = np.ravel(np.asarray(ax, dtype=object)) for j, i in enumerate(plot_indices_list): - ax = axs_flat[j] r, c = self.diffraction_origins[i] - ax.plot([c], [r], marker="+", color="red", markersize=16, markeredgewidth=2) - return fig, axs + axs[j].plot([c], [r], marker="+", color="red", markersize=16, markeredgewidth=2) return self - def diffraction_align( self, - edge_blend = 16.0, - padding = None, - pad_val = 'min', - upsample_factor = 100, - weight_scale = 1/8, - plot_aligned = True, - linewidth = 2, - **kwargs, - ): + edge_blend: float = 16.0, + padding=None, + pad_val: str | float = "min", + upsample_factor: int = 100, + weight_scale: float = 1 / 8, + plot_aligned: bool = True, + **plot_kwargs: Any, + ) -> MAPED: """ - Refine the diffraction space origins, set padding, align images + Align mean diffraction patterns using weighted cross-correlation in Fourier space. + Parameters + ---------- + edge_blend + Tukey window edge taper (pixels). + padding + Passed to shift_images for plotting. + pad_val + Passed to shift_images for plotting. + upsample_factor + Subpixel upsampling factor for correlation peak estimation. + weight_scale + Radial weight falloff scale (fraction of mean DP size). + plot_aligned + If True, plot aligned mean diffraction patterns. + **plot_kwargs + Passed to show_2d when plotting. + + Stores + ------ + self.diffraction_shifts : np.ndarray + Array of shape (n, 2) with (row, col) shifts to align diffraction patterns. """ + if not hasattr(self, "dp_mean"): + raise RuntimeError("Run preprocess() first so self.dp_mean exists.") + if not hasattr(self, "diffraction_origins"): + raise RuntimeError("Run diffraction_origin() first so self.diffraction_origins exists.") + + H, W = np.asarray(self.dp_mean[0]).shape + + w = tukey(H, alpha=2.0 * float(edge_blend) / float(H))[:, None] * tukey( + W, alpha=2.0 * float(edge_blend) / float(W) + )[None, :] - # window function - from scipy.signal.windows import tukey - w = tukey(self.dp_mean[0].shape[0], alpha=2.0*edge_blend/self.dp_mean[0].shape[0])[:,None] * \ - tukey(self.dp_mean[0].shape[1], alpha=2.0*edge_blend/self.dp_mean[0].shape[1])[None,:] + r = np.fft.fftfreq(H, 1.0 / float(H))[:, None] + c = np.fft.fftfreq(W, 1.0 / float(W))[None, :] - # coordinates - r = np.fft.fftfreq(self.dp_mean[0].shape[0],1/self.dp_mean[0].shape[0])[:,None] - c = np.fft.fftfreq(self.dp_mean[0].shape[1],1/self.dp_mean[0].shape[1])[None,:] + n = len(self.dp_mean) + self.diffraction_shifts = np.zeros((n, 2), dtype=float) - # init - self.diffraction_shifts = np.zeros((len(self.dp_mean),2)) + G_ref = np.fft.fft2(w * np.asarray(self.dp_mean[0])) + xy0 = np.asarray(self.diffraction_origins[0], dtype=float) - # correlation alignment - G_ref = np.fft.fft2(w * self.dp_mean[0]) - xy0 = self.diffraction_origins[0] - for ind in range(1,len(self.dp_mean)): - G = np.fft.fft2(w * self.dp_mean[ind]) - xy = self.diffraction_origins[ind] + for ind in range(1, n): + G = np.fft.fft2(w * np.asarray(self.dp_mean[ind])) + xy = np.asarray(self.diffraction_origins[ind], dtype=float) - dr2 = (r - xy0[0] + xy[0])**2 \ - + (c - xy0[1] + xy[1])**2 - im_weight = np.clip(1 - np.sqrt(dr2)/np.mean(self.dp_mean[0].shape)/weight_scale, 0.0, 1.0) - im_weight = np.sin(im_weight*np.pi/2)**2 + dr2 = (r - xy0[0] + xy[0]) ** 2 + (c - xy0[1] + xy[1]) ** 2 + im_weight = np.clip( + 1.0 - np.sqrt(dr2) / float(np.mean((H, W))) / float(weight_scale), + 0.0, + 1.0, + ) + im_weight = np.sin(im_weight * np.pi / 2.0) ** 2 - shift, G_shift = weighted_cross_correlation_shift( + shift_rc, G_shift = weighted_cross_correlation_shift( im_ref=G_ref, im=G, - weight_real=im_weight*0+1.0, - upsample_factor = upsample_factor, - fft_input = True, - fft_output = True, - return_shifted_image = True, + weight_real=im_weight * 0.0 + 1.0, + upsample_factor=int(upsample_factor), + fft_input=True, + fft_output=True, + return_shifted_image=True, ) - self.diffraction_shifts[ind,:] = shift - - # update reference - G_ref = G_ref*(ind/(ind+1)) + G_shift/(ind+1) + self.diffraction_shifts[ind, :] = np.asarray(shift_rc, dtype=float) - # Center shifts - self.diffraction_shifts -= np.mean(self.diffraction_shifts,axis=0)[None,:] + G_ref = G_ref * (ind / (ind + 1)) + G_shift / (ind + 1) - # Generate output image + self.diffraction_shifts -= np.mean(self.diffraction_shifts, axis=0)[None, :] if plot_aligned: im_aligned = shift_images( - images = self.dp_mean, - shifts_rc = self.diffraction_shifts, - edge_blend = edge_blend, - padding = padding, - pad_val = pad_val, - ) - show_2d( - im_aligned, - **kwargs, + images=self.dp_mean, + shifts_rc=self.diffraction_shifts, + edge_blend=float(edge_blend), + padding=padding, + pad_val=pad_val, ) + show_2d(im_aligned, **plot_kwargs) + + return self def real_space_align( @@ -266,16 +311,45 @@ def real_space_align( edge_sigma: float = 2.0, hanning_filter: bool = False, plot_aligned: bool = True, - **kwargs, - ): - import numpy as np - from scipy.ndimage import gaussian_filter, shift as ndi_shift - from scipy.signal import convolve2d - from scipy.signal.windows import tukey + **plot_kwargs: Any, + ) -> MAPED: + """ + Align real-space mean BF images using iterative average-reference correlation. - from quantem.core.utils.imaging_utils import weighted_cross_correlation_shift - from quantem.core.visualization import show_2d + Parameters + ---------- + num_images + If provided, align only the first num_images images. + num_iter + Number of refinement iterations. + edge_blend + Used to set default correlation padding when max_shift is None. + padding + Passed to shift_images for plotting. + pad_val + Passed to shift_images for plotting. + upsample_factor + Subpixel upsampling factor for correlation peak estimation. + max_shift + Optional maximum shift constraint passed to weighted_cross_correlation_shift. + shift_method + Passed to shift_images for plotting ('bilinear' or 'fourier'). + edge_filter + If True, correlate on gradient magnitude instead of raw intensity. + edge_sigma + Gaussian sigma applied to gradients when edge_filter is True. + hanning_filter + If True, apply a Hanning window prior to FFT. + plot_aligned + If True, plot aligned mean BF images. + **plot_kwargs + Passed to show_2d when plotting. + Stores + ------ + self.real_space_shifts : np.ndarray + Array of shape (n_total, 2) with (row, col) shifts for aligned datasets. + """ if not hasattr(self, "im_bf"): raise RuntimeError("Run preprocess() first so self.im_bf exists.") if len(self.im_bf) == 0: @@ -317,16 +391,13 @@ def real_space_align( if w_h_sum <= 0: raise RuntimeError("hanning window sum is zero") - wx = None if edge_filter: wx = np.array( - [ - [-1.0, -2.0, -1.0], - [ 0.0, 0.0, 0.0], - [ 1.0, 2.0, 1.0], - ], + [[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]], dtype=float, ) + else: + wx = None base_pad = np.zeros((n, Hp, Wp), dtype=float) for i in range(n): @@ -351,7 +422,7 @@ def real_space_align( for i in range(n): im_a = ndi_shift( base_pad[i], - shift=(float(shifts[i, 0]), float(shifts[i, 1])), + shift=(shifts[i, 0], shifts[i, 1]), order=1, mode="constant", cval=0.0, @@ -391,13 +462,12 @@ def real_space_align( edge_blend=float(edge_blend), padding=padding, pad_val=pad_val, - shift_method=str(shift_method), + shift_method=shift_method, ) - show_2d(im_aligned, **kwargs) + show_2d(im_aligned, **plot_kwargs) return self - def merge_datasets( self, real_space_padding=0, @@ -409,15 +479,46 @@ def merge_datasets( dtype=None, scale_output: bool = False, plot_result: bool = True, - **kwargs, - ): - import warnings + **plot_kwargs: Any, + ) -> Dataset4dstem: + """ + Merge aligned datasets into a single Dataset4dstem. - import numpy as np - from scipy.ndimage import shift as ndi_shift - from scipy.signal.windows import tukey - from tqdm import tqdm + Requires + -------- + self.real_space_shifts + From real_space_align(). + self.diffraction_shifts + From diffraction_align(). + Parameters + ---------- + real_space_padding + Output scan padding in pixels (adds border to scan grid). + real_space_edge_blend + Tukey taper width for scan-space interpolation weights. + diffraction_padding + Output diffraction padding in pixels (adds border around DPs). + diffraction_edge_blend + Tukey taper width for diffraction-space weights. + diffraction_pad_val + Pad value for diffraction padding ('min','max','mean','median' or float). + shift_method + Diffraction shift method: 'bilinear' or 'fourier'. + dtype + Output dtype. If None, uses parent dtype. + scale_output + If True and dtype is integer, scale to full dynamic range using global max. + plot_result + If True, plot merged BF and merged mean DP. + **plot_kwargs + Passed to show_2d. + + Returns + ------- + Dataset4dstem + Merged dataset. + """ if not hasattr(self, "real_space_shifts"): raise RuntimeError("Run real_space_align() first so self.real_space_shifts exists.") if not hasattr(self, "diffraction_shifts"): @@ -502,10 +603,10 @@ def merge_datasets( if method == "fourier": kr = np.fft.fftfreq(Hp)[:, None] kc = np.fft.fftfreq(Wp)[None, :] - ramps = [] Fw = np.fft.fft2(wdp_pad) + ramps: list[np.ndarray] = [] for i in range(n): - dr, dc = float(dp_shifts[i, 0]), float(dp_shifts[i, 1]) + dr, dc = dp_shifts[i, 0], dp_shifts[i, 1] ramp = np.exp(-2j * np.pi * (kr * dr + kc * dc)) ramps.append(ramp) w_i = np.fft.ifft2(Fw * ramp).real @@ -514,17 +615,17 @@ def merge_datasets( for i in range(n): w_i = ndi_shift( wdp_pad, - shift=(float(dp_shifts[i, 0]), float(dp_shifts[i, 1])), + shift=(dp_shifts[i, 0], dp_shifts[i, 1]), order=1, mode="constant", cval=0.0, prefilter=False, ) wdp_shifted[i] = np.clip(w_i, 0.0, 1.0) - ramps = None + ramps = [] - edge_w_dp = 1.0 - np.max(wdp_shifted, axis=0) - edge_w_dp = np.clip(edge_w_dp, 0.0, 1.0) + coverage = np.clip(np.sum(wdp_shifted, axis=0), 0.0, 1.0) + edge_w_dp = 1.0 - coverage merged = np.zeros((Rout, Cout, Hp, Wp), dtype=np.float64) @@ -535,25 +636,25 @@ def merge_datasets( den_tmp = np.zeros((Hp, Wp), dtype=np.float64) for ro in tqdm(range(Rout), desc="Merging (rows)"): - r_base = float(ro - real_space_padding) + r_base = ro - real_space_padding for co in range(Cout): - c_base = float(co - real_space_padding) + c_base = co - real_space_padding num_tmp.fill(0.0) den_tmp.fill(0.0) max_wi = 0.0 for i in range(n): - r_in = r_base - float(rs_shifts[i, 0]) - c_in = c_base - float(rs_shifts[i, 1]) + r_in = r_base - rs_shifts[i, 0] + c_in = c_base - rs_shifts[i, 1] r0 = int(np.floor(r_in)) c0 = int(np.floor(c_in)) if r0 < 0 or r0 >= Rs - 1 or c0 < 0 or c0 >= Cs - 1: continue - dr = float(r_in - r0) - dc = float(c_in - c0) + dr = r_in - r0 + dc = c_in - c0 w00 = (1.0 - dr) * (1.0 - dc) w10 = dr * (1.0 - dc) @@ -583,11 +684,12 @@ def merge_datasets( dp_pad[rp0 : rp0 + H, cp0 : cp0 + W] = dp_local * w_dp if method == "fourier": - dp_shifted_tmp[:] = np.fft.ifft2(np.fft.fft2(dp_pad) * ramps[i]).real + ramp = ramps[i] + dp_shifted_tmp[:] = np.fft.ifft2(np.fft.fft2(dp_pad) * ramp).real else: dp_shifted_tmp[:] = ndi_shift( dp_pad, - shift=(float(dp_shifts[i, 0]), float(dp_shifts[i, 1])), + shift=(dp_shifts[i, 0], dp_shifts[i, 1]), order=1, mode="constant", cval=0.0, @@ -617,38 +719,21 @@ def merge_datasets( dmin = float(info.min) dmax = float(info.max) - merged_f = merged # float64 + merged_f = merged if scale_output: peak = float(np.max(merged_f)) if peak <= 0.0: - scale = 1.0 merged_scaled = merged_f else: - scale = dmax / peak - merged_scaled = merged_f * scale + merged_scaled = merged_f * (dmax / peak) if np.issubdtype(dtype_out, np.unsignedinteger): - if float(np.min(merged_scaled)) < 0.0: - warnings.warn( - f"scale_output=True with unsigned dtype {dtype_out}: " - "negative values present; they will be clipped to 0.", - RuntimeWarning, - ) lo, hi = 0.0, dmax else: lo, hi = dmin, dmax - if float(np.min(merged_scaled)) < lo or float(np.max(merged_scaled)) > hi: - warnings.warn( - f"Output overflow for dtype {dtype_out} after scaling: " - f"data range [{float(np.min(merged_scaled))}, {float(np.max(merged_scaled))}] exceeds " - f"[{lo}, {hi}]. Values will be clipped.", - RuntimeWarning, - ) - merged_out = np.rint(np.clip(merged_scaled, lo, hi)).astype(dtype_out) - else: below = float(np.min(merged_f)) above = float(np.max(merged_f)) @@ -662,17 +747,15 @@ def merge_datasets( else: merged_out = merged.astype(dtype_out, copy=False) - dataset_merged = Dataset4dstem.from_array(array=merged_out) - dataset_merged.im_bf_merged = self.im_bf_merged dataset_merged.dp_mean_merged = self.dp_mean_merged if plot_result: show_2d( [[self.im_bf_merged, self.dp_mean_merged]], - titles=[["Merged Bright Field", "Merged Mean Diffraction Pattern"]], - **kwargs, + title=[["Merged Bright Field", "Merged Mean Diffraction Pattern"]], + **plot_kwargs, ) return dataset_merged @@ -683,13 +766,32 @@ def shift_images( shifts_rc, edge_blend: float = 8.0, padding=None, - pad_val=0.0, + pad_val: str | float = 0.0, shift_method: str = "bilinear", ): - import numpy as np - from scipy.ndimage import shift as ndi_shift - from scipy.signal.windows import tukey - + """ + Shift and blend a stack of 2D images into a common padded canvas. + + Parameters + ---------- + images + Sequence of (H, W) arrays. + shifts_rc + Array-like of shape (n, 2) with (row, col) shifts for each image. + edge_blend + Tukey taper width in pixels for image blending. + padding + Output padding. If None, set from max shift and edge_blend. + pad_val + Fill value outside support ('min','max','mean','median' or float). + shift_method + 'bilinear' or 'fourier'. + + Returns + ------- + np.ndarray + Blended image of shape (H + 2*padding, W + 2*padding). + """ images = [np.asarray(im, dtype=float) for im in images] if len(images) == 0: raise ValueError("images must be non-empty") @@ -707,17 +809,17 @@ def shift_images( s = pad_val.strip().lower() v = np.stack(images, axis=0).reshape(-1) if s == "min": - pad_val = float(np.min(v)) + pad_val_f = float(np.min(v)) elif s == "max": - pad_val = float(np.max(v)) + pad_val_f = float(np.max(v)) elif s == "mean": - pad_val = float(np.mean(v)) + pad_val_f = float(np.mean(v)) elif s == "median": - pad_val = float(np.median(v)) + pad_val_f = float(np.median(v)) else: raise ValueError("pad_val must be a float or one of {'min','max','mean','median'}") else: - pad_val = float(pad_val) + pad_val_f = float(pad_val) if padding is None: max_shift = float(np.max(np.abs(shifts_rc))) if shifts_rc.size else 0.0 @@ -749,7 +851,7 @@ def shift_images( kr = np.fft.fftfreq(Hp)[:, None] kc = np.fft.fftfreq(Wp)[None, :] for ind in range(len(images)): - dr, dc = float(shifts_rc[ind, 0]), float(shifts_rc[ind, 1]) + dr, dc = shifts_rc[ind, 0], shifts_rc[ind, 1] ramp = np.exp(-2j * np.pi * (kr * dr + kc * dc)) F = np.fft.fft2(stack[ind]) @@ -762,7 +864,7 @@ def shift_images( for ind in range(len(images)): stack[ind] = ndi_shift( stack[ind], - shift=(float(shifts_rc[ind, 0]), float(shifts_rc[ind, 1])), + shift=(shifts_rc[ind, 0], shifts_rc[ind, 1]), order=1, mode="constant", cval=0.0, @@ -770,7 +872,7 @@ def shift_images( ) stack_w[ind] = ndi_shift( stack_w[ind], - shift=(float(shifts_rc[ind, 0]), float(shifts_rc[ind, 1])), + shift=(shifts_rc[ind, 0], shifts_rc[ind, 1]), order=1, mode="constant", cval=0.0, @@ -778,11 +880,13 @@ def shift_images( ) stack_w[ind] = np.clip(stack_w[ind], 0.0, 1.0) - # edge_w = 1.0 - np.clip(np.max(stack_w, axis=0), 0.0, 1.0) - edge_w = len(images) - np.sum(stack_w, axis=0) + edge_w = np.clip(1.0 - np.sum(stack_w, axis=0), 0.0, 1.0) - num = np.sum(stack, axis=0) + edge_w * pad_val + num = np.sum(stack, axis=0) + edge_w * pad_val_f den = np.sum(stack_w, axis=0) + edge_w - out = num / den + + out = np.empty_like(num) + np.divide(num, den, out=out, where=den != 0.0) + out[den == 0.0] = 0.0 return out From bf4b2b87f00ac847df94903252bd3187b31e7691 Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 16 Feb 2026 11:42:05 -0800 Subject: [PATCH 020/113] initial commit for model based refinment --- src/quantem/core/models/__init__.py | 29 +++ src/quantem/core/models/background.py | 78 ++++++ src/quantem/core/models/base.py | 241 +++++++++++++++++++ src/quantem/core/models/diffraction.py | 294 +++++++++++++++++++++++ src/quantem/diffraction/__init__.py | 1 + src/quantem/diffraction/model_fitting.py | 272 +++++++++++++++++++++ 6 files changed, 915 insertions(+) create mode 100644 src/quantem/core/models/__init__.py create mode 100644 src/quantem/core/models/background.py create mode 100644 src/quantem/core/models/base.py create mode 100644 src/quantem/core/models/diffraction.py create mode 100644 src/quantem/diffraction/model_fitting.py diff --git a/src/quantem/core/models/__init__.py b/src/quantem/core/models/__init__.py new file mode 100644 index 000000000..82d81d8e0 --- /dev/null +++ b/src/quantem/core/models/__init__.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from quantem.core.models.base import Component as Component +from quantem.core.models.base import Model as Model +from quantem.core.models.base import ModelContext as ModelContext +from quantem.core.models.base import OriginND as OriginND +from quantem.core.models.base import Overlay as Overlay +from quantem.core.models.base import Parameter as Parameter +from quantem.core.models.base import PreparedModel as PreparedModel + +from quantem.core.models.background import DCBackground as DCBackground +from quantem.core.models.background import GaussianBackground as GaussianBackground + +from quantem.core.models.diffraction import DiskTemplate as DiskTemplate +from quantem.core.models.diffraction import SyntheticDiskLattice as SyntheticDiskLattice + +__all__ = [ + "Parameter", + "Component", + "Overlay", + "OriginND", + "ModelContext", + "PreparedModel", + "Model", + "DCBackground", + "GaussianBackground", + "DiskTemplate", + "SyntheticDiskLattice", +] diff --git a/src/quantem/core/models/background.py b/src/quantem/core/models/background.py new file mode 100644 index 000000000..0f41f6a57 --- /dev/null +++ b/src/quantem/core/models/background.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +import torch + +from quantem.core.models.base import Component, ModelContext, Overlay, Parameter + +__all__ = ["DCBackground", "GaussianBackground"] + + +class DCBackground(Component): + def __init__( + self, + *, + intensity: float | tuple[float, float] | tuple[float, float, float | None], + name: str = "DCBackground", + ): + super().__init__(name=name) + self.p_intensity = Parameter(intensity, tags={"role": "background_dc", "name": name}) + + def prepare(self, ctx: ModelContext) -> Any: + pI = self.p_intensity + + @dataclass + class PreparedDCBackground: + name: str + + def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: + out += pI.value(x, device=ctx.device, dtype=ctx.dtype) + + def overlays(self) -> list[Overlay]: + return [] + + return PreparedDCBackground(name=self.name) + + +class GaussianBackground(Component): + def __init__( + self, + *, + sigma: float | tuple[float, float] | tuple[float, float, float | None], + intensity: float | tuple[float, float] | tuple[float, float, float | None], + origin_key: str = "origin", + name: str = "GaussianBackground", + ): + super().__init__(name=name) + self.origin_key = str(origin_key) + self.p_sigma = Parameter(sigma, tags={"role": "background_gaussian_sigma", "name": name}) + self.p_intensity = Parameter(intensity, tags={"role": "background_gaussian_intensity", "name": name}) + + def prepare(self, ctx: ModelContext) -> Any: + ok = self.origin_key + pS = self.p_sigma + pI = self.p_intensity + + rr = torch.arange(ctx.H, device=ctx.device, dtype=ctx.dtype)[:, None] + cc = torch.arange(ctx.W, device=ctx.device, dtype=ctx.dtype)[None, :] + + @dataclass + class PreparedGaussianBackground: + origin_key: str + + def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: + if self.origin_key not in ctx.origins: + return + r0, c0 = ctx.origins[self.origin_key] + sig = torch.clamp(pS.value(x, device=ctx.device, dtype=ctx.dtype), min=1e-6) + inten = pI.value(x, device=ctx.device, dtype=ctx.dtype) + dr = rr - r0 + dc = cc - c0 + out += inten * torch.exp(-0.5 * (dr * dr + dc * dc) / (sig * sig)) + + def overlays(self) -> list[Overlay]: + return [] + + return PreparedGaussianBackground(origin_key=ok) diff --git a/src/quantem/core/models/base.py b/src/quantem/core/models/base.py new file mode 100644 index 000000000..16c15840f --- /dev/null +++ b/src/quantem/core/models/base.py @@ -0,0 +1,241 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Iterable, Mapping, Sequence + +import numpy as np +import torch + +__all__ = [ + "Overlay", + "Parameter", + "ModelContext", + "Component", + "OriginND", + "Model", + "PreparedModel", +] + + +@dataclass(frozen=True) +class Overlay: + kind: str + data: dict[str, Any] + + +class Parameter: + def __init__( + self, + value: float | Sequence[float], + *, + lower_bound: float | None = None, + upper_bound: float | None = None, + tags: Mapping[str, Any] | None = None, + ): + v0, lb, ub = self._parse(value, lower_bound, upper_bound) + self.initial_value = float(v0) + self.lower_bound = float(lb) + self.upper_bound = float(ub) + self.tags: dict[str, Any] = dict(tags) if tags is not None else {} + self._index: int | None = None + + @staticmethod + def _parse( + value: float | Sequence[float], + lower_bound: float | None, + upper_bound: float | None, + ) -> tuple[float, float, float]: + if isinstance(value, (list, tuple, np.ndarray)): + seq = list(value) + if len(seq) == 2: + v0 = float(seq[0]) + dev = float(seq[1]) + return v0, v0 - dev, v0 + dev + if len(seq) == 3: + v0 = float(seq[0]) + lb = -np.inf if seq[1] is None else float(seq[1]) + ub = np.inf if seq[2] is None else float(seq[2]) + return v0, lb, ub + raise ValueError("Parameter sequences must have length 2 or 3.") + v0 = float(value) + lb = -np.inf if lower_bound is None else float(lower_bound) + ub = np.inf if upper_bound is None else float(upper_bound) + return v0, lb, ub + + def bind(self, index: int) -> None: + self._index = int(index) + + def value(self, x: torch.Tensor, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor: + if self._index is None: + return torch.as_tensor(self.initial_value, device=device, dtype=dtype).reshape(()) + return x[int(self._index)].to(device=device, dtype=dtype).reshape(()) + + +class ModelContext: + def __init__( + self, + *, + H: int, + W: int, + device: torch.device, + dtype: torch.dtype, + mask: torch.Tensor | None = None, + fields: Mapping[str, Any] | None = None, + ): + self.H = int(H) + self.W = int(W) + self.device = device + self.dtype = dtype + self.mask = mask + self.fields: dict[str, Any] = dict(fields) if fields is not None else {} + self.origins: dict[str, tuple[torch.Tensor, ...]] = {} + + def __getattr__(self, name: str) -> Any: + if name in self.fields: + return self.fields[name] + raise AttributeError(name) + + +class Component: + def __init__(self, *, name: str): + self.name = str(name) + + def parameters(self) -> list[Parameter]: + out: list[Parameter] = [] + + def add(obj: Any) -> None: + if obj is None: + return + if isinstance(obj, Parameter): + out.append(obj) + return + if isinstance(obj, (list, tuple)): + for z in obj: + add(z) + + for v in self.__dict__.values(): + add(v) + return out + + def prepare(self, ctx: ModelContext) -> Any: + raise NotImplementedError + + +class OriginND(Component): + def __init__(self, *, key: str, coords: Sequence[Parameter]): + super().__init__(name=f"Origin[{key}]") + self.key = str(key) + self.coords = list(coords) + + @classmethod + def from_row_col( + cls, + *, + key: str, + origin_row: float | Sequence[float], + origin_col: float | Sequence[float], + ) -> "OriginND": + p_row = Parameter(origin_row, tags={"role": "origin_row", "origin_key": str(key)}) + p_col = Parameter(origin_col, tags={"role": "origin_col", "origin_key": str(key)}) + return cls(key=key, coords=[p_row, p_col]) + + def prepare(self, ctx: ModelContext) -> Any: + key = self.key + p0 = self.coords[0] + p1 = self.coords[1] + + r0 = torch.as_tensor(p0.initial_value, device=ctx.device, dtype=ctx.dtype).reshape(()) + c0 = torch.as_tensor(p1.initial_value, device=ctx.device, dtype=ctx.dtype).reshape(()) + ctx.origins[key] = (r0, c0) + + class PreparedOriginND: + origin_key = key + + def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: + r = p0.value(x, device=ctx.device, dtype=ctx.dtype) + c = p1.value(x, device=ctx.device, dtype=ctx.dtype) + ctx.origins[key] = (r, c) + + def overlays(self) -> list[Overlay]: + r = float(p0.initial_value) + c = float(p1.initial_value) + return [ + Overlay( + kind="points_rc", + data={"r": np.array([r]), "c": np.array([c]), "marker": "x", "s": 80.0, "color": "tab:blue"}, + ) + ] + + return PreparedOriginND() + + +class Model: + def __init__(self) -> None: + self._components: list[Component] = [] + + def add(self, items: Iterable[Component]) -> "Model": + for obj in items: + if not isinstance(obj, Component): + raise TypeError("Model.add expects Component objects.") + self._components.append(obj) + return self + + def compile(self, ctx: ModelContext) -> "PreparedModel": + seen: set[int] = set() + params: list[Parameter] = [] + for c in self._components: + for p in c.parameters(): + if id(p) in seen: + continue + seen.add(id(p)) + params.append(p) + + for i, p in enumerate(params): + p.bind(i) + + x0 = torch.as_tensor([p.initial_value for p in params], device=ctx.device, dtype=ctx.dtype) + + prepared_components: list[Any] = [] + for c in self._components: + prepared_components.append(c.prepare(ctx)) + + return PreparedModel( + components=prepared_components, + ctx=ctx, + params=params, + x0=x0, + ) + + +class PreparedModel: + def __init__( + self, + *, + components: list[Any], + ctx: ModelContext, + params: list[Parameter], + x0: torch.Tensor, + ): + self.components = components + self.ctx = ctx + self.params = params + self.x0 = x0 + + def render(self, x: torch.Tensor) -> torch.Tensor: + out = torch.zeros((self.ctx.H, self.ctx.W), device=self.ctx.device, dtype=self.ctx.dtype) + for c in self.components: + if hasattr(c, "render"): + c.render(out, x, self.ctx) + return out + + def render_initial(self) -> torch.Tensor: + return self.render(self.x0) + + def overlays(self) -> list[Overlay]: + out: list[Overlay] = [] + for c in self.components: + if hasattr(c, "overlays"): + ov = c.overlays() + if ov: + out.extend(list(ov)) + return out diff --git a/src/quantem/core/models/diffraction.py b/src/quantem/core/models/diffraction.py new file mode 100644 index 000000000..ebf820bd1 --- /dev/null +++ b/src/quantem/core/models/diffraction.py @@ -0,0 +1,294 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Iterable + +import numpy as np +import torch + +from quantem.core.models.base import Component, ModelContext, Overlay, Parameter + +__all__ = ["DiskTemplate", "DiskAtOrigin", "SyntheticDiskLattice"] + + +def _as_tensor(x: Any, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor: + if torch.is_tensor(x): + return x.to(device=device, dtype=dtype) + return torch.as_tensor(x, device=device, dtype=dtype) + + +def _keep_center(r: float, c: float, H: int, W: int, boundary_px: float) -> bool: + b = float(boundary_px) + return (r >= b) and (r <= (H - 1) - b) and (c >= b) and (c <= (W - 1) - b) + + +def _place_patch_add(out: torch.Tensor, patch: torch.Tensor, center_r: float, center_c: float) -> None: + H, W = int(out.shape[-2]), int(out.shape[-1]) + ph, pw = int(patch.shape[-2]), int(patch.shape[-1]) + pr = ph // 2 + pc = pw // 2 + + r0 = int(np.rint(center_r)) - pr + c0 = int(np.rint(center_c)) - pc + r1 = r0 + ph + c1 = c0 + pw + + rr0 = max(0, r0) + cc0 = max(0, c0) + rr1 = min(H, r1) + cc1 = min(W, c1) + if rr1 <= rr0 or cc1 <= cc0: + return + + pr0 = rr0 - r0 + pc0 = cc0 - c0 + pr1 = pr0 + (rr1 - rr0) + pc1 = pc0 + (cc1 - cc0) + + out[rr0:rr1, cc0:cc1] += patch[pr0:pr1, pc0:pc1] + + +class DiskTemplate(Component): + def __init__( + self, + *, + name: str, + array: np.ndarray, + refine_all_pixels: bool = False, + place_at_origin: bool = False, + origin_key: str = "origin", + origin_intensity: float | tuple[float, float] | tuple[float, float, float | None] = 1.0, + ): + super().__init__(name=name) + arr = np.asarray(array, dtype=np.float32) + if arr.ndim != 2: + raise ValueError("DiskTemplate.array must be 2D.") + self.shape = (int(arr.shape[0]), int(arr.shape[1])) + self.refine_all_pixels = bool(refine_all_pixels) + self.place_at_origin = bool(place_at_origin) + self.origin_key = str(origin_key) + + self._array0 = arr + self.p_origin_intensity = Parameter(origin_intensity, tags={"role": "disk_origin_intensity", "name": name}) + + self.p_pixels: list[Parameter] = [] + if self.refine_all_pixels: + flat = arr.reshape(-1) + for i, v in enumerate(flat): + self.p_pixels.append(Parameter(float(v), tags={"role": "disk_pixel", "name": name, "pixel_index": int(i)})) + + @classmethod + def from_array( + cls, + *, + name: str, + array: np.ndarray, + refine_all_pixels: bool = False, + place_at_origin: bool = False, + origin_key: str = "origin", + origin_intensity: float | tuple[float, float] | tuple[float, float, float | None] = 1.0, + ) -> "DiskTemplate": + return cls( + name=name, + array=array, + refine_all_pixels=refine_all_pixels, + place_at_origin=place_at_origin, + origin_key=origin_key, + origin_intensity=origin_intensity, + ) + + def prepare(self, ctx: ModelContext) -> Any: + disk = self + ok = disk.origin_key + + if disk.refine_all_pixels: + idxs = [p._index for p in disk.p_pixels] + if any(i is None for i in idxs): + raise RuntimeError("DiskTemplate pixel parameters not bound.") + idx_t = torch.as_tensor([int(i) for i in idxs], device=ctx.device, dtype=torch.int64) + + def patch(x: torch.Tensor) -> torch.Tensor: + return x.index_select(0, idx_t).to(device=ctx.device, dtype=ctx.dtype).reshape(disk.shape[0], disk.shape[1]) + + else: + patch0 = _as_tensor(disk._array0, device=ctx.device, dtype=ctx.dtype) + + def patch(x: torch.Tensor) -> torch.Tensor: + return patch0 + + @dataclass + class PreparedDiskTemplate: + origin_key: str + place_at_origin: bool + + def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: + if not self.place_at_origin: + return + if self.origin_key not in ctx.origins: + return + r0, c0 = ctx.origins[self.origin_key] + r = float(r0.detach().cpu().item()) + c = float(c0.detach().cpu().item()) + inten = disk.p_origin_intensity.value(x, device=ctx.device, dtype=ctx.dtype) + _place_patch_add(out, patch(x) * inten, r, c) + + def overlays(self) -> list[Overlay]: + return [] + + return PreparedDiskTemplate(origin_key=ok, place_at_origin=disk.place_at_origin) + + +class SyntheticDiskLattice(Component): + def __init__( + self, + *, + name: str, + disk: DiskTemplate, + u_row: float | tuple[float, float] | tuple[float, float, float | None], + u_col: float | tuple[float, float] | tuple[float, float, float | None], + v_row: float | tuple[float, float] | tuple[float, float, float | None], + v_col: float | tuple[float, float] | tuple[float, float, float | None], + u_max: int = 0, + v_max: int = 0, + intensity_0: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + intensity_row: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + intensity_col: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + exclude_indices: Iterable[tuple[int, int]] | None = [], + boundary_px: float = 0.0, + origin_key: str = "origin", + ): + super().__init__(name=name) + self.disk = disk + self.origin_key = str(origin_key) + self.u_max = int(u_max) + self.v_max = int(v_max) + self.boundary_px = float(boundary_px) + + if exclude_indices is None: + self.exclude_indices = {(0, 0)} + else: + self.exclude_indices = {(int(a), int(b)) for (a, b) in exclude_indices} + + self.p_u_row = Parameter(u_row, tags={"role": "lattice_u_row", "name": name}) + self.p_u_col = Parameter(u_col, tags={"role": "lattice_u_col", "name": name}) + self.p_v_row = Parameter(v_row, tags={"role": "lattice_v_row", "name": name}) + self.p_v_col = Parameter(v_col, tags={"role": "lattice_v_col", "name": name}) + + self.p_intensity_0 = Parameter(intensity_0, tags={"role": "disk_intensity_0", "name": name}) + self.p_intensity_row = Parameter(intensity_row, tags={"role": "disk_intensity_row", "name": name}) + self.p_intensity_col = Parameter(intensity_col, tags={"role": "disk_intensity_col", "name": name}) + + def _uv_list(self) -> list[tuple[int, int]]: + uv: list[tuple[int, int]] = [] + for u in range(-self.u_max, self.u_max + 1): + for v in range(-self.v_max, self.v_max + 1): + if (u, v) in self.exclude_indices: + continue + uv.append((u, v)) + return uv + + def prepare(self, ctx: ModelContext) -> Any: + lat = self + ok = lat.origin_key + uv = lat._uv_list() + + if lat.disk.refine_all_pixels: + didxs = [p._index for p in lat.disk.p_pixels] + if any(i is None for i in didxs): + raise RuntimeError("DiskTemplate pixel parameters not bound.") + didx_t = torch.as_tensor([int(i) for i in didxs], device=ctx.device, dtype=torch.int64) + + def patch(x: torch.Tensor) -> torch.Tensor: + return x.index_select(0, didx_t).to(device=ctx.device, dtype=ctx.dtype).reshape(lat.disk.shape[0], lat.disk.shape[1]) + + else: + patch0 = _as_tensor(lat.disk._array0, device=ctx.device, dtype=ctx.dtype) + + def patch(x: torch.Tensor) -> torch.Tensor: + return patch0 + + r0_list: list[float] = [] + c0_list: list[float] = [] + if ok in ctx.origins: + or0, oc0 = ctx.origins[ok] + orow0 = float(or0.detach().cpu().item()) + ocol0 = float(oc0.detach().cpu().item()) + urow0 = float(lat.p_u_row.initial_value) + ucol0 = float(lat.p_u_col.initial_value) + vrow0 = float(lat.p_v_row.initial_value) + vcol0 = float(lat.p_v_col.initial_value) + for u, v in uv: + rr = orow0 + u * urow0 + v * vrow0 + cc = ocol0 + u * ucol0 + v * vcol0 + if _keep_center(rr, cc, ctx.H, ctx.W, lat.boundary_px): + r0_list.append(rr) + c0_list.append(cc) + + r0_np = np.asarray(r0_list, dtype=np.float32) + c0_np = np.asarray(c0_list, dtype=np.float32) + + @dataclass + class PreparedSyntheticDiskLattice: + origin_key: str + boundary_px: float + uv: list[tuple[int, int]] + r0: np.ndarray + c0: np.ndarray + + def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: + if self.origin_key not in ctx.origins: + return + if not self.uv: + return + + r_origin, c_origin = ctx.origins[self.origin_key] + r_origin = r_origin.reshape(()) + c_origin = c_origin.reshape(()) + + urow = lat.p_u_row.value(x, device=ctx.device, dtype=ctx.dtype) + ucol = lat.p_u_col.value(x, device=ctx.device, dtype=ctx.dtype) + vrow = lat.p_v_row.value(x, device=ctx.device, dtype=ctx.dtype) + vcol = lat.p_v_col.value(x, device=ctx.device, dtype=ctx.dtype) + + I0 = lat.p_intensity_0.value(x, device=ctx.device, dtype=ctx.dtype) + Ir = lat.p_intensity_row.value(x, device=ctx.device, dtype=ctx.dtype) + Ic = lat.p_intensity_col.value(x, device=ctx.device, dtype=ctx.dtype) + + uv_t = torch.as_tensor(self.uv, device=ctx.device, dtype=torch.int32) + uu = uv_t[:, 0].to(device=ctx.device, dtype=ctx.dtype) + vv = uv_t[:, 1].to(device=ctx.device, dtype=ctx.dtype) + + rr = r_origin + uu * urow + vv * vrow + cc = c_origin + uu * ucol + vv * vcol + + p = patch(x) + + H = int(ctx.H) + W = int(ctx.W) + for i in range(int(rr.numel())): + r = float(rr[i].detach().cpu().item()) + c = float(cc[i].detach().cpu().item()) + if not _keep_center(r, c, H, W, self.boundary_px): + continue + dr = rr[i] - r_origin + dc = cc[i] - c_origin + inten = torch.clamp(I0 + Ir * dr + Ic * dc, min=0.0) + _place_patch_add(out, p * inten, r, c) + + def overlays(self) -> list[Overlay]: + if self.r0.size == 0: + return [] + return [ + Overlay( + kind="points_rc", + data={"r": self.r0, "c": self.c0, "marker": "x", "s": 60.0, "color": "orange"}, + ) + ] + + return PreparedSyntheticDiskLattice( + origin_key=ok, + boundary_px=lat.boundary_px, + uv=uv, + r0=r0_np, + c0=c0_np, + ) diff --git a/src/quantem/diffraction/__init__.py b/src/quantem/diffraction/__init__.py index 2a79312b0..a64a357e8 100644 --- a/src/quantem/diffraction/__init__.py +++ b/src/quantem/diffraction/__init__.py @@ -1,3 +1,4 @@ from quantem.diffraction.polar import RDF as RDF from quantem.diffraction.strain_autocorrelation import StrainMapAutocorrelation as StrainMapAutocorrelation from quantem.diffraction.maped import MAPED as MAPED +from quantem.diffraction.model_fitting import ModelDiffraction as ModelDiffraction diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py new file mode 100644 index 000000000..84d9e5c8d --- /dev/null +++ b/src/quantem/diffraction/model_fitting.py @@ -0,0 +1,272 @@ +from __future__ import annotations + +from typing import Any + +import numpy as np +import torch +from scipy.ndimage import shift as ndi_shift +from scipy.signal.windows import tukey + +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.datastructures.dataset3d import Dataset3d +from quantem.core.datastructures.dataset4d import Dataset4d +from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from quantem.core.io.serialize import AutoSerialize +from quantem.core.models.base import Model, ModelContext, OriginND, Overlay, PreparedModel +from quantem.core.utils.imaging_utils import cross_correlation_shift +from quantem.core.visualization import show_2d + +__all__ = ["ModelDiffraction"] + + +def _to_numpy(x: Any) -> np.ndarray: + if isinstance(x, np.ndarray): + return x + if torch.is_tensor(x): + return x.detach().cpu().numpy() + return np.asarray(x) + + +class ModelDiffraction(AutoSerialize): + _token = object() + + def __init__(self, dataset: Any, _token: object | None = None): + if _token is not self._token: + raise RuntimeError("Use ModelDiffraction.from_dataset() or .from_file().") + super().__init__() + self.dataset = dataset + self.metadata: dict[str, Any] = {} + self.index_shape: tuple[int, ...] | None = None + self.image_ref: np.ndarray | None = None + self.preprocess_shifts: np.ndarray | None = None + self.model: Model | None = None + self.prepared: PreparedModel | None = None + self.fit_mask: np.ndarray | None = None + + @classmethod + def from_dataset(self, dataset: Dataset2d | Dataset3d | Dataset4d | Dataset4dstem | Any) -> "ModelDiffraction": + if isinstance(dataset, (Dataset2d, Dataset3d, Dataset4d, Dataset4dstem)): + return ModelDiffraction(dataset=dataset, _token=ModelDiffraction._token) + raise TypeError("from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance.") + + def preprocess( + self, + *, + align: bool = False, + edge_blend: float = 8.0, + upsample_factor: int = 32, + max_shift: float | None = None, + shift_order: int = 1, + ) -> "ModelDiffraction": + arr = np.asarray(self.dataset.array) + if arr.ndim < 2: + raise ValueError("dataset.array must have at least 2 dimensions.") + H, W = int(arr.shape[-2]), int(arr.shape[-1]) + self.index_shape = tuple(arr.shape[:-2]) + + stack = arr.reshape((-1, H, W)).astype(np.float32, copy=False) + n = int(stack.shape[0]) + + if (not align) or n <= 1: + self.image_ref = np.mean(stack, axis=0) + self.preprocess_shifts = None + self.metadata["preprocess"] = { + "align": bool(align), + "edge_blend": float(edge_blend), + "upsample_factor": int(upsample_factor), + "max_shift": None if max_shift is None else float(max_shift), + "shift_order": int(shift_order), + } + return self + + alpha_r = 0.0 if edge_blend <= 0 else min(1.0, 2.0 * float(edge_blend) / float(H)) + alpha_c = 0.0 if edge_blend <= 0 else min(1.0, 2.0 * float(edge_blend) / float(W)) + w = tukey(H, alpha=alpha_r)[:, None] * tukey(W, alpha=alpha_c)[None, :] + w = w.astype(np.float32, copy=False) + + shifts = np.zeros((n, 2), dtype=np.float32) + F_ref = np.fft.fft2(w * stack[0]) + + for i in range(1, n): + F_i = np.fft.fft2(w * stack[i]) + drc, F_shift = cross_correlation_shift( + F_ref, + F_i, + upsample_factor=int(upsample_factor), + max_shift=max_shift, + fft_input=True, + fft_output=True, + return_shifted_image=True, + ) + shifts[i, 0] = float(drc[0]) + shifts[i, 1] = float(drc[1]) + F_ref = F_ref * (i / (i + 1)) + F_shift / (i + 1) + + shifts -= np.mean(shifts, axis=0, keepdims=True) + + aligned = np.empty_like(stack, dtype=np.float32) + for i in range(n): + aligned[i] = ndi_shift( + stack[i], + shift=(float(shifts[i, 0]), float(shifts[i, 1])), + order=int(shift_order), + mode="nearest", + prefilter=False, + ) + + self.image_ref = np.mean(aligned, axis=0) + self.preprocess_shifts = shifts.reshape(self.index_shape + (2,)) + + self.metadata["preprocess"] = { + "align": bool(align), + "edge_blend": float(edge_blend), + "upsample_factor": int(upsample_factor), + "max_shift": None if max_shift is None else float(max_shift), + "shift_order": int(shift_order), + } + return self + + def _ensure_image_ref(self) -> None: + if self.image_ref is not None: + return + arr = np.asarray(self.dataset.array) + if arr.ndim < 2: + raise ValueError("dataset.array must have at least 2 dimensions.") + H, W = int(arr.shape[-2]), int(arr.shape[-1]) + self.index_shape = tuple(arr.shape[:-2]) + stack = arr.reshape((-1, H, W)).astype(np.float32, copy=False) + self.image_ref = np.mean(stack, axis=0) + self.preprocess_shifts = None + + def define_model( + self, + *, + origin_row: float | tuple[float, float] | tuple[float, float, float | None], + origin_col: float | tuple[float, float] | tuple[float, float, float | None], + components: list[Any], + device: torch.device | str | None = None, + dtype: torch.dtype | None = None, + mask: np.ndarray | torch.Tensor | None = None, + origin_key: str = "origin", + ) -> "ModelDiffraction": + self._ensure_image_ref() + if self.image_ref is None: + raise RuntimeError("image_ref not available.") + H, W = int(self.image_ref.shape[-2]), int(self.image_ref.shape[-1]) + + dev = torch.device(device) if device is not None else torch.device("cpu") + dt = dtype if dtype is not None else torch.float32 + + mask_t = None + if mask is not None: + if torch.is_tensor(mask): + mask_t = mask.to(device=dev, dtype=torch.float32) + else: + m = np.asarray(mask) + if m.shape != (H, W): + raise ValueError("mask must have shape (H, W).") + mask_t = torch.as_tensor(m.astype(np.float32, copy=False), device=dev) + + ctx = ModelContext(H=H, W=W, device=dev, dtype=dt, mask=mask_t) + + m = Model() + origin = OriginND.from_row_col(key=str(origin_key), origin_row=origin_row, origin_col=origin_col) + comps = [origin] + list(components) + m.add(comps) + + self.model = m + self.prepared = m.compile(ctx) + self.metadata["define_model"] = {"origin_key": str(origin_key), "device": str(dev), "dtype": str(dt)} + return self + + def _apply_overlays(self, ax: Any, overlays: list[Overlay]) -> None: + for ov in overlays: + d = dict(ov.data) + if ov.kind in {"points", "points_rc"} and ("r" in d) and ("c" in d): + r = np.asarray(d["r"]).ravel() + c = np.asarray(d["c"]).ravel() + s = float(d.get("s", 60.0)) + marker = d.get("marker", "x") + color = d.get("color", "orange") + ax.scatter(c, r, s=s, marker=marker, c=color) + continue + + def plot_mean_model( + self, + *, + power: float = 0.25, + returnfig: bool = False, + show_overlays: bool = True, + axsize: tuple[int, int] = (6, 6), + ) -> tuple[Any, Any] | None: + self._ensure_image_ref() + if self.image_ref is None: + raise RuntimeError("image_ref not available.") + if self.prepared is None: + raise RuntimeError("No model defined. Call .define_model(...) first.") + + ref = np.asarray(self.image_ref, dtype=np.float32) + init = _to_numpy(self.prepared.render_initial()).astype(np.float32, copy=False) + + refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) + initp = init if power == 1.0 else np.maximum(init, 0.0) ** float(power) + + vmin = float(np.min([refp.min(), initp.min()])) + vmax = float(np.max([refp.max(), initp.max()])) + + fig, ax = show_2d( + [refp, initp], + title=["image_ref", "model"], + cmap="gray", + cbar=False, + returnfig=True, + axsize=axsize, + norm="linear_minmax", + vmin=vmin, + vmax=vmax, + ) + + H, W = int(ref.shape[-2]), int(ref.shape[-1]) + + boundaries: list[float] = [] + for comp in getattr(self.prepared, "components", []): + b = getattr(comp, "boundary_px", None) + if b is not None: + boundaries.append(float(b)) + + inset = 0 + pad = 0 + if boundaries: + pos = [b for b in boundaries if b > 0.0] + neg = [b for b in boundaries if b < 0.0] + if pos: + inset = int(np.ceil(max(pos))) + if neg: + pad = int(np.ceil(-min(neg))) + + if isinstance(ax, np.ndarray): + axes = list(ax.ravel()) + elif isinstance(ax, (list, tuple)): + axes = list(ax) + else: + axes = [ax] + + x0 = (-pad + inset) + x1 = (W - 1) + pad - inset + y0 = (-pad + inset) + y1 = (H - 1) + pad - inset + + for a in axes[:2]: + a.set_xlim(x0, x1) + a.set_ylim(y1, y0) + + if show_overlays: + overlays = self.prepared.overlays() + if len(axes) >= 1: + self._apply_overlays(axes[0], overlays) + if len(axes) >= 2: + self._apply_overlays(axes[1], overlays) + + if returnfig: + return fig, ax + return None From 9bb0a56331ba95d0572f65061446e7fac1c90fe4 Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 16 Feb 2026 16:56:41 -0800 Subject: [PATCH 021/113] fitting model working! --- src/quantem/core/models/__init__.py | 18 +- src/quantem/core/models/background.py | 65 +-- src/quantem/core/models/base.py | 218 ++++------ src/quantem/core/models/diffraction.py | 489 +++++++++++++++-------- src/quantem/diffraction/model_fitting.py | 478 +++++++++++++++++----- 5 files changed, 830 insertions(+), 438 deletions(-) diff --git a/src/quantem/core/models/__init__.py b/src/quantem/core/models/__init__.py index 82d81d8e0..7330baff8 100644 --- a/src/quantem/core/models/__init__.py +++ b/src/quantem/core/models/__init__.py @@ -3,27 +3,27 @@ from quantem.core.models.base import Component as Component from quantem.core.models.base import Model as Model from quantem.core.models.base import ModelContext as ModelContext -from quantem.core.models.base import OriginND as OriginND from quantem.core.models.base import Overlay as Overlay from quantem.core.models.base import Parameter as Parameter from quantem.core.models.base import PreparedModel as PreparedModel -from quantem.core.models.background import DCBackground as DCBackground -from quantem.core.models.background import GaussianBackground as GaussianBackground - from quantem.core.models.diffraction import DiskTemplate as DiskTemplate +from quantem.core.models.diffraction import Origin2D as Origin2D from quantem.core.models.diffraction import SyntheticDiskLattice as SyntheticDiskLattice +from quantem.core.models.background import DCBackground as DCBackground +from quantem.core.models.background import GaussianBackground as GaussianBackground + __all__ = [ "Parameter", - "Component", "Overlay", - "OriginND", "ModelContext", - "PreparedModel", + "Component", "Model", - "DCBackground", - "GaussianBackground", + "PreparedModel", + "Origin2D", "DiskTemplate", "SyntheticDiskLattice", + "DCBackground", + "GaussianBackground", ] diff --git a/src/quantem/core/models/background.py b/src/quantem/core/models/background.py index 0f41f6a57..d0a9adff5 100644 --- a/src/quantem/core/models/background.py +++ b/src/quantem/core/models/background.py @@ -1,78 +1,79 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any +from typing import Any, Iterable import torch from quantem.core.models.base import Component, ModelContext, Overlay, Parameter - -__all__ = ["DCBackground", "GaussianBackground"] +from quantem.core.models.diffraction import _origin_indices class DCBackground(Component): def __init__( self, *, - intensity: float | tuple[float, float] | tuple[float, float, float | None], - name: str = "DCBackground", + intensity: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + name: str = "dc_background", ): super().__init__(name=name) - self.p_intensity = Parameter(intensity, tags={"role": "background_dc", "name": name}) + self.p_intensity = Parameter(intensity, lower_bound=0.0, tags={"role": "dc_bg"}) + + def parameters(self) -> list[Parameter]: + return [self.p_intensity] def prepare(self, ctx: ModelContext) -> Any: - pI = self.p_intensity + idx = self.p_intensity.index @dataclass - class PreparedDCBackground: - name: str - + class Prepared: def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - out += pI.value(x, device=ctx.device, dtype=ctx.dtype) + out.add_(x[idx]) - def overlays(self) -> list[Overlay]: + def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: return [] - return PreparedDCBackground(name=self.name) + return Prepared() class GaussianBackground(Component): def __init__( self, *, - sigma: float | tuple[float, float] | tuple[float, float, float | None], - intensity: float | tuple[float, float] | tuple[float, float, float | None], + sigma: float | tuple[float, float] | tuple[float, float, float | None] = (40.0, 5.0, None), + intensity: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, origin_key: str = "origin", - name: str = "GaussianBackground", + name: str = "gaussian_background", ): super().__init__(name=name) self.origin_key = str(origin_key) - self.p_sigma = Parameter(sigma, tags={"role": "background_gaussian_sigma", "name": name}) - self.p_intensity = Parameter(intensity, tags={"role": "background_gaussian_intensity", "name": name}) + self.p_sigma = Parameter(sigma, lower_bound=1e-6, upper_bound=None, tags={"role": "gauss_sigma"}) + self.p_intensity = Parameter(intensity, lower_bound=0.0, tags={"role": "gauss_int"}) + + def parameters(self) -> list[Parameter]: + return [self.p_sigma, self.p_intensity] def prepare(self, ctx: ModelContext) -> Any: - ok = self.origin_key - pS = self.p_sigma - pI = self.p_intensity + i_sig = self.p_sigma.index + i_int = self.p_intensity.index + r_idx, c_idx = _origin_indices(ctx, self.origin_key) rr = torch.arange(ctx.H, device=ctx.device, dtype=ctx.dtype)[:, None] cc = torch.arange(ctx.W, device=ctx.device, dtype=ctx.dtype)[None, :] @dataclass - class PreparedGaussianBackground: - origin_key: str - + class Prepared: def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - if self.origin_key not in ctx.origins: - return - r0, c0 = ctx.origins[self.origin_key] - sig = torch.clamp(pS.value(x, device=ctx.device, dtype=ctx.dtype), min=1e-6) - inten = pI.value(x, device=ctx.device, dtype=ctx.dtype) + sig = torch.clamp(x[i_sig], min=1e-6) + inten = x[i_int] + r0 = x[r_idx] + c0 = x[c_idx] dr = rr - r0 dc = cc - c0 - out += inten * torch.exp(-0.5 * (dr * dr + dc * dc) / (sig * sig)) + r2 = dr * dr + dc * dc + out.add_(inten * torch.exp(-0.5 * r2 / (sig * sig))) - def overlays(self) -> list[Overlay]: + def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: return [] - return PreparedGaussianBackground(origin_key=ok) + return Prepared() diff --git a/src/quantem/core/models/base.py b/src/quantem/core/models/base.py index 16c15840f..7d453c59e 100644 --- a/src/quantem/core/models/base.py +++ b/src/quantem/core/models/base.py @@ -6,16 +6,6 @@ import numpy as np import torch -__all__ = [ - "Overlay", - "Parameter", - "ModelContext", - "Component", - "OriginND", - "Model", - "PreparedModel", -] - @dataclass(frozen=True) class Overlay: @@ -32,43 +22,47 @@ def __init__( upper_bound: float | None = None, tags: Mapping[str, Any] | None = None, ): - v0, lb, ub = self._parse(value, lower_bound, upper_bound) - self.initial_value = float(v0) - self.lower_bound = float(lb) - self.upper_bound = float(ub) - self.tags: dict[str, Any] = dict(tags) if tags is not None else {} + self.tags: dict[str, Any] = {} if tags is None else dict(tags) + self.initial_value, self.lower_bound, self.upper_bound = self._parse_value( + value, lower_bound, upper_bound + ) self._index: int | None = None @staticmethod - def _parse( + def _parse_value( value: float | Sequence[float], lower_bound: float | None, upper_bound: float | None, - ) -> tuple[float, float, float]: + ) -> tuple[float, float | None, float | None]: if isinstance(value, (list, tuple, np.ndarray)): - seq = list(value) - if len(seq) == 2: - v0 = float(seq[0]) - dev = float(seq[1]) - return v0, v0 - dev, v0 + dev - if len(seq) == 3: - v0 = float(seq[0]) - lb = -np.inf if seq[1] is None else float(seq[1]) - ub = np.inf if seq[2] is None else float(seq[2]) + v = list(value) + if len(v) == 2: + v0 = float(v[0]) + dev = float(v[1]) + lb = v0 - dev if lower_bound is None else float(lower_bound) + ub = v0 + dev if upper_bound is None else float(upper_bound) + return v0, lb, ub + if len(v) == 3: + v0 = float(v[0]) + lb = None if v[1] is None else float(v[1]) + ub = None if v[2] is None else float(v[2]) + if lower_bound is not None: + lb = float(lower_bound) + if upper_bound is not None: + ub = float(upper_bound) return v0, lb, ub raise ValueError("Parameter sequences must have length 2 or 3.") v0 = float(value) - lb = -np.inf if lower_bound is None else float(lower_bound) - ub = np.inf if upper_bound is None else float(upper_bound) - return v0, lb, ub + return v0, lower_bound, upper_bound def bind(self, index: int) -> None: self._index = int(index) - def value(self, x: torch.Tensor, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor: + @property + def index(self) -> int: if self._index is None: - return torch.as_tensor(self.initial_value, device=device, dtype=dtype).reshape(()) - return x[int(self._index)].to(device=device, dtype=dtype).reshape(()) + raise RuntimeError("Parameter is not bound. Call Model.compile(...) first.") + return self._index class ModelContext: @@ -87,8 +81,7 @@ def __init__( self.device = device self.dtype = dtype self.mask = mask - self.fields: dict[str, Any] = dict(fields) if fields is not None else {} - self.origins: dict[str, tuple[torch.Tensor, ...]] = {} + self.fields: dict[str, Any] = {} if fields is None else dict(fields) def __getattr__(self, name: str) -> Any: if name in self.fields: @@ -101,76 +94,53 @@ def __init__(self, *, name: str): self.name = str(name) def parameters(self) -> list[Parameter]: - out: list[Parameter] = [] - - def add(obj: Any) -> None: - if obj is None: - return - if isinstance(obj, Parameter): - out.append(obj) - return - if isinstance(obj, (list, tuple)): - for z in obj: - add(z) - - for v in self.__dict__.values(): - add(v) - return out + return [] def prepare(self, ctx: ModelContext) -> Any: raise NotImplementedError -class OriginND(Component): - def __init__(self, *, key: str, coords: Sequence[Parameter]): - super().__init__(name=f"Origin[{key}]") - self.key = str(key) - self.coords = list(coords) - - @classmethod - def from_row_col( - cls, +class PreparedModel: + def __init__( + self, *, - key: str, - origin_row: float | Sequence[float], - origin_col: float | Sequence[float], - ) -> "OriginND": - p_row = Parameter(origin_row, tags={"role": "origin_row", "origin_key": str(key)}) - p_col = Parameter(origin_col, tags={"role": "origin_col", "origin_key": str(key)}) - return cls(key=key, coords=[p_row, p_col]) - - def prepare(self, ctx: ModelContext) -> Any: - key = self.key - p0 = self.coords[0] - p1 = self.coords[1] - - r0 = torch.as_tensor(p0.initial_value, device=ctx.device, dtype=ctx.dtype).reshape(()) - c0 = torch.as_tensor(p1.initial_value, device=ctx.device, dtype=ctx.dtype).reshape(()) - ctx.origins[key] = (r0, c0) - - class PreparedOriginND: - origin_key = key + components: list[Any], + ctx: ModelContext, + params: list[Parameter], + x0: torch.Tensor, + lb: torch.Tensor, + ub: torch.Tensor, + ): + self.components = components + self.ctx = ctx + self.params = params + self.x0 = x0 + self.lb = lb + self.ub = ub - def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - r = p0.value(x, device=ctx.device, dtype=ctx.dtype) - c = p1.value(x, device=ctx.device, dtype=ctx.dtype) - ctx.origins[key] = (r, c) + def render(self, x: torch.Tensor) -> torch.Tensor: + out = torch.zeros((self.ctx.H, self.ctx.W), device=self.ctx.device, dtype=self.ctx.dtype) + for c in self.components: + c.render(out, x, self.ctx) + return out - def overlays(self) -> list[Overlay]: - r = float(p0.initial_value) - c = float(p1.initial_value) - return [ - Overlay( - kind="points_rc", - data={"r": np.array([r]), "c": np.array([c]), "marker": "x", "s": 80.0, "color": "tab:blue"}, - ) - ] + def render_initial(self) -> torch.Tensor: + return self.render(self.x0) - return PreparedOriginND() + def overlays(self, x: torch.Tensor | None = None) -> list[Overlay]: + out: list[Overlay] = [] + for c in self.components: + fn = getattr(c, "overlays", None) + if fn is None: + continue + ov = fn(self.x0 if x is None else x, self.ctx) + if ov: + out.extend(list(ov)) + return out class Model: - def __init__(self) -> None: + def __init__(self): self._components: list[Component] = [] def add(self, items: Iterable[Component]) -> "Model": @@ -180,62 +150,30 @@ def add(self, items: Iterable[Component]) -> "Model": self._components.append(obj) return self - def compile(self, ctx: ModelContext) -> "PreparedModel": - seen: set[int] = set() + def parameter_list(self) -> list[Parameter]: params: list[Parameter] = [] for c in self._components: - for p in c.parameters(): - if id(p) in seen: - continue - seen.add(id(p)) - params.append(p) + params.extend(c.parameters()) + return params + def compile(self, ctx: ModelContext) -> PreparedModel: + params = self.parameter_list() for i, p in enumerate(params): p.bind(i) - x0 = torch.as_tensor([p.initial_value for p in params], device=ctx.device, dtype=ctx.dtype) + x0 = torch.zeros((len(params),), device=ctx.device, dtype=ctx.dtype) + lb = torch.full((len(params),), -torch.inf, device=ctx.device, dtype=ctx.dtype) + ub = torch.full((len(params),), torch.inf, device=ctx.device, dtype=ctx.dtype) + + for p in params: + x0[p.index] = torch.as_tensor(p.initial_value, device=ctx.device, dtype=ctx.dtype) + if p.lower_bound is not None: + lb[p.index] = torch.as_tensor(p.lower_bound, device=ctx.device, dtype=ctx.dtype) + if p.upper_bound is not None: + ub[p.index] = torch.as_tensor(p.upper_bound, device=ctx.device, dtype=ctx.dtype) prepared_components: list[Any] = [] for c in self._components: prepared_components.append(c.prepare(ctx)) - return PreparedModel( - components=prepared_components, - ctx=ctx, - params=params, - x0=x0, - ) - - -class PreparedModel: - def __init__( - self, - *, - components: list[Any], - ctx: ModelContext, - params: list[Parameter], - x0: torch.Tensor, - ): - self.components = components - self.ctx = ctx - self.params = params - self.x0 = x0 - - def render(self, x: torch.Tensor) -> torch.Tensor: - out = torch.zeros((self.ctx.H, self.ctx.W), device=self.ctx.device, dtype=self.ctx.dtype) - for c in self.components: - if hasattr(c, "render"): - c.render(out, x, self.ctx) - return out - - def render_initial(self) -> torch.Tensor: - return self.render(self.x0) - - def overlays(self) -> list[Overlay]: - out: list[Overlay] = [] - for c in self.components: - if hasattr(c, "overlays"): - ov = c.overlays() - if ov: - out.extend(list(ov)) - return out + return PreparedModel(components=prepared_components, ctx=ctx, params=params, x0=x0, lb=lb, ub=ub) diff --git a/src/quantem/core/models/diffraction.py b/src/quantem/core/models/diffraction.py index ebf820bd1..8ecaf729d 100644 --- a/src/quantem/core/models/diffraction.py +++ b/src/quantem/core/models/diffraction.py @@ -1,51 +1,115 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any, Iterable +from typing import Any, Iterable, Sequence import numpy as np import torch from quantem.core.models.base import Component, ModelContext, Overlay, Parameter -__all__ = ["DiskTemplate", "DiskAtOrigin", "SyntheticDiskLattice"] - -def _as_tensor(x: Any, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor: +def _as_t(x: Any, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor: if torch.is_tensor(x): return x.to(device=device, dtype=dtype) return torch.as_tensor(x, device=device, dtype=dtype) -def _keep_center(r: float, c: float, H: int, W: int, boundary_px: float) -> bool: - b = float(boundary_px) - return (r >= b) and (r <= (H - 1) - b) and (c >= b) and (c <= (W - 1) - b) +def _splat_patch( + out: torch.Tensor, + *, + r0: torch.Tensor, + c0: torch.Tensor, + patch_vals: torch.Tensor, + dr: torch.Tensor, + dc: torch.Tensor, + scale: torch.Tensor, +) -> None: + H = out.shape[0] + W = out.shape[1] + + r = r0 + dr + c = c0 + dc + + r_base = torch.floor(r) + c_base = torch.floor(c) + + fr = r - r_base + fc = c - c_base + r0i = r_base.to(torch.long) + c0i = c_base.to(torch.long) -def _place_patch_add(out: torch.Tensor, patch: torch.Tensor, center_r: float, center_c: float) -> None: - H, W = int(out.shape[-2]), int(out.shape[-1]) - ph, pw = int(patch.shape[-2]), int(patch.shape[-1]) - pr = ph // 2 - pc = pw // 2 + w00 = (1.0 - fr) * (1.0 - fc) + w01 = (1.0 - fr) * fc + w10 = fr * (1.0 - fc) + w11 = fr * fc - r0 = int(np.rint(center_r)) - pr - c0 = int(np.rint(center_c)) - pc - r1 = r0 + ph - c1 = c0 + pw + v = patch_vals * scale - rr0 = max(0, r0) - cc0 = max(0, c0) - rr1 = min(H, r1) - cc1 = min(W, c1) - if rr1 <= rr0 or cc1 <= cc0: - return + def put(rr: torch.Tensor, cc: torch.Tensor, ww: torch.Tensor) -> None: + m = (rr >= 0) & (rr < H) & (cc >= 0) & (cc < W) + if torch.any(m): + out.index_put_((rr[m], cc[m]), (v[m] * ww[m]), accumulate=True) - pr0 = rr0 - r0 - pc0 = cc0 - c0 - pr1 = pr0 + (rr1 - rr0) - pc1 = pc0 + (cc1 - cc0) + put(r0i, c0i, w00) + put(r0i, c0i + 1, w01) + put(r0i + 1, c0i, w10) + put(r0i + 1, c0i + 1, w11) - out[rr0:rr1, cc0:cc1] += patch[pr0:pr1, pc0:pc1] + +def _origin_indices(ctx: ModelContext, origin_key: str) -> tuple[int, int]: + origins: dict[str, Any] = ctx.fields.get("origins", {}) + o = origins.get(origin_key) + if o is None or "row_param" not in o or "col_param" not in o: + raise RuntimeError(f"Origin '{origin_key}' not defined. Origin2D must be included first.") + return int(o["row_param"].index), int(o["col_param"].index) + + +class Origin2D(Component): + def __init__( + self, + *, + name: str = "origin", + origin_key: str = "origin", + row: float | tuple[float, float] | tuple[float, float, float | None] = (0.0, 0.0, None), + col: float | tuple[float, float] | tuple[float, float, float | None] = (0.0, 0.0, None), + ): + super().__init__(name=name) + self.origin_key = str(origin_key) + self.p_row = Parameter(row, tags={"role": "origin_row", "origin_key": self.origin_key}) + self.p_col = Parameter(col, tags={"role": "origin_col", "origin_key": self.origin_key}) + + def parameters(self) -> list[Parameter]: + return [self.p_row, self.p_col] + + def prepare(self, ctx: ModelContext) -> Any: + origins = ctx.fields.setdefault("origins", {}) + origins[self.origin_key] = {"row_param": self.p_row, "col_param": self.p_col} + + i_r = self.p_row.index + i_c = self.p_col.index + + @dataclass + class Prepared: + def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: + return + + def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: + return [ + Overlay( + kind="points_rc", + data={ + "r": x[i_r].detach(), + "c": x[i_c].detach(), + "marker": "+", + "s": 100.0, + "color": "dodgerblue", + }, + ) + ] + + return Prepared() class DiskTemplate(Component): @@ -56,26 +120,49 @@ def __init__( array: np.ndarray, refine_all_pixels: bool = False, place_at_origin: bool = False, + normalize: str = "none", origin_key: str = "origin", - origin_intensity: float | tuple[float, float] | tuple[float, float, float | None] = 1.0, + intensity: float | tuple[float, float] | tuple[float, float, float | None] = 1.0, ): super().__init__(name=name) - arr = np.asarray(array, dtype=np.float32) - if arr.ndim != 2: + + a = np.asarray(array, dtype=np.float32) + if a.ndim != 2: raise ValueError("DiskTemplate.array must be 2D.") - self.shape = (int(arr.shape[0]), int(arr.shape[1])) + + if normalize == "max": + mx = float(np.max(a)) + if mx > 0: + a = a / mx + elif normalize == "mean": + m = float(np.mean(a)) + if m != 0: + a = a / m + elif normalize == "none": + pass + else: + raise ValueError("normalize must be one of: 'none', 'max', 'mean'.") + + self.array = a self.refine_all_pixels = bool(refine_all_pixels) self.place_at_origin = bool(place_at_origin) self.origin_key = str(origin_key) - self._array0 = arr - self.p_origin_intensity = Parameter(origin_intensity, tags={"role": "disk_origin_intensity", "name": name}) - self.p_pixels: list[Parameter] = [] if self.refine_all_pixels: - flat = arr.reshape(-1) + flat = self.array.ravel() for i, v in enumerate(flat): - self.p_pixels.append(Parameter(float(v), tags={"role": "disk_pixel", "name": name, "pixel_index": int(i)})) + self.p_pixels.append( + Parameter( + float(v), + lower_bound=0.0, + tags={"role": "disk_pixel", "disk": self.name, "i": int(i)}, + ) + ) + + self.p_intensity: Parameter | None = None + if self.place_at_origin: + self.p_intensity = Parameter(intensity, lower_bound=0.0, tags={"role": "disk_intensity", "disk": self.name}) @classmethod def from_array( @@ -85,57 +172,79 @@ def from_array( array: np.ndarray, refine_all_pixels: bool = False, place_at_origin: bool = False, + normalize: str = "none", origin_key: str = "origin", - origin_intensity: float | tuple[float, float] | tuple[float, float, float | None] = 1.0, + intensity: float | tuple[float, float] | tuple[float, float, float | None] = 1.0, ) -> "DiskTemplate": return cls( name=name, array=array, refine_all_pixels=refine_all_pixels, place_at_origin=place_at_origin, + normalize=normalize, origin_key=origin_key, - origin_intensity=origin_intensity, + intensity=intensity, ) + def parameters(self) -> list[Parameter]: + out = list(self.p_pixels) + if self.p_intensity is not None: + out.append(self.p_intensity) + return out + def prepare(self, ctx: ModelContext) -> Any: - disk = self - ok = disk.origin_key + device = ctx.device + dtype = ctx.dtype - if disk.refine_all_pixels: - idxs = [p._index for p in disk.p_pixels] - if any(i is None for i in idxs): - raise RuntimeError("DiskTemplate pixel parameters not bound.") - idx_t = torch.as_tensor([int(i) for i in idxs], device=ctx.device, dtype=torch.int64) + Ht, Wt = self.array.shape + rr, cc = np.mgrid[0:Ht, 0:Wt] + rr = rr.astype(np.float32) - (Ht - 1) * 0.5 + cc = cc.astype(np.float32) - (Wt - 1) * 0.5 - def patch(x: torch.Tensor) -> torch.Tensor: - return x.index_select(0, idx_t).to(device=ctx.device, dtype=ctx.dtype).reshape(disk.shape[0], disk.shape[1]) + dr = _as_t(rr.ravel(), device=device, dtype=dtype) + dc = _as_t(cc.ravel(), device=device, dtype=dtype) + if self.refine_all_pixels: + pix_idx = torch.as_tensor([p.index for p in self.p_pixels], device=device, dtype=torch.long) + base_vals = None else: - patch0 = _as_tensor(disk._array0, device=ctx.device, dtype=ctx.dtype) - - def patch(x: torch.Tensor) -> torch.Tensor: - return patch0 + pix_idx = None + base_vals = _as_t(self.array.ravel(), device=device, dtype=dtype) + + ctx.fields.setdefault("disk_templates", {})[self.name] = { + "dr": dr, + "dc": dc, + "pix_idx": pix_idx, + "base": base_vals, + } + + i_int = None if self.p_intensity is None else self.p_intensity.index + if i_int is None: + r_idx = c_idx = None + else: + r_idx, c_idx = _origin_indices(ctx, self.origin_key) @dataclass - class PreparedDiskTemplate: - origin_key: str - place_at_origin: bool + class Prepared: + def patch(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + if pix_idx is None: + vals = base_vals + else: + vals = x[pix_idx] + return vals, dr, dc def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - if not self.place_at_origin: + if i_int is None: return - if self.origin_key not in ctx.origins: - return - r0, c0 = ctx.origins[self.origin_key] - r = float(r0.detach().cpu().item()) - c = float(c0.detach().cpu().item()) - inten = disk.p_origin_intensity.value(x, device=ctx.device, dtype=ctx.dtype) - _place_patch_add(out, patch(x) * inten, r, c) + vals, drl, dcl = self.patch(x) + r0 = x[r_idx] + c0 = x[c_idx] + _splat_patch(out, r0=r0, c0=c0, patch_vals=vals, dr=drl, dc=dcl, scale=x[i_int]) - def overlays(self) -> list[Overlay]: + def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: return [] - return PreparedDiskTemplate(origin_key=ok, place_at_origin=disk.place_at_origin) + return Prepared() class SyntheticDiskLattice(Component): @@ -153,142 +262,188 @@ def __init__( intensity_0: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, intensity_row: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, intensity_col: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, - exclude_indices: Iterable[tuple[int, int]] | None = [], + per_disk_intensity: bool = False, + per_disk_slopes: bool = True, + center_intensity_0: float | tuple[float, float] | tuple[float, float, float | None] | None = None, + exclude_indices: Iterable[tuple[int, int]] | None = None, boundary_px: float = 0.0, origin_key: str = "origin", ): super().__init__(name=name) self.disk = disk - self.origin_key = str(origin_key) self.u_max = int(u_max) self.v_max = int(v_max) self.boundary_px = float(boundary_px) + self.origin_key = str(origin_key) + self.per_disk_intensity = bool(per_disk_intensity) + self.per_disk_slopes = bool(per_disk_slopes) if exclude_indices is None: - self.exclude_indices = {(0, 0)} + self.exclude_indices = {(0, 0)} if bool(getattr(disk, "place_at_origin", False)) else set() else: - self.exclude_indices = {(int(a), int(b)) for (a, b) in exclude_indices} - - self.p_u_row = Parameter(u_row, tags={"role": "lattice_u_row", "name": name}) - self.p_u_col = Parameter(u_col, tags={"role": "lattice_u_col", "name": name}) - self.p_v_row = Parameter(v_row, tags={"role": "lattice_v_row", "name": name}) - self.p_v_col = Parameter(v_col, tags={"role": "lattice_v_col", "name": name}) - - self.p_intensity_0 = Parameter(intensity_0, tags={"role": "disk_intensity_0", "name": name}) - self.p_intensity_row = Parameter(intensity_row, tags={"role": "disk_intensity_row", "name": name}) - self.p_intensity_col = Parameter(intensity_col, tags={"role": "disk_intensity_col", "name": name}) + self.exclude_indices = set(exclude_indices) - def _uv_list(self) -> list[tuple[int, int]]: uv: list[tuple[int, int]] = [] for u in range(-self.u_max, self.u_max + 1): for v in range(-self.v_max, self.v_max + 1): if (u, v) in self.exclude_indices: continue uv.append((u, v)) - return uv + self.uv_indices = uv + + self.p_u_row = Parameter(u_row, tags={"role": "lat_u_row"}) + self.p_u_col = Parameter(u_col, tags={"role": "lat_u_col"}) + self.p_v_row = Parameter(v_row, tags={"role": "lat_v_row"}) + self.p_v_col = Parameter(v_col, tags={"role": "lat_v_col"}) + self.p_i0 = None + self.p_ir = None + self.p_ic = None + self.p_i0_list: list[Parameter] = [] + self.p_ir_list: list[Parameter] = [] + self.p_ic_list: list[Parameter] = [] + if self.per_disk_intensity: + for u, v in self.uv_indices: + i0_val = center_intensity_0 if (center_intensity_0 is not None and (u, v) == (0, 0)) else intensity_0 + self.p_i0_list.append(Parameter(i0_val, lower_bound=0.0, tags={"role": "lat_int0", "u": u, "v": v})) + if self.per_disk_slopes: + self.p_ir_list.append(Parameter(intensity_row, tags={"role": "lat_int_row", "u": u, "v": v})) + self.p_ic_list.append(Parameter(intensity_col, tags={"role": "lat_int_col", "u": u, "v": v})) + else: + self.p_i0 = Parameter(intensity_0, lower_bound=0.0, tags={"role": "lat_int0"}) + self.p_ir = Parameter(intensity_row, tags={"role": "lat_int_row"}) + self.p_ic = Parameter(intensity_col, tags={"role": "lat_int_col"}) + + def parameters(self) -> list[Parameter]: + out = [self.p_u_row, self.p_u_col, self.p_v_row, self.p_v_col] + if self.per_disk_intensity: + out.extend(self.p_i0_list) + if self.per_disk_slopes: + out.extend(self.p_ir_list) + out.extend(self.p_ic_list) + else: + out.extend([self.p_i0, self.p_ir, self.p_ic]) + return out def prepare(self, ctx: ModelContext) -> Any: - lat = self - ok = lat.origin_key - uv = lat._uv_list() + dt = ctx.fields.get("disk_templates", {}) + if self.disk.name not in dt: + raise RuntimeError("DiskTemplate must be included before SyntheticDiskLattice in components.") + d = dt[self.disk.name] + dr = d["dr"] + dc = d["dc"] + pix_idx = d["pix_idx"] + base_vals = d["base"] + + r_idx, c_idx = _origin_indices(ctx, self.origin_key) + + i_ur = self.p_u_row.index + i_uc = self.p_u_col.index + i_vr = self.p_v_row.index + i_vc = self.p_v_col.index + if self.per_disk_intensity: + i_i0 = i_ir = i_ic = None + else: + i_i0 = self.p_i0.index + i_ir = self.p_ir.index + i_ic = self.p_ic.index + + uv = self.uv_indices + uv_t = torch.as_tensor(uv, device=ctx.device, dtype=torch.long) if uv else None + + boundary = torch.as_tensor(self.boundary_px, device=ctx.device, dtype=ctx.dtype) + + if self.per_disk_intensity: + i0_idx = torch.as_tensor([p.index for p in self.p_i0_list], device=ctx.device, dtype=torch.long) + if self.per_disk_slopes: + ir_idx = torch.as_tensor([p.index for p in self.p_ir_list], device=ctx.device, dtype=torch.long) + ic_idx = torch.as_tensor([p.index for p in self.p_ic_list], device=ctx.device, dtype=torch.long) + else: + ir_idx = ic_idx = None + else: + i0_idx = ir_idx = ic_idx = None + per_disk_intensity = self.per_disk_intensity + per_disk_slopes = self.per_disk_slopes - if lat.disk.refine_all_pixels: - didxs = [p._index for p in lat.disk.p_pixels] - if any(i is None for i in didxs): - raise RuntimeError("DiskTemplate pixel parameters not bound.") - didx_t = torch.as_tensor([int(i) for i in didxs], device=ctx.device, dtype=torch.int64) + @dataclass + class Prepared: + boundary_px: float = float(self.boundary_px) - def patch(x: torch.Tensor) -> torch.Tensor: - return x.index_select(0, didx_t).to(device=ctx.device, dtype=ctx.dtype).reshape(lat.disk.shape[0], lat.disk.shape[1]) + def _patch(self, x: torch.Tensor) -> torch.Tensor: + if pix_idx is None: + return base_vals + return x[pix_idx] - else: - patch0 = _as_tensor(lat.disk._array0, device=ctx.device, dtype=ctx.dtype) - - def patch(x: torch.Tensor) -> torch.Tensor: - return patch0 - - r0_list: list[float] = [] - c0_list: list[float] = [] - if ok in ctx.origins: - or0, oc0 = ctx.origins[ok] - orow0 = float(or0.detach().cpu().item()) - ocol0 = float(oc0.detach().cpu().item()) - urow0 = float(lat.p_u_row.initial_value) - ucol0 = float(lat.p_u_col.initial_value) - vrow0 = float(lat.p_v_row.initial_value) - vcol0 = float(lat.p_v_col.initial_value) - for u, v in uv: - rr = orow0 + u * urow0 + v * vrow0 - cc = ocol0 + u * ucol0 + v * vcol0 - if _keep_center(rr, cc, ctx.H, ctx.W, lat.boundary_px): - r0_list.append(rr) - c0_list.append(cc) - - r0_np = np.asarray(r0_list, dtype=np.float32) - c0_np = np.asarray(c0_list, dtype=np.float32) + def _centers(self, x: torch.Tensor, ctx: ModelContext) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + if uv_t is None: + z = torch.empty((0,), device=ctx.device, dtype=ctx.dtype) + return z, z, z - @dataclass - class PreparedSyntheticDiskLattice: - origin_key: str - boundary_px: float - uv: list[tuple[int, int]] - r0: np.ndarray - c0: np.ndarray + r0 = x[r_idx] + c0 = x[c_idx] + + ur = x[i_ur] + uc = x[i_uc] + vr = x[i_vr] + vc = x[i_vc] + + u = uv_t[:, 0].to(ctx.dtype) + v = uv_t[:, 1].to(ctx.dtype) + + centers_r = r0 + u * ur + v * vr + centers_c = c0 + u * uc + v * vc + + keep = (centers_r >= boundary) & (centers_r <= (ctx.H - 1) - boundary) + keep &= (centers_c >= boundary) & (centers_c <= (ctx.W - 1) - boundary) + return centers_r[keep], centers_c[keep], keep def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - if self.origin_key not in ctx.origins: + if uv_t is None: return - if not self.uv: + + centers_r, centers_c, keep = self._centers(x, ctx) + if centers_r.numel() == 0: return - r_origin, c_origin = ctx.origins[self.origin_key] - r_origin = r_origin.reshape(()) - c_origin = c_origin.reshape(()) - - urow = lat.p_u_row.value(x, device=ctx.device, dtype=ctx.dtype) - ucol = lat.p_u_col.value(x, device=ctx.device, dtype=ctx.dtype) - vrow = lat.p_v_row.value(x, device=ctx.device, dtype=ctx.dtype) - vcol = lat.p_v_col.value(x, device=ctx.device, dtype=ctx.dtype) - - I0 = lat.p_intensity_0.value(x, device=ctx.device, dtype=ctx.dtype) - Ir = lat.p_intensity_row.value(x, device=ctx.device, dtype=ctx.dtype) - Ic = lat.p_intensity_col.value(x, device=ctx.device, dtype=ctx.dtype) - - uv_t = torch.as_tensor(self.uv, device=ctx.device, dtype=torch.int32) - uu = uv_t[:, 0].to(device=ctx.device, dtype=ctx.dtype) - vv = uv_t[:, 1].to(device=ctx.device, dtype=ctx.dtype) - - rr = r_origin + uu * urow + vv * vrow - cc = c_origin + uu * ucol + vv * vcol - - p = patch(x) - - H = int(ctx.H) - W = int(ctx.W) - for i in range(int(rr.numel())): - r = float(rr[i].detach().cpu().item()) - c = float(cc[i].detach().cpu().item()) - if not _keep_center(r, c, H, W, self.boundary_px): - continue - dr = rr[i] - r_origin - dc = cc[i] - c_origin - inten = torch.clamp(I0 + Ir * dr + Ic * dc, min=0.0) - _place_patch_add(out, p * inten, r, c) - - def overlays(self) -> list[Overlay]: - if self.r0.size == 0: + vals = self._patch(x) + + if per_disk_intensity: + keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) + i0_keep = x[i0_idx[keep_idx]] + if per_disk_slopes: + ir_keep = x[ir_idx[keep_idx]] + ic_keep = x[ic_idx[keep_idx]] + for j, (rr0, cc0) in enumerate(zip(centers_r, centers_c)): + inten_local = torch.clamp(i0_keep[j] + ir_keep[j] * dr + ic_keep[j] * dc, min=0.0) + _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten_local) + else: + for j, (rr0, cc0) in enumerate(zip(centers_r, centers_c)): + inten_local = torch.clamp(i0_keep[j], min=0.0) + _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten_local) + else: + i0 = x[i_i0] + ir = x[i_ir] + ic = x[i_ic] + for rr0, cc0 in zip(centers_r, centers_c): + inten = torch.clamp(i0 + ir * rr0 + ic * cc0, min=0.0) + _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten) + + def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: + if uv_t is None: + return [] + centers_r, centers_c, _ = self._centers(x, ctx) + if centers_r.numel() == 0: return [] return [ Overlay( kind="points_rc", - data={"r": self.r0, "c": self.c0, "marker": "x", "s": 60.0, "color": "orange"}, + data={ + "r": centers_r.detach(), + "c": centers_c.detach(), + "marker": "x", + "s": 60.0, + "color": "orange", + }, ) ] - return PreparedSyntheticDiskLattice( - origin_key=ok, - boundary_px=lat.boundary_px, - uv=uv, - r0=r0_np, - c0=c0_np, - ) + return Prepared() diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 84d9e5c8d..f382ab275 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Any +import warnings +from typing import Any, Sequence import numpy as np import torch @@ -12,12 +13,11 @@ from quantem.core.datastructures.dataset4d import Dataset4d from quantem.core.datastructures.dataset4dstem import Dataset4dstem from quantem.core.io.serialize import AutoSerialize -from quantem.core.models.base import Model, ModelContext, OriginND, Overlay, PreparedModel +from quantem.core.models.base import Model, ModelContext, Overlay, PreparedModel +from quantem.core.models.diffraction import Origin2D from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.core.visualization import show_2d -__all__ = ["ModelDiffraction"] - def _to_numpy(x: Any) -> np.ndarray: if isinstance(x, np.ndarray): @@ -36,17 +36,40 @@ def __init__(self, dataset: Any, _token: object | None = None): super().__init__() self.dataset = dataset self.metadata: dict[str, Any] = {} - self.index_shape: tuple[int, ...] | None = None self.image_ref: np.ndarray | None = None self.preprocess_shifts: np.ndarray | None = None + self.index_shape: tuple[int, ...] | None = None self.model: Model | None = None self.prepared: PreparedModel | None = None - self.fit_mask: np.ndarray | None = None + self.x_mean: torch.Tensor | None = None + self.x_defined: torch.Tensor | None = None + self.x_initial: torch.Tensor | None = None + self.mean_refined: bool = False + self.x_patterns: torch.Tensor | None = None + self.pattern_fit_losses: np.ndarray | None = None + self.pattern_fit_linear_indices: np.ndarray | None = None + self.pattern_fit_indices: list[tuple[int, ...]] | None = None + + @staticmethod + def _weak_softplus(x: torch.Tensor, *, scale: float) -> torch.Tensor: + s = torch.as_tensor(float(scale), device=x.device, dtype=x.dtype) + return torch.nn.functional.softplus(x / s) * s + + @classmethod + def _apply_intensity_transform( + cls, x: torch.Tensor, *, mode: str, weak_softplus_scale: float + ) -> torch.Tensor: + m = str(mode).lower() + if m == "none": + return x + if m == "weak_softplus": + return cls._weak_softplus(x, scale=weak_softplus_scale) + raise ValueError("intensity_transform must be one of: 'none', 'weak_softplus'.") @classmethod - def from_dataset(self, dataset: Dataset2d | Dataset3d | Dataset4d | Dataset4dstem | Any) -> "ModelDiffraction": + def from_dataset(cls, dataset: Dataset2d | Dataset3d | Dataset4d | Dataset4dstem | Any) -> "ModelDiffraction": if isinstance(dataset, (Dataset2d, Dataset3d, Dataset4d, Dataset4dstem)): - return ModelDiffraction(dataset=dataset, _token=ModelDiffraction._token) + return cls(dataset=dataset, _token=cls._token) raise TypeError("from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance.") def preprocess( @@ -61,22 +84,15 @@ def preprocess( arr = np.asarray(self.dataset.array) if arr.ndim < 2: raise ValueError("dataset.array must have at least 2 dimensions.") - H, W = int(arr.shape[-2]), int(arr.shape[-1]) + H, W = arr.shape[-2], arr.shape[-1] self.index_shape = tuple(arr.shape[:-2]) stack = arr.reshape((-1, H, W)).astype(np.float32, copy=False) - n = int(stack.shape[0]) + n = stack.shape[0] - if (not align) or n <= 1: + if not align or n <= 1: self.image_ref = np.mean(stack, axis=0) self.preprocess_shifts = None - self.metadata["preprocess"] = { - "align": bool(align), - "edge_blend": float(edge_blend), - "upsample_factor": int(upsample_factor), - "max_shift": None if max_shift is None else float(max_shift), - "shift_order": int(shift_order), - } return self alpha_r = 0.0 if edge_blend <= 0 else min(1.0, 2.0 * float(edge_blend) / float(H)) @@ -116,28 +132,8 @@ def preprocess( self.image_ref = np.mean(aligned, axis=0) self.preprocess_shifts = shifts.reshape(self.index_shape + (2,)) - - self.metadata["preprocess"] = { - "align": bool(align), - "edge_blend": float(edge_blend), - "upsample_factor": int(upsample_factor), - "max_shift": None if max_shift is None else float(max_shift), - "shift_order": int(shift_order), - } return self - def _ensure_image_ref(self) -> None: - if self.image_ref is not None: - return - arr = np.asarray(self.dataset.array) - if arr.ndim < 2: - raise ValueError("dataset.array must have at least 2 dimensions.") - H, W = int(arr.shape[-2]), int(arr.shape[-1]) - self.index_shape = tuple(arr.shape[:-2]) - stack = arr.reshape((-1, H, W)).astype(np.float32, copy=False) - self.image_ref = np.mean(stack, axis=0) - self.preprocess_shifts = None - def define_model( self, *, @@ -149,10 +145,12 @@ def define_model( mask: np.ndarray | torch.Tensor | None = None, origin_key: str = "origin", ) -> "ModelDiffraction": - self._ensure_image_ref() + if self.image_ref is None: + self.preprocess() + if self.image_ref is None: raise RuntimeError("image_ref not available.") - H, W = int(self.image_ref.shape[-2]), int(self.image_ref.shape[-1]) + H, W = int(self.image_ref.shape[0]), int(self.image_ref.shape[1]) dev = torch.device(device) if device is not None else torch.device("cpu") dt = dtype if dtype is not None else torch.float32 @@ -160,36 +158,346 @@ def define_model( mask_t = None if mask is not None: if torch.is_tensor(mask): - mask_t = mask.to(device=dev, dtype=torch.float32) + mask_t = mask.to(device=dev, dtype=dt) else: m = np.asarray(mask) if m.shape != (H, W): raise ValueError("mask must have shape (H, W).") - mask_t = torch.as_tensor(m.astype(np.float32, copy=False), device=dev) - - ctx = ModelContext(H=H, W=W, device=dev, dtype=dt, mask=mask_t) + mask_t = torch.as_tensor(m.astype(np.float32, copy=False), device=dev, dtype=dt) + ctx = ModelContext(H=H, W=W, device=dev, dtype=dt, mask=mask_t, fields={}) m = Model() - origin = OriginND.from_row_col(key=str(origin_key), origin_row=origin_row, origin_col=origin_col) - comps = [origin] + list(components) - m.add(comps) + m.add([Origin2D(origin_key=str(origin_key), row=origin_row, col=origin_col)]) + m.add(list(components)) self.model = m self.prepared = m.compile(ctx) - self.metadata["define_model"] = {"origin_key": str(origin_key), "device": str(dev), "dtype": str(dt)} + self.x_defined = self.prepared.x0.detach().clone() + self.x_initial = self.x_defined.detach().clone() + self.x_mean = self.x_initial.detach().clone() + self.mean_refined = False + self.x_patterns = None + self.pattern_fit_losses = None + self.pattern_fit_linear_indices = None + self.pattern_fit_indices = None + return self + + def _fit_target_image( + self, + *, + target: torch.Tensor, + x_start: torch.Tensor, + n_steps: int, + lr: float, + method: str, + power: float | None, + fit_disk_pixels: bool | None, + fit_only_disk_pixels: bool, + intensity_transform: str, + weak_softplus_scale: float, + progress: bool = False, + progress_desc: str | None = None, + ) -> tuple[torch.Tensor, float]: + if self.prepared is None: + raise RuntimeError("Call .define_model(...) first.") + + ctx = self.prepared.ctx + lb = self.prepared.lb + ub = self.prepared.ub + + x = x_start.detach().clone().to(device=ctx.device, dtype=ctx.dtype) + x.requires_grad_(True) + + if fit_disk_pixels is None: + fit_disk_pixels = any(p.tags.get("role") == "disk_pixel" for p in self.prepared.params) + + freeze = torch.zeros_like(x, dtype=torch.bool) + disk_mask = torch.zeros_like(x, dtype=torch.bool) + for p in self.prepared.params: + if p.tags.get("role") == "disk_pixel": + disk_mask[p.index] = True + + if fit_only_disk_pixels: + if not fit_disk_pixels: + raise ValueError("fit_only_disk_pixels=True requires fit_disk_pixels=True.") + freeze[:] = True + freeze[disk_mask] = False + elif not fit_disk_pixels: + freeze[disk_mask] = True + x_frozen = x.detach().clone() + + target_t = target.to(device=ctx.device, dtype=ctx.dtype) + target_t = self._apply_intensity_transform( + target_t, mode=intensity_transform, weak_softplus_scale=weak_softplus_scale + ) + if power is not None: + target_t = torch.clamp(target_t, min=0.0) ** float(power) + + def clamp_inplace() -> None: + with torch.no_grad(): + x.data = torch.max(torch.min(x.data, ub), lb) + if torch.any(freeze): + x.data[freeze] = x_frozen[freeze] + + def loss_fn() -> torch.Tensor: + clamp_inplace() + pred = self.prepared.render(x) + pred = self._apply_intensity_transform( + pred, mode=intensity_transform, weak_softplus_scale=weak_softplus_scale + ) + if power is not None: + pred = torch.clamp(pred, min=0.0) ** float(power) + if ctx.mask is not None: + m = ctx.mask + diff = (pred - target_t) * m + denom = torch.clamp(torch.sum(m), min=1.0) + return torch.sum(diff * diff) / denom + diff = pred - target_t + return torch.mean(diff * diff) + + if method == "adam": + opt = torch.optim.Adam([x], lr=lr) + step_iter: Any = range(int(n_steps)) + if progress: + try: + from tqdm.auto import trange + + step_iter = trange(int(n_steps), desc=progress_desc or "Refining", leave=False) + except Exception: + warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) + for _ in step_iter: + opt.zero_grad(set_to_none=True) + loss = loss_fn() + loss.backward() + opt.step() + clamp_inplace() + elif method == "lbfgs": + opt = torch.optim.LBFGS([x], lr=lr, max_iter=int(n_steps), line_search_fn="strong_wolfe") + + def closure() -> torch.Tensor: + opt.zero_grad(set_to_none=True) + loss = loss_fn() + loss.backward() + return loss + + opt.step(closure) + clamp_inplace() + else: + raise ValueError("method must be one of: 'lbfgs', 'adam'.") + + with torch.no_grad(): + final_loss = float(loss_fn().detach().cpu()) + return x.detach().clone(), final_loss + + def _resolve_pattern_indices(self, indices: Any, n: int, index_shape: tuple[int, ...]) -> np.ndarray: + if indices is None: + out = np.arange(n, dtype=np.int64) + elif isinstance(indices, (int, np.integer)): + out = np.asarray([int(indices)], dtype=np.int64) + elif isinstance(indices, slice): + out = np.arange(n, dtype=np.int64)[indices] + elif isinstance(indices, tuple) and all(isinstance(i, (int, np.integer)) for i in indices): + if len(index_shape) == 0: + if len(indices) != 0: + raise ValueError("indices tuple must be empty for single-pattern datasets.") + out = np.asarray([0], dtype=np.int64) + else: + out = np.asarray([np.ravel_multi_index(tuple(int(i) for i in indices), index_shape)], dtype=np.int64) + elif isinstance(indices, tuple): + if len(index_shape) == 0: + raise ValueError("slice tuple indices are not valid for single-pattern datasets.") + grid = np.arange(n, dtype=np.int64).reshape(index_shape) + out = np.asarray(grid[indices], dtype=np.int64).ravel() + elif isinstance(indices, np.ndarray) and indices.dtype == np.bool_: + if indices.shape != index_shape: + raise ValueError(f"Boolean mask must have shape {index_shape}.") + out = np.flatnonzero(indices.ravel()).astype(np.int64, copy=False) + elif isinstance(indices, Sequence) and not isinstance(indices, (str, bytes)): + seq = list(indices) + if len(seq) == 0: + out = np.asarray([], dtype=np.int64) + elif isinstance(seq[0], (tuple, list, np.ndarray)): + if len(index_shape) == 0: + raise ValueError("multi-index selection is not valid for single-pattern datasets.") + out = np.asarray( + [np.ravel_multi_index(tuple(int(j) for j in i), index_shape) for i in seq], + dtype=np.int64, + ) + else: + out = np.asarray([int(i) for i in seq], dtype=np.int64) + else: + raise TypeError("Unsupported indices type for fit_all_patterns.") + + if out.ndim != 1: + out = out.ravel() + if np.any(out < 0) or np.any(out >= n): + raise IndexError(f"indices must be in [0, {n - 1}].") + return out + + def refine_mean_model( + self, + *, + n_steps: int = 50, + lr: float = 1e-3, + method: str = "adam", + power: float | None = 1.0, + fit_disk_pixels: bool | None = None, + fit_only_disk_pixels: bool = False, + warmup_disk_steps: int = 0, + overwrite_initial: bool = True, + intensity_transform: str = "none", + weak_softplus_scale: float = 1e-3, + progress: bool = False, + ) -> "ModelDiffraction": + method = str(method).lower() + + if self.image_ref is None: + self.preprocess() + if self.image_ref is None or self.prepared is None or self.x_mean is None or self.x_initial is None: + raise RuntimeError("Call .define_model(...) first.") + + ctx = self.prepared.ctx + target = torch.as_tensor(self.image_ref, device=ctx.device, dtype=ctx.dtype) + x_start = self.x_initial if overwrite_initial else self.x_mean + if int(warmup_disk_steps) > 0: + x_start, _ = self._fit_target_image( + target=target, + x_start=x_start, + n_steps=int(warmup_disk_steps), + lr=float(lr), + method=method, + power=power, + fit_disk_pixels=True, + fit_only_disk_pixels=True, + intensity_transform=intensity_transform, + weak_softplus_scale=float(weak_softplus_scale), + progress=bool(progress), + progress_desc="Refine mean model (disk warmup)", + ) + x_fit, _ = self._fit_target_image( + target=target, + x_start=x_start, + n_steps=int(n_steps), + lr=float(lr), + method=method, + power=power, + fit_disk_pixels=fit_disk_pixels, + fit_only_disk_pixels=bool(fit_only_disk_pixels), + intensity_transform=intensity_transform, + weak_softplus_scale=float(weak_softplus_scale), + progress=bool(progress), + progress_desc="Refine mean model", + ) + + self.x_mean = x_fit + self.mean_refined = True + if overwrite_initial: + self.x_initial = x_fit.detach().clone() + return self + + def fit_all_patterns( + self, + *, + indices: Any = None, + use_refined_init: bool = True, + strict_refined_init: bool = False, + n_steps: int = 50, + lr: float = 1e-3, + method: str = "adam", + power: float | None = 1.0, + fit_disk_pixels: bool | None = None, + fit_only_disk_pixels: bool = False, + intensity_transform: str = "none", + weak_softplus_scale: float = 1e-3, + progress: bool = False, + ) -> "ModelDiffraction": + method = str(method).lower() + + if self.prepared is None or self.x_defined is None or self.x_initial is None: + raise RuntimeError("Call .define_model(...) first.") + + arr = np.asarray(self.dataset.array) + if arr.ndim < 2: + raise ValueError("dataset.array must have at least 2 dimensions.") + H, W = arr.shape[-2], arr.shape[-1] + index_shape = tuple(arr.shape[:-2]) + stack = arr.reshape((-1, H, W)).astype(np.float32, copy=False) + n = int(stack.shape[0]) + + linear = self._resolve_pattern_indices(indices=indices, n=n, index_shape=index_shape) + if linear.size == 0: + raise ValueError("No patterns selected for fitting.") + + if use_refined_init: + if not self.mean_refined: + msg = "fit_all_patterns(use_refined_init=True) was requested before refine_mean_model()." + if strict_refined_init: + raise RuntimeError(msg) + warnings.warn(f"{msg} Falling back to defined initial parameters.", stacklevel=2) + x_seed = self.x_defined + else: + x_seed = self.x_initial + else: + x_seed = self.x_defined + + n_sel = int(linear.size) + x_fit_all = torch.empty( + (n_sel, self.x_defined.numel()), + device=self.prepared.ctx.device, + dtype=self.prepared.ctx.dtype, + ) + losses = np.empty((n_sel,), dtype=np.float32) + + pat_iter: Any = enumerate(linear) + if progress: + try: + from tqdm.auto import tqdm + + pat_iter = enumerate(tqdm(linear, desc="Fit patterns", leave=False)) + except Exception: + warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) + + for j, i_lin in pat_iter: + target = torch.as_tensor(stack[int(i_lin)], device=self.prepared.ctx.device, dtype=self.prepared.ctx.dtype) + x_fit, loss = self._fit_target_image( + target=target, + x_start=x_seed, + n_steps=int(n_steps), + lr=float(lr), + method=method, + power=power, + fit_disk_pixels=fit_disk_pixels, + fit_only_disk_pixels=bool(fit_only_disk_pixels), + intensity_transform=intensity_transform, + weak_softplus_scale=float(weak_softplus_scale), + progress=False, + ) + x_fit_all[j] = x_fit + losses[j] = float(loss) + + self.x_patterns = x_fit_all + self.pattern_fit_losses = losses + self.pattern_fit_linear_indices = linear + if len(index_shape) == 0: + self.pattern_fit_indices = [tuple() for _ in linear] + else: + self.pattern_fit_indices = [tuple(int(k) for k in np.unravel_index(int(i), index_shape)) for i in linear] return self def _apply_overlays(self, ax: Any, overlays: list[Overlay]) -> None: for ov in overlays: - d = dict(ov.data) - if ov.kind in {"points", "points_rc"} and ("r" in d) and ("c" in d): - r = np.asarray(d["r"]).ravel() - c = np.asarray(d["c"]).ravel() - s = float(d.get("s", 60.0)) - marker = d.get("marker", "x") - color = d.get("color", "orange") - ax.scatter(c, r, s=s, marker=marker, c=color) + if ov.kind != "points_rc": continue + d = dict(ov.data) + r = _to_numpy(d["r"]).ravel() + c = _to_numpy(d["c"]).ravel() + ax.scatter( + c, + r, + s=float(d.get("s", 60.0)), + marker=d.get("marker", "x"), + color=d.get("color", "orange"), + ) def plot_mean_model( self, @@ -199,51 +507,46 @@ def plot_mean_model( show_overlays: bool = True, axsize: tuple[int, int] = (6, 6), ) -> tuple[Any, Any] | None: - self._ensure_image_ref() if self.image_ref is None: - raise RuntimeError("image_ref not available.") - if self.prepared is None: - raise RuntimeError("No model defined. Call .define_model(...) first.") + self.preprocess() + if self.image_ref is None or self.prepared is None: + raise RuntimeError("Call .define_model(...) first.") + if self.x_mean is None: + self.x_mean = self.prepared.x0.detach().clone() ref = np.asarray(self.image_ref, dtype=np.float32) - init = _to_numpy(self.prepared.render_initial()).astype(np.float32, copy=False) + mod = _to_numpy(self.prepared.render(self.x_mean)).astype(np.float32, copy=False) refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) - initp = init if power == 1.0 else np.maximum(init, 0.0) ** float(power) + modp = mod if power == 1.0 else np.maximum(mod, 0.0) ** float(power) - vmin = float(np.min([refp.min(), initp.min()])) - vmax = float(np.max([refp.max(), initp.max()])) + vmin = float(min(refp.min(), modp.min())) + vmax = float(max(refp.max(), modp.max())) fig, ax = show_2d( - [refp, initp], + [refp, modp], title=["image_ref", "model"], cmap="gray", cbar=False, returnfig=True, axsize=axsize, - norm="linear_minmax", vmin=vmin, vmax=vmax, ) - H, W = int(ref.shape[-2]), int(ref.shape[-1]) - - boundaries: list[float] = [] - for comp in getattr(self.prepared, "components", []): - b = getattr(comp, "boundary_px", None) + H, W = ref.shape[-2], ref.shape[-1] + pad = 0 + boundaries = [] + for c in getattr(self.prepared, "components", []): + b = getattr(c, "boundary_px", None) if b is not None: boundaries.append(float(b)) - - inset = 0 - pad = 0 if boundaries: - pos = [b for b in boundaries if b > 0.0] - neg = [b for b in boundaries if b < 0.0] - if pos: - inset = int(np.ceil(max(pos))) - if neg: - pad = int(np.ceil(-min(neg))) + min_b = float(np.min(boundaries)) + if min_b < 0.0: + pad = int(np.ceil(-min_b)) + axes: list[Any] if isinstance(ax, np.ndarray): axes = list(ax.ravel()) elif isinstance(ax, (list, tuple)): @@ -251,21 +554,16 @@ def plot_mean_model( else: axes = [ax] - x0 = (-pad + inset) - x1 = (W - 1) + pad - inset - y0 = (-pad + inset) - y1 = (H - 1) + pad - inset - for a in axes[:2]: - a.set_xlim(x0, x1) - a.set_ylim(y1, y0) + a.set_xlim(-pad, (W - 1) + pad) + a.set_ylim((H - 1) + pad, -pad) if show_overlays: - overlays = self.prepared.overlays() + ovs = self.prepared.overlays(self.x_mean) if len(axes) >= 1: - self._apply_overlays(axes[0], overlays) + self._apply_overlays(axes[0], ovs) if len(axes) >= 2: - self._apply_overlays(axes[1], overlays) + self._apply_overlays(axes[1], ovs) if returnfig: return fig, ax From 8cc81321491093ce7a19b11275f320923d7a73f7 Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 16 Feb 2026 20:18:27 -0800 Subject: [PATCH 022/113] adding docstrings --- src/quantem/core/models/background.py | 28 ++++ src/quantem/core/models/base.py | 94 +++++++++++ src/quantem/core/models/diffraction.py | 119 ++++++++++++++ src/quantem/diffraction/model_fitting.py | 191 +++++++++++++++++++++++ 4 files changed, 432 insertions(+) diff --git a/src/quantem/core/models/background.py b/src/quantem/core/models/background.py index d0a9adff5..f0231a433 100644 --- a/src/quantem/core/models/background.py +++ b/src/quantem/core/models/background.py @@ -1,5 +1,7 @@ from __future__ import annotations +"""Background components for diffraction model fitting.""" + from dataclasses import dataclass from typing import Any, Iterable @@ -10,6 +12,17 @@ class DCBackground(Component): + """ + Uniform additive background with nonnegative intensity. + + Parameters + ---------- + intensity + Constant background intensity parameter specification. + name + Component name. + """ + def __init__( self, *, @@ -37,6 +50,21 @@ def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: class GaussianBackground(Component): + """ + Radial Gaussian background centered at a named model origin. + + Parameters + ---------- + sigma + Gaussian width parameter specification (constrained to `>= 1e-6`). + intensity + Nonnegative Gaussian amplitude parameter specification. + origin_key + Name of the origin to use from `ModelContext.fields["origins"]`. + name + Component name. + """ + def __init__( self, *, diff --git a/src/quantem/core/models/base.py b/src/quantem/core/models/base.py index 7d453c59e..53a4c0aa5 100644 --- a/src/quantem/core/models/base.py +++ b/src/quantem/core/models/base.py @@ -1,5 +1,7 @@ from __future__ import annotations +"""Composable model primitives for diffraction-style forward models.""" + from dataclasses import dataclass from typing import Any, Iterable, Mapping, Sequence @@ -9,11 +11,34 @@ @dataclass(frozen=True) class Overlay: + """Lightweight plotting overlay emitted by prepared components.""" + kind: str data: dict[str, Any] class Parameter: + """ + Scalar model parameter with optional bounds and metadata tags. + + Parameters + ---------- + value + Initial parameter specification. Supported forms: + - scalar: `v0` + - 2-sequence: `(v0, dev)` interpreted as bounds `(v0-dev, v0+dev)` + - 3-sequence: `(v0, lb, ub)` where either bound can be None + lower_bound, upper_bound + Optional explicit overrides for lower/upper bound. + tags + Optional metadata dictionary used by higher-level fitting code for + grouping/freezing/selecting parameters. + + Notes + ----- + Parameter indices are assigned during `Model.compile(...)`. + """ + def __init__( self, value: float | Sequence[float], @@ -66,6 +91,22 @@ def index(self) -> int: class ModelContext: + """ + Execution context shared by components during preparation and rendering. + + Parameters + ---------- + H, W + Output image height and width. + device, dtype + Torch device and dtype for parameter tensors/rendering. + mask + Optional per-pixel mask used by fitting loss functions. + fields + Mutable shared dictionary used by components to publish/consume + prepared state (for example origins or template metadata). + """ + def __init__( self, *, @@ -90,6 +131,14 @@ def __getattr__(self, name: str) -> Any: class Component: + """ + Abstract base class for additive model components. + + Subclasses implement: + - `parameters()` to expose fit parameters + - `prepare(ctx)` to build prepared render objects + """ + def __init__(self, *, name: str): self.name = str(name) @@ -101,6 +150,17 @@ def prepare(self, ctx: ModelContext) -> Any: class PreparedModel: + """ + Compiled model bundle with bound parameter vectors and render helpers. + + Attributes + ---------- + x0, lb, ub + Initial parameter vector and bound vectors, all on `ctx.device`. + components + Prepared component objects called in sequence during `render`. + """ + def __init__( self, *, @@ -119,15 +179,18 @@ def __init__( self.ub = ub def render(self, x: torch.Tensor) -> torch.Tensor: + """Render a model image for parameter vector ``x``.""" out = torch.zeros((self.ctx.H, self.ctx.W), device=self.ctx.device, dtype=self.ctx.dtype) for c in self.components: c.render(out, x, self.ctx) return out def render_initial(self) -> torch.Tensor: + """Render the model using the compile-time initial parameter vector.""" return self.render(self.x0) def overlays(self, x: torch.Tensor | None = None) -> list[Overlay]: + """Collect component overlays for plotting/debugging.""" out: list[Overlay] = [] for c in self.components: fn = getattr(c, "overlays", None) @@ -140,10 +203,27 @@ def overlays(self, x: torch.Tensor | None = None) -> list[Overlay]: class Model: + """ + Container for additive components that compiles to a `PreparedModel`. + + Notes + ----- + Component order defines render order and can also define dependency order + when components share prepared state through `ModelContext.fields`. + """ + def __init__(self): self._components: list[Component] = [] def add(self, items: Iterable[Component]) -> "Model": + """ + Append components to the model in render order. + + Parameters + ---------- + items + Iterable of `Component` instances. + """ for obj in items: if not isinstance(obj, Component): raise TypeError("Model.add expects Component objects.") @@ -151,12 +231,26 @@ def add(self, items: Iterable[Component]) -> "Model": return self def parameter_list(self) -> list[Parameter]: + """Return the flattened parameter list across all components.""" params: list[Parameter] = [] for c in self._components: params.extend(c.parameters()) return params def compile(self, ctx: ModelContext) -> PreparedModel: + """ + Bind parameter indices and prepare components for fast rendering. + + Parameters + ---------- + ctx + Model execution context. + + Returns + ------- + PreparedModel + Compiled model with `x0`, bounds, and prepared component state. + """ params = self.parameter_list() for i, p in enumerate(params): p.bind(i) diff --git a/src/quantem/core/models/diffraction.py b/src/quantem/core/models/diffraction.py index 8ecaf729d..c324ecca2 100644 --- a/src/quantem/core/models/diffraction.py +++ b/src/quantem/core/models/diffraction.py @@ -1,5 +1,7 @@ from __future__ import annotations +"""Diffraction-specific model components and low-level rendering helpers.""" + from dataclasses import dataclass from typing import Any, Iterable, Sequence @@ -10,6 +12,16 @@ def _as_t(x: Any, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor: + """ + Convert values to tensors on the requested device/dtype. + + Parameters + ---------- + x + Input scalar/array/tensor. + device, dtype + Target torch device and dtype. + """ if torch.is_tensor(x): return x.to(device=device, dtype=dtype) return torch.as_tensor(x, device=device, dtype=dtype) @@ -25,6 +37,22 @@ def _splat_patch( dc: torch.Tensor, scale: torch.Tensor, ) -> None: + """ + Render a shifted patch into ``out`` with bilinear interpolation. + + Parameters + ---------- + out + Destination image `(H, W)` updated in-place. + r0, c0 + Patch center coordinates in output-image row/column space. + patch_vals + Flattened patch intensities. + dr, dc + Flattened local row/column offsets for each patch sample. + scale + Scalar or vector multiplier applied to `patch_vals`. + """ H = out.shape[0] W = out.shape[1] @@ -59,6 +87,16 @@ def put(rr: torch.Tensor, cc: torch.Tensor, ww: torch.Tensor) -> None: def _origin_indices(ctx: ModelContext, origin_key: str) -> tuple[int, int]: + """ + Resolve origin parameter indices from ``ModelContext``. + + Parameters + ---------- + ctx + Model context with an `origins` registry in `ctx.fields`. + origin_key + Origin name to resolve. + """ origins: dict[str, Any] = ctx.fields.get("origins", {}) o = origins.get(origin_key) if o is None or "row_param" not in o or "col_param" not in o: @@ -67,6 +105,19 @@ def _origin_indices(ctx: ModelContext, origin_key: str) -> tuple[int, int]: class Origin2D(Component): + """ + Named 2D origin component that exposes row/column fit parameters. + + Parameters + ---------- + name + Component name. + origin_key + Key used to publish this origin into `ctx.fields["origins"]`. + row, col + Parameter specifications for origin row/column. + """ + def __init__( self, *, @@ -113,6 +164,33 @@ def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: class DiskTemplate(Component): + """ + Template patch component used for central disks and lattice motifs. + + Parameters + ---------- + name + Template name used for registry lookup by dependent components. + array + 2D template image. + refine_all_pixels + If True, every template pixel is exposed as a fit parameter. + place_at_origin + If True, render this template once at the named origin with a + separate nonnegative intensity parameter. + normalize + Optional array normalization mode: `"none"`, `"max"`, `"mean"`. + origin_key + Origin key used when `place_at_origin=True`. + intensity + Intensity parameter specification used when `place_at_origin=True`. + + Notes + ----- + The template is also published into `ctx.fields["disk_templates"]` so + lattice components can reuse its prepared sampling offsets. + """ + def __init__( self, *, @@ -248,6 +326,47 @@ def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: class SyntheticDiskLattice(Component): + """ + Lattice of shifted ``DiskTemplate`` patches around a shared origin. + + Parameters + ---------- + name + Component name. + disk + `DiskTemplate` used as the motif for each lattice spot. + u_row, u_col, v_row, v_col + Basis vector parameter specifications in row/column coordinates. + u_max, v_max + Inclusive lattice index extents. + intensity_0, intensity_row, intensity_col + Intensity parameter specifications. Interpretation depends on + `per_disk_intensity` and `per_disk_slopes`. + per_disk_intensity + If True, allocate independent base intensities per lattice disk. + per_disk_slopes + If True and `per_disk_intensity=True`, also allocate per-disk local + slope parameters for template-local coordinates. + center_intensity_0 + Optional override for the `(u, v) == (0, 0)` disk base intensity. + exclude_indices + Optional set/list of lattice indices to skip. + boundary_px + Keep only lattice centers within `[boundary_px, size-1-boundary_px]`. + origin_key + Origin key used for center placement. + + Notes + ----- + Intensity models: + - shared mode (`per_disk_intensity=False`): + `max(i0 + ir*row_center + ic*col_center, 0)` + - per-disk scalar mode (`per_disk_intensity=True`, `per_disk_slopes=False`): + `max(i0_i, 0)` + - per-disk local affine mode (`per_disk_intensity=True`, `per_disk_slopes=True`): + `max(i0_i + ir_i*dr + ic_i*dc, 0)` where `dr/dc` are template-local offsets. + """ + def __init__( self, *, diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index f382ab275..69dda4ca1 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -1,5 +1,7 @@ from __future__ import annotations +"""High-level diffraction model fitting workflow utilities.""" + import warnings from typing import Any, Sequence @@ -20,6 +22,7 @@ def _to_numpy(x: Any) -> np.ndarray: + """Convert arrays/tensors to NumPy arrays.""" if isinstance(x, np.ndarray): return x if torch.is_tensor(x): @@ -28,6 +31,31 @@ def _to_numpy(x: Any) -> np.ndarray: class ModelDiffraction(AutoSerialize): + """ + End-to-end helper for defining and fitting additive diffraction forward models. + + This class wraps a diffraction dataset, builds an average reference image + (`image_ref`), compiles a composable component model, and provides optimization + routines for: + - fitting to the mean reference image, + - fitting selected individual diffraction patterns. + + Features + -------- + - Build a mean reference image with optional stack alignment. + - Define a composable model from origin/background/template/lattice components. + - Refine a mean model with Adam or L-BFGS. + - Fit all or selected patterns with optional progress bars. + - Plot reference/model comparisons and component overlays. + + Typical workflow + ---------------- + >>> md = ModelDiffraction.from_dataset(ds).preprocess().define_model(...) + >>> md.refine_mean_model(...) + >>> md.fit_all_patterns(...) + >>> md.plot_mean_model(...) + """ + _token = object() def __init__(self, dataset: Any, _token: object | None = None): @@ -68,6 +96,19 @@ def _apply_intensity_transform( @classmethod def from_dataset(cls, dataset: Dataset2d | Dataset3d | Dataset4d | Dataset4dstem | Any) -> "ModelDiffraction": + """ + Construct a ModelDiffraction object from a QuantEM dataset container. + + Parameters + ---------- + dataset + Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance. + + Returns + ------- + ModelDiffraction + New model-fitting helper bound to the provided dataset. + """ if isinstance(dataset, (Dataset2d, Dataset3d, Dataset4d, Dataset4dstem)): return cls(dataset=dataset, _token=cls._token) raise TypeError("from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance.") @@ -81,6 +122,34 @@ def preprocess( max_shift: float | None = None, shift_order: int = 1, ) -> "ModelDiffraction": + """ + Precompute the mean reference image used for model fitting. + + Parameters + ---------- + align + If True, align the flattened pattern stack before averaging. + edge_blend + Tukey edge taper width (pixels) used for robust FFT alignment. + upsample_factor + Sub-pixel alignment upsampling factor for cross-correlation shift. + max_shift + Optional maximum shift magnitude during alignment. + shift_order + Interpolation order used when applying shifts to patterns. + + Returns + ------- + ModelDiffraction + Returns self. + + Notes + ----- + - `dataset.array` is interpreted as `(..., H, W)`, where leading dimensions + are flattened into a pattern stack. + - The computed stack-average is stored in `self.image_ref`. + - If `align=False`, preprocessing is a direct mean over stack elements. + """ arr = np.asarray(self.dataset.array) if arr.ndim < 2: raise ValueError("dataset.array must have at least 2 dimensions.") @@ -145,6 +214,43 @@ def define_model( mask: np.ndarray | torch.Tensor | None = None, origin_key: str = "origin", ) -> "ModelDiffraction": + """ + Define and compile a diffraction model against `image_ref`. + + Parameters + ---------- + origin_row, origin_col + Initial origin parameter specification. Supported forms are: + - scalar: fixed initial value with no explicit bounds + - `(value, deviation)`: symmetric bounds `(value - deviation, value + deviation)` + - `(value, lower_bound, upper_bound)`: explicit bounds + components + Sequence of model components (e.g. `DiskTemplate`, backgrounds, lattice). + Components are rendered additively in the provided order. + device + Torch device used for compiled parameters and rendering. + dtype + Torch dtype used for compiled parameters and rendering. + mask + Optional `(H, W)` mask for weighted loss during optimization. + origin_key + Name used to register/retrieve the origin component in context fields. + + Returns + ------- + ModelDiffraction + Returns self with compiled model state. + + Notes + ----- + - If `image_ref` is missing, `preprocess()` is run automatically. + - `Origin2D` is inserted automatically before user components. + - Component dependency ordering still matters for shared context fields: + for example, `DiskTemplate` should appear before `SyntheticDiskLattice` + when the lattice references that template. + - This method resets fit state (`x_defined`, `x_initial`, `x_mean`, + `x_patterns`, and pattern-fit metadata). + """ if self.image_ref is None: self.preprocess() @@ -349,6 +455,43 @@ def refine_mean_model( weak_softplus_scale: float = 1e-3, progress: bool = False, ) -> "ModelDiffraction": + """ + Refine model parameters against the mean reference image. + + Parameters + ---------- + n_steps + Number of optimization steps/iterations for the main phase. + lr + Optimizer learning rate. + method + Optimizer name: `"adam"` or `"lbfgs"`. + power + Optional power-law transform applied to both target and prediction + before computing MSE loss. + fit_disk_pixels + Controls whether `disk_pixel` parameters are trainable. If None, + inferred from model parameters. + fit_only_disk_pixels + If True, freeze all non-disk parameters. + warmup_disk_steps + Optional number of disk-only warmup steps run before the main phase. + overwrite_initial + If True, store refined parameters as new initial parameters for + subsequent per-pattern fitting. + intensity_transform + Optional intensity transform (`"none"` or `"weak_softplus"`) applied + before the power transform and loss evaluation. + weak_softplus_scale + Scale parameter used when `intensity_transform="weak_softplus"`. + progress + If True, show a tqdm progress bar (when tqdm is available). + + Returns + ------- + ModelDiffraction + Returns self with updated `x_mean` (and optionally `x_initial`). + """ method = str(method).lower() if self.image_ref is None: @@ -411,6 +554,35 @@ def fit_all_patterns( weak_softplus_scale: float = 1e-3, progress: bool = False, ) -> "ModelDiffraction": + """ + Fit selected diffraction patterns using the compiled model. + + Parameters + ---------- + indices + Pattern selector. Supported forms include None (all patterns), + integer, slice, tuple indexing, list of linear indices, list of + multi-indices, or boolean mask shaped like scan dimensions. + use_refined_init + If True, initialize per-pattern fitting from `x_initial`. + strict_refined_init + If True, raise when refined init is requested before mean refinement. + If False, emit warning and fall back to defined initialization. + n_steps, lr, method, power, fit_disk_pixels, fit_only_disk_pixels + Optimization settings analogous to `refine_mean_model`. + intensity_transform, weak_softplus_scale + Prediction/target intensity transform options. + progress + If True, show a tqdm progress bar over selected patterns. + + Returns + ------- + ModelDiffraction + Returns self with: + - `x_patterns`: fitted parameter vectors `(n_selected, n_params)` + - `pattern_fit_losses`: per-pattern final losses + - index bookkeeping for selected patterns. + """ method = str(method).lower() if self.prepared is None or self.x_defined is None or self.x_initial is None: @@ -507,6 +679,25 @@ def plot_mean_model( show_overlays: bool = True, axsize: tuple[int, int] = (6, 6), ) -> tuple[Any, Any] | None: + """ + Plot `image_ref` and the current mean-model prediction side-by-side. + + Parameters + ---------- + power + Display transform exponent applied to both reference and model images. + returnfig + If True, return `(fig, ax)` from `show_2d`. + show_overlays + If True, draw component overlays (e.g., origin and lattice markers). + axsize + Base axis size passed to the plotting helper. + + Returns + ------- + tuple[Any, Any] | None + `(fig, ax)` if `returnfig=True`, else None. + """ if self.image_ref is None: self.preprocess() if self.image_ref is None or self.prepared is None: From 7dd327a7b8483a06aae478d1cfd1491633bb3c3c Mon Sep 17 00:00:00 2001 From: cophus Date: Thu, 19 Feb 2026 19:20:44 -0800 Subject: [PATCH 023/113] fixing CoM behaviour --- src/quantem/core/models/diffraction.py | 1 + src/quantem/diffraction/model_fitting.py | 84 ++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/src/quantem/core/models/diffraction.py b/src/quantem/core/models/diffraction.py index c324ecca2..7b6d00ae1 100644 --- a/src/quantem/core/models/diffraction.py +++ b/src/quantem/core/models/diffraction.py @@ -294,6 +294,7 @@ def prepare(self, ctx: ModelContext) -> Any: "dc": dc, "pix_idx": pix_idx, "base": base_vals, + "shape": (int(Ht), int(Wt)), } i_int = None if self.p_intensity is None else self.p_intensity.index diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 69dda4ca1..3362d60cd 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -7,6 +7,7 @@ import numpy as np import torch +import torch.nn.functional as F from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey @@ -299,6 +300,8 @@ def _fit_target_image( power: float | None, fit_disk_pixels: bool | None, fit_only_disk_pixels: bool, + enforce_disk_max_one: bool, + enforce_disk_center_of_mass: bool, intensity_transform: str, weak_softplus_scale: float, progress: bool = False, @@ -323,6 +326,40 @@ def _fit_target_image( if p.tags.get("role") == "disk_pixel": disk_mask[p.index] = True + # Cache template groups for optional per-step projection constraints. + disk_templates = self.prepared.ctx.fields.get("disk_templates", {}) + disk_param_groups: list[dict[str, Any]] = [] + if enforce_disk_max_one or enforce_disk_center_of_mass: + grouped: dict[str, list[tuple[int, int]]] = {} + for p in self.prepared.params: + if p.tags.get("role") != "disk_pixel": + continue + name = str(p.tags.get("disk")) + i_flat = int(p.tags.get("i")) + grouped.setdefault(name, []).append((i_flat, int(p.index))) + + for name, pairs in grouped.items(): + if name not in disk_templates: + continue + dmeta = disk_templates[name] + shape = dmeta.get("shape", None) + if shape is None: + continue + Ht, Wt = int(shape[0]), int(shape[1]) + order = sorted(pairs, key=lambda t: t[0]) + flat_i = torch.as_tensor([t[0] for t in order], device=ctx.device, dtype=torch.long) + p_idx = torch.as_tensor([t[1] for t in order], device=ctx.device, dtype=torch.long) + dr = dmeta["dr"][flat_i] + dc = dmeta["dc"][flat_i] + disk_param_groups.append( + { + "param_idx": p_idx, + "shape": (Ht, Wt), + "dr": dr, + "dc": dc, + } + ) + if fit_only_disk_pixels: if not fit_disk_pixels: raise ValueError("fit_only_disk_pixels=True requires fit_disk_pixels=True.") @@ -344,6 +381,43 @@ def clamp_inplace() -> None: x.data = torch.max(torch.min(x.data, ub), lb) if torch.any(freeze): x.data[freeze] = x_frozen[freeze] + if (enforce_disk_max_one or enforce_disk_center_of_mass) and disk_param_groups: + eps = torch.as_tensor(1e-12, device=ctx.device, dtype=ctx.dtype) + for g in disk_param_groups: + p_idx = g["param_idx"] + if torch.all(freeze[p_idx]): + continue + vals = x.data[p_idx] + vals = torch.clamp(vals, min=0.0) + + if enforce_disk_center_of_mass: + mass = torch.sum(vals) + if mass > eps: + r_com = torch.sum(vals * g["dr"]) / mass + c_com = torch.sum(vals * g["dc"]) / mass + Ht, Wt = g["shape"] + img = vals.reshape(Ht, Wt)[None, None, :, :] + yy = torch.linspace(-1.0, 1.0, Ht, device=ctx.device, dtype=ctx.dtype) + xx = torch.linspace(-1.0, 1.0, Wt, device=ctx.device, dtype=ctx.dtype) + gy, gx = torch.meshgrid(yy, xx, indexing="ij") + sx = gx + (2.0 * c_com / max(Wt - 1, 1)) + sy = gy + (2.0 * r_com / max(Ht - 1, 1)) + grid = torch.stack((sx, sy), dim=-1)[None, :, :, :] + img_shift = F.grid_sample( + img, + grid, + mode="bilinear", + padding_mode="zeros", + align_corners=True, + ) + vals = torch.clamp(img_shift[0, 0].reshape(-1), min=0.0) + + if enforce_disk_max_one: + vmax = torch.max(vals) + if vmax > eps: + vals = vals / vmax + + x.data[p_idx] = vals def loss_fn() -> torch.Tensor: clamp_inplace() @@ -449,6 +523,8 @@ def refine_mean_model( power: float | None = 1.0, fit_disk_pixels: bool | None = None, fit_only_disk_pixels: bool = False, + enforce_disk_max_one: bool = True, + enforce_disk_center_of_mass: bool = True, warmup_disk_steps: int = 0, overwrite_initial: bool = True, intensity_transform: str = "none", @@ -512,6 +588,8 @@ def refine_mean_model( power=power, fit_disk_pixels=True, fit_only_disk_pixels=True, + enforce_disk_max_one=bool(enforce_disk_max_one), + enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=bool(progress), @@ -526,6 +604,8 @@ def refine_mean_model( power=power, fit_disk_pixels=fit_disk_pixels, fit_only_disk_pixels=bool(fit_only_disk_pixels), + enforce_disk_max_one=bool(enforce_disk_max_one), + enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=bool(progress), @@ -550,6 +630,8 @@ def fit_all_patterns( power: float | None = 1.0, fit_disk_pixels: bool | None = None, fit_only_disk_pixels: bool = False, + enforce_disk_max_one: bool = True, + enforce_disk_center_of_mass: bool = True, intensity_transform: str = "none", weak_softplus_scale: float = 1e-3, progress: bool = False, @@ -640,6 +722,8 @@ def fit_all_patterns( power=power, fit_disk_pixels=fit_disk_pixels, fit_only_disk_pixels=bool(fit_only_disk_pixels), + enforce_disk_max_one=bool(enforce_disk_max_one), + enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=False, From 9bfaa7f02c3ac7f49f85c94494dc764cd909799d Mon Sep 17 00:00:00 2001 From: cophus Date: Fri, 20 Feb 2026 11:45:48 -0800 Subject: [PATCH 024/113] updates --- src/quantem/core/fitting/__init__.py | 29 +++ .../core/{models => fitting}/background.py | 4 +- src/quantem/core/{models => fitting}/base.py | 0 .../core/{models => fitting}/diffraction.py | 152 +++++++++++---- src/quantem/core/models/__init__.py | 29 --- src/quantem/diffraction/model_fitting.py | 182 +++++++++--------- 6 files changed, 242 insertions(+), 154 deletions(-) create mode 100644 src/quantem/core/fitting/__init__.py rename src/quantem/core/{models => fitting}/background.py (95%) rename src/quantem/core/{models => fitting}/base.py (100%) rename src/quantem/core/{models => fitting}/diffraction.py (71%) delete mode 100644 src/quantem/core/models/__init__.py diff --git a/src/quantem/core/fitting/__init__.py b/src/quantem/core/fitting/__init__.py new file mode 100644 index 000000000..5aa674593 --- /dev/null +++ b/src/quantem/core/fitting/__init__.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from quantem.core.fitting.base import Component as Component +from quantem.core.fitting.base import Model as Model +from quantem.core.fitting.base import ModelContext as ModelContext +from quantem.core.fitting.base import Overlay as Overlay +from quantem.core.fitting.base import Parameter as Parameter +from quantem.core.fitting.base import PreparedModel as PreparedModel + +from quantem.core.fitting.diffraction import DiskTemplate as DiskTemplate +from quantem.core.fitting.diffraction import Origin2D as Origin2D +from quantem.core.fitting.diffraction import SyntheticDiskLattice as SyntheticDiskLattice + +from quantem.core.fitting.background import DCBackground as DCBackground +from quantem.core.fitting.background import GaussianBackground as GaussianBackground + +__all__ = [ + "Parameter", + "Overlay", + "ModelContext", + "Component", + "Model", + "PreparedModel", + "Origin2D", + "DiskTemplate", + "SyntheticDiskLattice", + "DCBackground", + "GaussianBackground", +] diff --git a/src/quantem/core/models/background.py b/src/quantem/core/fitting/background.py similarity index 95% rename from src/quantem/core/models/background.py rename to src/quantem/core/fitting/background.py index f0231a433..2db886163 100644 --- a/src/quantem/core/models/background.py +++ b/src/quantem/core/fitting/background.py @@ -7,8 +7,8 @@ import torch -from quantem.core.models.base import Component, ModelContext, Overlay, Parameter -from quantem.core.models.diffraction import _origin_indices +from quantem.core.fitting.base import Component, ModelContext, Overlay, Parameter +from quantem.core.fitting.diffraction import _origin_indices class DCBackground(Component): diff --git a/src/quantem/core/models/base.py b/src/quantem/core/fitting/base.py similarity index 100% rename from src/quantem/core/models/base.py rename to src/quantem/core/fitting/base.py diff --git a/src/quantem/core/models/diffraction.py b/src/quantem/core/fitting/diffraction.py similarity index 71% rename from src/quantem/core/models/diffraction.py rename to src/quantem/core/fitting/diffraction.py index 7b6d00ae1..78a45c141 100644 --- a/src/quantem/core/models/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -8,7 +8,7 @@ import numpy as np import torch -from quantem.core.models.base import Component, ModelContext, Overlay, Parameter +from quantem.core.fitting.base import Component, ModelContext, Overlay, Parameter def _as_t(x: Any, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor: @@ -342,12 +342,19 @@ class SyntheticDiskLattice(Component): Inclusive lattice index extents. intensity_0, intensity_row, intensity_col Intensity parameter specifications. Interpretation depends on - `per_disk_intensity` and `per_disk_slopes`. + `per_disk_intensity` and intensity-order settings. + intensity_row_row, intensity_col_col, intensity_row_col + Optional quadratic intensity terms for order-2 models. per_disk_intensity If True, allocate independent base intensities per lattice disk. per_disk_slopes - If True and `per_disk_intensity=True`, also allocate per-disk local - slope parameters for template-local coordinates. + Backward-compatible switch controlling whether linear terms are included + when `max_intensity_order` is not explicitly provided. + max_intensity_order + Maximum polynomial order used by this component (`0`, `1`, or `2`). + default_pattern_intensity_order + Default active intensity order used during rendering unless overridden + in `ctx.fields["lattice_intensity_order_override"]`. center_intensity_0 Optional override for the `(u, v) == (0, 0)` disk base intensity. exclude_indices @@ -362,10 +369,12 @@ class SyntheticDiskLattice(Component): Intensity models: - shared mode (`per_disk_intensity=False`): `max(i0 + ir*row_center + ic*col_center, 0)` - - per-disk scalar mode (`per_disk_intensity=True`, `per_disk_slopes=False`): + - per-disk scalar mode (`per_disk_intensity=True`, order=0): `max(i0_i, 0)` - - per-disk local affine mode (`per_disk_intensity=True`, `per_disk_slopes=True`): + - per-disk local affine mode (`per_disk_intensity=True`, order=1): `max(i0_i + ir_i*dr + ic_i*dc, 0)` where `dr/dc` are template-local offsets. + - per-disk local quadratic mode (`per_disk_intensity=True`, order=2): + `max(i0_i + ir_i*dr + ic_i*dc + irr_i*dr^2 + icc_i*dc^2 + irc_i*dr*dc, 0)`. """ def __init__( @@ -382,8 +391,13 @@ def __init__( intensity_0: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, intensity_row: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, intensity_col: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + intensity_row_row: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + intensity_col_col: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + intensity_row_col: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, per_disk_intensity: bool = False, per_disk_slopes: bool = True, + max_intensity_order: int | None = None, + default_pattern_intensity_order: int | None = None, center_intensity_0: float | tuple[float, float] | tuple[float, float, float | None] | None = None, exclude_indices: Iterable[tuple[int, int]] | None = None, boundary_px: float = 0.0, @@ -396,7 +410,18 @@ def __init__( self.boundary_px = float(boundary_px) self.origin_key = str(origin_key) self.per_disk_intensity = bool(per_disk_intensity) - self.per_disk_slopes = bool(per_disk_slopes) + if max_intensity_order is None: + self.max_intensity_order = 1 if bool(per_disk_slopes) else 0 + else: + self.max_intensity_order = int(max_intensity_order) + if self.max_intensity_order < 0 or self.max_intensity_order > 2: + raise ValueError("max_intensity_order must be 0, 1, or 2.") + if default_pattern_intensity_order is None: + self.default_pattern_intensity_order = int(self.max_intensity_order) + else: + self.default_pattern_intensity_order = int(default_pattern_intensity_order) + if self.default_pattern_intensity_order < 0 or self.default_pattern_intensity_order > self.max_intensity_order: + raise ValueError("default_pattern_intensity_order must be in [0, max_intensity_order].") if exclude_indices is None: self.exclude_indices = {(0, 0)} if bool(getattr(disk, "place_at_origin", False)) else set() @@ -421,27 +446,57 @@ def __init__( self.p_i0_list: list[Parameter] = [] self.p_ir_list: list[Parameter] = [] self.p_ic_list: list[Parameter] = [] + self.p_irr_list: list[Parameter] = [] + self.p_icc_list: list[Parameter] = [] + self.p_irc_list: list[Parameter] = [] if self.per_disk_intensity: for u, v in self.uv_indices: i0_val = center_intensity_0 if (center_intensity_0 is not None and (u, v) == (0, 0)) else intensity_0 - self.p_i0_list.append(Parameter(i0_val, lower_bound=0.0, tags={"role": "lat_int0", "u": u, "v": v})) - if self.per_disk_slopes: - self.p_ir_list.append(Parameter(intensity_row, tags={"role": "lat_int_row", "u": u, "v": v})) - self.p_ic_list.append(Parameter(intensity_col, tags={"role": "lat_int_col", "u": u, "v": v})) + self.p_i0_list.append( + Parameter(i0_val, lower_bound=0.0, tags={"role": "lat_int0", "u": u, "v": v, "intensity_order": 0}) + ) + if self.max_intensity_order >= 1: + self.p_ir_list.append( + Parameter(intensity_row, tags={"role": "lat_int_row", "u": u, "v": v, "intensity_order": 1}) + ) + self.p_ic_list.append( + Parameter(intensity_col, tags={"role": "lat_int_col", "u": u, "v": v, "intensity_order": 1}) + ) + if self.max_intensity_order >= 2: + self.p_irr_list.append( + Parameter(intensity_row_row, tags={"role": "lat_int_rr", "u": u, "v": v, "intensity_order": 2}) + ) + self.p_icc_list.append( + Parameter(intensity_col_col, tags={"role": "lat_int_cc", "u": u, "v": v, "intensity_order": 2}) + ) + self.p_irc_list.append( + Parameter(intensity_row_col, tags={"role": "lat_int_rc", "u": u, "v": v, "intensity_order": 2}) + ) else: - self.p_i0 = Parameter(intensity_0, lower_bound=0.0, tags={"role": "lat_int0"}) - self.p_ir = Parameter(intensity_row, tags={"role": "lat_int_row"}) - self.p_ic = Parameter(intensity_col, tags={"role": "lat_int_col"}) + self.p_i0 = Parameter(intensity_0, lower_bound=0.0, tags={"role": "lat_int0", "intensity_order": 0}) + self.p_ir = Parameter(intensity_row, tags={"role": "lat_int_row", "intensity_order": 1}) + self.p_ic = Parameter(intensity_col, tags={"role": "lat_int_col", "intensity_order": 1}) + self.p_irr = Parameter(intensity_row_row, tags={"role": "lat_int_rr", "intensity_order": 2}) + self.p_icc = Parameter(intensity_col_col, tags={"role": "lat_int_cc", "intensity_order": 2}) + self.p_irc = Parameter(intensity_row_col, tags={"role": "lat_int_rc", "intensity_order": 2}) def parameters(self) -> list[Parameter]: out = [self.p_u_row, self.p_u_col, self.p_v_row, self.p_v_col] if self.per_disk_intensity: out.extend(self.p_i0_list) - if self.per_disk_slopes: + if self.max_intensity_order >= 1: out.extend(self.p_ir_list) out.extend(self.p_ic_list) + if self.max_intensity_order >= 2: + out.extend(self.p_irr_list) + out.extend(self.p_icc_list) + out.extend(self.p_irc_list) else: - out.extend([self.p_i0, self.p_ir, self.p_ic]) + out.append(self.p_i0) + if self.max_intensity_order >= 1: + out.extend([self.p_ir, self.p_ic]) + if self.max_intensity_order >= 2: + out.extend([self.p_irr, self.p_icc, self.p_irc]) return out def prepare(self, ctx: ModelContext) -> Any: @@ -461,11 +516,14 @@ def prepare(self, ctx: ModelContext) -> Any: i_vr = self.p_v_row.index i_vc = self.p_v_col.index if self.per_disk_intensity: - i_i0 = i_ir = i_ic = None + i_i0 = i_ir = i_ic = i_irr = i_icc = i_irc = None else: i_i0 = self.p_i0.index - i_ir = self.p_ir.index - i_ic = self.p_ic.index + i_ir = None if self.max_intensity_order < 1 else self.p_ir.index + i_ic = None if self.max_intensity_order < 1 else self.p_ic.index + i_irr = None if self.max_intensity_order < 2 else self.p_irr.index + i_icc = None if self.max_intensity_order < 2 else self.p_icc.index + i_irc = None if self.max_intensity_order < 2 else self.p_irc.index uv = self.uv_indices uv_t = torch.as_tensor(uv, device=ctx.device, dtype=torch.long) if uv else None @@ -474,15 +532,23 @@ def prepare(self, ctx: ModelContext) -> Any: if self.per_disk_intensity: i0_idx = torch.as_tensor([p.index for p in self.p_i0_list], device=ctx.device, dtype=torch.long) - if self.per_disk_slopes: + if self.max_intensity_order >= 1: ir_idx = torch.as_tensor([p.index for p in self.p_ir_list], device=ctx.device, dtype=torch.long) ic_idx = torch.as_tensor([p.index for p in self.p_ic_list], device=ctx.device, dtype=torch.long) else: ir_idx = ic_idx = None + if self.max_intensity_order >= 2: + irr_idx = torch.as_tensor([p.index for p in self.p_irr_list], device=ctx.device, dtype=torch.long) + icc_idx = torch.as_tensor([p.index for p in self.p_icc_list], device=ctx.device, dtype=torch.long) + irc_idx = torch.as_tensor([p.index for p in self.p_irc_list], device=ctx.device, dtype=torch.long) + else: + irr_idx = icc_idx = irc_idx = None else: - i0_idx = ir_idx = ic_idx = None + i0_idx = ir_idx = ic_idx = irr_idx = icc_idx = irc_idx = None per_disk_intensity = self.per_disk_intensity - per_disk_slopes = self.per_disk_slopes + max_intensity_order = self.max_intensity_order + default_pattern_intensity_order = self.default_pattern_intensity_order + comp_name = self.name @dataclass class Prepared: @@ -525,26 +591,46 @@ def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: return vals = self._patch(x) + dr2 = dr * dr + dc2 = dc * dc + drdc = dr * dc + + order_override = ctx.fields.get("lattice_intensity_order_override", None) + if isinstance(order_override, dict): + active_order = int(order_override.get(comp_name, default_pattern_intensity_order)) + elif order_override is None: + active_order = int(default_pattern_intensity_order) + else: + active_order = int(order_override) + active_order = max(0, min(active_order, int(max_intensity_order))) if per_disk_intensity: keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) i0_keep = x[i0_idx[keep_idx]] - if per_disk_slopes: + if active_order >= 1: ir_keep = x[ir_idx[keep_idx]] ic_keep = x[ic_idx[keep_idx]] - for j, (rr0, cc0) in enumerate(zip(centers_r, centers_c)): - inten_local = torch.clamp(i0_keep[j] + ir_keep[j] * dr + ic_keep[j] * dc, min=0.0) - _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten_local) - else: - for j, (rr0, cc0) in enumerate(zip(centers_r, centers_c)): - inten_local = torch.clamp(i0_keep[j], min=0.0) - _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten_local) + if active_order >= 2: + irr_keep = x[irr_idx[keep_idx]] + icc_keep = x[icc_idx[keep_idx]] + irc_keep = x[irc_idx[keep_idx]] + for j, (rr0, cc0) in enumerate(zip(centers_r, centers_c)): + inten_local = i0_keep[j] + if active_order >= 1: + inten_local = inten_local + ir_keep[j] * dr + ic_keep[j] * dc + if active_order >= 2: + inten_local = inten_local + irr_keep[j] * dr2 + icc_keep[j] * dc2 + irc_keep[j] * drdc + inten_local = torch.clamp(inten_local, min=0.0) + _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten_local) else: i0 = x[i_i0] - ir = x[i_ir] - ic = x[i_ic] for rr0, cc0 in zip(centers_r, centers_c): - inten = torch.clamp(i0 + ir * rr0 + ic * cc0, min=0.0) + inten = i0 + if active_order >= 1: + inten = inten + x[i_ir] * rr0 + x[i_ic] * cc0 + if active_order >= 2: + inten = inten + x[i_irr] * rr0 * rr0 + x[i_icc] * cc0 * cc0 + x[i_irc] * rr0 * cc0 + inten = torch.clamp(inten, min=0.0) _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten) def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: diff --git a/src/quantem/core/models/__init__.py b/src/quantem/core/models/__init__.py deleted file mode 100644 index 7330baff8..000000000 --- a/src/quantem/core/models/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -from quantem.core.models.base import Component as Component -from quantem.core.models.base import Model as Model -from quantem.core.models.base import ModelContext as ModelContext -from quantem.core.models.base import Overlay as Overlay -from quantem.core.models.base import Parameter as Parameter -from quantem.core.models.base import PreparedModel as PreparedModel - -from quantem.core.models.diffraction import DiskTemplate as DiskTemplate -from quantem.core.models.diffraction import Origin2D as Origin2D -from quantem.core.models.diffraction import SyntheticDiskLattice as SyntheticDiskLattice - -from quantem.core.models.background import DCBackground as DCBackground -from quantem.core.models.background import GaussianBackground as GaussianBackground - -__all__ = [ - "Parameter", - "Overlay", - "ModelContext", - "Component", - "Model", - "PreparedModel", - "Origin2D", - "DiskTemplate", - "SyntheticDiskLattice", - "DCBackground", - "GaussianBackground", -] diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 3362d60cd..5ed35999e 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -7,7 +7,6 @@ import numpy as np import torch -import torch.nn.functional as F from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey @@ -16,8 +15,8 @@ from quantem.core.datastructures.dataset4d import Dataset4d from quantem.core.datastructures.dataset4dstem import Dataset4dstem from quantem.core.io.serialize import AutoSerialize -from quantem.core.models.base import Model, ModelContext, Overlay, PreparedModel -from quantem.core.models.diffraction import Origin2D +from quantem.core.fitting.base import Model, ModelContext, Overlay, PreparedModel +from quantem.core.fitting.diffraction import Origin2D from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.core.visualization import show_2d @@ -300,8 +299,9 @@ def _fit_target_image( power: float | None, fit_disk_pixels: bool | None, fit_only_disk_pixels: bool, - enforce_disk_max_one: bool, + intensity_order: int | None, enforce_disk_center_of_mass: bool, + normalize_disk_template_max: bool, intensity_transform: str, weak_softplus_scale: float, progress: bool = False, @@ -325,11 +325,15 @@ def _fit_target_image( for p in self.prepared.params: if p.tags.get("role") == "disk_pixel": disk_mask[p.index] = True + if intensity_order is not None and str(p.tags.get("role", "")).startswith("lat_int"): + p_ord = int(p.tags.get("intensity_order", 0)) + if p_ord > int(intensity_order): + freeze[p.index] = True - # Cache template groups for optional per-step projection constraints. - disk_templates = self.prepared.ctx.fields.get("disk_templates", {}) + # Group disk pixel parameters by disk template for optional projection. disk_param_groups: list[dict[str, Any]] = [] - if enforce_disk_max_one or enforce_disk_center_of_mass: + if enforce_disk_center_of_mass or normalize_disk_template_max: + disk_templates = self.prepared.ctx.fields.get("disk_templates", {}) grouped: dict[str, list[tuple[int, int]]] = {} for p in self.prepared.params: if p.tags.get("role") != "disk_pixel": @@ -337,28 +341,18 @@ def _fit_target_image( name = str(p.tags.get("disk")) i_flat = int(p.tags.get("i")) grouped.setdefault(name, []).append((i_flat, int(p.index))) - for name, pairs in grouped.items(): - if name not in disk_templates: - continue - dmeta = disk_templates[name] - shape = dmeta.get("shape", None) - if shape is None: + dmeta = disk_templates.get(name) + if dmeta is None: continue - Ht, Wt = int(shape[0]), int(shape[1]) order = sorted(pairs, key=lambda t: t[0]) - flat_i = torch.as_tensor([t[0] for t in order], device=ctx.device, dtype=torch.long) p_idx = torch.as_tensor([t[1] for t in order], device=ctx.device, dtype=torch.long) - dr = dmeta["dr"][flat_i] - dc = dmeta["dc"][flat_i] - disk_param_groups.append( - { - "param_idx": p_idx, - "shape": (Ht, Wt), - "dr": dr, - "dc": dc, - } - ) + g: dict[str, Any] = {"param_idx": p_idx} + if enforce_disk_center_of_mass: + flat_i = torch.as_tensor([t[0] for t in order], device=ctx.device, dtype=torch.long) + g["dr"] = dmeta["dr"][flat_i] + g["dc"] = dmeta["dc"][flat_i] + disk_param_groups.append(g) if fit_only_disk_pixels: if not fit_disk_pixels: @@ -369,6 +363,10 @@ def _fit_target_image( freeze[disk_mask] = True x_frozen = x.detach().clone() + old_order_override = ctx.fields.get("lattice_intensity_order_override", None) + if intensity_order is not None: + ctx.fields["lattice_intensity_order_override"] = int(intensity_order) + target_t = target.to(device=ctx.device, dtype=ctx.dtype) target_t = self._apply_intensity_transform( target_t, mode=intensity_transform, weak_softplus_scale=weak_softplus_scale @@ -381,42 +379,33 @@ def clamp_inplace() -> None: x.data = torch.max(torch.min(x.data, ub), lb) if torch.any(freeze): x.data[freeze] = x_frozen[freeze] - if (enforce_disk_max_one or enforce_disk_center_of_mass) and disk_param_groups: + if (enforce_disk_center_of_mass or normalize_disk_template_max) and disk_param_groups: eps = torch.as_tensor(1e-12, device=ctx.device, dtype=ctx.dtype) for g in disk_param_groups: p_idx = g["param_idx"] if torch.all(freeze[p_idx]): continue - vals = x.data[p_idx] - vals = torch.clamp(vals, min=0.0) - + vals = torch.clamp(x.data[p_idx], min=0.0) if enforce_disk_center_of_mass: - mass = torch.sum(vals) - if mass > eps: - r_com = torch.sum(vals * g["dr"]) / mass - c_com = torch.sum(vals * g["dc"]) / mass - Ht, Wt = g["shape"] - img = vals.reshape(Ht, Wt)[None, None, :, :] - yy = torch.linspace(-1.0, 1.0, Ht, device=ctx.device, dtype=ctx.dtype) - xx = torch.linspace(-1.0, 1.0, Wt, device=ctx.device, dtype=ctx.dtype) - gy, gx = torch.meshgrid(yy, xx, indexing="ij") - sx = gx + (2.0 * c_com / max(Wt - 1, 1)) - sy = gy + (2.0 * r_com / max(Ht - 1, 1)) - grid = torch.stack((sx, sy), dim=-1)[None, :, :, :] - img_shift = F.grid_sample( - img, - grid, - mode="bilinear", - padding_mode="zeros", - align_corners=True, - ) - vals = torch.clamp(img_shift[0, 0].reshape(-1), min=0.0) - - if enforce_disk_max_one: + dr = g["dr"] + dc = g["dc"] + # Project template moments toward zero COM by iterative multiplicative reweighting. + for _ in range(12): + mass = torch.sum(vals) + if mass <= eps: + break + r_com = torch.sum(vals * dr) / mass + c_com = torch.sum(vals * dc) / mass + if torch.abs(r_com) <= 1e-5 and torch.abs(c_com) <= 1e-5: + break + var_r = torch.sum(vals * dr * dr) / mass + eps + var_c = torch.sum(vals * dc * dc) / mass + eps + vals = vals * torch.exp(-(r_com / var_r) * dr - (c_com / var_c) * dc) + vals = torch.clamp(vals, min=0.0) + if normalize_disk_template_max: vmax = torch.max(vals) if vmax > eps: vals = vals / vmax - x.data[p_idx] = vals def loss_fn() -> torch.Tensor: @@ -435,39 +424,46 @@ def loss_fn() -> torch.Tensor: diff = pred - target_t return torch.mean(diff * diff) - if method == "adam": - opt = torch.optim.Adam([x], lr=lr) - step_iter: Any = range(int(n_steps)) - if progress: - try: - from tqdm.auto import trange - - step_iter = trange(int(n_steps), desc=progress_desc or "Refining", leave=False) - except Exception: - warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) - for _ in step_iter: - opt.zero_grad(set_to_none=True) - loss = loss_fn() - loss.backward() - opt.step() + try: + if method == "adam": + opt = torch.optim.Adam([x], lr=lr) + step_iter: Any = range(int(n_steps)) + if progress: + try: + from tqdm.auto import trange + + step_iter = trange(int(n_steps), desc=progress_desc or "Refining", leave=False) + except Exception: + warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) + for _ in step_iter: + opt.zero_grad(set_to_none=True) + loss = loss_fn() + loss.backward() + opt.step() + clamp_inplace() + elif method == "lbfgs": + opt = torch.optim.LBFGS([x], lr=lr, max_iter=int(n_steps), line_search_fn="strong_wolfe") + + def closure() -> torch.Tensor: + opt.zero_grad(set_to_none=True) + loss = loss_fn() + loss.backward() + return loss + + opt.step(closure) clamp_inplace() - elif method == "lbfgs": - opt = torch.optim.LBFGS([x], lr=lr, max_iter=int(n_steps), line_search_fn="strong_wolfe") - - def closure() -> torch.Tensor: - opt.zero_grad(set_to_none=True) - loss = loss_fn() - loss.backward() - return loss - - opt.step(closure) - clamp_inplace() - else: - raise ValueError("method must be one of: 'lbfgs', 'adam'.") + else: + raise ValueError("method must be one of: 'lbfgs', 'adam'.") - with torch.no_grad(): - final_loss = float(loss_fn().detach().cpu()) - return x.detach().clone(), final_loss + with torch.no_grad(): + final_loss = float(loss_fn().detach().cpu()) + return x.detach().clone(), final_loss + finally: + if intensity_order is not None: + if old_order_override is None: + ctx.fields.pop("lattice_intensity_order_override", None) + else: + ctx.fields["lattice_intensity_order_override"] = old_order_override def _resolve_pattern_indices(self, indices: Any, n: int, index_shape: tuple[int, ...]) -> np.ndarray: if indices is None: @@ -523,8 +519,9 @@ def refine_mean_model( power: float | None = 1.0, fit_disk_pixels: bool | None = None, fit_only_disk_pixels: bool = False, - enforce_disk_max_one: bool = True, + intensity_order: int = 0, enforce_disk_center_of_mass: bool = True, + normalize_disk_template_max: bool = True, warmup_disk_steps: int = 0, overwrite_initial: bool = True, intensity_transform: str = "none", @@ -588,8 +585,9 @@ def refine_mean_model( power=power, fit_disk_pixels=True, fit_only_disk_pixels=True, - enforce_disk_max_one=bool(enforce_disk_max_one), + intensity_order=int(intensity_order), enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), + normalize_disk_template_max=bool(normalize_disk_template_max), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=bool(progress), @@ -604,8 +602,9 @@ def refine_mean_model( power=power, fit_disk_pixels=fit_disk_pixels, fit_only_disk_pixels=bool(fit_only_disk_pixels), - enforce_disk_max_one=bool(enforce_disk_max_one), + intensity_order=int(intensity_order), enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), + normalize_disk_template_max=bool(normalize_disk_template_max), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=bool(progress), @@ -630,8 +629,9 @@ def fit_all_patterns( power: float | None = 1.0, fit_disk_pixels: bool | None = None, fit_only_disk_pixels: bool = False, - enforce_disk_max_one: bool = True, + intensity_order: int | None = None, enforce_disk_center_of_mass: bool = True, + normalize_disk_template_max: bool = False, intensity_transform: str = "none", weak_softplus_scale: float = 1e-3, progress: bool = False, @@ -722,8 +722,9 @@ def fit_all_patterns( power=power, fit_disk_pixels=fit_disk_pixels, fit_only_disk_pixels=bool(fit_only_disk_pixels), - enforce_disk_max_one=bool(enforce_disk_max_one), + intensity_order=None if intensity_order is None else int(intensity_order), enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), + normalize_disk_template_max=bool(normalize_disk_template_max), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=False, @@ -830,8 +831,9 @@ def plot_mean_model( axes = [ax] for a in axes[:2]: - a.set_xlim(-pad, (W - 1) + pad) - a.set_ylim((H - 1) + pad, -pad) + # Match imshow's pixel-edge convention so overlay markers land on pixel centers. + a.set_xlim(-0.5 - pad, (W - 0.5) + pad) + a.set_ylim((H - 0.5) + pad, -0.5 - pad) if show_overlays: ovs = self.prepared.overlays(self.x_mean) From 73d12366dfbe50ea2d7f8c27705870175d45a4a2 Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 22 Feb 2026 09:25:50 -0800 Subject: [PATCH 025/113] slight tweaks --- src/quantem/diffraction/model_fitting.py | 57 +++++++++++++++++++++--- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 5ed35999e..e7728997a 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -302,6 +302,8 @@ def _fit_target_image( intensity_order: int | None, enforce_disk_center_of_mass: bool, normalize_disk_template_max: bool, + template_binary_weight: float, + template_binary_power: float, intensity_transform: str, weak_softplus_scale: float, progress: bool = False, @@ -332,7 +334,11 @@ def _fit_target_image( # Group disk pixel parameters by disk template for optional projection. disk_param_groups: list[dict[str, Any]] = [] - if enforce_disk_center_of_mass or normalize_disk_template_max: + if ( + enforce_disk_center_of_mass + or normalize_disk_template_max + or float(template_binary_weight) > 0.0 + ): disk_templates = self.prepared.ctx.fields.get("disk_templates", {}) grouped: dict[str, list[tuple[int, int]]] = {} for p in self.prepared.params: @@ -348,6 +354,9 @@ def _fit_target_image( order = sorted(pairs, key=lambda t: t[0]) p_idx = torch.as_tensor([t[1] for t in order], device=ctx.device, dtype=torch.long) g: dict[str, Any] = {"param_idx": p_idx} + shape = dmeta.get("shape", None) + if shape is not None: + g["shape"] = (int(shape[0]), int(shape[1])) if enforce_disk_center_of_mass: flat_i = torch.as_tensor([t[0] for t in order], device=ctx.device, dtype=torch.long) g["dr"] = dmeta["dr"][flat_i] @@ -379,7 +388,10 @@ def clamp_inplace() -> None: x.data = torch.max(torch.min(x.data, ub), lb) if torch.any(freeze): x.data[freeze] = x_frozen[freeze] - if (enforce_disk_center_of_mass or normalize_disk_template_max) and disk_param_groups: + if ( + enforce_disk_center_of_mass + or normalize_disk_template_max + ) and disk_param_groups: eps = torch.as_tensor(1e-12, device=ctx.device, dtype=ctx.dtype) for g in disk_param_groups: p_idx = g["param_idx"] @@ -420,9 +432,32 @@ def loss_fn() -> torch.Tensor: m = ctx.mask diff = (pred - target_t) * m denom = torch.clamp(torch.sum(m), min=1.0) - return torch.sum(diff * diff) / denom - diff = pred - target_t - return torch.mean(diff * diff) + loss = torch.sum(diff * diff) / denom + else: + diff = pred - target_t + loss = torch.mean(diff * diff) + + if float(template_binary_weight) > 0.0 and disk_param_groups: + bp = torch.as_tensor(0.0, device=ctx.device, dtype=ctx.dtype) + n_bp = 0 + eps = torch.as_tensor(1e-12, device=ctx.device, dtype=ctx.dtype) + pwr = float(template_binary_power) + for g in disk_param_groups: + p_idx = g["param_idx"] + vals = torch.clamp(x[p_idx], min=0.0) + vmax = torch.max(vals) + if vmax <= eps: + continue + vals_n = vals / (vmax + eps) + core = vals_n * (1.0 - vals_n) + if pwr != 1.0: + core = torch.pow(core + eps, pwr) + bp = bp + torch.mean(core) + n_bp += 1 + if n_bp > 0: + loss = loss + float(template_binary_weight) * (bp / n_bp) + + return loss try: if method == "adam": @@ -521,7 +556,9 @@ def refine_mean_model( fit_only_disk_pixels: bool = False, intensity_order: int = 0, enforce_disk_center_of_mass: bool = True, - normalize_disk_template_max: bool = True, + normalize_disk_template_max: bool = False, + template_binary_weight: float = 0.0, + template_binary_power: float = 1.0, warmup_disk_steps: int = 0, overwrite_initial: bool = True, intensity_transform: str = "none", @@ -588,6 +625,8 @@ def refine_mean_model( intensity_order=int(intensity_order), enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), normalize_disk_template_max=bool(normalize_disk_template_max), + template_binary_weight=float(template_binary_weight), + template_binary_power=float(template_binary_power), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=bool(progress), @@ -605,6 +644,8 @@ def refine_mean_model( intensity_order=int(intensity_order), enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), normalize_disk_template_max=bool(normalize_disk_template_max), + template_binary_weight=float(template_binary_weight), + template_binary_power=float(template_binary_power), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=bool(progress), @@ -632,6 +673,8 @@ def fit_all_patterns( intensity_order: int | None = None, enforce_disk_center_of_mass: bool = True, normalize_disk_template_max: bool = False, + template_binary_weight: float = 0.0, + template_binary_power: float = 1.0, intensity_transform: str = "none", weak_softplus_scale: float = 1e-3, progress: bool = False, @@ -725,6 +768,8 @@ def fit_all_patterns( intensity_order=None if intensity_order is None else int(intensity_order), enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), normalize_disk_template_max=bool(normalize_disk_template_max), + template_binary_weight=float(template_binary_weight), + template_binary_power=float(template_binary_power), intensity_transform=intensity_transform, weak_softplus_scale=float(weak_softplus_scale), progress=False, From 57563fbadb986e71cdd1d5e86f4269c302802eab Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Feb 2026 16:00:11 -0800 Subject: [PATCH 026/113] dataset4dstem.from_file consistent with read_4dstem args --- src/quantem/core/datastructures/dataset4dstem.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index dd92b9146..ed66f6a2c 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -1,3 +1,4 @@ +from os import PathLike from typing import Any, Self import matplotlib.pyplot as plt @@ -8,7 +9,6 @@ from quantem.core.datastructures.dataset2d import Dataset2d from quantem.core.datastructures.dataset4d import Dataset4d from quantem.core.datastructures.polar4dstem import dataset4dstem_polar_transform - from quantem.core.utils.validators import ensure_valid_array from quantem.core.visualization import show_2d from quantem.core.visualization.visualization_utils import ScalebarConfig @@ -74,7 +74,7 @@ def __init__( _token : object | None, optional Token to prevent direct instantiation, by default None """ - mdata_keys_4dstem = ["q_to_r_rotation_ccw_deg", 'q_transpose', "ellipticity"] + mdata_keys_4dstem = ["q_to_r_rotation_ccw_deg", "q_transpose", "ellipticity"] for k in mdata_keys_4dstem: if k not in metadata.keys(): metadata[k] = None @@ -93,13 +93,13 @@ def __init__( self._virtual_detectors = {} # Store detector information for regeneration @classmethod - def from_file(cls, file_path: str, file_type: str) -> "Dataset4dstem": + def from_file(cls, file_path: str | PathLike, file_type: str | None = None) -> "Dataset4dstem": """ Create a new Dataset4dstem from a file. Parameters ---------- - file_path : str + file_path : str | PathLike Path to the data file file_type : str The type of file reader needed. See rosettasciio for supported formats @@ -754,5 +754,4 @@ def median_filter_masked_pixels(self, mask: np.ndarray, kernel_width: int = 3): self.array[:, :, x_min:x_max, y_min:y_max], axis=(2, 3) ) - - polar_transform = dataset4dstem_polar_transform \ No newline at end of file + polar_transform = dataset4dstem_polar_transform From b2f5253165eab2aa6c67c785434bb4b2ad1fc4b8 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Thu, 26 Feb 2026 14:08:41 -0800 Subject: [PATCH 027/113] refactor in progress, base and rendering largely done, working not perfect --- src/quantem/core/fitting/__init__.py | 6 - src/quantem/core/fitting/background.py | 146 ++-- src/quantem/core/fitting/base.py | 293 +------ src/quantem/core/fitting/diffraction.py | 774 ++++++------------ src/quantem/diffraction/model_fitting.py | 994 ++++++----------------- 5 files changed, 605 insertions(+), 1608 deletions(-) diff --git a/src/quantem/core/fitting/__init__.py b/src/quantem/core/fitting/__init__.py index 5aa674593..f7d3c3e32 100644 --- a/src/quantem/core/fitting/__init__.py +++ b/src/quantem/core/fitting/__init__.py @@ -3,12 +3,9 @@ from quantem.core.fitting.base import Component as Component from quantem.core.fitting.base import Model as Model from quantem.core.fitting.base import ModelContext as ModelContext -from quantem.core.fitting.base import Overlay as Overlay from quantem.core.fitting.base import Parameter as Parameter -from quantem.core.fitting.base import PreparedModel as PreparedModel from quantem.core.fitting.diffraction import DiskTemplate as DiskTemplate -from quantem.core.fitting.diffraction import Origin2D as Origin2D from quantem.core.fitting.diffraction import SyntheticDiskLattice as SyntheticDiskLattice from quantem.core.fitting.background import DCBackground as DCBackground @@ -16,12 +13,9 @@ __all__ = [ "Parameter", - "Overlay", "ModelContext", "Component", "Model", - "PreparedModel", - "Origin2D", "DiskTemplate", "SyntheticDiskLattice", "DCBackground", diff --git a/src/quantem/core/fitting/background.py b/src/quantem/core/fitting/background.py index 2db886163..7c50ea428 100644 --- a/src/quantem/core/fitting/background.py +++ b/src/quantem/core/fitting/background.py @@ -1,107 +1,87 @@ from __future__ import annotations -"""Background components for diffraction model fitting.""" - -from dataclasses import dataclass -from typing import Any, Iterable +from typing import Sequence, cast +import numpy as np import torch +from torch import nn + +from quantem.core.fitting.base import RenderComponent, RenderContext +from quantem.core.fitting.diffraction import OriginND + -from quantem.core.fitting.base import Component, ModelContext, Overlay, Parameter -from quantem.core.fitting.diffraction import _origin_indices +def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: + if isinstance(value, (list, tuple, np.ndarray)): + if len(value) == 0: + raise ValueError(f"{name} cannot be empty.") + if value[0] is None: + raise ValueError(f"{name} initial value cannot be None.") + return float(value[0]) + return float(cast(float | int, value)) -class DCBackground(Component): - """ - Uniform additive background with nonnegative intensity. +def _softplus_pos(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: + return torch.nn.functional.softplus(x) + eps - Parameters - ---------- - intensity - Constant background intensity parameter specification. - name - Component name. - """ +def _relu_pos(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: + return torch.nn.functional.relu(x) + eps + + +class DCBackground(RenderComponent): def __init__( self, *, - intensity: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + intensity: float | int | Sequence[float | int | None] = 0.0, name: str = "dc_background", ): - super().__init__(name=name) - self.p_intensity = Parameter(intensity, lower_bound=0.0, tags={"role": "dc_bg"}) - - def parameters(self) -> list[Parameter]: - return [self.p_intensity] - - def prepare(self, ctx: ModelContext) -> Any: - idx = self.p_intensity.index - - @dataclass - class Prepared: - def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - out.add_(x[idx]) - - def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: - return [] - - return Prepared() - + super().__init__() + self.name = str(name) + self.intensity_raw = nn.Parameter( + torch.tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) + ) -class GaussianBackground(Component): - """ - Radial Gaussian background centered at a named model origin. + def forward(self, ctx: RenderContext) -> torch.Tensor: + inten = _relu_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + # inten = _softplus_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + return torch.ones(ctx.shape, device=ctx.device, dtype=ctx.dtype) * inten - Parameters - ---------- - sigma - Gaussian width parameter specification (constrained to `>= 1e-6`). - intensity - Nonnegative Gaussian amplitude parameter specification. - origin_key - Name of the origin to use from `ModelContext.fields["origins"]`. - name - Component name. - """ +class GaussianBackground(RenderComponent): def __init__( self, *, - sigma: float | tuple[float, float] | tuple[float, float, float | None] = (40.0, 5.0, None), - intensity: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + sigma: float | int | Sequence[float | int | None] = (40.0, 5.0, None), + intensity: float | int | Sequence[float | int | None] = 0.0, + origin: OriginND | None = None, origin_key: str = "origin", name: str = "gaussian_background", ): - super().__init__(name=name) + super().__init__() + self.name = str(name) + self.origin = origin self.origin_key = str(origin_key) - self.p_sigma = Parameter(sigma, lower_bound=1e-6, upper_bound=None, tags={"role": "gauss_sigma"}) - self.p_intensity = Parameter(intensity, lower_bound=0.0, tags={"role": "gauss_int"}) - - def parameters(self) -> list[Parameter]: - return [self.p_sigma, self.p_intensity] - - def prepare(self, ctx: ModelContext) -> Any: - i_sig = self.p_sigma.index - i_int = self.p_intensity.index - r_idx, c_idx = _origin_indices(ctx, self.origin_key) - - rr = torch.arange(ctx.H, device=ctx.device, dtype=ctx.dtype)[:, None] - cc = torch.arange(ctx.W, device=ctx.device, dtype=ctx.dtype)[None, :] - - @dataclass - class Prepared: - def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - sig = torch.clamp(x[i_sig], min=1e-6) - inten = x[i_int] - r0 = x[r_idx] - c0 = x[c_idx] - dr = rr - r0 - dc = cc - c0 - r2 = dr * dr + dc * dc - out.add_(inten * torch.exp(-0.5 * r2 / (sig * sig))) - - def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: - return [] - - return Prepared() + self.sigma_raw = nn.Parameter( + torch.tensor(_parse_init(sigma, name="sigma"), dtype=torch.float32) + ) + self.intensity_raw = nn.Parameter( + torch.tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) + ) + + def set_origin(self, origin: OriginND) -> None: + self.origin = origin + + def forward(self, ctx: RenderContext) -> torch.Tensor: + if self.origin is None: + raise RuntimeError("GaussianBackground requires an OriginND instance.") + + rr = torch.arange(ctx.shape[0], device=ctx.device, dtype=ctx.dtype)[:, None] + cc = torch.arange(ctx.shape[1], device=ctx.device, dtype=ctx.dtype)[None, :] + r0, c0 = self.origin.coords[0], self.origin.coords[1] + + sigma = _relu_pos(self.sigma_raw.to(device=ctx.device, dtype=ctx.dtype), eps=1e-6) + # sigma = _softplus_pos(self.sigma_raw.to(device=ctx.device, dtype=ctx.dtype), eps=1e-6) + inten = _relu_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + # inten = _softplus_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + r2 = (rr - r0) ** 2 + (cc - c0) ** 2 + return inten * torch.exp(-0.5 * r2 / (sigma * sigma)) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 53a4c0aa5..c37ccef98 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -1,273 +1,54 @@ from __future__ import annotations -"""Composable model primitives for diffraction-style forward models.""" +from dataclasses import dataclass, field +from typing import Any, cast -from dataclasses import dataclass -from typing import Any, Iterable, Mapping, Sequence - -import numpy as np import torch +from torch import nn -@dataclass(frozen=True) -class Overlay: - """Lightweight plotting overlay emitted by prepared components.""" - - kind: str - data: dict[str, Any] - - -class Parameter: - """ - Scalar model parameter with optional bounds and metadata tags. - - Parameters - ---------- - value - Initial parameter specification. Supported forms: - - scalar: `v0` - - 2-sequence: `(v0, dev)` interpreted as bounds `(v0-dev, v0+dev)` - - 3-sequence: `(v0, lb, ub)` where either bound can be None - lower_bound, upper_bound - Optional explicit overrides for lower/upper bound. - tags - Optional metadata dictionary used by higher-level fitting code for - grouping/freezing/selecting parameters. - - Notes - ----- - Parameter indices are assigned during `Model.compile(...)`. - """ - - def __init__( - self, - value: float | Sequence[float], - *, - lower_bound: float | None = None, - upper_bound: float | None = None, - tags: Mapping[str, Any] | None = None, - ): - self.tags: dict[str, Any] = {} if tags is None else dict(tags) - self.initial_value, self.lower_bound, self.upper_bound = self._parse_value( - value, lower_bound, upper_bound - ) - self._index: int | None = None - - @staticmethod - def _parse_value( - value: float | Sequence[float], - lower_bound: float | None, - upper_bound: float | None, - ) -> tuple[float, float | None, float | None]: - if isinstance(value, (list, tuple, np.ndarray)): - v = list(value) - if len(v) == 2: - v0 = float(v[0]) - dev = float(v[1]) - lb = v0 - dev if lower_bound is None else float(lower_bound) - ub = v0 + dev if upper_bound is None else float(upper_bound) - return v0, lb, ub - if len(v) == 3: - v0 = float(v[0]) - lb = None if v[1] is None else float(v[1]) - ub = None if v[2] is None else float(v[2]) - if lower_bound is not None: - lb = float(lower_bound) - if upper_bound is not None: - ub = float(upper_bound) - return v0, lb, ub - raise ValueError("Parameter sequences must have length 2 or 3.") - v0 = float(value) - return v0, lower_bound, upper_bound - - def bind(self, index: int) -> None: - self._index = int(index) - - @property - def index(self) -> int: - if self._index is None: - raise RuntimeError("Parameter is not bound. Call Model.compile(...) first.") - return self._index - - -class ModelContext: - """ - Execution context shared by components during preparation and rendering. - - Parameters - ---------- - H, W - Output image height and width. - device, dtype - Torch device and dtype for parameter tensors/rendering. - mask - Optional per-pixel mask used by fitting loss functions. - fields - Mutable shared dictionary used by components to publish/consume - prepared state (for example origins or template metadata). - """ - - def __init__( - self, - *, - H: int, - W: int, - device: torch.device, - dtype: torch.dtype, - mask: torch.Tensor | None = None, - fields: Mapping[str, Any] | None = None, - ): - self.H = int(H) - self.W = int(W) - self.device = device - self.dtype = dtype - self.mask = mask - self.fields: dict[str, Any] = {} if fields is None else dict(fields) - - def __getattr__(self, name: str) -> Any: - if name in self.fields: - return self.fields[name] - raise AttributeError(name) - - -class Component: - """ - Abstract base class for additive model components. +@dataclass +class RenderContext: + shape: tuple[int, ...] + device: torch.device + dtype: torch.dtype + mask: torch.Tensor | None = None + fields: dict[str, Any] = field(default_factory=dict) - Subclasses implement: - - `parameters()` to expose fit parameters - - `prepare(ctx)` to build prepared render objects - """ - def __init__(self, *, name: str): - self.name = str(name) - - def parameters(self) -> list[Parameter]: - return [] - - def prepare(self, ctx: ModelContext) -> Any: +class RenderComponent(nn.Module): + def forward(self, ctx: RenderContext) -> torch.Tensor: raise NotImplementedError + def constraint_loss(self, ctx: RenderContext) -> torch.Tensor: + return torch.zeros((), device=ctx.device, dtype=ctx.dtype) -class PreparedModel: - """ - Compiled model bundle with bound parameter vectors and render helpers. - - Attributes - ---------- - x0, lb, ub - Initial parameter vector and bound vectors, all on `ctx.device`. - components - Prepared component objects called in sequence during `render`. - """ - def __init__( - self, - *, - components: list[Any], - ctx: ModelContext, - params: list[Parameter], - x0: torch.Tensor, - lb: torch.Tensor, - ub: torch.Tensor, - ): - self.components = components - self.ctx = ctx - self.params = params - self.x0 = x0 - self.lb = lb - self.ub = ub +class AdditiveRenderModel(nn.Module): + def __init__(self, *, origin: nn.Module, components: list[RenderComponent]): + super().__init__() + self.origin = origin + self.components = nn.ModuleList(components) - def render(self, x: torch.Tensor) -> torch.Tensor: - """Render a model image for parameter vector ``x``.""" - out = torch.zeros((self.ctx.H, self.ctx.W), device=self.ctx.device, dtype=self.ctx.dtype) - for c in self.components: - c.render(out, x, self.ctx) + def forward(self, ctx: RenderContext) -> torch.Tensor: + if len(self.components) == 0: + return torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) + first = cast(RenderComponent, self.components[0]) + out = first(ctx) + for module in self.components[1:]: + component = cast(RenderComponent, module) + out = out + component(ctx) return out - def render_initial(self) -> torch.Tensor: - """Render the model using the compile-time initial parameter vector.""" - return self.render(self.x0) - - def overlays(self, x: torch.Tensor | None = None) -> list[Overlay]: - """Collect component overlays for plotting/debugging.""" - out: list[Overlay] = [] - for c in self.components: - fn = getattr(c, "overlays", None) - if fn is None: - continue - ov = fn(self.x0 if x is None else x, self.ctx) - if ov: - out.extend(list(ov)) - return out - - -class Model: - """ - Container for additive components that compiles to a `PreparedModel`. - - Notes - ----- - Component order defines render order and can also define dependency order - when components share prepared state through `ModelContext.fields`. - """ - - def __init__(self): - self._components: list[Component] = [] - - def add(self, items: Iterable[Component]) -> "Model": - """ - Append components to the model in render order. - - Parameters - ---------- - items - Iterable of `Component` instances. - """ - for obj in items: - if not isinstance(obj, Component): - raise TypeError("Model.add expects Component objects.") - self._components.append(obj) - return self - - def parameter_list(self) -> list[Parameter]: - """Return the flattened parameter list across all components.""" - params: list[Parameter] = [] - for c in self._components: - params.extend(c.parameters()) - return params - - def compile(self, ctx: ModelContext) -> PreparedModel: - """ - Bind parameter indices and prepare components for fast rendering. - - Parameters - ---------- - ctx - Model execution context. - - Returns - ------- - PreparedModel - Compiled model with `x0`, bounds, and prepared component state. - """ - params = self.parameter_list() - for i, p in enumerate(params): - p.bind(i) - - x0 = torch.zeros((len(params),), device=ctx.device, dtype=ctx.dtype) - lb = torch.full((len(params),), -torch.inf, device=ctx.device, dtype=ctx.dtype) - ub = torch.full((len(params),), torch.inf, device=ctx.device, dtype=ctx.dtype) - - for p in params: - x0[p.index] = torch.as_tensor(p.initial_value, device=ctx.device, dtype=ctx.dtype) - if p.lower_bound is not None: - lb[p.index] = torch.as_tensor(p.lower_bound, device=ctx.device, dtype=ctx.dtype) - if p.upper_bound is not None: - ub[p.index] = torch.as_tensor(p.upper_bound, device=ctx.device, dtype=ctx.dtype) + def total_constraint_loss(self, ctx: RenderContext) -> torch.Tensor: + loss = torch.zeros((), device=ctx.device, dtype=ctx.dtype) + for module in self.components: + component = cast(RenderComponent, module) + loss = loss + component.constraint_loss(ctx) + return loss - prepared_components: list[Any] = [] - for c in self._components: - prepared_components.append(c.prepare(ctx)) - return PreparedModel(components=prepared_components, ctx=ctx, params=params, x0=x0, lb=lb, ub=ub) +Component = RenderComponent +ModelContext = RenderContext +Model = AdditiveRenderModel +Parameter = nn.Parameter diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 78a45c141..13224632a 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -1,30 +1,30 @@ from __future__ import annotations -"""Diffraction-specific model components and low-level rendering helpers.""" - -from dataclasses import dataclass -from typing import Any, Iterable, Sequence +from typing import Iterable, Sequence, cast import numpy as np import torch +from torch import nn + +from quantem.core.fitting.base import RenderComponent, RenderContext + -from quantem.core.fitting.base import Component, ModelContext, Overlay, Parameter +def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: + if isinstance(value, (list, tuple, np.ndarray)): + if len(value) == 0: + raise ValueError(f"{name} cannot be empty.") + if value[0] is None: + raise ValueError(f"{name} initial value cannot be None.") + return float(value[0]) + return float(cast(float | int, value)) -def _as_t(x: Any, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor: - """ - Convert values to tensors on the requested device/dtype. +def _softplus_pos(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: + return torch.nn.functional.softplus(x) + eps - Parameters - ---------- - x - Input scalar/array/tensor. - device, dtype - Target torch device and dtype. - """ - if torch.is_tensor(x): - return x.to(device=device, dtype=dtype) - return torch.as_tensor(x, device=device, dtype=dtype) + +def _relu_pos(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: + return torch.nn.functional.relu(x) + eps def _splat_patch( @@ -37,34 +37,14 @@ def _splat_patch( dc: torch.Tensor, scale: torch.Tensor, ) -> None: - """ - Render a shifted patch into ``out`` with bilinear interpolation. - - Parameters - ---------- - out - Destination image `(H, W)` updated in-place. - r0, c0 - Patch center coordinates in output-image row/column space. - patch_vals - Flattened patch intensities. - dr, dc - Flattened local row/column offsets for each patch sample. - scale - Scalar or vector multiplier applied to `patch_vals`. - """ - H = out.shape[0] - W = out.shape[1] - + h, w = out.shape r = r0 + dr c = c0 + dc r_base = torch.floor(r) c_base = torch.floor(c) - fr = r - r_base fc = c - c_base - r0i = r_base.to(torch.long) c0i = c_base.to(torch.long) @@ -72,13 +52,12 @@ def _splat_patch( w01 = (1.0 - fr) * fc w10 = fr * (1.0 - fc) w11 = fr * fc - v = patch_vals * scale def put(rr: torch.Tensor, cc: torch.Tensor, ww: torch.Tensor) -> None: - m = (rr >= 0) & (rr < H) & (cc >= 0) & (cc < W) - if torch.any(m): - out.index_put_((rr[m], cc[m]), (v[m] * ww[m]), accumulate=True) + keep = (rr >= 0) & (rr < h) & (cc >= 0) & (cc < w) + if torch.any(keep): + out.index_put_((rr[keep], cc[keep]), v[keep] * ww[keep], accumulate=True) put(r0i, c0i, w00) put(r0i, c0i + 1, w01) @@ -86,111 +65,18 @@ def put(rr: torch.Tensor, cc: torch.Tensor, ww: torch.Tensor) -> None: put(r0i + 1, c0i + 1, w11) -def _origin_indices(ctx: ModelContext, origin_key: str) -> tuple[int, int]: - """ - Resolve origin parameter indices from ``ModelContext``. - - Parameters - ---------- - ctx - Model context with an `origins` registry in `ctx.fields`. - origin_key - Origin name to resolve. - """ - origins: dict[str, Any] = ctx.fields.get("origins", {}) - o = origins.get(origin_key) - if o is None or "row_param" not in o or "col_param" not in o: - raise RuntimeError(f"Origin '{origin_key}' not defined. Origin2D must be included first.") - return int(o["row_param"].index), int(o["col_param"].index) - - -class Origin2D(Component): - """ - Named 2D origin component that exposes row/column fit parameters. - - Parameters - ---------- - name - Component name. - origin_key - Key used to publish this origin into `ctx.fields["origins"]`. - row, col - Parameter specifications for origin row/column. - """ +class OriginND(nn.Module): + def __init__(self, *, ndim: int, init: Sequence[float]): + super().__init__() + if int(ndim) <= 0: + raise ValueError("ndim must be >= 1.") + if len(init) != int(ndim): + raise ValueError("init length must match ndim.") + self.ndim = int(ndim) + self.coords = nn.Parameter(torch.as_tensor(init, dtype=torch.float32).reshape(self.ndim)) - def __init__( - self, - *, - name: str = "origin", - origin_key: str = "origin", - row: float | tuple[float, float] | tuple[float, float, float | None] = (0.0, 0.0, None), - col: float | tuple[float, float] | tuple[float, float, float | None] = (0.0, 0.0, None), - ): - super().__init__(name=name) - self.origin_key = str(origin_key) - self.p_row = Parameter(row, tags={"role": "origin_row", "origin_key": self.origin_key}) - self.p_col = Parameter(col, tags={"role": "origin_col", "origin_key": self.origin_key}) - - def parameters(self) -> list[Parameter]: - return [self.p_row, self.p_col] - - def prepare(self, ctx: ModelContext) -> Any: - origins = ctx.fields.setdefault("origins", {}) - origins[self.origin_key] = {"row_param": self.p_row, "col_param": self.p_col} - - i_r = self.p_row.index - i_c = self.p_col.index - - @dataclass - class Prepared: - def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - return - - def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: - return [ - Overlay( - kind="points_rc", - data={ - "r": x[i_r].detach(), - "c": x[i_c].detach(), - "marker": "+", - "s": 100.0, - "color": "dodgerblue", - }, - ) - ] - - return Prepared() - - -class DiskTemplate(Component): - """ - Template patch component used for central disks and lattice motifs. - - Parameters - ---------- - name - Template name used for registry lookup by dependent components. - array - 2D template image. - refine_all_pixels - If True, every template pixel is exposed as a fit parameter. - place_at_origin - If True, render this template once at the named origin with a - separate nonnegative intensity parameter. - normalize - Optional array normalization mode: `"none"`, `"max"`, `"mean"`. - origin_key - Origin key used when `place_at_origin=True`. - intensity - Intensity parameter specification used when `place_at_origin=True`. - - Notes - ----- - The template is also published into `ctx.fields["disk_templates"]` so - lattice components can reuse its prepared sampling offsets. - """ +class DiskTemplate(RenderComponent): def __init__( self, *, @@ -199,48 +85,47 @@ def __init__( refine_all_pixels: bool = False, place_at_origin: bool = False, normalize: str = "none", + origin: OriginND | None = None, origin_key: str = "origin", - intensity: float | tuple[float, float] | tuple[float, float, float | None] = 1.0, + intensity: float | Sequence[float] = 1.0, ): - super().__init__(name=name) + super().__init__() + self.name = str(name) + self.refine_all_pixels = bool(refine_all_pixels) + self.place_at_origin = bool(place_at_origin) + self.origin = origin + self.origin_key = str(origin_key) a = np.asarray(array, dtype=np.float32) if a.ndim != 2: raise ValueError("DiskTemplate.array must be 2D.") - if normalize == "max": - mx = float(np.max(a)) - if mx > 0: - a = a / mx + s = float(np.max(a)) + if s > 0.0: + a = a / s elif normalize == "mean": - m = float(np.mean(a)) - if m != 0: - a = a / m - elif normalize == "none": - pass - else: + s = float(np.mean(a)) + if s != 0.0: + a = a / s + elif normalize != "none": raise ValueError("normalize must be one of: 'none', 'max', 'mean'.") - self.array = a - self.refine_all_pixels = bool(refine_all_pixels) - self.place_at_origin = bool(place_at_origin) - self.origin_key = str(origin_key) - - self.p_pixels: list[Parameter] = [] - if self.refine_all_pixels: - flat = self.array.ravel() - for i, v in enumerate(flat): - self.p_pixels.append( - Parameter( - float(v), - lower_bound=0.0, - tags={"role": "disk_pixel", "disk": self.name, "i": int(i)}, - ) - ) + template = torch.as_tensor(a, dtype=torch.float32) + self.template_raw = nn.Parameter(template.clone(), requires_grad=self.refine_all_pixels) - self.p_intensity: Parameter | None = None if self.place_at_origin: - self.p_intensity = Parameter(intensity, lower_bound=0.0, tags={"role": "disk_intensity", "disk": self.name}) + self.intensity_raw = nn.Parameter( + torch.as_tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) + ) + else: + self.intensity_raw = None + + ht, wt = int(template.shape[0]), int(template.shape[1]) + rr, cc = np.mgrid[0:ht, 0:wt] + rr = rr.astype(np.float32) - (ht - 1) * 0.5 + cc = cc.astype(np.float32) - (wt - 1) * 0.5 + self.register_buffer("dr", torch.as_tensor(rr.ravel(), dtype=torch.float32)) + self.register_buffer("dc", torch.as_tensor(cc.ravel(), dtype=torch.float32)) @classmethod def from_array( @@ -251,8 +136,9 @@ def from_array( refine_all_pixels: bool = False, place_at_origin: bool = False, normalize: str = "none", + origin: OriginND | None = None, origin_key: str = "origin", - intensity: float | tuple[float, float] | tuple[float, float, float | None] = 1.0, + intensity: float | Sequence[float] = 1.0, ) -> "DiskTemplate": return cls( name=name, @@ -260,396 +146,234 @@ def from_array( refine_all_pixels=refine_all_pixels, place_at_origin=place_at_origin, normalize=normalize, + origin=origin, origin_key=origin_key, intensity=intensity, ) - def parameters(self) -> list[Parameter]: - out = list(self.p_pixels) - if self.p_intensity is not None: - out.append(self.p_intensity) - return out - - def prepare(self, ctx: ModelContext) -> Any: - device = ctx.device - dtype = ctx.dtype - - Ht, Wt = self.array.shape - rr, cc = np.mgrid[0:Ht, 0:Wt] - rr = rr.astype(np.float32) - (Ht - 1) * 0.5 - cc = cc.astype(np.float32) - (Wt - 1) * 0.5 - - dr = _as_t(rr.ravel(), device=device, dtype=dtype) - dc = _as_t(cc.ravel(), device=device, dtype=dtype) + def set_origin(self, origin: OriginND) -> None: + self.origin = origin + def patch_values(self) -> torch.Tensor: if self.refine_all_pixels: - pix_idx = torch.as_tensor([p.index for p in self.p_pixels], device=device, dtype=torch.long) - base_vals = None - else: - pix_idx = None - base_vals = _as_t(self.array.ravel(), device=device, dtype=dtype) - - ctx.fields.setdefault("disk_templates", {})[self.name] = { - "dr": dr, - "dc": dc, - "pix_idx": pix_idx, - "base": base_vals, - "shape": (int(Ht), int(Wt)), - } - - i_int = None if self.p_intensity is None else self.p_intensity.index - if i_int is None: - r_idx = c_idx = None - else: - r_idx, c_idx = _origin_indices(ctx, self.origin_key) - - @dataclass - class Prepared: - def patch(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: - if pix_idx is None: - vals = base_vals - else: - vals = x[pix_idx] - return vals, dr, dc - - def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - if i_int is None: - return - vals, drl, dcl = self.patch(x) - r0 = x[r_idx] - c0 = x[c_idx] - _splat_patch(out, r0=r0, c0=c0, patch_vals=vals, dr=drl, dc=dcl, scale=x[i_int]) - - def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: - return [] - - return Prepared() - - -class SyntheticDiskLattice(Component): - """ - Lattice of shifted ``DiskTemplate`` patches around a shared origin. - - Parameters - ---------- - name - Component name. - disk - `DiskTemplate` used as the motif for each lattice spot. - u_row, u_col, v_row, v_col - Basis vector parameter specifications in row/column coordinates. - u_max, v_max - Inclusive lattice index extents. - intensity_0, intensity_row, intensity_col - Intensity parameter specifications. Interpretation depends on - `per_disk_intensity` and intensity-order settings. - intensity_row_row, intensity_col_col, intensity_row_col - Optional quadratic intensity terms for order-2 models. - per_disk_intensity - If True, allocate independent base intensities per lattice disk. - per_disk_slopes - Backward-compatible switch controlling whether linear terms are included - when `max_intensity_order` is not explicitly provided. - max_intensity_order - Maximum polynomial order used by this component (`0`, `1`, or `2`). - default_pattern_intensity_order - Default active intensity order used during rendering unless overridden - in `ctx.fields["lattice_intensity_order_override"]`. - center_intensity_0 - Optional override for the `(u, v) == (0, 0)` disk base intensity. - exclude_indices - Optional set/list of lattice indices to skip. - boundary_px - Keep only lattice centers within `[boundary_px, size-1-boundary_px]`. - origin_key - Origin key used for center placement. - - Notes - ----- - Intensity models: - - shared mode (`per_disk_intensity=False`): - `max(i0 + ir*row_center + ic*col_center, 0)` - - per-disk scalar mode (`per_disk_intensity=True`, order=0): - `max(i0_i, 0)` - - per-disk local affine mode (`per_disk_intensity=True`, order=1): - `max(i0_i + ir_i*dr + ic_i*dc, 0)` where `dr/dc` are template-local offsets. - - per-disk local quadratic mode (`per_disk_intensity=True`, order=2): - `max(i0_i + ir_i*dr + ic_i*dc + irr_i*dr^2 + icc_i*dc^2 + irc_i*dr*dc, 0)`. - """ + return _relu_pos(self.template_raw).reshape(-1) + # return _softplus_pos(self.template_raw).reshape(-1) + return self.template_raw.reshape(-1) + + def patch_offsets(self) -> tuple[torch.Tensor, torch.Tensor]: + return cast(torch.Tensor, self.dr), cast(torch.Tensor, self.dc) + + def add_patch( + self, out: torch.Tensor, *, r0: torch.Tensor, c0: torch.Tensor, scale: torch.Tensor + ) -> None: + vals = self.patch_values().to(device=out.device, dtype=out.dtype) + dr = cast(torch.Tensor, self.dr).to(device=out.device, dtype=out.dtype) + dc = cast(torch.Tensor, self.dc).to(device=out.device, dtype=out.dtype) + _splat_patch(out, r0=r0, c0=c0, patch_vals=vals, dr=dr, dc=dc, scale=scale) + + def forward(self, ctx: RenderContext) -> torch.Tensor: + out = torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) + if not self.place_at_origin: + return out + if self.origin is None: + raise RuntimeError( + "DiskTemplate with place_at_origin=True requires an OriginND instance." + ) + r0, c0 = self.origin.coords[0], self.origin.coords[1] + if self.intensity_raw is None: + raise RuntimeError("DiskTemplate intensity parameter is missing.") + scale = _relu_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + # scale = _softplus_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + self.add_patch(out, r0=r0, c0=c0, scale=scale) + return out + +class SyntheticDiskLattice(RenderComponent): def __init__( self, *, name: str, disk: DiskTemplate, - u_row: float | tuple[float, float] | tuple[float, float, float | None], - u_col: float | tuple[float, float] | tuple[float, float, float | None], - v_row: float | tuple[float, float] | tuple[float, float, float | None], - v_col: float | tuple[float, float] | tuple[float, float, float | None], + u_row: float | Sequence[float], + u_col: float | Sequence[float], + v_row: float | Sequence[float], + v_col: float | Sequence[float], u_max: int = 0, v_max: int = 0, - intensity_0: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, - intensity_row: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, - intensity_col: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, - intensity_row_row: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, - intensity_col_col: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, - intensity_row_col: float | tuple[float, float] | tuple[float, float, float | None] = 0.0, + intensity_0: float | Sequence[float] = 0.0, + intensity_row: float | Sequence[float] = 0.0, + intensity_col: float | Sequence[float] = 0.0, + intensity_row_row: float | Sequence[float] = 0.0, + intensity_col_col: float | Sequence[float] = 0.0, + intensity_row_col: float | Sequence[float] = 0.0, per_disk_intensity: bool = False, per_disk_slopes: bool = True, max_intensity_order: int | None = None, default_pattern_intensity_order: int | None = None, - center_intensity_0: float | tuple[float, float] | tuple[float, float, float | None] | None = None, + center_intensity_0: float | Sequence[float] | None = None, exclude_indices: Iterable[tuple[int, int]] | None = None, boundary_px: float = 0.0, + origin: OriginND | None = None, origin_key: str = "origin", ): - super().__init__(name=name) + super().__init__() + self.name = str(name) self.disk = disk + self.origin = origin + self.origin_key = str(origin_key) + self.per_disk_intensity = bool(per_disk_intensity) self.u_max = int(u_max) self.v_max = int(v_max) self.boundary_px = float(boundary_px) - self.origin_key = str(origin_key) - self.per_disk_intensity = bool(per_disk_intensity) + if max_intensity_order is None: - self.max_intensity_order = 1 if bool(per_disk_slopes) else 0 - else: - self.max_intensity_order = int(max_intensity_order) + max_intensity_order = 1 if bool(per_disk_slopes) else 0 + self.max_intensity_order = int(max_intensity_order) if self.max_intensity_order < 0 or self.max_intensity_order > 2: raise ValueError("max_intensity_order must be 0, 1, or 2.") + if default_pattern_intensity_order is None: - self.default_pattern_intensity_order = int(self.max_intensity_order) - else: - self.default_pattern_intensity_order = int(default_pattern_intensity_order) - if self.default_pattern_intensity_order < 0 or self.default_pattern_intensity_order > self.max_intensity_order: - raise ValueError("default_pattern_intensity_order must be in [0, max_intensity_order].") + default_pattern_intensity_order = self.max_intensity_order + self.default_pattern_intensity_order = int(default_pattern_intensity_order) + + self.u_row = nn.Parameter( + torch.tensor(_parse_init(u_row, name="u_row"), dtype=torch.float32) + ) + self.u_col = nn.Parameter( + torch.tensor(_parse_init(u_col, name="u_col"), dtype=torch.float32) + ) + self.v_row = nn.Parameter( + torch.tensor(_parse_init(v_row, name="v_row"), dtype=torch.float32) + ) + self.v_col = nn.Parameter( + torch.tensor(_parse_init(v_col, name="v_col"), dtype=torch.float32) + ) if exclude_indices is None: - self.exclude_indices = {(0, 0)} if bool(getattr(disk, "place_at_origin", False)) else set() + exclude = {(0, 0)} if bool(getattr(disk, "place_at_origin", False)) else set() else: - self.exclude_indices = set(exclude_indices) - + exclude = set(exclude_indices) uv: list[tuple[int, int]] = [] for u in range(-self.u_max, self.u_max + 1): for v in range(-self.v_max, self.v_max + 1): - if (u, v) in self.exclude_indices: - continue - uv.append((u, v)) - self.uv_indices = uv - - self.p_u_row = Parameter(u_row, tags={"role": "lat_u_row"}) - self.p_u_col = Parameter(u_col, tags={"role": "lat_u_col"}) - self.p_v_row = Parameter(v_row, tags={"role": "lat_v_row"}) - self.p_v_col = Parameter(v_col, tags={"role": "lat_v_col"}) - self.p_i0 = None - self.p_ir = None - self.p_ic = None - self.p_i0_list: list[Parameter] = [] - self.p_ir_list: list[Parameter] = [] - self.p_ic_list: list[Parameter] = [] - self.p_irr_list: list[Parameter] = [] - self.p_icc_list: list[Parameter] = [] - self.p_irc_list: list[Parameter] = [] - if self.per_disk_intensity: - for u, v in self.uv_indices: - i0_val = center_intensity_0 if (center_intensity_0 is not None and (u, v) == (0, 0)) else intensity_0 - self.p_i0_list.append( - Parameter(i0_val, lower_bound=0.0, tags={"role": "lat_int0", "u": u, "v": v, "intensity_order": 0}) - ) - if self.max_intensity_order >= 1: - self.p_ir_list.append( - Parameter(intensity_row, tags={"role": "lat_int_row", "u": u, "v": v, "intensity_order": 1}) - ) - self.p_ic_list.append( - Parameter(intensity_col, tags={"role": "lat_int_col", "u": u, "v": v, "intensity_order": 1}) - ) - if self.max_intensity_order >= 2: - self.p_irr_list.append( - Parameter(intensity_row_row, tags={"role": "lat_int_rr", "u": u, "v": v, "intensity_order": 2}) - ) - self.p_icc_list.append( - Parameter(intensity_col_col, tags={"role": "lat_int_cc", "u": u, "v": v, "intensity_order": 2}) - ) - self.p_irc_list.append( - Parameter(intensity_row_col, tags={"role": "lat_int_rc", "u": u, "v": v, "intensity_order": 2}) - ) - else: - self.p_i0 = Parameter(intensity_0, lower_bound=0.0, tags={"role": "lat_int0", "intensity_order": 0}) - self.p_ir = Parameter(intensity_row, tags={"role": "lat_int_row", "intensity_order": 1}) - self.p_ic = Parameter(intensity_col, tags={"role": "lat_int_col", "intensity_order": 1}) - self.p_irr = Parameter(intensity_row_row, tags={"role": "lat_int_rr", "intensity_order": 2}) - self.p_icc = Parameter(intensity_col_col, tags={"role": "lat_int_cc", "intensity_order": 2}) - self.p_irc = Parameter(intensity_row_col, tags={"role": "lat_int_rc", "intensity_order": 2}) - - def parameters(self) -> list[Parameter]: - out = [self.p_u_row, self.p_u_col, self.p_v_row, self.p_v_col] - if self.per_disk_intensity: - out.extend(self.p_i0_list) - if self.max_intensity_order >= 1: - out.extend(self.p_ir_list) - out.extend(self.p_ic_list) - if self.max_intensity_order >= 2: - out.extend(self.p_irr_list) - out.extend(self.p_icc_list) - out.extend(self.p_irc_list) - else: - out.append(self.p_i0) - if self.max_intensity_order >= 1: - out.extend([self.p_ir, self.p_ic]) - if self.max_intensity_order >= 2: - out.extend([self.p_irr, self.p_icc, self.p_irc]) - return out - - def prepare(self, ctx: ModelContext) -> Any: - dt = ctx.fields.get("disk_templates", {}) - if self.disk.name not in dt: - raise RuntimeError("DiskTemplate must be included before SyntheticDiskLattice in components.") - d = dt[self.disk.name] - dr = d["dr"] - dc = d["dc"] - pix_idx = d["pix_idx"] - base_vals = d["base"] - - r_idx, c_idx = _origin_indices(ctx, self.origin_key) - - i_ur = self.p_u_row.index - i_uc = self.p_u_col.index - i_vr = self.p_v_row.index - i_vc = self.p_v_col.index - if self.per_disk_intensity: - i_i0 = i_ir = i_ic = i_irr = i_icc = i_irc = None - else: - i_i0 = self.p_i0.index - i_ir = None if self.max_intensity_order < 1 else self.p_ir.index - i_ic = None if self.max_intensity_order < 1 else self.p_ic.index - i_irr = None if self.max_intensity_order < 2 else self.p_irr.index - i_icc = None if self.max_intensity_order < 2 else self.p_icc.index - i_irc = None if self.max_intensity_order < 2 else self.p_irc.index - - uv = self.uv_indices - uv_t = torch.as_tensor(uv, device=ctx.device, dtype=torch.long) if uv else None - - boundary = torch.as_tensor(self.boundary_px, device=ctx.device, dtype=ctx.dtype) + if (u, v) not in exclude: + uv.append((u, v)) + uv_t = ( + torch.as_tensor(uv, dtype=torch.long) if uv else torch.zeros((0, 2), dtype=torch.long) + ) + self.register_buffer("uv_indices", uv_t) + + n_uv = int(uv_t.shape[0]) + i0_init = _parse_init(intensity_0, name="intensity_0") + i0_center = ( + i0_init + if center_intensity_0 is None + else _parse_init(center_intensity_0, name="center_intensity_0") + ) + ir_init = _parse_init(intensity_row, name="intensity_row") + ic_init = _parse_init(intensity_col, name="intensity_col") + irr_init = _parse_init(intensity_row_row, name="intensity_row_row") + icc_init = _parse_init(intensity_col_col, name="intensity_col_col") + irc_init = _parse_init(intensity_row_col, name="intensity_row_col") if self.per_disk_intensity: - i0_idx = torch.as_tensor([p.index for p in self.p_i0_list], device=ctx.device, dtype=torch.long) + i0_values = torch.full((n_uv,), float(i0_init), dtype=torch.float32) + if n_uv > 0: + center_mask = (uv_t[:, 0] == 0) & (uv_t[:, 1] == 0) + i0_values[center_mask] = float(i0_center) + self.i0_raw = nn.Parameter(i0_values) if self.max_intensity_order >= 1: - ir_idx = torch.as_tensor([p.index for p in self.p_ir_list], device=ctx.device, dtype=torch.long) - ic_idx = torch.as_tensor([p.index for p in self.p_ic_list], device=ctx.device, dtype=torch.long) + self.ir = nn.Parameter(torch.full((n_uv,), float(ir_init), dtype=torch.float32)) + self.ic = nn.Parameter(torch.full((n_uv,), float(ic_init), dtype=torch.float32)) else: - ir_idx = ic_idx = None + self.ir = None + self.ic = None if self.max_intensity_order >= 2: - irr_idx = torch.as_tensor([p.index for p in self.p_irr_list], device=ctx.device, dtype=torch.long) - icc_idx = torch.as_tensor([p.index for p in self.p_icc_list], device=ctx.device, dtype=torch.long) - irc_idx = torch.as_tensor([p.index for p in self.p_irc_list], device=ctx.device, dtype=torch.long) + self.irr = nn.Parameter(torch.full((n_uv,), float(irr_init), dtype=torch.float32)) + self.icc = nn.Parameter(torch.full((n_uv,), float(icc_init), dtype=torch.float32)) + self.irc = nn.Parameter(torch.full((n_uv,), float(irc_init), dtype=torch.float32)) else: - irr_idx = icc_idx = irc_idx = None + self.irr = None + self.icc = None + self.irc = None else: - i0_idx = ir_idx = ic_idx = irr_idx = icc_idx = irc_idx = None - per_disk_intensity = self.per_disk_intensity - max_intensity_order = self.max_intensity_order - default_pattern_intensity_order = self.default_pattern_intensity_order - comp_name = self.name - - @dataclass - class Prepared: - boundary_px: float = float(self.boundary_px) - - def _patch(self, x: torch.Tensor) -> torch.Tensor: - if pix_idx is None: - return base_vals - return x[pix_idx] - - def _centers(self, x: torch.Tensor, ctx: ModelContext) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: - if uv_t is None: - z = torch.empty((0,), device=ctx.device, dtype=ctx.dtype) - return z, z, z - - r0 = x[r_idx] - c0 = x[c_idx] - - ur = x[i_ur] - uc = x[i_uc] - vr = x[i_vr] - vc = x[i_vc] - - u = uv_t[:, 0].to(ctx.dtype) - v = uv_t[:, 1].to(ctx.dtype) - - centers_r = r0 + u * ur + v * vr - centers_c = c0 + u * uc + v * vc - - keep = (centers_r >= boundary) & (centers_r <= (ctx.H - 1) - boundary) - keep &= (centers_c >= boundary) & (centers_c <= (ctx.W - 1) - boundary) - return centers_r[keep], centers_c[keep], keep - - def render(self, out: torch.Tensor, x: torch.Tensor, ctx: ModelContext) -> None: - if uv_t is None: - return - - centers_r, centers_c, keep = self._centers(x, ctx) - if centers_r.numel() == 0: - return - - vals = self._patch(x) - dr2 = dr * dr - dc2 = dc * dc - drdc = dr * dc - - order_override = ctx.fields.get("lattice_intensity_order_override", None) - if isinstance(order_override, dict): - active_order = int(order_override.get(comp_name, default_pattern_intensity_order)) - elif order_override is None: - active_order = int(default_pattern_intensity_order) - else: - active_order = int(order_override) - active_order = max(0, min(active_order, int(max_intensity_order))) - - if per_disk_intensity: - keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) - i0_keep = x[i0_idx[keep_idx]] - if active_order >= 1: - ir_keep = x[ir_idx[keep_idx]] - ic_keep = x[ic_idx[keep_idx]] - if active_order >= 2: - irr_keep = x[irr_idx[keep_idx]] - icc_keep = x[icc_idx[keep_idx]] - irc_keep = x[irc_idx[keep_idx]] - for j, (rr0, cc0) in enumerate(zip(centers_r, centers_c)): - inten_local = i0_keep[j] - if active_order >= 1: - inten_local = inten_local + ir_keep[j] * dr + ic_keep[j] * dc - if active_order >= 2: - inten_local = inten_local + irr_keep[j] * dr2 + icc_keep[j] * dc2 + irc_keep[j] * drdc - inten_local = torch.clamp(inten_local, min=0.0) - _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten_local) - else: - i0 = x[i_i0] - for rr0, cc0 in zip(centers_r, centers_c): - inten = i0 - if active_order >= 1: - inten = inten + x[i_ir] * rr0 + x[i_ic] * cc0 - if active_order >= 2: - inten = inten + x[i_irr] * rr0 * rr0 + x[i_icc] * cc0 * cc0 + x[i_irc] * rr0 * cc0 - inten = torch.clamp(inten, min=0.0) - _splat_patch(out, r0=rr0, c0=cc0, patch_vals=vals, dr=dr, dc=dc, scale=inten) - - def overlays(self, x: torch.Tensor, ctx: ModelContext) -> Iterable[Overlay]: - if uv_t is None: - return [] - centers_r, centers_c, _ = self._centers(x, ctx) - if centers_r.numel() == 0: - return [] - return [ - Overlay( - kind="points_rc", - data={ - "r": centers_r.detach(), - "c": centers_c.detach(), - "marker": "x", - "s": 60.0, - "color": "orange", - }, + self.i0_raw = nn.Parameter(torch.tensor(i0_init, dtype=torch.float32)) + self.ir = nn.Parameter(torch.tensor(ir_init, dtype=torch.float32)) + self.ic = nn.Parameter(torch.tensor(ic_init, dtype=torch.float32)) + self.irr = nn.Parameter(torch.tensor(irr_init, dtype=torch.float32)) + self.icc = nn.Parameter(torch.tensor(icc_init, dtype=torch.float32)) + self.irc = nn.Parameter(torch.tensor(irc_init, dtype=torch.float32)) + + def set_origin(self, origin: OriginND) -> None: + self.origin = origin + + def forward(self, ctx: RenderContext) -> torch.Tensor: + if self.origin is None: + raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") + + out = torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) + uv_indices = cast(torch.Tensor, self.uv_indices) + if torch.numel(uv_indices) == 0: + return out + + uv = torch.as_tensor(uv_indices, device=ctx.device) + u = uv[:, 0].to(dtype=ctx.dtype) + v = uv[:, 1].to(dtype=ctx.dtype) + r0, c0 = self.origin.coords[0], self.origin.coords[1] + centers_r = r0 + u * self.u_row + v * self.v_row + centers_c = c0 + u * self.u_col + v * self.v_col + + b = torch.as_tensor(self.boundary_px, device=ctx.device, dtype=ctx.dtype) + keep = (centers_r >= b) & (centers_r <= (ctx.shape[0] - 1) - b) + keep = keep & (centers_c >= b) & (centers_c <= (ctx.shape[1] - 1) - b) + keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) + if keep_idx.numel() == 0: + return out + + active_order = int( + ctx.fields.get( + "lattice_intensity_order_override", self.default_pattern_intensity_order + ) + ) + active_order = max(0, min(active_order, self.max_intensity_order)) + + dr, dc = self.disk.patch_offsets() + dr = dr.to(device=ctx.device, dtype=ctx.dtype) + dc = dc.to(device=ctx.device, dtype=ctx.dtype) + dr2 = dr * dr + dc2 = dc * dc + drdc = dr * dc + + for j in keep_idx: + rr0 = centers_r[j] + cc0 = centers_c[j] + + if self.per_disk_intensity: + inten = _relu_pos(self.i0_raw[j]) + # inten = _softplus_pos(self.i0_raw[j]) + if active_order >= 1 and self.ir is not None and self.ic is not None: + inten = inten + self.ir[j] * dr + self.ic[j] * dc + if ( + active_order >= 2 + and self.irr is not None + and self.icc is not None + and self.irc is not None + ): + inten = inten + self.irr[j] * dr2 + self.icc[j] * dc2 + self.irc[j] * drdc + inten = torch.clamp(inten, min=0.0) + else: + inten = _relu_pos(self.i0_raw) + # inten = _softplus_pos(self.i0_raw) + if active_order >= 1: + assert self.ir is not None and self.ic is not None + inten = inten + self.ir * rr0 + self.ic * cc0 + if active_order >= 2: + assert self.irr is not None and self.icc is not None and self.irc is not None + inten = ( + inten + self.irr * rr0 * rr0 + self.icc * cc0 * cc0 + self.irc * rr0 * cc0 ) - ] + inten = torch.clamp(inten, min=0.0) - return Prepared() + self.disk.add_patch(out, r0=rr0, c0=cc0, scale=inten) + + return out diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index e7728997a..10477d4f8 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -1,117 +1,75 @@ from __future__ import annotations -"""High-level diffraction model fitting workflow utilities.""" - import warnings -from typing import Any, Sequence +from typing import Any, Sequence, cast import numpy as np import torch from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey +from torch.nn.utils import parameters_to_vector -from quantem.core.datastructures.dataset2d import Dataset2d -from quantem.core.datastructures.dataset3d import Dataset3d -from quantem.core.datastructures.dataset4d import Dataset4d -from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem +from quantem.core.fitting.base import AdditiveRenderModel, RenderComponent, RenderContext +from quantem.core.fitting.diffraction import OriginND from quantem.core.io.serialize import AutoSerialize -from quantem.core.fitting.base import Model, ModelContext, Overlay, PreparedModel -from quantem.core.fitting.diffraction import Origin2D +from quantem.core.ml.optimizer_mixin import OptimizerMixin 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 -def _to_numpy(x: Any) -> np.ndarray: - """Convert arrays/tensors to NumPy arrays.""" - if isinstance(x, np.ndarray): - return x - if torch.is_tensor(x): - return x.detach().cpu().numpy() - return np.asarray(x) - - -class ModelDiffraction(AutoSerialize): - """ - End-to-end helper for defining and fitting additive diffraction forward models. - - This class wraps a diffraction dataset, builds an average reference image - (`image_ref`), compiles a composable component model, and provides optimization - routines for: - - fitting to the mean reference image, - - fitting selected individual diffraction patterns. - - Features - -------- - - Build a mean reference image with optional stack alignment. - - Define a composable model from origin/background/template/lattice components. - - Refine a mean model with Adam or L-BFGS. - - Fit all or selected patterns with optional progress bars. - - Plot reference/model comparisons and component overlays. - - Typical workflow - ---------------- - >>> md = ModelDiffraction.from_dataset(ds).preprocess().define_model(...) - >>> md.refine_mean_model(...) - >>> md.fit_all_patterns(...) - >>> md.plot_mean_model(...) - """ +def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: + if isinstance(value, (list, tuple, np.ndarray)): + if len(value) == 0: + raise ValueError(f"{name} cannot be empty.") + if value[0] is None: + raise ValueError(f"{name} initial value cannot be None.") + return float(value[0]) + return float(cast(float | int, value)) + +class ModelDiffraction(OptimizerMixin, AutoSerialize): _token = object() + DEFAULT_LR = 1e-3 + DEFAULT_OPTIMIZER_TYPE = "adamw" def __init__(self, dataset: Any, _token: object | None = None): if _token is not self._token: raise RuntimeError("Use ModelDiffraction.from_dataset() or .from_file().") - super().__init__() + AutoSerialize.__init__(self) + OptimizerMixin.__init__(self) self.dataset = dataset self.metadata: dict[str, Any] = {} self.image_ref: np.ndarray | None = None self.preprocess_shifts: np.ndarray | None = None self.index_shape: tuple[int, ...] | None = None - self.model: Model | None = None - self.prepared: PreparedModel | None = None - self.x_mean: torch.Tensor | None = None + + self.ctx: RenderContext | None = None + self.model: AdditiveRenderModel | None = None + self.target_mean: torch.Tensor | None = None + self.x_defined: torch.Tensor | None = None self.x_initial: torch.Tensor | None = None + self.x_mean: torch.Tensor | None = None self.mean_refined: bool = False - self.x_patterns: torch.Tensor | None = None - self.pattern_fit_losses: np.ndarray | None = None - self.pattern_fit_linear_indices: np.ndarray | None = None - self.pattern_fit_indices: list[tuple[int, ...]] | None = None - - @staticmethod - def _weak_softplus(x: torch.Tensor, *, scale: float) -> torch.Tensor: - s = torch.as_tensor(float(scale), device=x.device, dtype=x.dtype) - return torch.nn.functional.softplus(x / s) * s - - @classmethod - def _apply_intensity_transform( - cls, x: torch.Tensor, *, mode: str, weak_softplus_scale: float - ) -> torch.Tensor: - m = str(mode).lower() - if m == "none": - return x - if m == "weak_softplus": - return cls._weak_softplus(x, scale=weak_softplus_scale) - raise ValueError("intensity_transform must be one of: 'none', 'weak_softplus'.") + self.mean_fit_history: list[float] = [] + self.mean_fit_lrs: list[float] = [] @classmethod - def from_dataset(cls, dataset: Dataset2d | Dataset3d | Dataset4d | Dataset4dstem | Any) -> "ModelDiffraction": - """ - Construct a ModelDiffraction object from a QuantEM dataset container. - - Parameters - ---------- - dataset - Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance. - - Returns - ------- - ModelDiffraction - New model-fitting helper bound to the provided dataset. - """ + def from_dataset( + cls, dataset: Dataset2d | Dataset3d | Dataset4d | Dataset4dstem | Any + ) -> "ModelDiffraction": if isinstance(dataset, (Dataset2d, Dataset3d, Dataset4d, Dataset4dstem)): return cls(dataset=dataset, _token=cls._token) - raise TypeError("from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance.") + raise TypeError( + "from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance." + ) + + def get_optimization_parameters(self) -> Any: + if self.model is None: + return [] + return self.model.parameters() def preprocess( self, @@ -122,73 +80,44 @@ def preprocess( max_shift: float | None = None, shift_order: int = 1, ) -> "ModelDiffraction": - """ - Precompute the mean reference image used for model fitting. - - Parameters - ---------- - align - If True, align the flattened pattern stack before averaging. - edge_blend - Tukey edge taper width (pixels) used for robust FFT alignment. - upsample_factor - Sub-pixel alignment upsampling factor for cross-correlation shift. - max_shift - Optional maximum shift magnitude during alignment. - shift_order - Interpolation order used when applying shifts to patterns. - - Returns - ------- - ModelDiffraction - Returns self. - - Notes - ----- - - `dataset.array` is interpreted as `(..., H, W)`, where leading dimensions - are flattened into a pattern stack. - - The computed stack-average is stored in `self.image_ref`. - - If `align=False`, preprocessing is a direct mean over stack elements. - """ arr = np.asarray(self.dataset.array) if arr.ndim < 2: raise ValueError("dataset.array must have at least 2 dimensions.") - H, W = arr.shape[-2], arr.shape[-1] + h, w = arr.shape[-2], arr.shape[-1] self.index_shape = tuple(arr.shape[:-2]) - stack = arr.reshape((-1, H, W)).astype(np.float32, copy=False) + stack = arr.reshape((-1, h, w)).astype(np.float32, copy=False) n = stack.shape[0] - if not align or n <= 1: self.image_ref = np.mean(stack, axis=0) self.preprocess_shifts = None return self - alpha_r = 0.0 if edge_blend <= 0 else min(1.0, 2.0 * float(edge_blend) / float(H)) - alpha_c = 0.0 if edge_blend <= 0 else min(1.0, 2.0 * float(edge_blend) / float(W)) - w = tukey(H, alpha=alpha_r)[:, None] * tukey(W, alpha=alpha_c)[None, :] - w = w.astype(np.float32, copy=False) + alpha_r = 0.0 if edge_blend <= 0 else min(1.0, 2.0 * float(edge_blend) / float(h)) + alpha_c = 0.0 if edge_blend <= 0 else min(1.0, 2.0 * float(edge_blend) / float(w)) + window = tukey(h, alpha=alpha_r)[:, None] * tukey(w, alpha=alpha_c)[None, :] + window = window.astype(np.float32, copy=False) shifts = np.zeros((n, 2), dtype=np.float32) - F_ref = np.fft.fft2(w * stack[0]) - + fft_ref = np.fft.fft2(window * stack[0]) for i in range(1, n): - F_i = np.fft.fft2(w * stack[i]) - drc, F_shift = cross_correlation_shift( - F_ref, - F_i, + fft_i = np.fft.fft2(window * stack[i]) + drc, fft_shift = cross_correlation_shift( + fft_ref, + fft_i, upsample_factor=int(upsample_factor), max_shift=max_shift, fft_input=True, fft_output=True, return_shifted_image=True, ) + if not isinstance(drc, (list, tuple, np.ndarray)) or len(drc) < 2: + raise RuntimeError("cross_correlation_shift returned an invalid shift vector.") shifts[i, 0] = float(drc[0]) shifts[i, 1] = float(drc[1]) - F_ref = F_ref * (i / (i + 1)) + F_shift / (i + 1) + fft_ref = fft_ref * (i / (i + 1)) + fft_shift / (i + 1) shifts -= np.mean(shifts, axis=0, keepdims=True) - aligned = np.empty_like(stack, dtype=np.float32) for i in range(n): aligned[i] = ndi_shift( @@ -206,646 +135,268 @@ def preprocess( def define_model( self, *, - origin_row: float | tuple[float, float] | tuple[float, float, float | None], - origin_col: float | tuple[float, float] | tuple[float, float, float | None], - components: list[Any], + origin_row: float | Sequence[float], + origin_col: float | Sequence[float], + components: list[RenderComponent], device: torch.device | str | None = None, dtype: torch.dtype | None = None, mask: np.ndarray | torch.Tensor | None = None, origin_key: str = "origin", ) -> "ModelDiffraction": - """ - Define and compile a diffraction model against `image_ref`. - - Parameters - ---------- - origin_row, origin_col - Initial origin parameter specification. Supported forms are: - - scalar: fixed initial value with no explicit bounds - - `(value, deviation)`: symmetric bounds `(value - deviation, value + deviation)` - - `(value, lower_bound, upper_bound)`: explicit bounds - components - Sequence of model components (e.g. `DiskTemplate`, backgrounds, lattice). - Components are rendered additively in the provided order. - device - Torch device used for compiled parameters and rendering. - dtype - Torch dtype used for compiled parameters and rendering. - mask - Optional `(H, W)` mask for weighted loss during optimization. - origin_key - Name used to register/retrieve the origin component in context fields. - - Returns - ------- - ModelDiffraction - Returns self with compiled model state. - - Notes - ----- - - If `image_ref` is missing, `preprocess()` is run automatically. - - `Origin2D` is inserted automatically before user components. - - Component dependency ordering still matters for shared context fields: - for example, `DiskTemplate` should appear before `SyntheticDiskLattice` - when the lattice references that template. - - This method resets fit state (`x_defined`, `x_initial`, `x_mean`, - `x_patterns`, and pattern-fit metadata). - """ if self.image_ref is None: self.preprocess() - if self.image_ref is None: raise RuntimeError("image_ref not available.") - H, W = int(self.image_ref.shape[0]), int(self.image_ref.shape[1]) + h, w = int(self.image_ref.shape[0]), int(self.image_ref.shape[1]) dev = torch.device(device) if device is not None else torch.device("cpu") dt = dtype if dtype is not None else torch.float32 mask_t = None if mask is not None: - if torch.is_tensor(mask): - mask_t = mask.to(device=dev, dtype=dt) - else: - m = np.asarray(mask) - if m.shape != (H, W): - raise ValueError("mask must have shape (H, W).") - mask_t = torch.as_tensor(m.astype(np.float32, copy=False), device=dev, dtype=dt) - - ctx = ModelContext(H=H, W=W, device=dev, dtype=dt, mask=mask_t, fields={}) - m = Model() - m.add([Origin2D(origin_key=str(origin_key), row=origin_row, col=origin_col)]) - m.add(list(components)) - - self.model = m - self.prepared = m.compile(ctx) - self.x_defined = self.prepared.x0.detach().clone() - self.x_initial = self.x_defined.detach().clone() - self.x_mean = self.x_initial.detach().clone() + mask_t = ( + mask.to(device=dev, dtype=dt) + if torch.is_tensor(mask) + else torch.as_tensor(mask, device=dev, dtype=dt) + ) + if tuple(mask_t.shape) != (h, w): + raise ValueError("mask must have shape (H, W).") + + origin = OriginND( + ndim=2, + init=[ + _parse_init(origin_row, name="origin_row"), + _parse_init(origin_col, name="origin_col"), + ], + ) + origin._quantem_origin_key = str(origin_key) # type: ignore[attr-defined] + + for component in components: + if hasattr(component, "set_origin"): + component.set_origin(origin) # type: ignore[misc] + elif hasattr(component, "origin") and getattr(component, "origin") is None: + component.origin = origin # type: ignore[attr-defined] + + self.model = AdditiveRenderModel(origin=origin, components=list(components)).to( + device=dev, dtype=dt + ) + self.ctx = RenderContext(shape=(h, w), device=dev, dtype=dt, mask=mask_t, fields={}) + self.target_mean = torch.as_tensor(self.image_ref, device=dev, dtype=dt) + + x0 = parameters_to_vector(self.model.parameters()).detach().clone() + self.x_defined = x0.detach().clone() + self.x_initial = x0.detach().clone() + self.x_mean = x0.detach().clone() self.mean_refined = False - self.x_patterns = None - self.pattern_fit_losses = None - self.pattern_fit_linear_indices = None - self.pattern_fit_indices = None + self.mean_fit_history = [] + self.mean_fit_lrs = [] + self.remove_optimizer() return self - def _fit_target_image( + def fit_mean_diffraction_pattern( self, *, - target: torch.Tensor, - x_start: torch.Tensor, - n_steps: int, - lr: float, - method: str, - power: float | None, - fit_disk_pixels: bool | None, - fit_only_disk_pixels: bool, - intensity_order: int | None, - enforce_disk_center_of_mass: bool, - normalize_disk_template_max: bool, - template_binary_weight: float, - template_binary_power: float, - intensity_transform: str, - weak_softplus_scale: float, + n_steps: int = 200, + optimizer_params: dict | None = None, + scheduler_params: dict | None = None, + constraint_weight: float = 1.0, + overwrite_initial: bool = True, progress: bool = False, - progress_desc: str | None = None, - ) -> tuple[torch.Tensor, float]: - if self.prepared is None: + **kwargs: Any, + ) -> "ModelDiffraction": + if self.model is None or self.ctx is None or self.target_mean is None: raise RuntimeError("Call .define_model(...) first.") - ctx = self.prepared.ctx - lb = self.prepared.lb - ub = self.prepared.ub - - x = x_start.detach().clone().to(device=ctx.device, dtype=ctx.dtype) - x.requires_grad_(True) - - if fit_disk_pixels is None: - fit_disk_pixels = any(p.tags.get("role") == "disk_pixel" for p in self.prepared.params) - - freeze = torch.zeros_like(x, dtype=torch.bool) - disk_mask = torch.zeros_like(x, dtype=torch.bool) - for p in self.prepared.params: - if p.tags.get("role") == "disk_pixel": - disk_mask[p.index] = True - if intensity_order is not None and str(p.tags.get("role", "")).startswith("lat_int"): - p_ord = int(p.tags.get("intensity_order", 0)) - if p_ord > int(intensity_order): - freeze[p.index] = True - - # Group disk pixel parameters by disk template for optional projection. - disk_param_groups: list[dict[str, Any]] = [] - if ( - enforce_disk_center_of_mass - or normalize_disk_template_max - or float(template_binary_weight) > 0.0 - ): - disk_templates = self.prepared.ctx.fields.get("disk_templates", {}) - grouped: dict[str, list[tuple[int, int]]] = {} - for p in self.prepared.params: - if p.tags.get("role") != "disk_pixel": - continue - name = str(p.tags.get("disk")) - i_flat = int(p.tags.get("i")) - grouped.setdefault(name, []).append((i_flat, int(p.index))) - for name, pairs in grouped.items(): - dmeta = disk_templates.get(name) - if dmeta is None: - continue - order = sorted(pairs, key=lambda t: t[0]) - p_idx = torch.as_tensor([t[1] for t in order], device=ctx.device, dtype=torch.long) - g: dict[str, Any] = {"param_idx": p_idx} - shape = dmeta.get("shape", None) - if shape is not None: - g["shape"] = (int(shape[0]), int(shape[1])) - if enforce_disk_center_of_mass: - flat_i = torch.as_tensor([t[0] for t in order], device=ctx.device, dtype=torch.long) - g["dr"] = dmeta["dr"][flat_i] - g["dc"] = dmeta["dc"][flat_i] - disk_param_groups.append(g) - - if fit_only_disk_pixels: - if not fit_disk_pixels: - raise ValueError("fit_only_disk_pixels=True requires fit_disk_pixels=True.") - freeze[:] = True - freeze[disk_mask] = False - elif not fit_disk_pixels: - freeze[disk_mask] = True - x_frozen = x.detach().clone() - - old_order_override = ctx.fields.get("lattice_intensity_order_override", None) - if intensity_order is not None: - ctx.fields["lattice_intensity_order_override"] = int(intensity_order) - - target_t = target.to(device=ctx.device, dtype=ctx.dtype) - target_t = self._apply_intensity_transform( - target_t, mode=intensity_transform, weak_softplus_scale=weak_softplus_scale - ) - if power is not None: - target_t = torch.clamp(target_t, min=0.0) ** float(power) - - def clamp_inplace() -> None: - with torch.no_grad(): - x.data = torch.max(torch.min(x.data, ub), lb) - if torch.any(freeze): - x.data[freeze] = x_frozen[freeze] - if ( - enforce_disk_center_of_mass - or normalize_disk_template_max - ) and disk_param_groups: - eps = torch.as_tensor(1e-12, device=ctx.device, dtype=ctx.dtype) - for g in disk_param_groups: - p_idx = g["param_idx"] - if torch.all(freeze[p_idx]): - continue - vals = torch.clamp(x.data[p_idx], min=0.0) - if enforce_disk_center_of_mass: - dr = g["dr"] - dc = g["dc"] - # Project template moments toward zero COM by iterative multiplicative reweighting. - for _ in range(12): - mass = torch.sum(vals) - if mass <= eps: - break - r_com = torch.sum(vals * dr) / mass - c_com = torch.sum(vals * dc) / mass - if torch.abs(r_com) <= 1e-5 and torch.abs(c_com) <= 1e-5: - break - var_r = torch.sum(vals * dr * dr) / mass + eps - var_c = torch.sum(vals * dc * dc) / mass + eps - vals = vals * torch.exp(-(r_com / var_r) * dr - (c_com / var_c) * dc) - vals = torch.clamp(vals, min=0.0) - if normalize_disk_template_max: - vmax = torch.max(vals) - if vmax > eps: - vals = vals / vmax - x.data[p_idx] = vals - - def loss_fn() -> torch.Tensor: - clamp_inplace() - pred = self.prepared.render(x) - pred = self._apply_intensity_transform( - pred, mode=intensity_transform, weak_softplus_scale=weak_softplus_scale - ) - if power is not None: - pred = torch.clamp(pred, min=0.0) ** float(power) - if ctx.mask is not None: - m = ctx.mask - diff = (pred - target_t) * m - denom = torch.clamp(torch.sum(m), min=1.0) - loss = torch.sum(diff * diff) / denom - else: - diff = pred - target_t - loss = torch.mean(diff * diff) - - if float(template_binary_weight) > 0.0 and disk_param_groups: - bp = torch.as_tensor(0.0, device=ctx.device, dtype=ctx.dtype) - n_bp = 0 - eps = torch.as_tensor(1e-12, device=ctx.device, dtype=ctx.dtype) - pwr = float(template_binary_power) - for g in disk_param_groups: - p_idx = g["param_idx"] - vals = torch.clamp(x[p_idx], min=0.0) - vmax = torch.max(vals) - if vmax <= eps: - continue - vals_n = vals / (vmax + eps) - core = vals_n * (1.0 - vals_n) - if pwr != 1.0: - core = torch.pow(core + eps, pwr) - bp = bp + torch.mean(core) - n_bp += 1 - if n_bp > 0: - loss = loss + float(template_binary_weight) * (bp / n_bp) - - return loss - - try: - if method == "adam": - opt = torch.optim.Adam([x], lr=lr) - step_iter: Any = range(int(n_steps)) - if progress: - try: - from tqdm.auto import trange - - step_iter = trange(int(n_steps), desc=progress_desc or "Refining", leave=False) - except Exception: - warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) - for _ in step_iter: - opt.zero_grad(set_to_none=True) - loss = loss_fn() - loss.backward() - opt.step() - clamp_inplace() - elif method == "lbfgs": - opt = torch.optim.LBFGS([x], lr=lr, max_iter=int(n_steps), line_search_fn="strong_wolfe") - - def closure() -> torch.Tensor: - opt.zero_grad(set_to_none=True) - loss = loss_fn() - loss.backward() - return loss - - opt.step(closure) - clamp_inplace() - else: - raise ValueError("method must be one of: 'lbfgs', 'adam'.") - - with torch.no_grad(): - final_loss = float(loss_fn().detach().cpu()) - return x.detach().clone(), final_loss - finally: - if intensity_order is not None: - if old_order_override is None: - ctx.fields.pop("lattice_intensity_order_override", None) - else: - ctx.fields["lattice_intensity_order_override"] = old_order_override - - def _resolve_pattern_indices(self, indices: Any, n: int, index_shape: tuple[int, ...]) -> np.ndarray: - if indices is None: - out = np.arange(n, dtype=np.int64) - elif isinstance(indices, (int, np.integer)): - out = np.asarray([int(indices)], dtype=np.int64) - elif isinstance(indices, slice): - out = np.arange(n, dtype=np.int64)[indices] - elif isinstance(indices, tuple) and all(isinstance(i, (int, np.integer)) for i in indices): - if len(index_shape) == 0: - if len(indices) != 0: - raise ValueError("indices tuple must be empty for single-pattern datasets.") - out = np.asarray([0], dtype=np.int64) + optimizer_rebuilt = False + if optimizer_params is not None: + self.set_optimizer(optimizer_params) + optimizer_rebuilt = True + elif self.optimizer is None: + if self.optimizer_params: + self.set_optimizer(self.optimizer_params) else: - out = np.asarray([np.ravel_multi_index(tuple(int(i) for i in indices), index_shape)], dtype=np.int64) - elif isinstance(indices, tuple): - if len(index_shape) == 0: - raise ValueError("slice tuple indices are not valid for single-pattern datasets.") - grid = np.arange(n, dtype=np.int64).reshape(index_shape) - out = np.asarray(grid[indices], dtype=np.int64).ravel() - elif isinstance(indices, np.ndarray) and indices.dtype == np.bool_: - if indices.shape != index_shape: - raise ValueError(f"Boolean mask must have shape {index_shape}.") - out = np.flatnonzero(indices.ravel()).astype(np.int64, copy=False) - elif isinstance(indices, Sequence) and not isinstance(indices, (str, bytes)): - seq = list(indices) - if len(seq) == 0: - out = np.asarray([], dtype=np.int64) - elif isinstance(seq[0], (tuple, list, np.ndarray)): - if len(index_shape) == 0: - raise ValueError("multi-index selection is not valid for single-pattern datasets.") - out = np.asarray( - [np.ravel_multi_index(tuple(int(j) for j in i), index_shape) for i in seq], - dtype=np.int64, - ) - else: - out = np.asarray([int(i) for i in seq], dtype=np.int64) - else: - raise TypeError("Unsupported indices type for fit_all_patterns.") + self.set_optimizer({"type": self.DEFAULT_OPTIMIZER_TYPE, "lr": self.DEFAULT_LR}) + optimizer_rebuilt = True - if out.ndim != 1: - out = out.ravel() - if np.any(out < 0) or np.any(out >= n): - raise IndexError(f"indices must be in [0, {n - 1}].") - return out + if scheduler_params is not None: + self.set_scheduler(scheduler_params, num_iter=int(n_steps)) + elif self.scheduler is None and self.scheduler_params: + self.set_scheduler(self.scheduler_params, num_iter=int(n_steps)) + elif optimizer_rebuilt and self.scheduler is not None and self.optimizer is not None: + self.scheduler.optimizer = self.optimizer - def refine_mean_model( - self, - *, - n_steps: int = 50, - lr: float = 1e-3, - method: str = "adam", - power: float | None = 1.0, - fit_disk_pixels: bool | None = None, - fit_only_disk_pixels: bool = False, - intensity_order: int = 0, - enforce_disk_center_of_mass: bool = True, - normalize_disk_template_max: bool = False, - template_binary_weight: float = 0.0, - template_binary_power: float = 1.0, - warmup_disk_steps: int = 0, - overwrite_initial: bool = True, - intensity_transform: str = "none", - weak_softplus_scale: float = 1e-3, - progress: bool = False, - ) -> "ModelDiffraction": - """ - Refine model parameters against the mean reference image. - - Parameters - ---------- - n_steps - Number of optimization steps/iterations for the main phase. - lr - Optimizer learning rate. - method - Optimizer name: `"adam"` or `"lbfgs"`. - power - Optional power-law transform applied to both target and prediction - before computing MSE loss. - fit_disk_pixels - Controls whether `disk_pixel` parameters are trainable. If None, - inferred from model parameters. - fit_only_disk_pixels - If True, freeze all non-disk parameters. - warmup_disk_steps - Optional number of disk-only warmup steps run before the main phase. - overwrite_initial - If True, store refined parameters as new initial parameters for - subsequent per-pattern fitting. - intensity_transform - Optional intensity transform (`"none"` or `"weak_softplus"`) applied - before the power transform and loss evaluation. - weak_softplus_scale - Scale parameter used when `intensity_transform="weak_softplus"`. - progress - If True, show a tqdm progress bar (when tqdm is available). - - Returns - ------- - ModelDiffraction - Returns self with updated `x_mean` (and optionally `x_initial`). - """ - method = str(method).lower() + iterator: Any = range(int(n_steps)) + if progress: + try: + from tqdm.auto import trange - if self.image_ref is None: - self.preprocess() - if self.image_ref is None or self.prepared is None or self.x_mean is None or self.x_initial is None: - raise RuntimeError("Call .define_model(...) first.") + iterator = trange(int(n_steps), desc="Fit mean diffraction", leave=False) + except Exception: + warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) - ctx = self.prepared.ctx - target = torch.as_tensor(self.image_ref, device=ctx.device, dtype=ctx.dtype) - x_start = self.x_initial if overwrite_initial else self.x_mean - if int(warmup_disk_steps) > 0: - x_start, _ = self._fit_target_image( - target=target, - x_start=x_start, - n_steps=int(warmup_disk_steps), - lr=float(lr), - method=method, - power=power, - fit_disk_pixels=True, - fit_only_disk_pixels=True, - intensity_order=int(intensity_order), - enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), - normalize_disk_template_max=bool(normalize_disk_template_max), - template_binary_weight=float(template_binary_weight), - template_binary_power=float(template_binary_power), - intensity_transform=intensity_transform, - weak_softplus_scale=float(weak_softplus_scale), - progress=bool(progress), - progress_desc="Refine mean model (disk warmup)", + self.mean_fit_history = [] + self.mean_fit_lrs = [] + for _ in iterator: + self.zero_optimizer_grad() + pred = self.model(self.ctx) + if self.ctx.mask is not None: + diff = (pred - self.target_mean) * self.ctx.mask + denom = torch.clamp(torch.sum(self.ctx.mask), min=1.0) + loss_data = torch.sum(diff * diff) / denom + else: + loss_data = torch.mean((pred - self.target_mean) ** 2) + loss = loss_data + float(constraint_weight) * self.model.total_constraint_loss( + self.ctx ) - x_fit, _ = self._fit_target_image( - target=target, - x_start=x_start, - n_steps=int(n_steps), - lr=float(lr), - method=method, - power=power, - fit_disk_pixels=fit_disk_pixels, - fit_only_disk_pixels=bool(fit_only_disk_pixels), - intensity_order=int(intensity_order), - enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), - normalize_disk_template_max=bool(normalize_disk_template_max), - template_binary_weight=float(template_binary_weight), - template_binary_power=float(template_binary_power), - intensity_transform=intensity_transform, - weak_softplus_scale=float(weak_softplus_scale), - progress=bool(progress), - progress_desc="Refine mean model", - ) - + loss.backward() + self.step_optimizer() + loss_value = float(loss.detach().cpu()) + self.step_scheduler(loss_value) + self.mean_fit_history.append(loss_value) + self.mean_fit_lrs.append(float(self.get_current_lr())) + + x_fit = parameters_to_vector(self.model.parameters()).detach().clone() self.x_mean = x_fit self.mean_refined = True if overwrite_initial: self.x_initial = x_fit.detach().clone() return self - def fit_all_patterns( - self, - *, - indices: Any = None, - use_refined_init: bool = True, - strict_refined_init: bool = False, - n_steps: int = 50, - lr: float = 1e-3, - method: str = "adam", - power: float | None = 1.0, - fit_disk_pixels: bool | None = None, - fit_only_disk_pixels: bool = False, - intensity_order: int | None = None, - enforce_disk_center_of_mass: bool = True, - normalize_disk_template_max: bool = False, - template_binary_weight: float = 0.0, - template_binary_power: float = 1.0, - intensity_transform: str = "none", - weak_softplus_scale: float = 1e-3, - progress: bool = False, - ) -> "ModelDiffraction": - """ - Fit selected diffraction patterns using the compiled model. - - Parameters - ---------- - indices - Pattern selector. Supported forms include None (all patterns), - integer, slice, tuple indexing, list of linear indices, list of - multi-indices, or boolean mask shaped like scan dimensions. - use_refined_init - If True, initialize per-pattern fitting from `x_initial`. - strict_refined_init - If True, raise when refined init is requested before mean refinement. - If False, emit warning and fall back to defined initialization. - n_steps, lr, method, power, fit_disk_pixels, fit_only_disk_pixels - Optimization settings analogous to `refine_mean_model`. - intensity_transform, weak_softplus_scale - Prediction/target intensity transform options. - progress - If True, show a tqdm progress bar over selected patterns. - - Returns - ------- - ModelDiffraction - Returns self with: - - `x_patterns`: fitted parameter vectors `(n_selected, n_params)` - - `pattern_fit_losses`: per-pattern final losses - - index bookkeeping for selected patterns. - """ - method = str(method).lower() - - if self.prepared is None or self.x_defined is None or self.x_initial is None: - raise RuntimeError("Call .define_model(...) first.") + def plot_losses( + self, figax: tuple[Any, Any] | None = None, plot_lrs: bool = True + ) -> tuple[Any, Any]: + import matplotlib.pyplot as plt - arr = np.asarray(self.dataset.array) - if arr.ndim < 2: - raise ValueError("dataset.array must have at least 2 dimensions.") - H, W = arr.shape[-2], arr.shape[-1] - index_shape = tuple(arr.shape[:-2]) - stack = arr.reshape((-1, H, W)).astype(np.float32, copy=False) - n = int(stack.shape[0]) - - linear = self._resolve_pattern_indices(indices=indices, n=n, index_shape=index_shape) - if linear.size == 0: - raise ValueError("No patterns selected for fitting.") - - if use_refined_init: - if not self.mean_refined: - msg = "fit_all_patterns(use_refined_init=True) was requested before refine_mean_model()." - if strict_refined_init: - raise RuntimeError(msg) - warnings.warn(f"{msg} Falling back to defined initial parameters.", stacklevel=2) - x_seed = self.x_defined - else: - x_seed = self.x_initial + if figax is None: + fig, ax = plt.subplots() else: - x_seed = self.x_defined + fig, ax = figax + + losses = np.asarray(self.mean_fit_history, dtype=np.float64) + if losses.size == 0: + ax.text( + 0.5, + 0.5, + "No fit history available", + ha="center", + va="center", + transform=ax.transAxes, + ) + ax.set_xlabel("Iterations") + ax.set_ylabel("Loss") + if figax is None: + plt.tight_layout() + plt.show() + return fig, ax - n_sel = int(linear.size) - x_fit_all = torch.empty( - (n_sel, self.x_defined.numel()), - device=self.prepared.ctx.device, - dtype=self.prepared.ctx.dtype, - ) - losses = np.empty((n_sel,), dtype=np.float32) + iters = np.arange(losses.size) + lines: list[Any] = [] + lines.extend(ax.semilogy(iters, losses, c="k", lw=2, label="loss")) + ax.set_xlabel("Iterations") + ax.set_ylabel("Loss", color="k") + ax.tick_params(axis="y", which="both", colors="k") + ax.spines["left"].set_color("k") + ax.set_xbound(-2, max(1, int(iters.max())) + 2) + + lrs = np.asarray(self.mean_fit_lrs, dtype=np.float64) + if plot_lrs and lrs.size > 0: + if lrs.size == losses.size and not np.allclose(lrs, lrs[0]): + ax_lr = ax.twinx() + ax.set_zorder(2) + ax_lr.set_zorder(1) + ax.patch.set_visible(False) + ax_lr.spines["left"].set_visible(False) + lines.extend( + ax_lr.semilogy( + np.arange(lrs.size), lrs, c="tab:blue", lw=2, ls="--", label="LR" + ) + ) + ax_lr.set_ylabel("LR", color="tab:blue") + ax_lr.tick_params(axis="y", which="both", colors="tab:blue") + ax_lr.spines["right"].set_color("tab:blue") + else: + ax.set_title(f"LR: {float(lrs[-1]):.2e}", fontsize=10) - pat_iter: Any = enumerate(linear) - if progress: - try: - from tqdm.auto import tqdm + labels = [line.get_label() for line in lines] + if len(labels) > 1: + ax.legend(lines, labels, loc="upper right") - pat_iter = enumerate(tqdm(linear, desc="Fit patterns", leave=False)) - except Exception: - warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) + if figax is None: + plt.tight_layout() + plt.show() + return fig, ax - for j, i_lin in pat_iter: - target = torch.as_tensor(stack[int(i_lin)], device=self.prepared.ctx.device, dtype=self.prepared.ctx.dtype) - x_fit, loss = self._fit_target_image( - target=target, - x_start=x_seed, - n_steps=int(n_steps), - lr=float(lr), - method=method, - power=power, - fit_disk_pixels=fit_disk_pixels, - fit_only_disk_pixels=bool(fit_only_disk_pixels), - intensity_order=None if intensity_order is None else int(intensity_order), - enforce_disk_center_of_mass=bool(enforce_disk_center_of_mass), - normalize_disk_template_max=bool(normalize_disk_template_max), - template_binary_weight=float(template_binary_weight), - template_binary_power=float(template_binary_power), - intensity_transform=intensity_transform, - weak_softplus_scale=float(weak_softplus_scale), - progress=False, - ) - x_fit_all[j] = x_fit - losses[j] = float(loss) - - self.x_patterns = x_fit_all - self.pattern_fit_losses = losses - self.pattern_fit_linear_indices = linear - if len(index_shape) == 0: - self.pattern_fit_indices = [tuple() for _ in linear] - else: - self.pattern_fit_indices = [tuple(int(k) for k in np.unravel_index(int(i), index_shape)) for i in linear] - return self + def visualize( + self, *, power: float = 0.25, cbar: bool = False, axsize: tuple[int, int] = (6, 6) + ) -> tuple[Any, Any]: + import matplotlib.pyplot as plt + from matplotlib import gridspec - def _apply_overlays(self, ax: Any, overlays: list[Overlay]) -> None: - for ov in overlays: - if ov.kind != "points_rc": - continue - d = dict(ov.data) - r = _to_numpy(d["r"]).ravel() - c = _to_numpy(d["c"]).ravel() - ax.scatter( - c, - r, - s=float(d.get("s", 60.0)), - marker=d.get("marker", "x"), - color=d.get("color", "orange"), + if self.image_ref is None: + self.preprocess() + if self.image_ref is None or self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + + fig = plt.figure(figsize=(12, 7)) + gs = gridspec.GridSpec(2, 1, height_ratios=[1, 2], hspace=0.3) + ax_top = fig.add_subplot(gs[0]) + self.plot_losses(figax=(fig, ax_top), plot_lrs=True) + + ref = np.asarray(self.image_ref, dtype=np.float32) + pred = to_numpy(self.model.forward(self.ctx)).astype(np.float32, copy=False) + refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) + predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) + vmin = float(min(refp.min(), predp.min())) + vmax = float(max(refp.max(), predp.max())) + + gs_bot = gridspec.GridSpecFromSubplotSpec(1, 2, subplot_spec=gs[1], wspace=0.15) + axs = np.array( + [fig.add_subplot(gs_bot[0, 0]), fig.add_subplot(gs_bot[0, 1])], dtype=object + ) + show_2d( + [refp, predp], + figax=(fig, axs), + title=["image_ref", "model"], + cmap="gray", + cbar=bool(cbar), + returnfig=False, + axsize=axsize, + vmin=vmin, + vmax=vmax, + ) + + if len(self.mean_fit_history) > 0: + fig.suptitle( + f"Final loss: {self.mean_fit_history[-1]:.3e} | Iters: {len(self.mean_fit_history)}", + fontsize=13, + y=0.98, ) + plt.show() + return fig, axs def plot_mean_model( self, *, power: float = 0.25, returnfig: bool = False, - show_overlays: bool = True, axsize: tuple[int, int] = (6, 6), + **_: Any, ) -> tuple[Any, Any] | None: - """ - Plot `image_ref` and the current mean-model prediction side-by-side. - - Parameters - ---------- - power - Display transform exponent applied to both reference and model images. - returnfig - If True, return `(fig, ax)` from `show_2d`. - show_overlays - If True, draw component overlays (e.g., origin and lattice markers). - axsize - Base axis size passed to the plotting helper. - - Returns - ------- - tuple[Any, Any] | None - `(fig, ax)` if `returnfig=True`, else None. - """ if self.image_ref is None: self.preprocess() - if self.image_ref is None or self.prepared is None: + if self.image_ref is None or self.model is None or self.ctx is None: raise RuntimeError("Call .define_model(...) first.") - if self.x_mean is None: - self.x_mean = self.prepared.x0.detach().clone() ref = np.asarray(self.image_ref, dtype=np.float32) - mod = _to_numpy(self.prepared.render(self.x_mean)).astype(np.float32, copy=False) + pred = self.model.forward(self.ctx).detach().cpu().numpy() refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) - modp = mod if power == 1.0 else np.maximum(mod, 0.0) ** float(power) - - vmin = float(min(refp.min(), modp.min())) - vmax = float(max(refp.max(), modp.max())) + predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) + vmin = float(min(refp.min(), predp.min())) + vmax = float(max(refp.max(), predp.max())) fig, ax = show_2d( - [refp, modp], + [refp, predp], title=["image_ref", "model"], cmap="gray", cbar=False, @@ -854,39 +405,6 @@ def plot_mean_model( vmin=vmin, vmax=vmax, ) - - H, W = ref.shape[-2], ref.shape[-1] - pad = 0 - boundaries = [] - for c in getattr(self.prepared, "components", []): - b = getattr(c, "boundary_px", None) - if b is not None: - boundaries.append(float(b)) - if boundaries: - min_b = float(np.min(boundaries)) - if min_b < 0.0: - pad = int(np.ceil(-min_b)) - - axes: list[Any] - if isinstance(ax, np.ndarray): - axes = list(ax.ravel()) - elif isinstance(ax, (list, tuple)): - axes = list(ax) - else: - axes = [ax] - - for a in axes[:2]: - # Match imshow's pixel-edge convention so overlay markers land on pixel centers. - a.set_xlim(-0.5 - pad, (W - 0.5) + pad) - a.set_ylim((H - 0.5) + pad, -0.5 - pad) - - if show_overlays: - ovs = self.prepared.overlays(self.x_mean) - if len(axes) >= 1: - self._apply_overlays(axes[0], ovs) - if len(axes) >= 2: - self._apply_overlays(axes[1], ovs) - if returnfig: return fig, ax return None From c7f32a564c366ffa91e193d448336fe153041fdc Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Fri, 27 Feb 2026 13:49:12 -0800 Subject: [PATCH 028/113] adding state saving to ModelDiffraction --- src/quantem/diffraction/model_fitting.py | 119 +++++++++++++++++++---- 1 file changed, 99 insertions(+), 20 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 10477d4f8..81c3af66d 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -1,13 +1,13 @@ from __future__ import annotations import warnings -from typing import Any, Sequence, cast +from typing import Any, Literal, Sequence, cast import numpy as np import torch from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey -from torch.nn.utils import parameters_to_vector +from torch.nn.utils import parameters_to_vector, vector_to_parameters from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem from quantem.core.fitting.base import AdditiveRenderModel, RenderComponent, RenderContext @@ -15,7 +15,6 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.optimizer_mixin import OptimizerMixin 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 @@ -49,9 +48,9 @@ def __init__(self, dataset: Any, _token: object | None = None): self.model: AdditiveRenderModel | None = None self.target_mean: torch.Tensor | None = None - self.x_defined: torch.Tensor | None = None - self.x_initial: torch.Tensor | None = None - self.x_mean: torch.Tensor | None = None + self.state_initialized: torch.Tensor | None = None + self.state_mean_refined: torch.Tensor | None = None + self.state_current: torch.Tensor | None = None self.mean_refined: bool = False self.mean_fit_history: list[float] = [] self.mean_fit_lrs: list[float] = [] @@ -71,6 +70,82 @@ def get_optimization_parameters(self) -> Any: return [] return self.model.parameters() + def _get_model_state_vector(self) -> torch.Tensor: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return parameters_to_vector(self.model.parameters()).detach().clone() + + def _load_model_state_vector(self, state: torch.Tensor) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + dst = next(self.model.parameters(), None) + if dst is None: + raise RuntimeError("Model has no parameters.") + vec = state.detach().clone().to(device=dst.device, dtype=dst.dtype) + vector_to_parameters(vec, self.model.parameters()) + + def _clear_histories(self) -> None: + for name in ("mean_fit_history", "mean_fit_lrs", "fit_history", "fit_lrs"): + if hasattr(self, name): + val = getattr(self, name) + if isinstance(val, list): + val.clear() + + def _render_state_array(self, state: torch.Tensor) -> np.ndarray: + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + live = self._get_model_state_vector() + try: + self._load_model_state_vector(state) + arr = self.model(self.ctx).cpu().detach().numpy() + # arr = to_numpy(self.model(self.ctx)).astype(np.float32, copy=False) + finally: + self._load_model_state_vector(live) + return arr + + @property + def render_initialized(self) -> np.ndarray: + if self.state_initialized is None: + raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") + return self._render_state_array(self.state_initialized) + + @property + def render_mean_refined(self) -> np.ndarray: + if self.state_mean_refined is None: + raise RuntimeError( + "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." + ) + return self._render_state_array(self.state_mean_refined) + + @property + def render_current(self) -> np.ndarray: + if self.state_current is None: + raise RuntimeError("current state is unavailable. Call .define_model(...) first.") + return self._render_state_array(self.state_current) + + def reset( + self, reset_to: Literal["initialized", "mean_refined"] = "mean_refined" + ) -> "ModelDiffraction": + if reset_to == "initialized": + state = self.state_initialized + if state is None: + raise RuntimeError( + "initialized state is unavailable. Call .define_model(...) first." + ) + elif reset_to == "mean_refined": + state = self.state_mean_refined + if state is None: + raise RuntimeError( + "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." + ) + else: + raise ValueError("reset_to must be 'initialized' or 'mean_refined'.") + + self._load_model_state_vector(state) + self.state_current = self._get_model_state_vector() + self._clear_histories() + return self + def preprocess( self, *, @@ -184,12 +259,11 @@ def define_model( self.target_mean = torch.as_tensor(self.image_ref, device=dev, dtype=dt) x0 = parameters_to_vector(self.model.parameters()).detach().clone() - self.x_defined = x0.detach().clone() - self.x_initial = x0.detach().clone() - self.x_mean = x0.detach().clone() + self.state_initialized = x0.detach().clone() + self.state_current = x0.detach().clone() + self.state_mean_refined = None self.mean_refined = False - self.mean_fit_history = [] - self.mean_fit_lrs = [] + self._clear_histories() self.remove_optimizer() return self @@ -197,15 +271,23 @@ def fit_mean_diffraction_pattern( self, *, n_steps: int = 200, + reset: bool | Literal["initialized", "mean_refined"] = False, optimizer_params: dict | None = None, scheduler_params: dict | None = None, constraint_weight: float = 1.0, - overwrite_initial: bool = True, progress: bool = False, **kwargs: Any, ) -> "ModelDiffraction": if self.model is None or self.ctx is None or self.target_mean is None: raise RuntimeError("Call .define_model(...) first.") + if reset is True: + self.reset("initialized") + elif isinstance(reset, str): + if reset not in ("initialized", "mean_refined"): + raise ValueError("reset must be False, True, 'initialized', or 'mean_refined'.") + self.reset(reset_to=cast(Literal["initialized", "mean_refined"], reset)) + elif reset not in (False,): + raise ValueError("reset must be False, True, 'initialized', or 'mean_refined'.") optimizer_rebuilt = False if optimizer_params is not None: @@ -234,8 +316,6 @@ def fit_mean_diffraction_pattern( except Exception: warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) - self.mean_fit_history = [] - self.mean_fit_lrs = [] for _ in iterator: self.zero_optimizer_grad() pred = self.model(self.ctx) @@ -255,11 +335,10 @@ def fit_mean_diffraction_pattern( self.mean_fit_history.append(loss_value) self.mean_fit_lrs.append(float(self.get_current_lr())) - x_fit = parameters_to_vector(self.model.parameters()).detach().clone() - self.x_mean = x_fit + x_fit = self._get_model_state_vector() + self.state_current = x_fit.detach().clone() + self.state_mean_refined = x_fit.detach().clone() self.mean_refined = True - if overwrite_initial: - self.x_initial = x_fit.detach().clone() return self def plot_losses( @@ -343,7 +422,7 @@ def visualize( self.plot_losses(figax=(fig, ax_top), plot_lrs=True) ref = np.asarray(self.image_ref, dtype=np.float32) - pred = to_numpy(self.model.forward(self.ctx)).astype(np.float32, copy=False) + pred = self.render_current refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) vmin = float(min(refp.min(), predp.min())) @@ -388,7 +467,7 @@ def plot_mean_model( raise RuntimeError("Call .define_model(...) first.") ref = np.asarray(self.image_ref, dtype=np.float32) - pred = self.model.forward(self.ctx).detach().cpu().numpy() + pred = self.render_current refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) From 2a9d6403b3c2b9cc0bbfa8c983e87aa0efc36eb3 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Fri, 27 Feb 2026 13:58:21 -0800 Subject: [PATCH 029/113] switching to state_dict saving --- src/quantem/diffraction/model_fitting.py | 54 ++++++++++++------------ 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 81c3af66d..7fdd43e48 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -7,7 +7,6 @@ import torch from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey -from torch.nn.utils import parameters_to_vector, vector_to_parameters from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem from quantem.core.fitting.base import AdditiveRenderModel, RenderComponent, RenderContext @@ -48,9 +47,9 @@ def __init__(self, dataset: Any, _token: object | None = None): self.model: AdditiveRenderModel | None = None self.target_mean: torch.Tensor | None = None - self.state_initialized: torch.Tensor | None = None - self.state_mean_refined: torch.Tensor | None = None - self.state_current: torch.Tensor | None = None + self.state_initialized: dict[str, torch.Tensor] | None = None + self.state_mean_refined: dict[str, torch.Tensor] | None = None + self.state_current: dict[str, torch.Tensor] | None = None self.mean_refined: bool = False self.mean_fit_history: list[float] = [] self.mean_fit_lrs: list[float] = [] @@ -70,19 +69,18 @@ def get_optimization_parameters(self) -> Any: return [] return self.model.parameters() - def _get_model_state_vector(self) -> torch.Tensor: + def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: + return {k: v.detach().clone() for k, v in state.items()} + + def _get_model_state_dict_copy(self) -> dict[str, torch.Tensor]: if self.model is None: raise RuntimeError("Call .define_model(...) first.") - return parameters_to_vector(self.model.parameters()).detach().clone() + return self._clone_state_dict(self.model.state_dict()) - def _load_model_state_vector(self, state: torch.Tensor) -> None: + def _load_model_state_dict_copy(self, state: dict[str, torch.Tensor]) -> None: if self.model is None: raise RuntimeError("Call .define_model(...) first.") - dst = next(self.model.parameters(), None) - if dst is None: - raise RuntimeError("Model has no parameters.") - vec = state.detach().clone().to(device=dst.device, dtype=dst.dtype) - vector_to_parameters(vec, self.model.parameters()) + self.model.load_state_dict(self._clone_state_dict(state), strict=True) def _clear_histories(self) -> None: for name in ("mean_fit_history", "mean_fit_lrs", "fit_history", "fit_lrs"): @@ -91,16 +89,15 @@ def _clear_histories(self) -> None: if isinstance(val, list): val.clear() - def _render_state_array(self, state: torch.Tensor) -> np.ndarray: + def _render_state_array(self, state: dict[str, torch.Tensor]) -> np.ndarray: if self.model is None or self.ctx is None: raise RuntimeError("Call .define_model(...) first.") - live = self._get_model_state_vector() + live = self._get_model_state_dict_copy() try: - self._load_model_state_vector(state) + self._load_model_state_dict_copy(state) arr = self.model(self.ctx).cpu().detach().numpy() - # arr = to_numpy(self.model(self.ctx)).astype(np.float32, copy=False) finally: - self._load_model_state_vector(live) + self._load_model_state_dict_copy(live) return arr @property @@ -119,9 +116,10 @@ def render_mean_refined(self) -> np.ndarray: @property def render_current(self) -> np.ndarray: - if self.state_current is None: - raise RuntimeError("current state is unavailable. Call .define_model(...) first.") - return self._render_state_array(self.state_current) + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + # Current render follows live module parameters by design. + return self.model(self.ctx).cpu().detach().numpy() def reset( self, reset_to: Literal["initialized", "mean_refined"] = "mean_refined" @@ -141,8 +139,8 @@ def reset( else: raise ValueError("reset_to must be 'initialized' or 'mean_refined'.") - self._load_model_state_vector(state) - self.state_current = self._get_model_state_vector() + self._load_model_state_dict_copy(state) + self.state_current = self._get_model_state_dict_copy() self._clear_histories() return self @@ -258,9 +256,9 @@ def define_model( self.ctx = RenderContext(shape=(h, w), device=dev, dtype=dt, mask=mask_t, fields={}) self.target_mean = torch.as_tensor(self.image_ref, device=dev, dtype=dt) - x0 = parameters_to_vector(self.model.parameters()).detach().clone() - self.state_initialized = x0.detach().clone() - self.state_current = x0.detach().clone() + s0 = self._get_model_state_dict_copy() + self.state_initialized = s0 + self.state_current = self._clone_state_dict(s0) self.state_mean_refined = None self.mean_refined = False self._clear_histories() @@ -335,9 +333,9 @@ def fit_mean_diffraction_pattern( self.mean_fit_history.append(loss_value) self.mean_fit_lrs.append(float(self.get_current_lr())) - x_fit = self._get_model_state_vector() - self.state_current = x_fit.detach().clone() - self.state_mean_refined = x_fit.detach().clone() + s_fit = self._get_model_state_dict_copy() + self.state_current = s_fit + self.state_mean_refined = self._clone_state_dict(s_fit) self.mean_refined = True return self From a9b4eaabffa0d4e1d613383f939e25b4262cbb13 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Fri, 27 Feb 2026 14:43:58 -0800 Subject: [PATCH 030/113] first version of FitBase --- src/quantem/core/fitting/base.py | 103 +++++++++++++++++++++++ src/quantem/diffraction/model_fitting.py | 97 ++++++++++----------- 2 files changed, 148 insertions(+), 52 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index c37ccef98..fa3dbf258 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -1,10 +1,14 @@ from __future__ import annotations +from abc import abstractmethod from dataclasses import dataclass, field from typing import Any, cast import torch from torch import nn +from tqdm.auto import tqdm + +from quantem.core.ml.optimizer_mixin import OptimizerMixin @dataclass @@ -48,6 +52,105 @@ def total_constraint_loss(self, ctx: RenderContext) -> torch.Tensor: return loss +@dataclass +class FitResult: + losses: list[float] + lrs: list[float] + final_loss: float + num_steps: int + metrics: dict[str, list[float]] = field(default_factory=dict) + + +class FitBase(OptimizerMixin): + DEFAULT_LR = 1e-2 + DEFAULT_OPTIMIZER_TYPE = "adam" + + def __init__(self): + super().__init__() + self.fit_history_by_run: dict[str, FitResult] = {} + + @abstractmethod + def _forward_for_fit(self, *, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: + raise NotImplementedError + + @abstractmethod + def _fidelity_loss( + self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any + ) -> torch.Tensor: + raise NotImplementedError + + @abstractmethod + def _soft_losses( + self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any + ) -> dict[str, torch.Tensor]: + return {} + + def fit_render( + self, + *, + target: torch.Tensor, + n_steps: int, + optimizer_params: dict | None = None, + scheduler_params: dict | None = None, + progress: bool = False, + run_key: str = "default", + **kwargs: Any, + ) -> FitResult: + optimizer_rebuilt = False + if optimizer_params is not None: + self.set_optimizer(optimizer_params) + optimizer_rebuilt = True + elif self.optimizer is None: + if self.optimizer_params: + self.set_optimizer(self.optimizer_params) + else: + self.set_optimizer( + { + "type": getattr(self, "DEFAULT_OPTIMIZER_TYPE", "adamw"), + "lr": float(getattr(self, "DEFAULT_LR", self.DEFAULT_LR)), + } + ) + optimizer_rebuilt = True + + if scheduler_params is not None: + self.set_scheduler(scheduler_params, num_iter=int(n_steps)) + elif self.scheduler is None and self.scheduler_params: + self.set_scheduler(self.scheduler_params, num_iter=int(n_steps)) + elif optimizer_rebuilt and self.scheduler is not None and self.optimizer is not None: + self.scheduler.optimizer = self.optimizer + + pbar = tqdm(range(int(n_steps)), desc="Fit render", disable=not progress) + + losses: list[float] = [] + lrs: list[float] = [] + metrics: dict[str, list[float]] = {} + for _ in pbar: + self.zero_optimizer_grad() + pred = self._forward_for_fit(target=target, **kwargs) + fidelity_loss = self._fidelity_loss(pred, target, **kwargs) + soft_losses = self._soft_losses(pred, target, **kwargs) + total_loss = fidelity_loss + for k, v in soft_losses.items(): + metrics.setdefault(k, []).append(float(v.detach().cpu())) + total_loss = total_loss + v + total_loss.backward() + self.step_optimizer() + total_loss_value = float(total_loss.detach().cpu()) + self.step_scheduler(total_loss_value) + losses.append(total_loss_value) + lrs.append(float(self.get_current_lr())) + + result = FitResult( + losses=losses, + lrs=lrs, + final_loss=(losses[-1] if losses else float("nan")), + num_steps=int(n_steps), + metrics=metrics, + ) + self.fit_history_by_run[str(run_key)] = result + return result + + Component = RenderComponent ModelContext = RenderContext Model = AdditiveRenderModel diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 7fdd43e48..61fbef824 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -1,6 +1,5 @@ from __future__ import annotations -import warnings from typing import Any, Literal, Sequence, cast import numpy as np @@ -9,10 +8,14 @@ from scipy.signal.windows import tukey from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem -from quantem.core.fitting.base import AdditiveRenderModel, RenderComponent, RenderContext +from quantem.core.fitting.base import ( + AdditiveRenderModel, + FitBase, + RenderComponent, + RenderContext, +) from quantem.core.fitting.diffraction import OriginND from quantem.core.io.serialize import AutoSerialize -from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.core.visualization import show_2d @@ -27,16 +30,16 @@ def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) return float(cast(float | int, value)) -class ModelDiffraction(OptimizerMixin, AutoSerialize): +class ModelDiffraction(FitBase, AutoSerialize): _token = object() - DEFAULT_LR = 1e-3 - DEFAULT_OPTIMIZER_TYPE = "adamw" + DEFAULT_LR = 5e-2 + DEFAULT_OPTIMIZER_TYPE = "adam" def __init__(self, dataset: Any, _token: object | None = None): if _token is not self._token: raise RuntimeError("Use ModelDiffraction.from_dataset() or .from_file().") AutoSerialize.__init__(self) - OptimizerMixin.__init__(self) + FitBase.__init__(self) self.dataset = dataset self.metadata: dict[str, Any] = {} self.image_ref: np.ndarray | None = None @@ -69,6 +72,30 @@ def get_optimization_parameters(self) -> Any: return [] return self.model.parameters() + def _forward_for_fit(self, *, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + return self.model(self.ctx) + + def _fidelity_loss( + self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any + ) -> torch.Tensor: + if self.ctx is not None and self.ctx.mask is not None: + diff = (pred - target) * self.ctx.mask + denom = torch.clamp(torch.sum(self.ctx.mask), min=1.0) + return torch.sum(diff * diff) / denom + return torch.mean((pred - target) ** 2) + + def _soft_constraints( + self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any + ) -> dict[str, torch.Tensor]: + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + constraint_weight = float(kwargs.get("constraint_weight", 1.0)) + if constraint_weight == 0.0: + return {} + return {"constraint": constraint_weight * self.model.total_constraint_loss(self.ctx)} + def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: return {k: v.detach().clone() for k, v in state.items()} @@ -287,51 +314,17 @@ def fit_mean_diffraction_pattern( elif reset not in (False,): raise ValueError("reset must be False, True, 'initialized', or 'mean_refined'.") - optimizer_rebuilt = False - if optimizer_params is not None: - self.set_optimizer(optimizer_params) - optimizer_rebuilt = True - elif self.optimizer is None: - if self.optimizer_params: - self.set_optimizer(self.optimizer_params) - else: - self.set_optimizer({"type": self.DEFAULT_OPTIMIZER_TYPE, "lr": self.DEFAULT_LR}) - optimizer_rebuilt = True - - if scheduler_params is not None: - self.set_scheduler(scheduler_params, num_iter=int(n_steps)) - elif self.scheduler is None and self.scheduler_params: - self.set_scheduler(self.scheduler_params, num_iter=int(n_steps)) - elif optimizer_rebuilt and self.scheduler is not None and self.optimizer is not None: - self.scheduler.optimizer = self.optimizer - - iterator: Any = range(int(n_steps)) - if progress: - try: - from tqdm.auto import trange - - iterator = trange(int(n_steps), desc="Fit mean diffraction", leave=False) - except Exception: - warnings.warn("progress=True requested but tqdm is unavailable.", stacklevel=2) - - for _ in iterator: - self.zero_optimizer_grad() - pred = self.model(self.ctx) - if self.ctx.mask is not None: - diff = (pred - self.target_mean) * self.ctx.mask - denom = torch.clamp(torch.sum(self.ctx.mask), min=1.0) - loss_data = torch.sum(diff * diff) / denom - else: - loss_data = torch.mean((pred - self.target_mean) ** 2) - loss = loss_data + float(constraint_weight) * self.model.total_constraint_loss( - self.ctx - ) - loss.backward() - self.step_optimizer() - loss_value = float(loss.detach().cpu()) - self.step_scheduler(loss_value) - self.mean_fit_history.append(loss_value) - self.mean_fit_lrs.append(float(self.get_current_lr())) + fit_result = self.fit_render( + target=self.target_mean, + n_steps=int(n_steps), + optimizer_params=optimizer_params, + scheduler_params=scheduler_params, + progress=bool(progress), + run_key="mean", + constraint_weight=float(constraint_weight), + ) + self.mean_fit_history.extend(fit_result.losses) + self.mean_fit_lrs.extend(fit_result.lrs) s_fit = self._get_model_state_dict_copy() self.state_current = s_fit From b79e32c855a910349e1005cb65348e27ef2d321a Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Fri, 27 Feb 2026 16:29:05 -0800 Subject: [PATCH 031/113] cleaning up FitBase and ModelDiffraction --- src/quantem/core/fitting/__init__.py | 2 + src/quantem/core/fitting/background.py | 20 +--- src/quantem/core/fitting/base.py | 116 ++++++++++++++++------- src/quantem/core/fitting/diffraction.py | 33 +------ src/quantem/diffraction/model_fitting.py | 79 ++++----------- 5 files changed, 108 insertions(+), 142 deletions(-) diff --git a/src/quantem/core/fitting/__init__.py b/src/quantem/core/fitting/__init__.py index f7d3c3e32..4dae16615 100644 --- a/src/quantem/core/fitting/__init__.py +++ b/src/quantem/core/fitting/__init__.py @@ -3,6 +3,7 @@ from quantem.core.fitting.base import Component as Component from quantem.core.fitting.base import Model as Model from quantem.core.fitting.base import ModelContext as ModelContext +from quantem.core.fitting.base import OriginND as OriginND from quantem.core.fitting.base import Parameter as Parameter from quantem.core.fitting.diffraction import DiskTemplate as DiskTemplate @@ -16,6 +17,7 @@ "ModelContext", "Component", "Model", + "OriginND", "DiskTemplate", "SyntheticDiskLattice", "DCBackground", diff --git a/src/quantem/core/fitting/background.py b/src/quantem/core/fitting/background.py index 7c50ea428..7a15d37b4 100644 --- a/src/quantem/core/fitting/background.py +++ b/src/quantem/core/fitting/background.py @@ -6,8 +6,7 @@ import torch from torch import nn -from quantem.core.fitting.base import RenderComponent, RenderContext -from quantem.core.fitting.diffraction import OriginND +from quantem.core.fitting.base import OriginND, RenderComponent, RenderContext def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: @@ -20,14 +19,6 @@ def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) return float(cast(float | int, value)) -def _softplus_pos(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: - return torch.nn.functional.softplus(x) + eps - - -def _relu_pos(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: - return torch.nn.functional.relu(x) + eps - - class DCBackground(RenderComponent): def __init__( self, @@ -42,8 +33,7 @@ def __init__( ) def forward(self, ctx: RenderContext) -> torch.Tensor: - inten = _relu_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) - # inten = _softplus_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + inten = torch.clamp(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype), min=0.0) return torch.ones(ctx.shape, device=ctx.device, dtype=ctx.dtype) * inten @@ -79,9 +69,7 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: cc = torch.arange(ctx.shape[1], device=ctx.device, dtype=ctx.dtype)[None, :] r0, c0 = self.origin.coords[0], self.origin.coords[1] - sigma = _relu_pos(self.sigma_raw.to(device=ctx.device, dtype=ctx.dtype), eps=1e-6) - # sigma = _softplus_pos(self.sigma_raw.to(device=ctx.device, dtype=ctx.dtype), eps=1e-6) - inten = _relu_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) - # inten = _softplus_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + sigma = torch.clamp(self.sigma_raw.to(device=ctx.device, dtype=ctx.dtype), min=1e-6) + inten = torch.clamp(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype), min=0.0) r2 = (rr - r0) ** 2 + (cc - c0) ** 2 return inten * torch.exp(-0.5 * r2 / (sigma * sigma)) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index fa3dbf258..d919a499d 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -1,8 +1,7 @@ from __future__ import annotations -from abc import abstractmethod from dataclasses import dataclass, field -from typing import Any, cast +from typing import Any, Sequence, cast import torch from torch import nn @@ -20,6 +19,17 @@ class RenderContext: fields: dict[str, Any] = field(default_factory=dict) +class OriginND(nn.Module): + def __init__(self, *, ndim: int, init: Sequence[float]): + super().__init__() + if int(ndim) <= 0: + raise ValueError("ndim must be >= 1.") + if len(init) != int(ndim): + raise ValueError("init length must match ndim.") + self.ndim = int(ndim) + self.coords = nn.Parameter(torch.as_tensor(init, dtype=torch.float32).reshape(self.ndim)) + + class RenderComponent(nn.Module): def forward(self, ctx: RenderContext) -> torch.Tensor: raise NotImplementedError @@ -37,10 +47,8 @@ def __init__(self, *, origin: nn.Module, components: list[RenderComponent]): def forward(self, ctx: RenderContext) -> torch.Tensor: if len(self.components) == 0: return torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) - first = cast(RenderComponent, self.components[0]) - out = first(ctx) - for module in self.components[1:]: - component = cast(RenderComponent, module) + out = self.components[0](ctx) + for component in self.components[1:]: out = out + component(ctx) return out @@ -67,29 +75,62 @@ class FitBase(OptimizerMixin): def __init__(self): super().__init__() - self.fit_history_by_run: dict[str, FitResult] = {} + self.model: AdditiveRenderModel | None = None + self.ctx: RenderContext | None = None + self.fit_history: dict[str, FitResult] = {} + self.state_initialized: dict[str, torch.Tensor] | None = None + + def get_optimization_parameters(self) -> Any: + if self.model is None: + return [] + return self.model.parameters() + + def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: + return {k: v.detach().clone() for k, v in state.items()} + + def _get_model_state_dict_copy(self) -> dict[str, torch.Tensor]: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return self._clone_state_dict(self.model.state_dict()) + + def _load_model_state_dict_copy(self, state: dict[str, torch.Tensor]) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + self.model.load_state_dict(self._clone_state_dict(state), strict=True) + + @property + def state_current(self) -> dict[str, torch.Tensor] | None: + if self.model is None: + return None + return self._get_model_state_dict_copy() + + def _clear_fit_history_all(self) -> None: + self.fit_history.clear() + + def _clear_fit_history_run(self, run_key: str) -> None: + self.fit_history.pop(str(run_key), None) - @abstractmethod def _forward_for_fit(self, *, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: - raise NotImplementedError + if self.model is None or self.ctx is None: + raise RuntimeError("Model and context are not defined for fitting.") + return self.model(self.ctx) - @abstractmethod - def _fidelity_loss( - self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any - ) -> torch.Tensor: + def _data_loss(self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: raise NotImplementedError - @abstractmethod - def _soft_losses( + def _constraint_loss( self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any - ) -> dict[str, torch.Tensor]: - return {} + ) -> torch.Tensor: + if self.model is None or self.ctx is None: + raise RuntimeError("Model and context are not defined for fitting.") + return self.model.total_constraint_loss(self.ctx) def fit_render( self, *, target: torch.Tensor, n_steps: int, + constraint_weight: float = 1.0, optimizer_params: dict | None = None, scheduler_params: dict | None = None, progress: bool = False, @@ -112,27 +153,24 @@ def fit_render( ) optimizer_rebuilt = True + n_steps = int(n_steps) if scheduler_params is not None: - self.set_scheduler(scheduler_params, num_iter=int(n_steps)) + self.set_scheduler(scheduler_params, num_iter=n_steps) elif self.scheduler is None and self.scheduler_params: - self.set_scheduler(self.scheduler_params, num_iter=int(n_steps)) + self.set_scheduler(self.scheduler_params, num_iter=n_steps) elif optimizer_rebuilt and self.scheduler is not None and self.optimizer is not None: self.scheduler.optimizer = self.optimizer - pbar = tqdm(range(int(n_steps)), desc="Fit render", disable=not progress) + pbar = tqdm(range(n_steps), desc="Fit render", disable=not progress) losses: list[float] = [] lrs: list[float] = [] - metrics: dict[str, list[float]] = {} for _ in pbar: self.zero_optimizer_grad() pred = self._forward_for_fit(target=target, **kwargs) - fidelity_loss = self._fidelity_loss(pred, target, **kwargs) - soft_losses = self._soft_losses(pred, target, **kwargs) - total_loss = fidelity_loss - for k, v in soft_losses.items(): - metrics.setdefault(k, []).append(float(v.detach().cpu())) - total_loss = total_loss + v + data_loss = self._data_loss(pred, target, **kwargs) + constraint_loss = self._constraint_loss(pred, target, **kwargs) + total_loss = data_loss + constraint_weight * constraint_loss total_loss.backward() self.step_optimizer() total_loss_value = float(total_loss.detach().cpu()) @@ -140,14 +178,22 @@ def fit_render( losses.append(total_loss_value) lrs.append(float(self.get_current_lr())) - result = FitResult( - losses=losses, - lrs=lrs, - final_loss=(losses[-1] if losses else float("nan")), - num_steps=int(n_steps), - metrics=metrics, - ) - self.fit_history_by_run[str(run_key)] = result + key = str(run_key) + if key in self.fit_history: + prev = self.fit_history[key] + prev.losses.extend(losses) + prev.lrs.extend(lrs) + prev.final_loss = prev.losses[-1] if prev.losses else float("nan") + prev.num_steps = len(prev.losses) + result = prev + else: + result = FitResult( + losses=losses, + lrs=lrs, + final_loss=(losses[-1] if losses else float("nan")), + num_steps=n_steps, + ) + self.fit_history[key] = result return result diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 13224632a..0872ffb5c 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -6,7 +6,7 @@ import torch from torch import nn -from quantem.core.fitting.base import RenderComponent, RenderContext +from quantem.core.fitting.base import OriginND, RenderComponent, RenderContext def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: @@ -19,14 +19,6 @@ def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) return float(cast(float | int, value)) -def _softplus_pos(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: - return torch.nn.functional.softplus(x) + eps - - -def _relu_pos(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: - return torch.nn.functional.relu(x) + eps - - def _splat_patch( out: torch.Tensor, *, @@ -65,17 +57,6 @@ def put(rr: torch.Tensor, cc: torch.Tensor, ww: torch.Tensor) -> None: put(r0i + 1, c0i + 1, w11) -class OriginND(nn.Module): - def __init__(self, *, ndim: int, init: Sequence[float]): - super().__init__() - if int(ndim) <= 0: - raise ValueError("ndim must be >= 1.") - if len(init) != int(ndim): - raise ValueError("init length must match ndim.") - self.ndim = int(ndim) - self.coords = nn.Parameter(torch.as_tensor(init, dtype=torch.float32).reshape(self.ndim)) - - class DiskTemplate(RenderComponent): def __init__( self, @@ -156,8 +137,7 @@ def set_origin(self, origin: OriginND) -> None: def patch_values(self) -> torch.Tensor: if self.refine_all_pixels: - return _relu_pos(self.template_raw).reshape(-1) - # return _softplus_pos(self.template_raw).reshape(-1) + return torch.clamp(self.template_raw, min=0.0).reshape(-1) return self.template_raw.reshape(-1) def patch_offsets(self) -> tuple[torch.Tensor, torch.Tensor]: @@ -182,8 +162,7 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: r0, c0 = self.origin.coords[0], self.origin.coords[1] if self.intensity_raw is None: raise RuntimeError("DiskTemplate intensity parameter is missing.") - scale = _relu_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) - # scale = _softplus_pos(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype)) + scale = torch.clamp(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype), min=0.0) self.add_patch(out, r0=r0, c0=c0, scale=scale) return out @@ -349,8 +328,7 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: cc0 = centers_c[j] if self.per_disk_intensity: - inten = _relu_pos(self.i0_raw[j]) - # inten = _softplus_pos(self.i0_raw[j]) + inten = torch.clamp(self.i0_raw[j], min=0.0) if active_order >= 1 and self.ir is not None and self.ic is not None: inten = inten + self.ir[j] * dr + self.ic[j] * dc if ( @@ -362,8 +340,7 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: inten = inten + self.irr[j] * dr2 + self.icc[j] * dc2 + self.irc[j] * drdc inten = torch.clamp(inten, min=0.0) else: - inten = _relu_pos(self.i0_raw) - # inten = _softplus_pos(self.i0_raw) + inten = torch.clamp(self.i0_raw, min=0.0) if active_order >= 1: assert self.ir is not None and self.ic is not None inten = inten + self.ir * rr0 + self.ic * cc0 diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 61fbef824..8b6df7c3b 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -11,10 +11,10 @@ from quantem.core.fitting.base import ( AdditiveRenderModel, FitBase, + OriginND, RenderComponent, RenderContext, ) -from quantem.core.fitting.diffraction import OriginND from quantem.core.io.serialize import AutoSerialize from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.core.visualization import show_2d @@ -46,16 +46,10 @@ def __init__(self, dataset: Any, _token: object | None = None): self.preprocess_shifts: np.ndarray | None = None self.index_shape: tuple[int, ...] | None = None - self.ctx: RenderContext | None = None - self.model: AdditiveRenderModel | None = None self.target_mean: torch.Tensor | None = None - self.state_initialized: dict[str, torch.Tensor] | None = None self.state_mean_refined: dict[str, torch.Tensor] | None = None - self.state_current: dict[str, torch.Tensor] | None = None self.mean_refined: bool = False - self.mean_fit_history: list[float] = [] - self.mean_fit_lrs: list[float] = [] @classmethod def from_dataset( @@ -67,55 +61,13 @@ def from_dataset( "from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance." ) - def get_optimization_parameters(self) -> Any: - if self.model is None: - return [] - return self.model.parameters() - - def _forward_for_fit(self, *, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: - if self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - return self.model(self.ctx) - - def _fidelity_loss( - self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any - ) -> torch.Tensor: + def _data_loss(self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: if self.ctx is not None and self.ctx.mask is not None: diff = (pred - target) * self.ctx.mask denom = torch.clamp(torch.sum(self.ctx.mask), min=1.0) return torch.sum(diff * diff) / denom return torch.mean((pred - target) ** 2) - def _soft_constraints( - self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any - ) -> dict[str, torch.Tensor]: - if self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - constraint_weight = float(kwargs.get("constraint_weight", 1.0)) - if constraint_weight == 0.0: - return {} - return {"constraint": constraint_weight * self.model.total_constraint_loss(self.ctx)} - - def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: - return {k: v.detach().clone() for k, v in state.items()} - - def _get_model_state_dict_copy(self) -> dict[str, torch.Tensor]: - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - return self._clone_state_dict(self.model.state_dict()) - - def _load_model_state_dict_copy(self, state: dict[str, torch.Tensor]) -> None: - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - self.model.load_state_dict(self._clone_state_dict(state), strict=True) - - def _clear_histories(self) -> None: - for name in ("mean_fit_history", "mean_fit_lrs", "fit_history", "fit_lrs"): - if hasattr(self, name): - val = getattr(self, name) - if isinstance(val, list): - val.clear() - def _render_state_array(self, state: dict[str, torch.Tensor]) -> np.ndarray: if self.model is None or self.ctx is None: raise RuntimeError("Call .define_model(...) first.") @@ -157,18 +109,21 @@ def reset( raise RuntimeError( "initialized state is unavailable. Call .define_model(...) first." ) + self._clear_fit_history_all() elif reset_to == "mean_refined": state = self.state_mean_refined if state is None: raise RuntimeError( "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." ) + mean_hist = self.fit_history.get("mean") + self._clear_fit_history_all() + if mean_hist is not None: + self.fit_history["mean"] = mean_hist else: raise ValueError("reset_to must be 'initialized' or 'mean_refined'.") self._load_model_state_dict_copy(state) - self.state_current = self._get_model_state_dict_copy() - self._clear_histories() return self def preprocess( @@ -285,10 +240,9 @@ def define_model( s0 = self._get_model_state_dict_copy() self.state_initialized = s0 - self.state_current = self._clone_state_dict(s0) self.state_mean_refined = None self.mean_refined = False - self._clear_histories() + self._clear_fit_history_all() self.remove_optimizer() return self @@ -314,20 +268,17 @@ def fit_mean_diffraction_pattern( elif reset not in (False,): raise ValueError("reset must be False, True, 'initialized', or 'mean_refined'.") - fit_result = self.fit_render( + self.fit_render( target=self.target_mean, n_steps=int(n_steps), + constraint_weight=float(constraint_weight), optimizer_params=optimizer_params, scheduler_params=scheduler_params, progress=bool(progress), run_key="mean", - constraint_weight=float(constraint_weight), ) - self.mean_fit_history.extend(fit_result.losses) - self.mean_fit_lrs.extend(fit_result.lrs) s_fit = self._get_model_state_dict_copy() - self.state_current = s_fit self.state_mean_refined = self._clone_state_dict(s_fit) self.mean_refined = True return self @@ -342,7 +293,8 @@ def plot_losses( else: fig, ax = figax - losses = np.asarray(self.mean_fit_history, dtype=np.float64) + mean_hist = self.fit_history.get("mean") + losses = np.asarray([] if mean_hist is None else mean_hist.losses, dtype=np.float64) if losses.size == 0: ax.text( 0.5, @@ -368,7 +320,7 @@ def plot_losses( ax.spines["left"].set_color("k") ax.set_xbound(-2, max(1, int(iters.max())) + 2) - lrs = np.asarray(self.mean_fit_lrs, dtype=np.float64) + lrs = np.asarray([] if mean_hist is None else mean_hist.lrs, dtype=np.float64) if plot_lrs and lrs.size > 0: if lrs.size == losses.size and not np.allclose(lrs, lrs[0]): ax_lr = ax.twinx() @@ -435,9 +387,10 @@ def visualize( vmax=vmax, ) - if len(self.mean_fit_history) > 0: + mean_hist = self.fit_history.get("mean") + if mean_hist is not None and len(mean_hist.losses) > 0: fig.suptitle( - f"Final loss: {self.mean_fit_history[-1]:.3e} | Iters: {len(self.mean_fit_history)}", + f"Final loss: {mean_hist.losses[-1]:.3e} | Iters: {len(mean_hist.losses)}", fontsize=13, y=0.98, ) From e0aeb0bfe1fe33a889c77ec64425d202fa2c5695 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Fri, 27 Feb 2026 16:43:11 -0800 Subject: [PATCH 032/113] moving more stuff to FitBase --- src/quantem/core/fitting/base.py | 53 ++++++++++++++++++++++-- src/quantem/diffraction/model_fitting.py | 34 +-------------- 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index d919a499d..4b60fa5ce 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -1,8 +1,9 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Any, Sequence, cast +from typing import Any, Literal, Self, Sequence, cast +import numpy as np import torch from torch import nn from tqdm.auto import tqdm @@ -79,6 +80,7 @@ def __init__(self): self.ctx: RenderContext | None = None self.fit_history: dict[str, FitResult] = {} self.state_initialized: dict[str, torch.Tensor] | None = None + self.loss_fn = torch.nn.MSELoss(reduction="mean") def get_optimization_parameters(self) -> Any: if self.model is None: @@ -110,13 +112,56 @@ def _clear_fit_history_all(self) -> None: def _clear_fit_history_run(self, run_key: str) -> None: self.fit_history.pop(str(run_key), None) + def _render_state_array(self, state: dict[str, torch.Tensor]) -> np.ndarray: + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + live = self._get_model_state_dict_copy() + try: + self._load_model_state_dict_copy(state) + arr = self.model(self.ctx).detach().cpu().numpy() + finally: + self._load_model_state_dict_copy(live) + return arr + + @property + def render_initialized(self) -> np.ndarray: + if self.state_initialized is None: + raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") + return self._render_state_array(self.state_initialized) + + @property + def render_current(self) -> np.ndarray: + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + return self.model(self.ctx).detach().cpu().numpy() + + def reset( + self, + reset_to: Literal["initialized"] = "initialized", + ) -> Self: + if reset_to != "initialized": + raise ValueError("FitBase.reset only supports reset_to='initialized'.") + if self.state_initialized is None: + raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") + self._load_model_state_dict_copy(self.state_initialized) + self._clear_fit_history_all() + return self + def _forward_for_fit(self, *, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: if self.model is None or self.ctx is None: raise RuntimeError("Model and context are not defined for fitting.") return self.model(self.ctx) - def _data_loss(self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: - raise NotImplementedError + def _fidelity_loss( + self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any + ) -> torch.Tensor: + if self.ctx is not None and self.ctx.mask is not None: + # TODO -- use loss modules (currently implemented in tomo branch) + # and update them to allow for masking at module level + diff = (pred - target) * self.ctx.mask + denom = torch.clamp(torch.sum(self.ctx.mask), min=1.0) + return torch.sum(diff * diff) / denom + return self.loss_fn(pred, target) def _constraint_loss( self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any @@ -168,7 +213,7 @@ def fit_render( for _ in pbar: self.zero_optimizer_grad() pred = self._forward_for_fit(target=target, **kwargs) - data_loss = self._data_loss(pred, target, **kwargs) + data_loss = self._fidelity_loss(pred, target, **kwargs) constraint_loss = self._constraint_loss(pred, target, **kwargs) total_loss = data_loss + constraint_weight * constraint_loss total_loss.backward() diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 8b6df7c3b..1cbef1620 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -61,30 +61,6 @@ def from_dataset( "from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance." ) - def _data_loss(self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: - if self.ctx is not None and self.ctx.mask is not None: - diff = (pred - target) * self.ctx.mask - denom = torch.clamp(torch.sum(self.ctx.mask), min=1.0) - return torch.sum(diff * diff) / denom - return torch.mean((pred - target) ** 2) - - def _render_state_array(self, state: dict[str, torch.Tensor]) -> np.ndarray: - if self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - live = self._get_model_state_dict_copy() - try: - self._load_model_state_dict_copy(state) - arr = self.model(self.ctx).cpu().detach().numpy() - finally: - self._load_model_state_dict_copy(live) - return arr - - @property - def render_initialized(self) -> np.ndarray: - if self.state_initialized is None: - raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") - return self._render_state_array(self.state_initialized) - @property def render_mean_refined(self) -> np.ndarray: if self.state_mean_refined is None: @@ -93,15 +69,9 @@ def render_mean_refined(self) -> np.ndarray: ) return self._render_state_array(self.state_mean_refined) - @property - def render_current(self) -> np.ndarray: - if self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - # Current render follows live module parameters by design. - return self.model(self.ctx).cpu().detach().numpy() - def reset( - self, reset_to: Literal["initialized", "mean_refined"] = "mean_refined" + self, + reset_to: Literal["initialized", "mean_refined"] = "mean_refined", ) -> "ModelDiffraction": if reset_to == "initialized": state = self.state_initialized From 2c67dfebebce89abe14c25603b7ba94787da6ced Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Fri, 27 Feb 2026 16:54:11 -0800 Subject: [PATCH 033/113] reorganizing classes -- no functional change --- src/quantem/core/fitting/__init__.py | 18 ++-- src/quantem/core/fitting/base.py | 115 ++++++++++++----------- src/quantem/diffraction/model_fitting.py | 78 +++++++-------- 3 files changed, 108 insertions(+), 103 deletions(-) diff --git a/src/quantem/core/fitting/__init__.py b/src/quantem/core/fitting/__init__.py index 4dae16615..1bb926205 100644 --- a/src/quantem/core/fitting/__init__.py +++ b/src/quantem/core/fitting/__init__.py @@ -1,25 +1,21 @@ -from __future__ import annotations - +from quantem.core.fitting.background import DCBackground as DCBackground +from quantem.core.fitting.background import GaussianBackground as GaussianBackground from quantem.core.fitting.base import Component as Component from quantem.core.fitting.base import Model as Model from quantem.core.fitting.base import ModelContext as ModelContext from quantem.core.fitting.base import OriginND as OriginND from quantem.core.fitting.base import Parameter as Parameter - from quantem.core.fitting.diffraction import DiskTemplate as DiskTemplate from quantem.core.fitting.diffraction import SyntheticDiskLattice as SyntheticDiskLattice -from quantem.core.fitting.background import DCBackground as DCBackground -from quantem.core.fitting.background import GaussianBackground as GaussianBackground - __all__ = [ - "Parameter", - "ModelContext", "Component", + "DCBackground", + "DiskTemplate", + "GaussianBackground", "Model", + "ModelContext", "OriginND", - "DiskTemplate", + "Parameter", "SyntheticDiskLattice", - "DCBackground", - "GaussianBackground", ] diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 4b60fa5ce..dfce2e7b3 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -76,53 +76,28 @@ class FitBase(OptimizerMixin): def __init__(self): super().__init__() + # Core wiring + self.loss_fn = torch.nn.MSELoss(reduction="mean") self.model: AdditiveRenderModel | None = None self.ctx: RenderContext | None = None - self.fit_history: dict[str, FitResult] = {} + + # State/checkpoints self.state_initialized: dict[str, torch.Tensor] | None = None - self.loss_fn = torch.nn.MSELoss(reduction="mean") + + # Histories/results + self.fit_history: dict[str, FitResult] = {} def get_optimization_parameters(self) -> Any: if self.model is None: return [] return self.model.parameters() - def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: - return {k: v.detach().clone() for k, v in state.items()} - - def _get_model_state_dict_copy(self) -> dict[str, torch.Tensor]: - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - return self._clone_state_dict(self.model.state_dict()) - - def _load_model_state_dict_copy(self, state: dict[str, torch.Tensor]) -> None: - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - self.model.load_state_dict(self._clone_state_dict(state), strict=True) - @property def state_current(self) -> dict[str, torch.Tensor] | None: if self.model is None: return None return self._get_model_state_dict_copy() - def _clear_fit_history_all(self) -> None: - self.fit_history.clear() - - def _clear_fit_history_run(self, run_key: str) -> None: - self.fit_history.pop(str(run_key), None) - - def _render_state_array(self, state: dict[str, torch.Tensor]) -> np.ndarray: - if self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - live = self._get_model_state_dict_copy() - try: - self._load_model_state_dict_copy(state) - arr = self.model(self.ctx).detach().cpu().numpy() - finally: - self._load_model_state_dict_copy(live) - return arr - @property def render_initialized(self) -> np.ndarray: if self.state_initialized is None: @@ -147,29 +122,6 @@ def reset( self._clear_fit_history_all() return self - def _forward_for_fit(self, *, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: - if self.model is None or self.ctx is None: - raise RuntimeError("Model and context are not defined for fitting.") - return self.model(self.ctx) - - def _fidelity_loss( - self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any - ) -> torch.Tensor: - if self.ctx is not None and self.ctx.mask is not None: - # TODO -- use loss modules (currently implemented in tomo branch) - # and update them to allow for masking at module level - diff = (pred - target) * self.ctx.mask - denom = torch.clamp(torch.sum(self.ctx.mask), min=1.0) - return torch.sum(diff * diff) / denom - return self.loss_fn(pred, target) - - def _constraint_loss( - self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any - ) -> torch.Tensor: - if self.model is None or self.ctx is None: - raise RuntimeError("Model and context are not defined for fitting.") - return self.model.total_constraint_loss(self.ctx) - def fit_render( self, *, @@ -241,6 +193,59 @@ def fit_render( self.fit_history[key] = result return result + def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: + return {k: v.detach().clone() for k, v in state.items()} + + def _get_model_state_dict_copy(self) -> dict[str, torch.Tensor]: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return self._clone_state_dict(self.model.state_dict()) + + def _load_model_state_dict_copy(self, state: dict[str, torch.Tensor]) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + self.model.load_state_dict(self._clone_state_dict(state), strict=True) + + def _clear_fit_history_all(self) -> None: + self.fit_history.clear() + + def _clear_fit_history_run(self, run_key: str) -> None: + self.fit_history.pop(str(run_key), None) + + def _render_state_array(self, state: dict[str, torch.Tensor]) -> np.ndarray: + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + live = self._get_model_state_dict_copy() + try: + self._load_model_state_dict_copy(state) + arr = self.model(self.ctx).detach().cpu().numpy() + finally: + self._load_model_state_dict_copy(live) + return arr + + def _forward_for_fit(self, *, target: torch.Tensor, **kwargs: Any) -> torch.Tensor: + if self.model is None or self.ctx is None: + raise RuntimeError("Model and context are not defined for fitting.") + return self.model(self.ctx) + + def _fidelity_loss( + self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any + ) -> torch.Tensor: + if self.ctx is not None and self.ctx.mask is not None: + # TODO -- use loss modules (currently implemented in tomo branch) + # and update them to allow for masking at module level + diff = (pred - target) * self.ctx.mask + denom = torch.clamp(torch.sum(self.ctx.mask), min=1.0) + return torch.sum(diff * diff) / denom + return self.loss_fn(pred, target) + + def _constraint_loss( + self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any + ) -> torch.Tensor: + if self.model is None or self.ctx is None: + raise RuntimeError("Model and context are not defined for fitting.") + return self.model.total_constraint_loss(self.ctx) + Component = RenderComponent ModelContext = RenderContext diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 1cbef1620..465bfdb87 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -40,17 +40,21 @@ def __init__(self, dataset: Any, _token: object | None = None): raise RuntimeError("Use ModelDiffraction.from_dataset() or .from_file().") AutoSerialize.__init__(self) FitBase.__init__(self) + + # Dataset/input references self.dataset = dataset - self.metadata: dict[str, Any] = {} self.image_ref: np.ndarray | None = None self.preprocess_shifts: np.ndarray | None = None self.index_shape: tuple[int, ...] | None = None - self.target_mean: torch.Tensor | None = None + # Diffraction-specific state/checkpoints self.state_mean_refined: dict[str, torch.Tensor] | None = None self.mean_refined: bool = False + # Misc metadata + self.metadata: dict[str, Any] = {} + @classmethod def from_dataset( cls, dataset: Dataset2d | Dataset3d | Dataset4d | Dataset4dstem | Any @@ -61,41 +65,6 @@ def from_dataset( "from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance." ) - @property - def render_mean_refined(self) -> np.ndarray: - if self.state_mean_refined is None: - raise RuntimeError( - "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." - ) - return self._render_state_array(self.state_mean_refined) - - def reset( - self, - reset_to: Literal["initialized", "mean_refined"] = "mean_refined", - ) -> "ModelDiffraction": - if reset_to == "initialized": - state = self.state_initialized - if state is None: - raise RuntimeError( - "initialized state is unavailable. Call .define_model(...) first." - ) - self._clear_fit_history_all() - elif reset_to == "mean_refined": - state = self.state_mean_refined - if state is None: - raise RuntimeError( - "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." - ) - mean_hist = self.fit_history.get("mean") - self._clear_fit_history_all() - if mean_hist is not None: - self.fit_history["mean"] = mean_hist - else: - raise ValueError("reset_to must be 'initialized' or 'mean_refined'.") - - self._load_model_state_dict_copy(state) - return self - def preprocess( self, *, @@ -253,6 +222,41 @@ def fit_mean_diffraction_pattern( self.mean_refined = True return self + def reset( + self, + reset_to: Literal["initialized", "mean_refined"] = "mean_refined", + ) -> "ModelDiffraction": + if reset_to == "initialized": + state = self.state_initialized + if state is None: + raise RuntimeError( + "initialized state is unavailable. Call .define_model(...) first." + ) + self._clear_fit_history_all() + elif reset_to == "mean_refined": + state = self.state_mean_refined + if state is None: + raise RuntimeError( + "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." + ) + mean_hist = self.fit_history.get("mean") + self._clear_fit_history_all() + if mean_hist is not None: + self.fit_history["mean"] = mean_hist + else: + raise ValueError("reset_to must be 'initialized' or 'mean_refined'.") + + self._load_model_state_dict_copy(state) + return self + + @property + def render_mean_refined(self) -> np.ndarray: + if self.state_mean_refined is None: + raise RuntimeError( + "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." + ) + return self._render_state_array(self.state_mean_refined) + def plot_losses( self, figax: tuple[Any, Any] | None = None, plot_lrs: bool = True ) -> tuple[Any, Any]: From fc283c7cfb4962cdffb5c34e3dbb6635a2f24914 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Fri, 27 Feb 2026 17:05:25 -0800 Subject: [PATCH 034/113] splitting off ModelDiffractionVisualizations into separate file --- src/quantem/diffraction/model_fitting.py | 153 +--------------- .../model_fitting_visualizations.py | 165 ++++++++++++++++++ 2 files changed, 167 insertions(+), 151 deletions(-) create mode 100644 src/quantem/diffraction/model_fitting_visualizations.py diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 465bfdb87..d5c87477c 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -17,7 +17,7 @@ ) from quantem.core.io.serialize import AutoSerialize from quantem.core.utils.imaging_utils import cross_correlation_shift -from quantem.core.visualization import show_2d +from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: @@ -30,7 +30,7 @@ def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) return float(cast(float | int, value)) -class ModelDiffraction(FitBase, AutoSerialize): +class ModelDiffraction(ModelDiffractionVisualizations, FitBase, AutoSerialize): _token = object() DEFAULT_LR = 5e-2 DEFAULT_OPTIMIZER_TYPE = "adam" @@ -256,152 +256,3 @@ def render_mean_refined(self) -> np.ndarray: "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." ) return self._render_state_array(self.state_mean_refined) - - def plot_losses( - self, figax: tuple[Any, Any] | None = None, plot_lrs: bool = True - ) -> tuple[Any, Any]: - import matplotlib.pyplot as plt - - if figax is None: - fig, ax = plt.subplots() - else: - fig, ax = figax - - mean_hist = self.fit_history.get("mean") - losses = np.asarray([] if mean_hist is None else mean_hist.losses, dtype=np.float64) - if losses.size == 0: - ax.text( - 0.5, - 0.5, - "No fit history available", - ha="center", - va="center", - transform=ax.transAxes, - ) - ax.set_xlabel("Iterations") - ax.set_ylabel("Loss") - if figax is None: - plt.tight_layout() - plt.show() - return fig, ax - - iters = np.arange(losses.size) - lines: list[Any] = [] - lines.extend(ax.semilogy(iters, losses, c="k", lw=2, label="loss")) - ax.set_xlabel("Iterations") - ax.set_ylabel("Loss", color="k") - ax.tick_params(axis="y", which="both", colors="k") - ax.spines["left"].set_color("k") - ax.set_xbound(-2, max(1, int(iters.max())) + 2) - - lrs = np.asarray([] if mean_hist is None else mean_hist.lrs, dtype=np.float64) - if plot_lrs and lrs.size > 0: - if lrs.size == losses.size and not np.allclose(lrs, lrs[0]): - ax_lr = ax.twinx() - ax.set_zorder(2) - ax_lr.set_zorder(1) - ax.patch.set_visible(False) - ax_lr.spines["left"].set_visible(False) - lines.extend( - ax_lr.semilogy( - np.arange(lrs.size), lrs, c="tab:blue", lw=2, ls="--", label="LR" - ) - ) - ax_lr.set_ylabel("LR", color="tab:blue") - ax_lr.tick_params(axis="y", which="both", colors="tab:blue") - ax_lr.spines["right"].set_color("tab:blue") - else: - ax.set_title(f"LR: {float(lrs[-1]):.2e}", fontsize=10) - - labels = [line.get_label() for line in lines] - if len(labels) > 1: - ax.legend(lines, labels, loc="upper right") - - if figax is None: - plt.tight_layout() - plt.show() - return fig, ax - - def visualize( - self, *, power: float = 0.25, cbar: bool = False, axsize: tuple[int, int] = (6, 6) - ) -> tuple[Any, Any]: - import matplotlib.pyplot as plt - from matplotlib import gridspec - - if self.image_ref is None: - self.preprocess() - if self.image_ref is None or self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - - fig = plt.figure(figsize=(12, 7)) - gs = gridspec.GridSpec(2, 1, height_ratios=[1, 2], hspace=0.3) - ax_top = fig.add_subplot(gs[0]) - self.plot_losses(figax=(fig, ax_top), plot_lrs=True) - - ref = np.asarray(self.image_ref, dtype=np.float32) - pred = self.render_current - refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) - predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) - vmin = float(min(refp.min(), predp.min())) - vmax = float(max(refp.max(), predp.max())) - - gs_bot = gridspec.GridSpecFromSubplotSpec(1, 2, subplot_spec=gs[1], wspace=0.15) - axs = np.array( - [fig.add_subplot(gs_bot[0, 0]), fig.add_subplot(gs_bot[0, 1])], dtype=object - ) - show_2d( - [refp, predp], - figax=(fig, axs), - title=["image_ref", "model"], - cmap="gray", - cbar=bool(cbar), - returnfig=False, - axsize=axsize, - vmin=vmin, - vmax=vmax, - ) - - mean_hist = self.fit_history.get("mean") - if mean_hist is not None and len(mean_hist.losses) > 0: - fig.suptitle( - f"Final loss: {mean_hist.losses[-1]:.3e} | Iters: {len(mean_hist.losses)}", - fontsize=13, - y=0.98, - ) - plt.show() - return fig, axs - - def plot_mean_model( - self, - *, - power: float = 0.25, - returnfig: bool = False, - axsize: tuple[int, int] = (6, 6), - **_: Any, - ) -> tuple[Any, Any] | None: - if self.image_ref is None: - self.preprocess() - if self.image_ref is None or self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - - ref = np.asarray(self.image_ref, dtype=np.float32) - pred = self.render_current - - refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) - predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) - vmin = float(min(refp.min(), predp.min())) - vmax = float(max(refp.max(), predp.max())) - - fig, ax = show_2d( - [refp, predp], - title=["image_ref", "model"], - cmap="gray", - cbar=False, - returnfig=True, - axsize=axsize, - vmin=vmin, - vmax=vmax, - ) - if returnfig: - return fig, ax - return None diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py new file mode 100644 index 000000000..9bd26ef15 --- /dev/null +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -0,0 +1,165 @@ +from typing import TYPE_CHECKING, Any, cast + +import numpy as np +from matplotlib import gridspec +from matplotlib import pyplot as plt + +from quantem.core.visualization import show_2d + +if TYPE_CHECKING: + from quantem.diffraction.model_fitting import ModelDiffraction + + +class ModelDiffractionVisualizations: + def plot_losses( + self, figax: tuple[Any, Any] | None = None, plot_lrs: bool = True + ) -> tuple[Any, Any]: + md = cast("ModelDiffraction", self) + + if figax is None: + fig, ax = plt.subplots() + else: + fig, ax = figax + + mean_hist = md.fit_history.get("mean") + losses = np.asarray([] if mean_hist is None else mean_hist.losses, dtype=np.float64) + if losses.size == 0: + ax.text( + 0.5, + 0.5, + "No fit history available", + ha="center", + va="center", + transform=ax.transAxes, + ) + ax.set_xlabel("Iterations") + ax.set_ylabel("Loss") + if figax is None: + plt.tight_layout() + plt.show() + return fig, ax + + iters = np.arange(losses.size) + lines: list[Any] = [] + lines.extend(ax.semilogy(iters, losses, c="k", lw=2, label="loss")) + ax.set_xlabel("Iterations") + ax.set_ylabel("Loss", color="k") + ax.tick_params(axis="y", which="both", colors="k") + ax.spines["left"].set_color("k") + ax.set_xbound(-2, max(1, int(iters.max())) + 2) + + lrs = np.asarray([] if mean_hist is None else mean_hist.lrs, dtype=np.float64) + if plot_lrs and lrs.size > 0: + if lrs.size == losses.size and not np.allclose(lrs, lrs[0]): + ax_lr = ax.twinx() + ax.set_zorder(2) + ax_lr.set_zorder(1) + ax.patch.set_visible(False) + ax_lr.spines["left"].set_visible(False) + lines.extend( + ax_lr.semilogy( + np.arange(lrs.size), lrs, c="tab:blue", lw=2, ls="--", label="LR" + ) + ) + ax_lr.set_ylabel("LR", color="tab:blue") + ax_lr.tick_params(axis="y", which="both", colors="tab:blue") + ax_lr.spines["right"].set_color("tab:blue") + else: + ax.set_title(f"LR: {float(lrs[-1]):.2e}", fontsize=10) + + labels = [line.get_label() for line in lines] + if len(labels) > 1: + ax.legend(lines, labels, loc="upper right") + + if figax is None: + plt.tight_layout() + plt.show() + return fig, ax + + def visualize( + self, + *, + power: float = 0.25, + cbar: bool = False, + axsize: tuple[int, int] = (6, 6), + ) -> tuple[Any, Any]: + md = cast("ModelDiffraction", self) + + if md.image_ref is None: + md.preprocess() + if md.image_ref is None or md.model is None or md.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + + fig = plt.figure(figsize=(12, 7)) + gs = gridspec.GridSpec(2, 1, height_ratios=[1, 2], hspace=0.3) + ax_top = fig.add_subplot(gs[0]) + md.plot_losses(figax=(fig, ax_top), plot_lrs=True) + + ref = np.asarray(md.image_ref, dtype=np.float32) + pred = md.render_current + refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) + predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) + vmin = float(min(refp.min(), predp.min())) + vmax = float(max(refp.max(), predp.max())) + + gs_bot = gridspec.GridSpecFromSubplotSpec(1, 2, subplot_spec=gs[1], wspace=0.15) + axs = np.array( + [fig.add_subplot(gs_bot[0, 0]), fig.add_subplot(gs_bot[0, 1])], dtype=object + ) + show_2d( + [refp, predp], + figax=(fig, axs), + title=["image_ref", "model"], + cmap="gray", + cbar=bool(cbar), + returnfig=False, + axsize=axsize, + vmin=vmin, + vmax=vmax, + ) + + mean_hist = md.fit_history.get("mean") + if mean_hist is not None and len(mean_hist.losses) > 0: + fig.suptitle( + f"Final loss: {mean_hist.losses[-1]:.3e} | Iters: {len(mean_hist.losses)}", + fontsize=13, + y=0.98, + ) + plt.show() + return fig, axs + + def plot_mean_model( + self, + *, + power: float = 0.25, + returnfig: bool = False, + axsize: tuple[int, int] = (6, 6), + **_: Any, + ) -> tuple[Any, Any] | None: + md = cast("ModelDiffraction", self) + if md.image_ref is None: + md.preprocess() + if md.image_ref is None or md.model is None or md.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + + ref = np.asarray(md.image_ref, dtype=np.float32) + pred = md.render_current + + refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) + predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) + vmin = float(min(refp.min(), predp.min())) + vmax = float(max(refp.max(), predp.max())) + + fig, ax = show_2d( + [refp, predp], + title=["image_ref", "model"], + cmap="gray", + cbar=False, + returnfig=True, + axsize=axsize, + vmin=vmin, + vmax=vmax, + ) + if returnfig: + return fig, ax + return None From 59aba0861a28f54d138d0f6ccb4a5c29376a53b6 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 15:00:02 -0800 Subject: [PATCH 035/113] adding hard constraints like force_center for DiskTemplate --- src/quantem/core/fitting/background.py | 8 +- src/quantem/core/fitting/base.py | 163 ++++++++++++++++++++++- src/quantem/core/fitting/diffraction.py | 87 +++++++++++- src/quantem/diffraction/model_fitting.py | 78 ++++++++++- 4 files changed, 324 insertions(+), 12 deletions(-) diff --git a/src/quantem/core/fitting/background.py b/src/quantem/core/fitting/background.py index 7a15d37b4..47333a297 100644 --- a/src/quantem/core/fitting/background.py +++ b/src/quantem/core/fitting/background.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Sequence, cast +from typing import Any, Sequence, cast import numpy as np import torch @@ -25,12 +25,15 @@ def __init__( *, intensity: float | int | Sequence[float | int | None] = 0.0, name: str = "dc_background", + constraint_params: dict[str, Any] | None = None, ): super().__init__() self.name = str(name) self.intensity_raw = nn.Parameter( torch.tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) ) + if constraint_params is not None: + self.apply_constraint_params(constraint_params, strict=True) def forward(self, ctx: RenderContext) -> torch.Tensor: inten = torch.clamp(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype), min=0.0) @@ -46,6 +49,7 @@ def __init__( origin: OriginND | None = None, origin_key: str = "origin", name: str = "gaussian_background", + constraint_params: dict[str, Any] | None = None, ): super().__init__() self.name = str(name) @@ -57,6 +61,8 @@ def __init__( self.intensity_raw = nn.Parameter( torch.tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) ) + if constraint_params is not None: + self.apply_constraint_params(constraint_params, strict=True) def set_origin(self, origin: OriginND) -> None: self.origin = origin diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index dfce2e7b3..9217ddf68 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -32,10 +32,91 @@ def __init__(self, *, ndim: int, init: Sequence[float]): class RenderComponent(nn.Module): + DEFAULT_HARD_CONSTRAINTS: dict[str, Any] = {} + DEFAULT_SOFT_CONSTRAINTS: dict[str, Any] = {} + + def __init__(self) -> None: + super().__init__() + self.hard_constraints: dict[str, Any] = dict(self.DEFAULT_HARD_CONSTRAINTS) + self.soft_constraints: dict[str, Any] = dict(self.DEFAULT_SOFT_CONSTRAINTS) + + def _set_constraints( + self, + current: dict[str, Any], + defaults: dict[str, Any], + constraints: dict[str, Any], + *, + strict: bool, + ) -> None: + if strict: + unknown = [k for k in constraints if k not in defaults] + if unknown: + keys = ", ".join(str(k) for k in unknown) + raise KeyError(f"Unknown constraint keys: {keys}") + current.update(constraints) + + def set_hard_constraints(self, constraints: dict[str, Any], strict: bool = True) -> None: + self._set_constraints( + self.hard_constraints, self.DEFAULT_HARD_CONSTRAINTS, constraints, strict=strict + ) + + def set_soft_constraints(self, constraints: dict[str, Any], strict: bool = True) -> None: + self._set_constraints( + self.soft_constraints, self.DEFAULT_SOFT_CONSTRAINTS, constraints, strict=strict + ) + + def apply_constraint_params(self, params: dict[str, Any], strict: bool = True) -> None: + if not isinstance(params, dict): + raise TypeError("constraint params must be a dict.") + if "hard" in params or "soft" in params: + hard = params.get("hard") + soft = params.get("soft") + if hard is not None: + if not isinstance(hard, dict): + raise TypeError("constraint params 'hard' value must be a dict.") + self.set_hard_constraints(hard, strict=strict) + if soft is not None: + if not isinstance(soft, dict): + raise TypeError("constraint params 'soft' value must be a dict.") + self.set_soft_constraints(soft, strict=strict) + return + + hard_updates: dict[str, Any] = {} + soft_updates: dict[str, Any] = {} + unknown: dict[str, Any] = {} + for k, v in params.items(): + if k in self.DEFAULT_HARD_CONSTRAINTS: + hard_updates[k] = v + elif k in self.DEFAULT_SOFT_CONSTRAINTS: + soft_updates[k] = v + else: + unknown[k] = v + + if unknown and strict: + keys = ", ".join(str(k) for k in unknown.keys()) + raise KeyError(f"Unknown constraint keys for {self.__class__.__name__}: {keys}") + if unknown: + soft_updates.update(unknown) + if hard_updates: + self.set_hard_constraints(hard_updates, strict=strict) + if soft_updates: + self.set_soft_constraints(soft_updates, strict=strict) + + def effective_soft_constraints(self, params: dict[str, Any] | None = None) -> dict[str, Any]: + effective = dict(self.soft_constraints) + if isinstance(params, dict): + effective.update(params) + return effective + + def enforce_hard_constraints(self, ctx: RenderContext) -> None: + return None + def forward(self, ctx: RenderContext) -> torch.Tensor: raise NotImplementedError - def constraint_loss(self, ctx: RenderContext) -> torch.Tensor: + def constraint_loss( + self, ctx: RenderContext, params: dict[str, Any] | None = None + ) -> torch.Tensor: return torch.zeros((), device=ctx.device, dtype=ctx.dtype) @@ -53,11 +134,65 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: out = out + component(ctx) return out - def total_constraint_loss(self, ctx: RenderContext) -> torch.Tensor: - loss = torch.zeros((), device=ctx.device, dtype=ctx.dtype) + def _component_constraint_name(self, component: RenderComponent, idx: int) -> str: + name = getattr(component, "name", None) + if isinstance(name, str) and name: + return name + class_name = component.__class__.__name__ + if class_name: + return class_name + return f"component_{idx}" + + def apply_constraint_params( + self, constraint_params: dict[str, Any], strict: bool = True + ) -> None: + if not isinstance(constraint_params, dict): + raise TypeError("constraint_params must be a dict.") + source = constraint_params.get("components") + component_map = source if isinstance(source, dict) else constraint_params + for target, params in component_map.items(): + if not isinstance(params, dict): + if strict: + raise TypeError(f"Constraint params for '{target}' must be a dict.") + continue + target_str = str(target) + name_matches: list[RenderComponent] = [] + class_matches: list[RenderComponent] = [] + for idx, module in enumerate(self.components): + component = cast(RenderComponent, module) + if self._component_constraint_name(component, idx) == target_str: + name_matches.append(component) + if component.__class__.__name__ == target_str: + class_matches.append(component) + targets = name_matches if name_matches else class_matches + if not targets: + if strict: + raise KeyError(f"No matching component for constraint target '{target_str}'.") + continue + for component in targets: + component.apply_constraint_params(params, strict=strict) + + def apply_hard_constraints(self, ctx: RenderContext) -> None: for module in self.components: component = cast(RenderComponent, module) - loss = loss + component.constraint_loss(ctx) + component.enforce_hard_constraints(ctx) + + def total_constraint_loss( + self, ctx: RenderContext, constraint_params: dict[str, Any] | None = None + ) -> torch.Tensor: + loss = torch.zeros((), device=ctx.device, dtype=ctx.dtype) + per_component = None + if isinstance(constraint_params, dict): + comp_cfg = constraint_params.get("components") + if isinstance(comp_cfg, dict): + per_component = comp_cfg + for idx, module in enumerate(self.components): + component = cast(RenderComponent, module) + resolved_name = self._component_constraint_name(component, idx) + component_params = per_component.get(resolved_name) if per_component else None + if not isinstance(component_params, dict): + component_params = None + loss = loss + component.constraint_loss(ctx, params=component_params) return loss @@ -128,12 +263,18 @@ def fit_render( target: torch.Tensor, n_steps: int, constraint_weight: float = 1.0, + constraint_params: dict[str, Any] | None = None, optimizer_params: dict | None = None, scheduler_params: dict | None = None, progress: bool = False, run_key: str = "default", **kwargs: Any, ) -> FitResult: + if self.model is None or self.ctx is None: + raise RuntimeError("Model and context are not defined for fitting.") + if constraint_params is not None: + self.model.apply_constraint_params(constraint_params, strict=True) + optimizer_rebuilt = False if optimizer_params is not None: self.set_optimizer(optimizer_params) @@ -166,10 +307,13 @@ def fit_render( self.zero_optimizer_grad() pred = self._forward_for_fit(target=target, **kwargs) data_loss = self._fidelity_loss(pred, target, **kwargs) - constraint_loss = self._constraint_loss(pred, target, **kwargs) + constraint_loss = self._constraint_loss(pred, target, constraint_params=None, **kwargs) total_loss = data_loss + constraint_weight * constraint_loss total_loss.backward() self.step_optimizer() + if self.model is None or self.ctx is None: + raise RuntimeError("Model and context are not defined for fitting.") + self.model.apply_hard_constraints(self.ctx) total_loss_value = float(total_loss.detach().cpu()) self.step_scheduler(total_loss_value) losses.append(total_loss_value) @@ -240,11 +384,16 @@ def _fidelity_loss( return self.loss_fn(pred, target) def _constraint_loss( - self, pred: torch.Tensor, target: torch.Tensor, **kwargs: Any + self, + pred: torch.Tensor, + target: torch.Tensor, + *, + constraint_params: dict[str, Any] | None = None, + **kwargs: Any, ) -> torch.Tensor: if self.model is None or self.ctx is None: raise RuntimeError("Model and context are not defined for fitting.") - return self.model.total_constraint_loss(self.ctx) + return self.model.total_constraint_loss(self.ctx, constraint_params=constraint_params) Component = RenderComponent diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 0872ffb5c..204b88038 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -1,9 +1,10 @@ from __future__ import annotations -from typing import Iterable, Sequence, cast +from typing import Any, Iterable, Sequence, cast import numpy as np import torch +import torch.nn.functional as F from torch import nn from quantem.core.fitting.base import OriginND, RenderComponent, RenderContext @@ -58,6 +59,12 @@ def put(rr: torch.Tensor, cc: torch.Tensor, ww: torch.Tensor) -> None: class DiskTemplate(RenderComponent): + DEFAULT_HARD_CONSTRAINTS: dict[str, bool] = { + "force_center": False, + "force_positive": True, + } + DEFAULT_SOFT_CONSTRAINTS: dict[str, float] = {"tv_weight": 0.0} + def __init__( self, *, @@ -69,6 +76,7 @@ def __init__( origin: OriginND | None = None, origin_key: str = "origin", intensity: float | Sequence[float] = 1.0, + constraint_params: dict[str, Any] | None = None, ): super().__init__() self.name = str(name) @@ -107,6 +115,10 @@ def __init__( cc = cc.astype(np.float32) - (wt - 1) * 0.5 self.register_buffer("dr", torch.as_tensor(rr.ravel(), dtype=torch.float32)) self.register_buffer("dc", torch.as_tensor(cc.ravel(), dtype=torch.float32)) + if constraint_params is not None: + self.apply_constraint_params(constraint_params, strict=True) + if bool(self.hard_constraints.get("force_positive", False)): + self._enforce_positivity() @classmethod def from_array( @@ -120,6 +132,7 @@ def from_array( origin: OriginND | None = None, origin_key: str = "origin", intensity: float | Sequence[float] = 1.0, + constraint_params: dict[str, Any] | None = None, ) -> "DiskTemplate": return cls( name=name, @@ -130,14 +143,13 @@ def from_array( origin=origin, origin_key=origin_key, intensity=intensity, + constraint_params=constraint_params, ) def set_origin(self, origin: OriginND) -> None: self.origin = origin def patch_values(self) -> torch.Tensor: - if self.refine_all_pixels: - return torch.clamp(self.template_raw, min=0.0).reshape(-1) return self.template_raw.reshape(-1) def patch_offsets(self) -> tuple[torch.Tensor, torch.Tensor]: @@ -166,6 +178,72 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: self.add_patch(out, r0=r0, c0=c0, scale=scale) return out + def _center_disk(self) -> None: + with torch.no_grad(): + template = self.template_raw + h, w = int(template.shape[0]), int(template.shape[1]) + weights = torch.clamp(template, min=0.0) + mass = torch.sum(weights) + if float(mass.detach().cpu()) <= 1e-12: + return + rr = torch.arange(h, device=template.device, dtype=template.dtype)[:, None] + cc = torch.arange(w, device=template.device, dtype=template.dtype)[None, :] + com_r = torch.sum(weights * rr) / mass + com_c = torch.sum(weights * cc) / mass + target_r = torch.as_tensor((h - 1) * 0.5, device=template.device, dtype=template.dtype) + target_c = torch.as_tensor((w - 1) * 0.5, device=template.device, dtype=template.dtype) + shift_r = target_r - com_r + shift_c = target_c - com_c + denom_h = max(h - 1, 1) + denom_w = max(w - 1, 1) + ty = -2.0 * shift_r / float(denom_h) + tx = -2.0 * shift_c / float(denom_w) + theta = torch.as_tensor( + [[1.0, 0.0, tx], [0.0, 1.0, ty]], + device=template.device, + dtype=template.dtype, + )[None, ...] + src = template[None, None, :, :] + grid = F.affine_grid(theta, [1, 1, h, w], align_corners=True) + shifted = F.grid_sample( + src, + grid, + mode="bilinear", + padding_mode="zeros", + align_corners=True, + )[0, 0] + self.template_raw.copy_(shifted) + + def _enforce_positivity(self) -> None: + with torch.no_grad(): + self.template_raw.clamp_(min=0.0) + + def enforce_hard_constraints(self, ctx: RenderContext) -> None: + if bool(self.hard_constraints.get("force_center", False)): + self._center_disk() + if bool(self.hard_constraints.get("force_positive", False)): + self._enforce_positivity() + + def constraint_loss( + self, ctx: RenderContext, params: dict[str, object] | None = None + ) -> torch.Tensor: + cfg = self.effective_soft_constraints(cast(dict[str, object] | None, params)) + tv_weight = float(cfg.get("tv_weight", 0.0)) + if tv_weight <= 0.0: + return torch.zeros((), device=ctx.device, dtype=ctx.dtype) + template = self.template_raw.to(device=ctx.device, dtype=ctx.dtype) + tv_r = ( + torch.mean(torch.abs(template[1:, :] - template[:-1, :])) + if template.shape[0] > 1 + else torch.zeros((), device=ctx.device, dtype=ctx.dtype) + ) + tv_c = ( + torch.mean(torch.abs(template[:, 1:] - template[:, :-1])) + if template.shape[1] > 1 + else torch.zeros((), device=ctx.device, dtype=ctx.dtype) + ) + return torch.as_tensor(tv_weight, device=ctx.device, dtype=ctx.dtype) * (tv_r + tv_c) + class SyntheticDiskLattice(RenderComponent): def __init__( @@ -194,6 +272,7 @@ def __init__( boundary_px: float = 0.0, origin: OriginND | None = None, origin_key: str = "origin", + constraint_params: dict[str, Any] | None = None, ): super().__init__() self.name = str(name) @@ -282,6 +361,8 @@ def __init__( self.irr = nn.Parameter(torch.tensor(irr_init, dtype=torch.float32)) self.icc = nn.Parameter(torch.tensor(icc_init, dtype=torch.float32)) self.irc = nn.Parameter(torch.tensor(irc_init, dtype=torch.float32)) + if constraint_params is not None: + self.apply_constraint_params(constraint_params, strict=True) def set_origin(self, origin: OriginND) -> None: self.origin = origin diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index d5c87477c..5578e25f2 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -65,6 +65,81 @@ def from_dataset( "from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance." ) + def _resolve_component_by_name(self, name: str) -> RenderComponent: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + target = str(name) + for idx, module in enumerate(self.model.components): + component = cast(RenderComponent, module) + resolved_name = self.model._component_constraint_name(component, idx) + if resolved_name == target: + return component + raise KeyError(f"Component not found: {target}") + + def get_component(self, name: str) -> RenderComponent: + return self._resolve_component_by_name(name) + + def _render_disk_template_component( + self, component: RenderComponent, ctx: RenderContext + ) -> torch.Tensor: + from quantem.core.fitting.diffraction import DiskTemplate + + if not isinstance(component, DiskTemplate): + raise TypeError("Component is not a DiskTemplate.") + out = torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) + r0 = torch.as_tensor((ctx.shape[0] - 1) * 0.5, device=ctx.device, dtype=ctx.dtype) + c0 = torch.as_tensor((ctx.shape[1] - 1) * 0.5, device=ctx.device, dtype=ctx.dtype) + scale = torch.as_tensor(1.0, device=ctx.device, dtype=ctx.dtype) + component.add_patch(out, r0=r0, c0=c0, scale=scale) + return out + + def get_rendered_component( + self, name: str, apply_hard_constraints: bool = False + ) -> np.ndarray: + if self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + component = self._resolve_component_by_name(name) + if apply_hard_constraints: + component.enforce_hard_constraints(self.ctx) + from quantem.core.fitting.diffraction import DiskTemplate + + if isinstance(component, DiskTemplate): + rendered = self._render_disk_template_component(component, self.ctx) + else: + rendered = component(self.ctx) + return rendered.detach().cpu().numpy() + + def get_rendered_disk_template( + self, apply_hard_constraints: bool = True, name: str | None = None + ) -> np.ndarray: + if self.ctx is None or self.model is None: + raise RuntimeError("Call .define_model(...) first.") + from quantem.core.fitting.diffraction import DiskTemplate + + if name is not None: + component = self._resolve_component_by_name(name) + if not isinstance(component, DiskTemplate): + raise TypeError(f"Component '{name}' is not a DiskTemplate.") + if apply_hard_constraints: + component.enforce_hard_constraints(self.ctx) + return component.template_raw.detach().cpu().numpy() + matches = [m for m in self.model.components if isinstance(m, DiskTemplate)] + if len(matches) == 0: + raise RuntimeError("No DiskTemplate components found.") + if len(matches) > 1: + raise RuntimeError("Multiple DiskTemplate components found; pass name explicitly.") + disk = matches[0] + if apply_hard_constraints: + disk.enforce_hard_constraints(self.ctx) + return disk.template_raw.detach().cpu().numpy() + + def get_component_constraints(self, name: str) -> dict[str, dict[str, Any]]: + component = self._resolve_component_by_name(name) + return { + "hard": dict(component.hard_constraints), + "soft": dict(component.soft_constraints), + } + def preprocess( self, *, @@ -193,8 +268,8 @@ def fit_mean_diffraction_pattern( optimizer_params: dict | None = None, scheduler_params: dict | None = None, constraint_weight: float = 1.0, + constraint_params: dict[str, Any] | None = None, progress: bool = False, - **kwargs: Any, ) -> "ModelDiffraction": if self.model is None or self.ctx is None or self.target_mean is None: raise RuntimeError("Call .define_model(...) first.") @@ -211,6 +286,7 @@ def fit_mean_diffraction_pattern( target=self.target_mean, n_steps=int(n_steps), constraint_weight=float(constraint_weight), + constraint_params=constraint_params, optimizer_params=optimizer_params, scheduler_params=scheduler_params, progress=bool(progress), From 7443869cf8398de07a8d88094d016f98a62c7941 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 15:50:38 -0800 Subject: [PATCH 036/113] adding docstrings and cleaning --- src/quantem/core/fitting/base.py | 62 +++++++--- src/quantem/core/fitting/diffraction.py | 24 +--- src/quantem/diffraction/model_fitting.py | 138 +++++++++++++++++------ 3 files changed, 151 insertions(+), 73 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 9217ddf68..898ad90ad 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -177,22 +177,11 @@ def apply_hard_constraints(self, ctx: RenderContext) -> None: component = cast(RenderComponent, module) component.enforce_hard_constraints(ctx) - def total_constraint_loss( - self, ctx: RenderContext, constraint_params: dict[str, Any] | None = None - ) -> torch.Tensor: + def total_constraint_loss(self, ctx: RenderContext) -> torch.Tensor: loss = torch.zeros((), device=ctx.device, dtype=ctx.dtype) - per_component = None - if isinstance(constraint_params, dict): - comp_cfg = constraint_params.get("components") - if isinstance(comp_cfg, dict): - per_component = comp_cfg - for idx, module in enumerate(self.components): + for module in self.components: component = cast(RenderComponent, module) - resolved_name = self._component_constraint_name(component, idx) - component_params = per_component.get(resolved_name) if per_component else None - if not isinstance(component_params, dict): - component_params = None - loss = loss + component.constraint_loss(ctx, params=component_params) + loss = loss + component.constraint_loss(ctx) return loss @@ -270,6 +259,45 @@ def fit_render( run_key: str = "default", **kwargs: Any, ) -> FitResult: + """ + Fit model parameters to a target render. + + Parameters + ---------- + target : torch.Tensor + Target tensor to fit. + n_steps : int + Number of optimization steps. + constraint_weight : float, optional + Multiplier applied to the summed soft-constraint loss. + constraint_params : dict[str, Any] | None, optional + Optional constraint updates applied once to matching components before + optimization starts. If ``None``, existing component constraints are reused. + optimizer_params : dict | None, optional + Optimizer configuration override for this call. + scheduler_params : dict | None, optional + Scheduler configuration override for this call. + progress : bool, optional + If ``True``, display a progress bar. + run_key : str, optional + History key used to store/append fit metrics. + **kwargs : Any + Forwarded to internal forward/loss hooks. + + Returns + ------- + FitResult + Fit history and final loss metadata for this run key. + + Raises + ------ + RuntimeError + If model/context are undefined. + + Notes + ----- + Hard constraints are applied after each optimizer step. + """ if self.model is None or self.ctx is None: raise RuntimeError("Model and context are not defined for fitting.") if constraint_params is not None: @@ -307,7 +335,7 @@ def fit_render( self.zero_optimizer_grad() pred = self._forward_for_fit(target=target, **kwargs) data_loss = self._fidelity_loss(pred, target, **kwargs) - constraint_loss = self._constraint_loss(pred, target, constraint_params=None, **kwargs) + constraint_loss = self._constraint_loss(pred, target, **kwargs) total_loss = data_loss + constraint_weight * constraint_loss total_loss.backward() self.step_optimizer() @@ -387,13 +415,11 @@ def _constraint_loss( self, pred: torch.Tensor, target: torch.Tensor, - *, - constraint_params: dict[str, Any] | None = None, **kwargs: Any, ) -> torch.Tensor: if self.model is None or self.ctx is None: raise RuntimeError("Model and context are not defined for fitting.") - return self.model.total_constraint_loss(self.ctx, constraint_params=constraint_params) + return self.model.total_constraint_loss(self.ctx) Component = RenderComponent diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 204b88038..900701060 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -71,7 +71,6 @@ def __init__( name: str, array: np.ndarray, refine_all_pixels: bool = False, - place_at_origin: bool = False, normalize: str = "none", origin: OriginND | None = None, origin_key: str = "origin", @@ -81,7 +80,6 @@ def __init__( super().__init__() self.name = str(name) self.refine_all_pixels = bool(refine_all_pixels) - self.place_at_origin = bool(place_at_origin) self.origin = origin self.origin_key = str(origin_key) @@ -102,13 +100,6 @@ def __init__( template = torch.as_tensor(a, dtype=torch.float32) self.template_raw = nn.Parameter(template.clone(), requires_grad=self.refine_all_pixels) - if self.place_at_origin: - self.intensity_raw = nn.Parameter( - torch.as_tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) - ) - else: - self.intensity_raw = None - ht, wt = int(template.shape[0]), int(template.shape[1]) rr, cc = np.mgrid[0:ht, 0:wt] rr = rr.astype(np.float32) - (ht - 1) * 0.5 @@ -127,7 +118,6 @@ def from_array( name: str, array: np.ndarray, refine_all_pixels: bool = False, - place_at_origin: bool = False, normalize: str = "none", origin: OriginND | None = None, origin_key: str = "origin", @@ -138,7 +128,6 @@ def from_array( name=name, array=array, refine_all_pixels=refine_all_pixels, - place_at_origin=place_at_origin, normalize=normalize, origin=origin, origin_key=origin_key, @@ -165,17 +154,10 @@ def add_patch( def forward(self, ctx: RenderContext) -> torch.Tensor: out = torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) - if not self.place_at_origin: - return out if self.origin is None: - raise RuntimeError( - "DiskTemplate with place_at_origin=True requires an OriginND instance." - ) + raise RuntimeError("DiskTemplate.forward() requires an OriginND instance.") r0, c0 = self.origin.coords[0], self.origin.coords[1] - if self.intensity_raw is None: - raise RuntimeError("DiskTemplate intensity parameter is missing.") - scale = torch.clamp(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype), min=0.0) - self.add_patch(out, r0=r0, c0=c0, scale=scale) + self.add_patch(out, r0=r0, c0=c0, scale=torch.tensor(1.0)) # scale learned directly return out def _center_disk(self) -> None: @@ -308,7 +290,7 @@ def __init__( ) if exclude_indices is None: - exclude = {(0, 0)} if bool(getattr(disk, "place_at_origin", False)) else set() + exclude = set() else: exclude = set(exclude_indices) uv: list[tuple[int, int]] = [] diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 5578e25f2..9fd487e0b 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -15,6 +15,7 @@ RenderComponent, RenderContext, ) +from quantem.core.fitting.diffraction import DiskTemplate from quantem.core.io.serialize import AutoSerialize from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations @@ -77,60 +78,92 @@ def _resolve_component_by_name(self, name: str) -> RenderComponent: raise KeyError(f"Component not found: {target}") def get_component(self, name: str) -> RenderComponent: + """ + Return a live model component by resolved name. + + Parameters + ---------- + name : str + Resolved component name. + + Returns + ------- + RenderComponent + The live component object. + + Raises + ------ + RuntimeError + If the model is not defined. + KeyError + If no component matches ``name``. + """ return self._resolve_component_by_name(name) - def _render_disk_template_component( - self, component: RenderComponent, ctx: RenderContext - ) -> torch.Tensor: - from quantem.core.fitting.diffraction import DiskTemplate - - if not isinstance(component, DiskTemplate): - raise TypeError("Component is not a DiskTemplate.") - out = torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) - r0 = torch.as_tensor((ctx.shape[0] - 1) * 0.5, device=ctx.device, dtype=ctx.dtype) - c0 = torch.as_tensor((ctx.shape[1] - 1) * 0.5, device=ctx.device, dtype=ctx.dtype) - scale = torch.as_tensor(1.0, device=ctx.device, dtype=ctx.dtype) - component.add_patch(out, r0=r0, c0=c0, scale=scale) - return out - - def get_rendered_component( - self, name: str, apply_hard_constraints: bool = False - ) -> np.ndarray: + def get_rendered_component(self, name: str) -> np.ndarray: + """ + Render a component and return a NumPy array. + + Parameters + ---------- + name : str + Resolved component name. + + Returns + ------- + np.ndarray + Rendered component image. + + Raises + ------ + RuntimeError + If model/context are not defined. + KeyError + If no component matches ``name``. + """ if self.ctx is None: raise RuntimeError("Call .define_model(...) first.") + ctx = self.ctx component = self._resolve_component_by_name(name) - if apply_hard_constraints: - component.enforce_hard_constraints(self.ctx) - from quantem.core.fitting.diffraction import DiskTemplate - - if isinstance(component, DiskTemplate): - rendered = self._render_disk_template_component(component, self.ctx) - else: - rendered = component(self.ctx) + rendered = component(ctx) return rendered.detach().cpu().numpy() - def get_rendered_disk_template( - self, apply_hard_constraints: bool = True, name: str | None = None - ) -> np.ndarray: + def get_rendered_disk_template(self, name: str | None = None) -> np.ndarray: + """ + Return a DiskTemplate patch as a numpy array--not rendered onto the full frame. + + Parameters + ---------- + name : str | None, optional + DiskTemplate component name. If omitted, requires exactly one DiskTemplate. + + Returns + ------- + np.ndarray + Template-sized array from ``template_raw``. + + Raises + ------ + RuntimeError + If model/context are not defined, no DiskTemplate exists, or multiple + DiskTemplates exist when ``name`` is omitted. + TypeError + If a named component exists but is not a DiskTemplate. + """ if self.ctx is None or self.model is None: raise RuntimeError("Call .define_model(...) first.") - from quantem.core.fitting.diffraction import DiskTemplate if name is not None: component = self._resolve_component_by_name(name) if not isinstance(component, DiskTemplate): raise TypeError(f"Component '{name}' is not a DiskTemplate.") - if apply_hard_constraints: - component.enforce_hard_constraints(self.ctx) return component.template_raw.detach().cpu().numpy() matches = [m for m in self.model.components if isinstance(m, DiskTemplate)] if len(matches) == 0: raise RuntimeError("No DiskTemplate components found.") if len(matches) > 1: raise RuntimeError("Multiple DiskTemplate components found; pass name explicitly.") - disk = matches[0] - if apply_hard_constraints: - disk.enforce_hard_constraints(self.ctx) + disk = cast(DiskTemplate, matches[0]) return disk.template_raw.detach().cpu().numpy() def get_component_constraints(self, name: str) -> dict[str, dict[str, Any]]: @@ -271,6 +304,43 @@ def fit_mean_diffraction_pattern( constraint_params: dict[str, Any] | None = None, progress: bool = False, ) -> "ModelDiffraction": + """ + Fit the mean diffraction pattern. + + Parameters + ---------- + n_steps : int, optional + Number of optimization steps. + reset : bool | Literal["initialized", "mean_refined"], optional + Reset behavior before fitting. + optimizer_params : dict | None, optional + Optimizer override for this fit call. + scheduler_params : dict | None, optional + Scheduler override for this fit call. + constraint_weight : float, optional + Global multiplier for soft-constraint loss. + constraint_params : dict[str, Any] | None, optional + Optional constraint updates applied once to components before fitting. + If ``None``, previously assigned constraints are reused. + progress : bool, optional + If ``True``, show progress bar. + + Returns + ------- + ModelDiffraction + Self, with updated fit state and history. + + Raises + ------ + RuntimeError + If model/context/target are not defined. + ValueError + If ``reset`` has an unsupported value. + + Notes + ----- + Constraint assignments persist on components across fit calls. + """ if self.model is None or self.ctx is None or self.target_mean is None: raise RuntimeError("Call .define_model(...) first.") if reset is True: From 136f626f0a182875e1526b4f787327f9433f56f0 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 20:42:05 -0800 Subject: [PATCH 037/113] adding visualizations and overlays --- src/quantem/diffraction/model_fitting.py | 80 +++++- .../model_fitting_visualizations.py | 264 +++++++++++++++++- 2 files changed, 342 insertions(+), 2 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 9fd487e0b..12f8235d6 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -15,7 +15,7 @@ RenderComponent, RenderContext, ) -from quantem.core.fitting.diffraction import DiskTemplate +from quantem.core.fitting.diffraction import DiskTemplate, SyntheticDiskLattice from quantem.core.io.serialize import AutoSerialize from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations @@ -66,6 +66,18 @@ def from_dataset( "from_dataset expects a Dataset2d, Dataset3d, Dataset4d, or Dataset4dstem instance." ) + @property + def components(self) -> torch.nn.ModuleList: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return self.model.components + + @property + def component_names(self) -> list[str]: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return [cast(str, c.name) for c in self.components] + def _resolve_component_by_name(self, name: str) -> RenderComponent: if self.model is None: raise RuntimeError("Call .define_model(...) first.") @@ -173,6 +185,72 @@ def get_component_constraints(self, name: str) -> dict[str, dict[str, Any]]: "soft": dict(component.soft_constraints), } + def get_overlay_coordinates(self) -> tuple[np.ndarray, np.ndarray]: + """ + Return origin and lattice disk-center coordinates for overlay plotting. + + Parameters + ---------- + None + + Returns + ------- + origin_rc : np.ndarray + Origin coordinate array with shape ``(2,)`` as ``(row, col)``. + disk_centers_rc : np.ndarray + Disk-center array with shape ``(N, 2)`` as ``(row, col)``. + + Raises + ------ + RuntimeError + If model/context are not defined. + + Notes + ----- + Coordinates are computed from current model parameters without mutating state. + Boundary filtering matches ``SyntheticDiskLattice.forward`` behavior. + """ + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + + with torch.no_grad(): + origin = cast(OriginND, self.model.origin) + origin_rc = origin.coords[:2].detach().cpu().numpy().astype(np.float32, copy=False) + + centers: list[np.ndarray] = [] + for module in self.model.components: + component = cast(RenderComponent, module) + if not isinstance(component, SyntheticDiskLattice): + continue + if component.origin is None: + continue + uv_indices = cast(torch.Tensor, component.uv_indices) + if torch.numel(uv_indices) == 0: + continue + + uv = torch.as_tensor(uv_indices, device=self.ctx.device) + u = uv[:, 0].to(dtype=self.ctx.dtype) + v = uv[:, 1].to(dtype=self.ctx.dtype) + r0, c0 = component.origin.coords[0], component.origin.coords[1] + centers_r = r0 + u * component.u_row + v * component.v_row + centers_c = c0 + u * component.u_col + v * component.v_col + + b = torch.as_tensor( + component.boundary_px, device=self.ctx.device, dtype=self.ctx.dtype + ) + keep = (centers_r >= b) & (centers_r <= (self.ctx.shape[0] - 1) - b) + keep = keep & (centers_c >= b) & (centers_c <= (self.ctx.shape[1] - 1) - b) + if torch.any(keep): + rc = torch.stack((centers_r[keep], centers_c[keep]), dim=1) + centers.append(rc.detach().cpu().numpy().astype(np.float32, copy=False)) + + if centers: + disk_centers_rc = np.concatenate(centers, axis=0) + else: + disk_centers_rc = np.zeros((0, 2), dtype=np.float32) + + return origin_rc, disk_centers_rc + def preprocess( self, *, diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index 9bd26ef15..95245aa1c 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, Literal, cast import numpy as np from matplotlib import gridspec @@ -11,6 +11,65 @@ class ModelDiffractionVisualizations: + def _plot_overlays( + self, + ax: Any, + origin_rc: np.ndarray, + disk_centers_rc: np.ndarray, + *, + overlay_origin: bool = True, + overlay_disks: bool = True, + origin_marker_kwargs: dict[str, Any] | None = None, + disk_marker_kwargs: dict[str, Any] | None = None, + ) -> None: + """ + Plot origin and disk-center overlays on an axis. + + Parameters + ---------- + ax : Any + Matplotlib axis receiving overlays. + origin_rc : np.ndarray + Origin coordinate as ``(row, col)``. + disk_centers_rc : np.ndarray + Disk centers as ``(N, 2)`` in ``(row, col)`` order. + overlay_origin : bool, optional + If ``True``, plot origin marker. + overlay_disks : bool, optional + If ``True``, plot disk-center markers. + origin_marker_kwargs : dict[str, Any] | None, optional + Matplotlib kwargs merged onto origin marker defaults. + disk_marker_kwargs : dict[str, Any] | None, optional + Matplotlib kwargs merged onto disk marker defaults. + + Returns + ------- + None + """ + if overlay_origin and origin_rc.shape == (2,): + kw_origin = { + "marker": "+", + "color": "red", + "markersize": 10, + "markeredgewidth": 1.5, + "linestyle": "None", + } + if origin_marker_kwargs is not None: + kw_origin.update(origin_marker_kwargs) + ax.plot(float(origin_rc[1]), float(origin_rc[0]), **kw_origin) + + if overlay_disks and disk_centers_rc.ndim == 2 and disk_centers_rc.shape[0] > 0: + kw_disks = { + "marker": "x", + "color": "cyan", + "markersize": 5, + "markeredgewidth": 1.0, + "linestyle": "None", + } + if disk_marker_kwargs is not None: + kw_disks.update(disk_marker_kwargs) + ax.plot(disk_centers_rc[:, 1], disk_centers_rc[:, 0], **kw_disks) + def plot_losses( self, figax: tuple[Any, Any] | None = None, plot_lrs: bool = True ) -> tuple[Any, Any]: @@ -82,7 +141,49 @@ def visualize( power: float = 0.25, cbar: bool = False, axsize: tuple[int, int] = (6, 6), + overlay: bool = True, + overlay_origin: bool = True, + overlay_disks: bool = True, + overlay_on: Literal["model", "both"] = "model", + origin_marker_kwargs: dict[str, Any] | None = None, + disk_marker_kwargs: dict[str, Any] | None = None, ) -> tuple[Any, Any]: + """ + Visualize fit losses with reference/model image panels. + + Parameters + ---------- + power : float, optional + Power-law display scaling. + cbar : bool, optional + If ``True``, draw colorbars. + axsize : tuple[int, int], optional + Axis size passed through to ``show_2d``. + overlay : bool, optional + If ``True``, draw coordinate overlays. + overlay_origin : bool, optional + If ``True``, include origin marker in overlays. + overlay_disks : bool, optional + If ``True``, include disk-center markers in overlays. + overlay_on : {"model", "both"}, optional + Which image panel(s) receive overlays. + origin_marker_kwargs : dict[str, Any] | None, optional + Marker kwargs override for origin marker. + disk_marker_kwargs : dict[str, Any] | None, optional + Marker kwargs override for disk-center markers. + + Returns + ------- + tuple[Any, Any] + ``(fig, axs)`` for further editing. + + Raises + ------ + RuntimeError + If model/context are not defined. + ValueError + If ``overlay_on`` is invalid. + """ md = cast("ModelDiffraction", self) if md.image_ref is None: @@ -118,6 +219,22 @@ def visualize( vmax=vmax, ) + if overlay: + if overlay_on not in ("model", "both"): + raise ValueError("overlay_on must be 'model' or 'both'.") + origin_rc, disk_centers_rc = md.get_overlay_coordinates() + axes = [axs[1]] if overlay_on == "model" else [axs[0], axs[1]] + for ax in axes: + self._plot_overlays( + ax, + origin_rc, + disk_centers_rc, + overlay_origin=overlay_origin, + overlay_disks=overlay_disks, + origin_marker_kwargs=origin_marker_kwargs, + disk_marker_kwargs=disk_marker_kwargs, + ) + mean_hist = md.fit_history.get("mean") if mean_hist is not None and len(mean_hist.losses) > 0: fig.suptitle( @@ -134,8 +251,52 @@ def plot_mean_model( power: float = 0.25, returnfig: bool = False, axsize: tuple[int, int] = (6, 6), + overlay: bool = True, + overlay_origin: bool = True, + overlay_disks: bool = True, + overlay_on: Literal["model", "both"] = "model", + origin_marker_kwargs: dict[str, Any] | None = None, + disk_marker_kwargs: dict[str, Any] | None = None, **_: Any, ) -> tuple[Any, Any] | None: + """ + Plot reference and model mean diffraction images. + + Parameters + ---------- + power : float, optional + Power-law display scaling. + returnfig : bool, optional + If ``True``, return ``(fig, ax)``. + axsize : tuple[int, int], optional + Axis size passed through to ``show_2d``. + overlay : bool, optional + If ``True``, draw coordinate overlays. + overlay_origin : bool, optional + If ``True``, include origin marker in overlays. + overlay_disks : bool, optional + If ``True``, include disk-center markers in overlays. + overlay_on : {"model", "both"}, optional + Which image panel(s) receive overlays. + origin_marker_kwargs : dict[str, Any] | None, optional + Marker kwargs override for origin marker. + disk_marker_kwargs : dict[str, Any] | None, optional + Marker kwargs override for disk-center markers. + **_ : Any + Ignored extra kwargs for backward compatibility. + + Returns + ------- + tuple[Any, Any] | None + Figure/axes tuple when ``returnfig=True``; otherwise ``None``. + + Raises + ------ + RuntimeError + If model/context are not defined. + ValueError + If ``overlay_on`` is invalid. + """ md = cast("ModelDiffraction", self) if md.image_ref is None: md.preprocess() @@ -160,6 +321,107 @@ def plot_mean_model( vmin=vmin, vmax=vmax, ) + if overlay: + if overlay_on not in ("model", "both"): + raise ValueError("overlay_on must be 'model' or 'both'.") + origin_rc, disk_centers_rc = md.get_overlay_coordinates() + axs_arr = np.asarray(ax, dtype=object).reshape(-1) + axes = [axs_arr[1]] if overlay_on == "model" else [axs_arr[0], axs_arr[1]] + for a in axes: + self._plot_overlays( + a, + origin_rc, + disk_centers_rc, + overlay_origin=overlay_origin, + overlay_disks=overlay_disks, + origin_marker_kwargs=origin_marker_kwargs, + disk_marker_kwargs=disk_marker_kwargs, + ) + if returnfig: + return fig, ax + return None + + def visualize_components( + self, + components: str | list[str], + *, + power: float = 0.25, + cbar: bool = False, + axsize: tuple[int, int] = (6, 6), + returnfig: bool = False, + overlay: bool = True, + overlay_origin: bool = True, + overlay_disks: bool = True, + origin_marker_kwargs: dict[str, Any] | None = None, + disk_marker_kwargs: dict[str, Any] | None = None, + ) -> tuple[Any, Any] | None: + """ + Render and display one or more named components. + + Parameters + ---------- + components : str | list[str] + Component name or list of component names. + power : float, optional + Power-law display scaling. + cbar : bool, optional + If ``True``, draw colorbars. + axsize : tuple[int, int], optional + Axis size passed through to ``show_2d``. + returnfig : bool, optional + If ``True``, return ``(fig, ax)``. + overlay : bool, optional + If ``True``, draw coordinate overlays on all component panels. + overlay_origin : bool, optional + If ``True``, include origin marker in overlays. + overlay_disks : bool, optional + If ``True``, include disk-center markers in overlays. + origin_marker_kwargs : dict[str, Any] | None, optional + Marker kwargs override for origin marker. + disk_marker_kwargs : dict[str, Any] | None, optional + Marker kwargs override for disk-center markers. + + Returns + ------- + tuple[Any, Any] | None + Figure/axes tuple when ``returnfig=True``; otherwise ``None``. + """ + md = cast("ModelDiffraction", self) + names = [components] if isinstance(components, str) else list(components) + if len(names) == 0: + raise ValueError("components must contain at least one component name.") + + rendered = [md.get_rendered_component(name) for name in names] + rendered_scaled = [ + arr + if power == 1.0 + else np.maximum(np.asarray(arr, dtype=np.float32), 0.0) ** float(power) + for arr in rendered + ] + + fig, ax = show_2d( + rendered_scaled, + title=names, + cmap="gray", + cbar=bool(cbar), + returnfig=True, + axsize=axsize, + ) + + if overlay: + origin_rc, disk_centers_rc = md.get_overlay_coordinates() + axs_arr = np.asarray(ax, dtype=object).reshape(-1) + for a in axs_arr: + self._plot_overlays( + a, + origin_rc, + disk_centers_rc, + overlay_origin=overlay_origin, + overlay_disks=overlay_disks, + origin_marker_kwargs=origin_marker_kwargs, + disk_marker_kwargs=disk_marker_kwargs, + ) + if returnfig: return fig, ax return None From 6429e255e576e17622a54537b1fc59d4a10e98a0 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 21:45:35 -0800 Subject: [PATCH 038/113] adding turning on/off individual components and parameters --- src/quantem/core/fitting/base.py | 202 ++++++++++++++++++++++- src/quantem/diffraction/model_fitting.py | 82 +++++++-- 2 files changed, 271 insertions(+), 13 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 898ad90ad..f4136cdde 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -214,7 +214,7 @@ def __init__(self): def get_optimization_parameters(self) -> Any: if self.model is None: return [] - return self.model.parameters() + return [p for p in self.model.parameters() if p.requires_grad] @property def state_current(self) -> dict[str, torch.Tensor] | None: @@ -246,6 +246,164 @@ def reset( self._clear_fit_history_all() return self + def set_component_trainable( + self, component_name: str, enabled: bool, rebuild_optimizer: bool = True + ) -> None: + """ + Enable or disable optimization for all parameters in one component. + + Parameters + ---------- + component_name : str + Resolved component name. + enabled : bool + If ``True``, mark component parameters trainable. + rebuild_optimizer : bool, optional + If ``True``, rebuild optimizer param groups after toggling. + + Returns + ------- + None + + Raises + ------ + RuntimeError + If the model is not defined. + KeyError + If ``component_name`` is unknown. + + Notes + ----- + When rebuilding, the optimizer is reconstructed from stored optimizer + parameters if available, otherwise inferred from the current optimizer + type and learning rate, else defaults. Scheduler state is cleared + predictably by setting scheduler type to ``"none"``. + """ + component = self._resolve_component_by_name(component_name) + for _, param in component.named_parameters(recurse=True): + param.requires_grad_(bool(enabled)) + if rebuild_optimizer: + self._rebuild_optimizer_after_trainability_change() + + def set_parameter_trainable( + self, + component_name: str, + parameter_name: str, + enabled: bool, + rebuild_optimizer: bool = True, + ) -> None: + """ + Enable or disable optimization for one component parameter. + + Parameters + ---------- + component_name : str + Resolved component name. + parameter_name : str + Parameter name from ``component.named_parameters()``. + enabled : bool + If ``True``, mark parameter trainable. + rebuild_optimizer : bool, optional + If ``True``, rebuild optimizer param groups after toggling. + + Returns + ------- + None + + Raises + ------ + RuntimeError + If the model is not defined. + KeyError + If ``component_name`` or ``parameter_name`` is unknown. + + Notes + ----- + When rebuilding, scheduler state is cleared by setting scheduler type + to ``"none"``. + """ + component = self._resolve_component_by_name(component_name) + params = dict(component.named_parameters(recurse=True)) + if parameter_name not in params: + known = ", ".join(sorted(params.keys())) + raise KeyError( + f"Parameter '{parameter_name}' not found in component '{component_name}'. " + f"Known parameters: {known}" + ) + params[parameter_name].requires_grad_(bool(enabled)) + if rebuild_optimizer: + self._rebuild_optimizer_after_trainability_change() + + def set_parameters_trainable( + self, + component_name: str, + parameter_names: list[str], + enabled: bool, + rebuild_optimizer: bool = True, + ) -> None: + """ + Enable or disable optimization for multiple component parameters. + + Parameters + ---------- + component_name : str + Resolved component name. + parameter_names : list[str] + Parameter names from ``component.named_parameters()``. + enabled : bool + If ``True``, mark parameters trainable. + rebuild_optimizer : bool, optional + If ``True``, rebuild optimizer param groups after toggling. + + Returns + ------- + None + + Raises + ------ + RuntimeError + If the model is not defined. + KeyError + If any parameter name is unknown. + """ + component = self._resolve_component_by_name(component_name) + params = dict(component.named_parameters(recurse=True)) + missing = [name for name in parameter_names if name not in params] + if missing: + known = ", ".join(sorted(params.keys())) + raise KeyError( + f"Unknown parameters for component '{component_name}': {', '.join(missing)}. " + f"Known parameters: {known}" + ) + for name in parameter_names: + params[name].requires_grad_(bool(enabled)) + if rebuild_optimizer: + self._rebuild_optimizer_after_trainability_change() + + def get_component_trainable(self, component_name: str) -> dict[str, bool]: + """ + Return trainability flags for one component's parameters. + + Parameters + ---------- + component_name : str + Resolved component name. + + Returns + ------- + dict[str, bool] + Mapping of parameter name to ``requires_grad``. + + Raises + ------ + RuntimeError + If the model is not defined. + KeyError + If ``component_name`` is unknown. + """ + component = self._resolve_component_by_name(component_name) + return {name: bool(param.requires_grad) for name, param in component.named_parameters()} + def fit_render( self, *, @@ -365,6 +523,48 @@ def fit_render( self.fit_history[key] = result return result + def _resolve_component_by_name(self, component_name: str) -> RenderComponent: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + target = str(component_name) + for idx, module in enumerate(self.model.components): + component = cast(RenderComponent, module) + resolved_name = self.model._component_constraint_name(component, idx) + if resolved_name == target: + return component + raise KeyError(f"Component not found: {target}") + + def _infer_optimizer_rebuild_params(self) -> dict[str, Any]: + if self.optimizer_params: + return dict(self.optimizer_params) + if self.optimizer is not None: + opt_type: str | type[torch.optim.Optimizer] + if isinstance(self.optimizer, torch.optim.AdamW): + opt_type = "adamw" + elif isinstance(self.optimizer, torch.optim.Adam): + opt_type = "adam" + elif isinstance(self.optimizer, torch.optim.SGD): + opt_type = "sgd" + else: + opt_type = type(self.optimizer) + lr = float( + self.optimizer.param_groups[0].get( + "lr", getattr(self, "DEFAULT_LR", self.DEFAULT_LR) + ) + ) + return {"type": opt_type, "lr": lr} + return { + "type": getattr(self, "DEFAULT_OPTIMIZER_TYPE", self.DEFAULT_OPTIMIZER_TYPE), + "lr": float(getattr(self, "DEFAULT_LR", self.DEFAULT_LR)), + } + + def _rebuild_optimizer_after_trainability_change(self) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + rebuild_params = self._infer_optimizer_rebuild_params() + self.set_optimizer(rebuild_params) + self.set_scheduler({"type": "none"}) + def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: return {k: v.detach().clone() for k, v in state.items()} diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 12f8235d6..991653e92 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -78,17 +78,6 @@ def component_names(self) -> list[str]: raise RuntimeError("Call .define_model(...) first.") return [cast(str, c.name) for c in self.components] - def _resolve_component_by_name(self, name: str) -> RenderComponent: - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - target = str(name) - for idx, module in enumerate(self.model.components): - component = cast(RenderComponent, module) - resolved_name = self.model._component_constraint_name(component, idx) - if resolved_name == target: - return component - raise KeyError(f"Component not found: {target}") - def get_component(self, name: str) -> RenderComponent: """ Return a live model component by resolved name. @@ -178,6 +167,75 @@ def get_rendered_disk_template(self, name: str | None = None) -> np.ndarray: disk = cast(DiskTemplate, matches[0]) return disk.template_raw.detach().cpu().numpy() + def set_disk_template_trainable( + self, enabled: bool, name: str | None = None, rebuild_optimizer: bool = True + ) -> None: + """ + Toggle DiskTemplate ``template_raw`` trainability. + + Parameters + ---------- + enabled : bool + If ``True``, enable optimization of ``template_raw``. + name : str | None, optional + DiskTemplate component name. If ``None``, applies to all DiskTemplate + components in the current model. + rebuild_optimizer : bool, optional + If ``True``, rebuild optimizer param groups after toggling. + + Returns + ------- + None + + Raises + ------ + KeyError + If ``name`` does not match any component. + RuntimeError + If model is not defined or no DiskTemplate components are found. + TypeError + If ``name`` resolves to a non-DiskTemplate component. + + Notes + ----- + This toggles only ``template_raw.requires_grad``. Other DiskTemplate + parameters (for example ``intensity_raw``) are unchanged. When + ``rebuild_optimizer=True``, optimizer param groups are rebuilt to match + current ``requires_grad`` flags. + """ + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + + if name is not None: + component = self._resolve_component_by_name(name) + if not isinstance(component, DiskTemplate): + raise TypeError(f"Component '{name}' is not a DiskTemplate.") + self.set_parameter_trainable( + name, + "template_raw", + enabled=enabled, + rebuild_optimizer=rebuild_optimizer, + ) + return + + disk_names: list[str] = [] + for idx, module in enumerate(self.model.components): + component = cast(RenderComponent, module) + if isinstance(component, DiskTemplate): + disk_names.append(self.model._component_constraint_name(component, idx)) + if len(disk_names) == 0: + raise RuntimeError("No DiskTemplate components found.") + + for disk_name in disk_names: + self.set_parameter_trainable( + disk_name, + "template_raw", + enabled=enabled, + rebuild_optimizer=False, + ) + if rebuild_optimizer: + self._rebuild_optimizer_after_trainability_change() + def get_component_constraints(self, name: str) -> dict[str, dict[str, Any]]: component = self._resolve_component_by_name(name) return { @@ -380,7 +438,7 @@ def fit_mean_diffraction_pattern( scheduler_params: dict | None = None, constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, - progress: bool = False, + progress: bool = True, ) -> "ModelDiffraction": """ Fit the mean diffraction pattern. From 55886091bb3572d508775b4f1d2f232a2a4d4e7e Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 22:22:38 -0800 Subject: [PATCH 039/113] fixing center disk duplication and a couple viz bugs --- src/quantem/core/fitting/diffraction.py | 126 ++++++++++++++++-- .../model_fitting_visualizations.py | 40 +++--- 2 files changed, 136 insertions(+), 30 deletions(-) diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 900701060..a38c358d9 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -77,11 +77,36 @@ def __init__( intensity: float | Sequence[float] = 1.0, constraint_params: dict[str, Any] | None = None, ): + """ + Build a disk template renderer centered at the shared origin. + + Parameters + ---------- + intensity : float | Sequence[float], optional + Trainable scalar amplitude applied to the rendered template. + + Returns + ------- + None + + Raises + ------ + ValueError + If ``array`` is not 2D or if ``normalize`` is unsupported. + + Notes + ----- + ``template_raw`` controls template shape and ``intensity_raw`` controls + center-disk amplitude. + """ super().__init__() self.name = str(name) self.refine_all_pixels = bool(refine_all_pixels) self.origin = origin self.origin_key = str(origin_key) + self.intensity_raw = nn.Parameter( + torch.tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) + ) a = np.asarray(array, dtype=np.float32) if a.ndim != 2: @@ -138,6 +163,11 @@ def from_array( def set_origin(self, origin: OriginND) -> None: self.origin = origin + def set_intensity(self, value: float | int) -> None: + """Assign ``intensity_raw`` in-place.""" + with torch.no_grad(): + self.intensity_raw.copy_(torch.as_tensor(float(value), dtype=self.intensity_raw.dtype)) + def patch_values(self) -> torch.Tensor: return self.template_raw.reshape(-1) @@ -153,11 +183,25 @@ def add_patch( _splat_patch(out, r0=r0, c0=c0, patch_vals=vals, dr=dr, dc=dc, scale=scale) def forward(self, ctx: RenderContext) -> torch.Tensor: + """ + Render template at origin with scalar amplitude ``intensity_raw``. + + Parameters + ---------- + ctx : RenderContext + Rendering context. + + Returns + ------- + torch.Tensor + Rendered center disk image. + """ out = torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) if self.origin is None: raise RuntimeError("DiskTemplate.forward() requires an OriginND instance.") r0, c0 = self.origin.coords[0], self.origin.coords[1] - self.add_patch(out, r0=r0, c0=c0, scale=torch.tensor(1.0)) # scale learned directly + scale = self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype) + self.add_patch(out, r0=r0, c0=c0, scale=scale) return out def _center_disk(self) -> None: @@ -199,6 +243,7 @@ def _center_disk(self) -> None: def _enforce_positivity(self) -> None: with torch.no_grad(): self.template_raw.clamp_(min=0.0) + self.intensity_raw.clamp_(min=0.0) def enforce_hard_constraints(self, ctx: RenderContext) -> None: if bool(self.hard_constraints.get("force_center", False)): @@ -228,6 +273,10 @@ def constraint_loss( class SyntheticDiskLattice(RenderComponent): + DEFAULT_HARD_CONSTRAINTS: dict[str, bool] = { + "force_positive_intensity": True, + } + def __init__( self, *, @@ -256,6 +305,41 @@ def __init__( origin_key: str = "origin", constraint_params: dict[str, Any] | None = None, ): + """ + Build a synthetic disk lattice renderer. + + Parameters + ---------- + intensity_0 : float | Sequence[float], optional + Baseline intensity for included lattice disks. + center_intensity_0 : float | Sequence[float] | None, optional + Optional center-disk baseline used only when ``(0, 0)`` is included + in the lattice index set. + exclude_indices : Iterable[tuple[int, int]] | None, optional + Lattice indices excluded from rendering. By default, ``(0, 0)`` is + excluded. To include center explicitly, pass ``exclude_indices`` that + does not contain ``(0, 0)``. + + Returns + ------- + None + + Raises + ------ + ValueError + If ``center_intensity_0`` is provided with center included while + ``per_disk_intensity=False``. + + Notes + ----- + Center-intensity routing is explicit: + - Center excluded (default): ``center_intensity_0`` maps to + ``disk.intensity_raw``. + - Center included with ``per_disk_intensity=True``: + ``center_intensity_0`` maps to lattice center ``i0_raw`` entry. + In this case ``disk.intensity_raw`` is set to ``0`` to avoid + duplicate center ownership when disk is rendered standalone. + """ super().__init__() self.name = str(name) self.disk = disk @@ -289,10 +373,13 @@ def __init__( torch.tensor(_parse_init(v_col, name="v_col"), dtype=torch.float32) ) - if exclude_indices is None: - exclude = set() - else: - exclude = set(exclude_indices) + exclude = {(0, 0)} if exclude_indices is None else set(exclude_indices) + center_included = (0, 0) not in exclude + if center_intensity_0 is not None and center_included and not self.per_disk_intensity: + raise ValueError( + "center_intensity_0 with center included requires per_disk_intensity=True, " + "or exclude (0,0) and use DiskTemplate intensity ownership." + ) uv: list[tuple[int, int]] = [] for u in range(-self.u_max, self.u_max + 1): for v in range(-self.v_max, self.v_max + 1): @@ -310,6 +397,8 @@ def __init__( if center_intensity_0 is None else _parse_init(center_intensity_0, name="center_intensity_0") ) + if center_intensity_0 is not None and not center_included: + self.disk.set_intensity(float(i0_center)) ir_init = _parse_init(intensity_row, name="intensity_row") ic_init = _parse_init(intensity_col, name="intensity_col") irr_init = _parse_init(intensity_row_row, name="intensity_row_row") @@ -322,6 +411,8 @@ def __init__( center_mask = (uv_t[:, 0] == 0) & (uv_t[:, 1] == 0) i0_values[center_mask] = float(i0_center) self.i0_raw = nn.Parameter(i0_values) + if center_intensity_0 is not None and center_included: + self.disk.set_intensity(0.0) if self.max_intensity_order >= 1: self.ir = nn.Parameter(torch.full((n_uv,), float(ir_init), dtype=torch.float32)) self.ic = nn.Parameter(torch.full((n_uv,), float(ic_init), dtype=torch.float32)) @@ -345,10 +436,29 @@ def __init__( self.irc = nn.Parameter(torch.tensor(irc_init, dtype=torch.float32)) if constraint_params is not None: self.apply_constraint_params(constraint_params, strict=True) + if bool(self.hard_constraints.get("force_positive_intensity", False)): + self._enforce_positive_intensity_params() def set_origin(self, origin: OriginND) -> None: self.origin = origin + def _enforce_positive_intensity_params(self) -> None: + """ + Project base intensity parameter(s) to nonnegative values. + + Notes + ----- + Positivity is enforced as a hard projection after optimizer steps. + The forward path intentionally avoids clamp-based dead gradients. + Only ``i0_raw`` is projected; slope terms remain unconstrained. + """ + with torch.no_grad(): + self.i0_raw.clamp_(min=0.0) + + def enforce_hard_constraints(self, ctx: RenderContext) -> None: + if bool(self.hard_constraints.get("force_positive_intensity", False)): + self._enforce_positive_intensity_params() + def forward(self, ctx: RenderContext) -> torch.Tensor: if self.origin is None: raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") @@ -391,7 +501,7 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: cc0 = centers_c[j] if self.per_disk_intensity: - inten = torch.clamp(self.i0_raw[j], min=0.0) + inten = self.i0_raw[j] if active_order >= 1 and self.ir is not None and self.ic is not None: inten = inten + self.ir[j] * dr + self.ic[j] * dc if ( @@ -401,9 +511,8 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: and self.irc is not None ): inten = inten + self.irr[j] * dr2 + self.icc[j] * dc2 + self.irc[j] * drdc - inten = torch.clamp(inten, min=0.0) else: - inten = torch.clamp(self.i0_raw, min=0.0) + inten = self.i0_raw if active_order >= 1: assert self.ir is not None and self.ic is not None inten = inten + self.ir * rr0 + self.ic * cc0 @@ -412,7 +521,6 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: inten = ( inten + self.irr * rr0 * rr0 + self.icc * cc0 * cc0 + self.irc * rr0 * cc0 ) - inten = torch.clamp(inten, min=0.0) self.disk.add_patch(out, r0=rr0, c0=cc0, scale=inten) diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index 95245aa1c..7be1f4709 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -356,12 +356,13 @@ def visualize_components( disk_marker_kwargs: dict[str, Any] | None = None, ) -> tuple[Any, Any] | None: """ - Render and display one or more named components. + Render and display a summed component image. Parameters ---------- components : str | list[str] - Component name or list of component names. + Component name or list of component names. Multiple names are + composited by summing component renders. power : float, optional Power-law display scaling. cbar : bool, optional @@ -391,17 +392,16 @@ def visualize_components( if len(names) == 0: raise ValueError("components must contain at least one component name.") - rendered = [md.get_rendered_component(name) for name in names] - rendered_scaled = [ - arr - if power == 1.0 - else np.maximum(np.asarray(arr, dtype=np.float32), 0.0) ** float(power) - for arr in rendered + rendered = [ + np.asarray(md.get_rendered_component(name), dtype=np.float32) for name in names ] + summed = np.sum(np.stack(rendered, axis=0), axis=0) + summed_scaled = summed if power == 1.0 else np.maximum(summed, 0.0) ** float(power) + title = names[0] if len(names) == 1 else " + ".join(names) fig, ax = show_2d( - rendered_scaled, - title=names, + summed_scaled, + title=title, cmap="gray", cbar=bool(cbar), returnfig=True, @@ -410,17 +410,15 @@ def visualize_components( if overlay: origin_rc, disk_centers_rc = md.get_overlay_coordinates() - axs_arr = np.asarray(ax, dtype=object).reshape(-1) - for a in axs_arr: - self._plot_overlays( - a, - origin_rc, - disk_centers_rc, - overlay_origin=overlay_origin, - overlay_disks=overlay_disks, - origin_marker_kwargs=origin_marker_kwargs, - disk_marker_kwargs=disk_marker_kwargs, - ) + self._plot_overlays( + ax, + origin_rc, + disk_centers_rc, + overlay_origin=overlay_origin, + overlay_disks=overlay_disks, + origin_marker_kwargs=origin_marker_kwargs, + disk_marker_kwargs=disk_marker_kwargs, + ) if returnfig: return fig, ax From 1fbc76c9589a3ced07ba55e1bfcf2797d31d93ad Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 22:41:02 -0800 Subject: [PATCH 040/113] adding back parameter bounds --- src/quantem/core/fitting/background.py | 36 +++--- src/quantem/core/fitting/base.py | 153 +++++++++++++++++++++++- src/quantem/core/fitting/diffraction.py | 130 ++++++++++++++------ 3 files changed, 260 insertions(+), 59 deletions(-) diff --git a/src/quantem/core/fitting/background.py b/src/quantem/core/fitting/background.py index 47333a297..31878decc 100644 --- a/src/quantem/core/fitting/background.py +++ b/src/quantem/core/fitting/background.py @@ -1,24 +1,13 @@ from __future__ import annotations -from typing import Any, Sequence, cast +from typing import Any, Sequence -import numpy as np import torch from torch import nn from quantem.core.fitting.base import OriginND, RenderComponent, RenderContext -def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: - if isinstance(value, (list, tuple, np.ndarray)): - if len(value) == 0: - raise ValueError(f"{name} cannot be empty.") - if value[0] is None: - raise ValueError(f"{name} initial value cannot be None.") - return float(value[0]) - return float(cast(float | int, value)) - - class DCBackground(RenderComponent): def __init__( self, @@ -29,9 +18,12 @@ def __init__( ): super().__init__() self.name = str(name) - self.intensity_raw = nn.Parameter( - torch.tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) + intensity_init, intensity_lo, intensity_hi = self.parse_bounded_init( + intensity, name="intensity" ) + self.intensity_raw = nn.Parameter(torch.tensor(intensity_init, dtype=torch.float32)) + if intensity_lo is not None or intensity_hi is not None: + self.register_parameter_bounds("intensity_raw", intensity_lo, intensity_hi) if constraint_params is not None: self.apply_constraint_params(constraint_params, strict=True) @@ -40,7 +32,7 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: return torch.ones(ctx.shape, device=ctx.device, dtype=ctx.dtype) * inten -class GaussianBackground(RenderComponent): +class GaussianBackground(RenderComponent): # TODO this should be N dimensional by default def __init__( self, *, @@ -55,12 +47,16 @@ def __init__( self.name = str(name) self.origin = origin self.origin_key = str(origin_key) - self.sigma_raw = nn.Parameter( - torch.tensor(_parse_init(sigma, name="sigma"), dtype=torch.float32) - ) - self.intensity_raw = nn.Parameter( - torch.tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) + sigma_init, sigma_lo, sigma_hi = self.parse_bounded_init(sigma, name="sigma") + intensity_init, intensity_lo, intensity_hi = self.parse_bounded_init( + intensity, name="intensity" ) + self.sigma_raw = nn.Parameter(torch.tensor(sigma_init, dtype=torch.float32)) + if sigma_lo is not None or sigma_hi is not None: + self.register_parameter_bounds("sigma_raw", sigma_lo, sigma_hi) + self.intensity_raw = nn.Parameter(torch.tensor(intensity_init, dtype=torch.float32)) + if intensity_lo is not None or intensity_hi is not None: + self.register_parameter_bounds("intensity_raw", intensity_lo, intensity_hi) if constraint_params is not None: self.apply_constraint_params(constraint_params, strict=True) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index f4136cdde..29933e149 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -11,6 +11,65 @@ from quantem.core.ml.optimizer_mixin import OptimizerMixin +def parse_bounded_init( + value: float | int | Sequence[float | int | None], *, name: str +) -> tuple[float, float | None, float | None]: + """ + Parse a scalar or bounded initializer specification. + + Parameters + ---------- + value : float | int | Sequence[float | int | None] + Accepted forms: + - ``x`` -> init ``x`` with no bounds. + - ``(x0, delta)`` -> init ``x0`` with bounds ``[x0-|delta|, x0+|delta|]``. + - ``(x0, lo, hi)`` -> init ``x0`` with explicit bounds. + name : str + Parameter name used in error messages. + + Returns + ------- + tuple[float, float | None, float | None] + Parsed ``(init, lo, hi)``. + + Raises + ------ + ValueError + If the sequence form is invalid, contains required ``None`` entries, + has invalid ordering, or ``init`` lies outside explicit bounds. + """ + if not isinstance(value, (list, tuple, np.ndarray)): + x = float(cast(float | int, value)) + return x, None, None + + seq = list(value) + if len(seq) == 0: + raise ValueError(f"{name} cannot be empty.") + if seq[0] is None: + raise ValueError(f"{name} initial value cannot be None.") + x0 = float(cast(float | int, seq[0])) + + if len(seq) == 1: + return x0, None, None + if len(seq) == 2: + if seq[1] is None: + raise ValueError(f"{name} delta cannot be None.") + delta = abs(float(cast(float | int, seq[1]))) + return x0, x0 - delta, x0 + delta + if len(seq) == 3: + if seq[1] is None or seq[2] is None: + raise ValueError(f"{name} bounds cannot contain None.") + lo = float(cast(float | int, seq[1])) + hi = float(cast(float | int, seq[2])) + if lo > hi: + raise ValueError(f"{name} has invalid bounds: lo ({lo}) > hi ({hi}).") + if x0 < lo or x0 > hi: + raise ValueError(f"{name} initial value {x0} is outside bounds [{lo}, {hi}].") + return x0, lo, hi + + raise ValueError(f"{name} must be scalar, (x0, delta), or (x0, lo, hi).") + + @dataclass class RenderContext: shape: tuple[int, ...] @@ -39,6 +98,98 @@ def __init__(self) -> None: super().__init__() self.hard_constraints: dict[str, Any] = dict(self.DEFAULT_HARD_CONSTRAINTS) self.soft_constraints: dict[str, Any] = dict(self.DEFAULT_SOFT_CONSTRAINTS) + self.parameter_bounds: dict[str, tuple[float | None, float | None]] = {} + + @staticmethod + def parse_bounded_init( + value: float | int | Sequence[float | int | None], *, name: str + ) -> tuple[float, float | None, float | None]: + """ + Parse bounded initializer forms into ``(init, lo, hi)``. + + Parameters + ---------- + value : float | int | Sequence[float | int | None] + Scalar, ``(x0, delta)``, or ``(x0, lo, hi)``. + name : str + Parameter name used in error messages. + + Returns + ------- + tuple[float, float | None, float | None] + Parsed ``(init, lo, hi)``. + """ + return parse_bounded_init(value, name=name) + + def register_parameter_bounds( + self, parameter_name: str, lo: float | None, hi: float | None + ) -> None: + """ + Register hard bounds for a trainable parameter. + + Parameters + ---------- + parameter_name : str + Name of an ``nn.Parameter`` attribute on this component. + lo : float | None + Lower bound, or ``None`` for unbounded lower side. + hi : float | None + Upper bound, or ``None`` for unbounded upper side. + + Returns + ------- + None + + Raises + ------ + ValueError + If ``lo > hi``. + """ + if lo is not None and hi is not None and float(lo) > float(hi): + raise ValueError(f"Invalid bounds for {parameter_name}: lo ({lo}) > hi ({hi}).") + self.parameter_bounds[str(parameter_name)] = ( + None if lo is None else float(lo), + None if hi is None else float(hi), + ) + + def _enforce_parameter_bounds(self) -> None: + """ + Clamp registered parameters in-place to configured bounds. + + Returns + ------- + None + + Raises + ------ + AttributeError + If a registered parameter attribute is missing. + TypeError + If a registered attribute is not an ``nn.Parameter``. + """ + if not self.parameter_bounds: + return + with torch.no_grad(): + for param_name, (lo, hi) in self.parameter_bounds.items(): + if not hasattr(self, param_name): + raise AttributeError( + f"Parameter '{param_name}' is not an attribute of {self.__class__.__name__}." + ) + param = getattr(self, param_name) + if not isinstance(param, nn.Parameter): + raise TypeError( + f"Attribute '{param_name}' on {self.__class__.__name__} is not an nn.Parameter." + ) + if lo is None and hi is None: + continue + if lo is None: + assert hi is not None + param.clamp_(max=float(hi)) + elif hi is None: + assert lo is not None + param.clamp_(min=float(lo)) + else: + param.clamp_(min=float(lo), max=float(hi)) def _set_constraints( self, @@ -109,7 +260,7 @@ def effective_soft_constraints(self, params: dict[str, Any] | None = None) -> di return effective def enforce_hard_constraints(self, ctx: RenderContext) -> None: - return None + self._enforce_parameter_bounds() def forward(self, ctx: RenderContext) -> torch.Tensor: raise NotImplementedError diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index a38c358d9..30934bf54 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -10,16 +10,6 @@ from quantem.core.fitting.base import OriginND, RenderComponent, RenderContext -def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: - if isinstance(value, (list, tuple, np.ndarray)): - if len(value) == 0: - raise ValueError(f"{name} cannot be empty.") - if value[0] is None: - raise ValueError(f"{name} initial value cannot be None.") - return float(value[0]) - return float(cast(float | int, value)) - - def _splat_patch( out: torch.Tensor, *, @@ -84,6 +74,7 @@ def __init__( ---------- intensity : float | Sequence[float], optional Trainable scalar amplitude applied to the rendered template. + Accepts ``x``, ``(x0, delta)``, or ``(x0, lo, hi)``. Returns ------- @@ -104,9 +95,12 @@ def __init__( self.refine_all_pixels = bool(refine_all_pixels) self.origin = origin self.origin_key = str(origin_key) - self.intensity_raw = nn.Parameter( - torch.tensor(_parse_init(intensity, name="intensity"), dtype=torch.float32) + intensity_init, intensity_lo, intensity_hi = self.parse_bounded_init( + intensity, name="intensity" ) + self.intensity_raw = nn.Parameter(torch.tensor(intensity_init, dtype=torch.float32)) + if intensity_lo is not None or intensity_hi is not None: + self.register_parameter_bounds("intensity_raw", intensity_lo, intensity_hi) a = np.asarray(array, dtype=np.float32) if a.ndim != 2: @@ -250,6 +244,7 @@ def enforce_hard_constraints(self, ctx: RenderContext) -> None: self._center_disk() if bool(self.hard_constraints.get("force_positive", False)): self._enforce_positivity() + super().enforce_hard_constraints(ctx) def constraint_loss( self, ctx: RenderContext, params: dict[str, object] | None = None @@ -310,11 +305,19 @@ def __init__( Parameters ---------- + u_row, u_col, v_row, v_col : float | Sequence[float | int | None] + Lattice basis parameters. Accept ``x``, ``(x0, delta)``, or + ``(x0, lo, hi)``. intensity_0 : float | Sequence[float], optional - Baseline intensity for included lattice disks. + Baseline intensity for included lattice disks. Accepts ``x``, + ``(x0, delta)``, or ``(x0, lo, hi)``. + intensity_row, intensity_col, intensity_row_row, intensity_col_col, intensity_row_col : + float | Sequence[float | int | None] + Intensity polynomial parameters. Accept ``x``, ``(x0, delta)``, or + ``(x0, lo, hi)``. center_intensity_0 : float | Sequence[float] | None, optional - Optional center-disk baseline used only when ``(0, 0)`` is included - in the lattice index set. + Optional center-disk baseline. Accepts ``x``, ``(x0, delta)``, or + ``(x0, lo, hi)`` and routes by center ownership rules. exclude_indices : Iterable[tuple[int, int]] | None, optional Lattice indices excluded from rendering. By default, ``(0, 0)`` is excluded. To include center explicitly, pass ``exclude_indices`` that @@ -360,18 +363,22 @@ def __init__( default_pattern_intensity_order = self.max_intensity_order self.default_pattern_intensity_order = int(default_pattern_intensity_order) - self.u_row = nn.Parameter( - torch.tensor(_parse_init(u_row, name="u_row"), dtype=torch.float32) - ) - self.u_col = nn.Parameter( - torch.tensor(_parse_init(u_col, name="u_col"), dtype=torch.float32) - ) - self.v_row = nn.Parameter( - torch.tensor(_parse_init(v_row, name="v_row"), dtype=torch.float32) - ) - self.v_col = nn.Parameter( - torch.tensor(_parse_init(v_col, name="v_col"), dtype=torch.float32) - ) + u_row_init, u_row_lo, u_row_hi = self.parse_bounded_init(u_row, name="u_row") + u_col_init, u_col_lo, u_col_hi = self.parse_bounded_init(u_col, name="u_col") + v_row_init, v_row_lo, v_row_hi = self.parse_bounded_init(v_row, name="v_row") + v_col_init, v_col_lo, v_col_hi = self.parse_bounded_init(v_col, name="v_col") + self.u_row = nn.Parameter(torch.tensor(u_row_init, dtype=torch.float32)) + if u_row_lo is not None or u_row_hi is not None: + self.register_parameter_bounds("u_row", u_row_lo, u_row_hi) + self.u_col = nn.Parameter(torch.tensor(u_col_init, dtype=torch.float32)) + if u_col_lo is not None or u_col_hi is not None: + self.register_parameter_bounds("u_col", u_col_lo, u_col_hi) + self.v_row = nn.Parameter(torch.tensor(v_row_init, dtype=torch.float32)) + if v_row_lo is not None or v_row_hi is not None: + self.register_parameter_bounds("v_row", v_row_lo, v_row_hi) + self.v_col = nn.Parameter(torch.tensor(v_col_init, dtype=torch.float32)) + if v_col_lo is not None or v_col_hi is not None: + self.register_parameter_bounds("v_col", v_col_lo, v_col_hi) exclude = {(0, 0)} if exclude_indices is None else set(exclude_indices) center_included = (0, 0) not in exclude @@ -391,31 +398,51 @@ def __init__( self.register_buffer("uv_indices", uv_t) n_uv = int(uv_t.shape[0]) - i0_init = _parse_init(intensity_0, name="intensity_0") - i0_center = ( - i0_init - if center_intensity_0 is None - else _parse_init(center_intensity_0, name="center_intensity_0") - ) + i0_init, i0_lo, i0_hi = self.parse_bounded_init(intensity_0, name="intensity_0") + if center_intensity_0 is None: + i0_center, i0_center_lo, i0_center_hi = i0_init, None, None + else: + i0_center, i0_center_lo, i0_center_hi = self.parse_bounded_init( + center_intensity_0, name="center_intensity_0" + ) if center_intensity_0 is not None and not center_included: self.disk.set_intensity(float(i0_center)) - ir_init = _parse_init(intensity_row, name="intensity_row") - ic_init = _parse_init(intensity_col, name="intensity_col") - irr_init = _parse_init(intensity_row_row, name="intensity_row_row") - icc_init = _parse_init(intensity_col_col, name="intensity_col_col") - irc_init = _parse_init(intensity_row_col, name="intensity_row_col") + if i0_center_lo is not None or i0_center_hi is not None: + self.disk.register_parameter_bounds("intensity_raw", i0_center_lo, i0_center_hi) + ir_init, ir_lo, ir_hi = self.parse_bounded_init(intensity_row, name="intensity_row") + ic_init, ic_lo, ic_hi = self.parse_bounded_init(intensity_col, name="intensity_col") + irr_init, irr_lo, irr_hi = self.parse_bounded_init( + intensity_row_row, name="intensity_row_row" + ) + icc_init, icc_lo, icc_hi = self.parse_bounded_init( + intensity_col_col, name="intensity_col_col" + ) + irc_init, irc_lo, irc_hi = self.parse_bounded_init( + intensity_row_col, name="intensity_row_col" + ) + self._center_i0_bounds: tuple[float | None, float | None] | None = None + self._center_i0_index: int | None = None if self.per_disk_intensity: i0_values = torch.full((n_uv,), float(i0_init), dtype=torch.float32) if n_uv > 0: center_mask = (uv_t[:, 0] == 0) & (uv_t[:, 1] == 0) i0_values[center_mask] = float(i0_center) + if center_intensity_0 is not None and bool(torch.any(center_mask)): + self._center_i0_index = int(torch.nonzero(center_mask, as_tuple=False)[0, 0]) + self._center_i0_bounds = (i0_center_lo, i0_center_hi) self.i0_raw = nn.Parameter(i0_values) + if i0_lo is not None or i0_hi is not None: + self.register_parameter_bounds("i0_raw", i0_lo, i0_hi) if center_intensity_0 is not None and center_included: self.disk.set_intensity(0.0) if self.max_intensity_order >= 1: self.ir = nn.Parameter(torch.full((n_uv,), float(ir_init), dtype=torch.float32)) self.ic = nn.Parameter(torch.full((n_uv,), float(ic_init), dtype=torch.float32)) + if ir_lo is not None or ir_hi is not None: + self.register_parameter_bounds("ir", ir_lo, ir_hi) + if ic_lo is not None or ic_hi is not None: + self.register_parameter_bounds("ic", ic_lo, ic_hi) else: self.ir = None self.ic = None @@ -423,17 +450,35 @@ def __init__( self.irr = nn.Parameter(torch.full((n_uv,), float(irr_init), dtype=torch.float32)) self.icc = nn.Parameter(torch.full((n_uv,), float(icc_init), dtype=torch.float32)) self.irc = nn.Parameter(torch.full((n_uv,), float(irc_init), dtype=torch.float32)) + if irr_lo is not None or irr_hi is not None: + self.register_parameter_bounds("irr", irr_lo, irr_hi) + if icc_lo is not None or icc_hi is not None: + self.register_parameter_bounds("icc", icc_lo, icc_hi) + if irc_lo is not None or irc_hi is not None: + self.register_parameter_bounds("irc", irc_lo, irc_hi) else: self.irr = None self.icc = None self.irc = None else: self.i0_raw = nn.Parameter(torch.tensor(i0_init, dtype=torch.float32)) + if i0_lo is not None or i0_hi is not None: + self.register_parameter_bounds("i0_raw", i0_lo, i0_hi) self.ir = nn.Parameter(torch.tensor(ir_init, dtype=torch.float32)) + if ir_lo is not None or ir_hi is not None: + self.register_parameter_bounds("ir", ir_lo, ir_hi) self.ic = nn.Parameter(torch.tensor(ic_init, dtype=torch.float32)) + if ic_lo is not None or ic_hi is not None: + self.register_parameter_bounds("ic", ic_lo, ic_hi) self.irr = nn.Parameter(torch.tensor(irr_init, dtype=torch.float32)) + if irr_lo is not None or irr_hi is not None: + self.register_parameter_bounds("irr", irr_lo, irr_hi) self.icc = nn.Parameter(torch.tensor(icc_init, dtype=torch.float32)) + if icc_lo is not None or icc_hi is not None: + self.register_parameter_bounds("icc", icc_lo, icc_hi) self.irc = nn.Parameter(torch.tensor(irc_init, dtype=torch.float32)) + if irc_lo is not None or irc_hi is not None: + self.register_parameter_bounds("irc", irc_lo, irc_hi) if constraint_params is not None: self.apply_constraint_params(constraint_params, strict=True) if bool(self.hard_constraints.get("force_positive_intensity", False)): @@ -458,6 +503,15 @@ def _enforce_positive_intensity_params(self) -> None: def enforce_hard_constraints(self, ctx: RenderContext) -> None: if bool(self.hard_constraints.get("force_positive_intensity", False)): self._enforce_positive_intensity_params() + if self._center_i0_bounds is not None and self._center_i0_index is not None: + with torch.no_grad(): + lo, hi = self._center_i0_bounds + idx = self._center_i0_index + if lo is not None: + self.i0_raw[idx].clamp_(min=float(lo)) + if hi is not None: + self.i0_raw[idx].clamp_(max=float(hi)) + super().enforce_hard_constraints(ctx) def forward(self, ctx: RenderContext) -> torch.Tensor: if self.origin is None: From 20b2c58f2c26b1e5c271dca7e5216033df4f2762 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 22:48:17 -0800 Subject: [PATCH 041/113] updating colormaps --- .../model_fitting_visualizations.py | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index 7be1f4709..6dfb4f629 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -4,6 +4,7 @@ from matplotlib import gridspec from matplotlib import pyplot as plt +from quantem.core import config from quantem.core.visualization import show_2d if TYPE_CHECKING: @@ -46,12 +47,13 @@ def _plot_overlays( ------- None """ + colors = config.get("viz.colors.set") if overlay_origin and origin_rc.shape == (2,): kw_origin = { "marker": "+", - "color": "red", + "color": colors[0], "markersize": 10, - "markeredgewidth": 1.5, + "markeredgewidth": 3, "linestyle": "None", } if origin_marker_kwargs is not None: @@ -61,9 +63,9 @@ def _plot_overlays( if overlay_disks and disk_centers_rc.ndim == 2 and disk_centers_rc.shape[0] > 0: kw_disks = { "marker": "x", - "color": "cyan", + "color": colors[1], "markersize": 5, - "markeredgewidth": 1.0, + "markeredgewidth": 2.0, "linestyle": "None", } if disk_marker_kwargs is not None: @@ -74,6 +76,9 @@ def plot_losses( self, figax: tuple[Any, Any] | None = None, plot_lrs: bool = True ) -> tuple[Any, Any]: md = cast("ModelDiffraction", self) + colors = config.get("viz.colors.set") + loss_color = "k" + lr_color = colors[8] if figax is None: fig, ax = plt.subplots() @@ -100,11 +105,11 @@ def plot_losses( iters = np.arange(losses.size) lines: list[Any] = [] - lines.extend(ax.semilogy(iters, losses, c="k", lw=2, label="loss")) + lines.extend(ax.semilogy(iters, losses, c=loss_color, lw=2, label="loss")) ax.set_xlabel("Iterations") - ax.set_ylabel("Loss", color="k") - ax.tick_params(axis="y", which="both", colors="k") - ax.spines["left"].set_color("k") + ax.set_ylabel("Loss", color=loss_color) + ax.tick_params(axis="y", which="both", colors=loss_color) + ax.spines["left"].set_color(loss_color) ax.set_xbound(-2, max(1, int(iters.max())) + 2) lrs = np.asarray([] if mean_hist is None else mean_hist.lrs, dtype=np.float64) @@ -116,13 +121,11 @@ def plot_losses( ax.patch.set_visible(False) ax_lr.spines["left"].set_visible(False) lines.extend( - ax_lr.semilogy( - np.arange(lrs.size), lrs, c="tab:blue", lw=2, ls="--", label="LR" - ) + ax_lr.semilogy(np.arange(lrs.size), lrs, c=lr_color, lw=2, ls="--", label="LR") ) - ax_lr.set_ylabel("LR", color="tab:blue") - ax_lr.tick_params(axis="y", which="both", colors="tab:blue") - ax_lr.spines["right"].set_color("tab:blue") + ax_lr.set_ylabel("LR", color=lr_color) + ax_lr.tick_params(axis="y", which="both", colors=lr_color) + ax_lr.spines["right"].set_color(lr_color) else: ax.set_title(f"LR: {float(lrs[-1]):.2e}", fontsize=10) @@ -211,7 +214,7 @@ def visualize( [refp, predp], figax=(fig, axs), title=["image_ref", "model"], - cmap="gray", + cmap=config.get("viz.cmap"), cbar=bool(cbar), returnfig=False, axsize=axsize, @@ -314,7 +317,7 @@ def plot_mean_model( fig, ax = show_2d( [refp, predp], title=["image_ref", "model"], - cmap="gray", + cmap=config.get("viz.cmap"), cbar=False, returnfig=True, axsize=axsize, @@ -402,7 +405,7 @@ def visualize_components( fig, ax = show_2d( summed_scaled, title=title, - cmap="gray", + cmap=config.get("viz.cmap"), cbar=bool(cbar), returnfig=True, axsize=axsize, From f68742ea93e735c4b76f0365631eea81c91f5bba Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 22:58:21 -0800 Subject: [PATCH 042/113] more consistent hard constraints of ranges --- src/quantem/core/fitting/background.py | 53 ++++++++++++++++++++---- src/quantem/diffraction/model_fitting.py | 5 ++- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/quantem/core/fitting/background.py b/src/quantem/core/fitting/background.py index 31878decc..000d4a01a 100644 --- a/src/quantem/core/fitting/background.py +++ b/src/quantem/core/fitting/background.py @@ -16,19 +16,36 @@ def __init__( name: str = "dc_background", constraint_params: dict[str, Any] | None = None, ): + """ + Build a constant background component. + + Notes + ----- + Validity is enforced via hard constraints/parameter bounds. Forward + intentionally avoids hard clamps for gradient flow. + """ super().__init__() self.name = str(name) intensity_init, intensity_lo, intensity_hi = self.parse_bounded_init( intensity, name="intensity" ) self.intensity_raw = nn.Parameter(torch.tensor(intensity_init, dtype=torch.float32)) - if intensity_lo is not None or intensity_hi is not None: - self.register_parameter_bounds("intensity_raw", intensity_lo, intensity_hi) + bounded_lo = 0.0 if intensity_lo is None else max(float(intensity_lo), 0.0) + self.register_parameter_bounds("intensity_raw", bounded_lo, intensity_hi) if constraint_params is not None: self.apply_constraint_params(constraint_params, strict=True) + self._enforce_parameter_bounds() def forward(self, ctx: RenderContext) -> torch.Tensor: - inten = torch.clamp(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype), min=0.0) + """ + Render constant background from raw trainable intensity. + + Notes + ----- + Validity is enforced via hard constraints/parameter bounds, not via + forward-time hard clamps. + """ + inten = self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype) return torch.ones(ctx.shape, device=ctx.device, dtype=ctx.dtype) * inten @@ -43,6 +60,15 @@ def __init__( name: str = "gaussian_background", constraint_params: dict[str, Any] | None = None, ): + """ + Build a Gaussian background component centered at origin. + + Notes + ----- + ``sigma_raw`` and ``intensity_raw`` validity is enforced via hard + constraints/parameter bounds. Forward intentionally avoids hard clamps + for gradient flow. + """ super().__init__() self.name = str(name) self.origin = origin @@ -52,18 +78,27 @@ def __init__( intensity, name="intensity" ) self.sigma_raw = nn.Parameter(torch.tensor(sigma_init, dtype=torch.float32)) - if sigma_lo is not None or sigma_hi is not None: - self.register_parameter_bounds("sigma_raw", sigma_lo, sigma_hi) + sigma_bounded_lo = 1e-6 if sigma_lo is None else max(float(sigma_lo), 1e-6) + self.register_parameter_bounds("sigma_raw", sigma_bounded_lo, sigma_hi) self.intensity_raw = nn.Parameter(torch.tensor(intensity_init, dtype=torch.float32)) - if intensity_lo is not None or intensity_hi is not None: - self.register_parameter_bounds("intensity_raw", intensity_lo, intensity_hi) + intensity_bounded_lo = 0.0 if intensity_lo is None else max(float(intensity_lo), 0.0) + self.register_parameter_bounds("intensity_raw", intensity_bounded_lo, intensity_hi) if constraint_params is not None: self.apply_constraint_params(constraint_params, strict=True) + self._enforce_parameter_bounds() def set_origin(self, origin: OriginND) -> None: self.origin = origin def forward(self, ctx: RenderContext) -> torch.Tensor: + """ + Render Gaussian background from raw trainable parameters. + + Notes + ----- + Validity is enforced via hard constraints/parameter bounds, not via + forward-time hard clamps. + """ if self.origin is None: raise RuntimeError("GaussianBackground requires an OriginND instance.") @@ -71,7 +106,7 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: cc = torch.arange(ctx.shape[1], device=ctx.device, dtype=ctx.dtype)[None, :] r0, c0 = self.origin.coords[0], self.origin.coords[1] - sigma = torch.clamp(self.sigma_raw.to(device=ctx.device, dtype=ctx.dtype), min=1e-6) - inten = torch.clamp(self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype), min=0.0) + sigma = self.sigma_raw.to(device=ctx.device, dtype=ctx.dtype) + inten = self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype) r2 = (rr - r0) ** 2 + (cc - c0) ** 2 return inten * torch.exp(-0.5 * r2 / (sigma * sigma)) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 991653e92..709280225 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -76,7 +76,10 @@ def components(self) -> torch.nn.ModuleList: def component_names(self) -> list[str]: if self.model is None: raise RuntimeError("Call .define_model(...) first.") - return [cast(str, c.name) for c in self.components] + return [ + self.model._component_constraint_name(cast(RenderComponent, module), idx) + for idx, module in enumerate(self.model.components) + ] def get_component(self, name: str) -> RenderComponent: """ From b787564507e39e50c8c8537740d8fde2ae878385 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Mar 2026 23:05:56 -0800 Subject: [PATCH 043/113] cleaning up naming of Components --- src/quantem/core/fitting/base.py | 41 +++++++++++++++++++++--- src/quantem/diffraction/model_fitting.py | 19 +++-------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 29933e149..b21e1126b 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -674,16 +674,49 @@ def fit_render( self.fit_history[key] = result return result - def _resolve_component_by_name(self, component_name: str) -> RenderComponent: + def _iter_named_components(self) -> list[tuple[str, RenderComponent]]: + """ + Return canonical component names paired with components. + + Returns + ------- + list[tuple[str, RenderComponent]] + ``(name, component)`` entries using the model's canonical naming + rule. Names fall back to class-name/index behavior when ``.name`` is + missing. + + Raises + ------ + RuntimeError + If the model is not defined. + """ if self.model is None: raise RuntimeError("Call .define_model(...) first.") - target = str(component_name) + entries: list[tuple[str, RenderComponent]] = [] for idx, module in enumerate(self.model.components): component = cast(RenderComponent, module) - resolved_name = self.model._component_constraint_name(component, idx) + name = self.model._component_constraint_name(component, idx) + entries.append((name, component)) + return entries + + def get_component_names(self) -> list[str]: + """ + Return canonical component names. + + Returns + ------- + list[str] + Canonical component names. + """ + return [name for name, _ in self._iter_named_components()] + + def _resolve_component_by_name(self, component_name: str) -> RenderComponent: + target = str(component_name) + for resolved_name, component in self._iter_named_components(): if resolved_name == target: return component - raise KeyError(f"Component not found: {target}") + known = ", ".join(self.get_component_names()) + raise KeyError(f"Component not found: {target}. Known components: {known}") def _infer_optimizer_rebuild_params(self) -> dict[str, Any]: if self.optimizer_params: diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 709280225..55cddfcb3 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -72,15 +72,6 @@ def components(self) -> torch.nn.ModuleList: raise RuntimeError("Call .define_model(...) first.") return self.model.components - @property - def component_names(self) -> list[str]: - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - return [ - self.model._component_constraint_name(cast(RenderComponent, module), idx) - for idx, module in enumerate(self.model.components) - ] - def get_component(self, name: str) -> RenderComponent: """ Return a live model component by resolved name. @@ -221,11 +212,11 @@ def set_disk_template_trainable( ) return - disk_names: list[str] = [] - for idx, module in enumerate(self.model.components): - component = cast(RenderComponent, module) - if isinstance(component, DiskTemplate): - disk_names.append(self.model._component_constraint_name(component, idx)) + disk_names = [ + component_name + for component_name, component in self._iter_named_components() + if isinstance(component, DiskTemplate) + ] if len(disk_names) == 0: raise RuntimeError("No DiskTemplate components found.") From 41b5e909af7d646bb8c03ddf9683e0811924f769 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Fri, 6 Mar 2026 11:48:12 -0800 Subject: [PATCH 044/113] Individual pattern fitting function for fitting_models --- src/quantem/diffraction/model_fitting.py | 143 +++++++++++++++++- .../model_fitting_visualizations.py | 32 +++- .../diffraction/strain_autocorrelation.py | 2 +- 3 files changed, 162 insertions(+), 15 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 55cddfcb3..9008ebbd6 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -6,6 +6,7 @@ import torch from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey +from tqdm import tqdm from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem from quantem.core.fitting.base import ( @@ -53,6 +54,9 @@ def __init__(self, dataset: Any, _token: object | None = None): self.state_mean_refined: dict[str, torch.Tensor] | None = None self.mean_refined: bool = False + self.state_individual_refined: np.ndarray | None = None + self.individual_refined: bool = False + # Misc metadata self.metadata: dict[str, Any] = {} @@ -500,7 +504,10 @@ def fit_mean_diffraction_pattern( def reset( self, - reset_to: Literal["initialized", "mean_refined"] = "mean_refined", + reset_to: Literal["initialized", "mean_refined", "individual"] = "mean_refined", + reset_history: bool = True, + individual_row: int = 0, + individual_col: int = 0, ) -> "ModelDiffraction": if reset_to == "initialized": state = self.state_initialized @@ -508,22 +515,144 @@ def reset( raise RuntimeError( "initialized state is unavailable. Call .define_model(...) first." ) - self._clear_fit_history_all() + if reset_history: + self._clear_fit_history_all() elif reset_to == "mean_refined": state = self.state_mean_refined if state is None: raise RuntimeError( "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." ) - mean_hist = self.fit_history.get("mean") - self._clear_fit_history_all() - if mean_hist is not None: - self.fit_history["mean"] = mean_hist + if reset_history: + mean_hist = self.fit_history.get("mean") + self._clear_fit_history_all() + if mean_hist is not None: + self.fit_history["mean"] = mean_hist + elif reset_to == "individual": + if self.state_individual_refined is None: + raise ValueError("individual states is unavalible. Run fit_individual_diffraction_pattern(....) first") + if (individual_row >= self.state_individual_refined.shape[0]) or (individual_col >= self.state_individual_refined.shape[1]): + raise ValueError("row and column values not in range") + state = self.state_individual_refined[individual_row, individual_col] + if reset_history: + self._clear_fit_history_all() else: - raise ValueError("reset_to must be 'initialized' or 'mean_refined'.") + raise ValueError("reset_to must be 'initialized' or 'mean_refined' or 'individual'.") self._load_model_state_dict_copy(state) return self + + def fit_individual_diffraction_pattern( + self, + *, + rows=None, + cols = None, + n_steps: int = 200, + reset: bool | Literal["initialized", "mean_refined"], + optimizer_params: dict | None = None, + scheduler_params: dict | None = None, + constraint_weight: float = 1.0, + constraint_params: dict[str, Any] | None = None, + progress: bool = True, + ) -> "ModelDiffraction": + + if self.model is None or self.ctx is None or self.target_mean is None: + raise RuntimeError("Call .define_model(...) first.") + if reset not in ("initialized", "mean_refined"): + raise ValueError("reset must be initialized', or 'mean_refined'.") + self.reset(reset_to=cast(Literal["initialized", "mean_refined"], reset)) + if not isinstance(self.dataset, Dataset4d): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem for fit_individual_diffraction_pattern") + + scan_r = self.dataset.shape[0] + scan_c = self.dataset.shape[1] + + if rows is None and cols is None: + rows = range(self.dataset.shape[0]) + cols = range(self.dataset.shape[1]) + elif rows is not None and cols is None: + cols = range(self.dataset.shape[1]) + elif rows is None and cols is not None: + rows = range(self.dataset.shape[0]) + else: + rows = rows + cols = cols + + if isinstance(rows, int): + rows = [rows] + if isinstance(cols, int): + cols = [cols] + + self.state_individual_refined = np.full(shape=(scan_r, scan_c), fill_value=None, dtype=object) + + if progress: + pbar = tqdm(total=len(rows) * len(cols), desc="Fit individual") + + for r in rows: + for c in cols: + # print(self.dataset.array[r,c].shape) + self.fit_render( + target=torch.as_tensor(self.dataset.array[r,c],device=self.ctx.device,dtype=self.ctx.dtype), + n_steps=int(n_steps), + constraint_weight=float(constraint_weight), + constraint_params=constraint_params, + optimizer_params=optimizer_params, + scheduler_params=scheduler_params, + progress=False, + run_key=f"individual_{r}_{c}", + ) + + s_fit = self._get_model_state_dict_copy() + self.state_individual_refined[r,c] = self._clone_state_dict(s_fit) + self.reset(reset_to=cast(Literal["initialized", "mean_refined"], reset), reset_history=False) + + if progress: + pbar.update(1) + if progress: + pbar.close() + self.individual_refined=True + return self + + def get_individual_uv_vectors(self) ->tuple[np.ndarray, np.ndarray]: + scan_r = self.dataset.shape[0] + scan_c = self.dataset.shape[1] + # print(scan_r) + + u_array = np.empty(shape=(scan_r, scan_c, 2)) + v_array = np.empty(shape=(scan_r, scan_c, 2)) + if self.state_individual_refined is None: + raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") + for r in range(scan_r): + for c in range(scan_c): + pos_state = self.state_individual_refined[r,c] + if pos_state is None: + u_array[r,c,:] = None + v_array[r,c,:] = None + for key in pos_state.keys(): + key_parts = key.split('.') + if(key_parts[-1] == 'u_row'): + u_array[r,c,0] = pos_state[key] + if(key_parts[-1] == 'u_col'): + u_array[r,c,1] = pos_state[key] + if(key_parts[-1] == 'v_row'): + v_array[r,c,0] = pos_state[key] + if(key_parts[-1] == 'v_col'): + v_array[r,c,1] = pos_state[key] + + return u_array, v_array + + def render_indivdual_pattern(self, row, col): + if self.state_individual_refined is None: + raise RuntimeError( + "individual_refined_state is unavalible. Run fit_individual_diffraction_pattern(...) first." + ) + if self.dataset.shape[0] <= row or self.dataset.shape[1] <= col: + raise ValueError("individual row or column outside bounds of dataset") + if self.state_individual_refined[row, col] is None: + raise RuntimeError( + "individual_refined_state is not avalible for given row and column. Run fit_individual_diffraction_pattern(...) for that row and column." + ) + return self._render_state_array(self.state_individual_refined[row, col]) @property def render_mean_refined(self) -> np.ndarray: diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index 6dfb4f629..8c8978339 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -73,7 +73,12 @@ def _plot_overlays( ax.plot(disk_centers_rc[:, 1], disk_centers_rc[:, 0], **kw_disks) def plot_losses( - self, figax: tuple[Any, Any] | None = None, plot_lrs: bool = True + self, + figax: tuple[Any, Any] | None = None, + plot_lrs: bool = True, + plot_individual: bool = False, + individual_row: int = 0, + individual_col: int = 0, ) -> tuple[Any, Any]: md = cast("ModelDiffraction", self) colors = config.get("viz.colors.set") @@ -84,9 +89,12 @@ def plot_losses( fig, ax = plt.subplots() else: fig, ax = figax - - mean_hist = md.fit_history.get("mean") - losses = np.asarray([] if mean_hist is None else mean_hist.losses, dtype=np.float64) + if plot_individual: + mean_hist = md.fit_history.get(f"individual_{individual_row}_{individual_col}") + losses = np.asarray([] if mean_hist is None else mean_hist.losses, dtype=np.float64) + else: + mean_hist = md.fit_history.get("mean") + losses = np.asarray([] if mean_hist is None else mean_hist.losses, dtype=np.float64) if losses.size == 0: ax.text( 0.5, @@ -141,6 +149,9 @@ def plot_losses( def visualize( self, *, + plot_individual: bool = False, + individual_row: int = 0, + individual_col: int = 0, power: float = 0.25, cbar: bool = False, axsize: tuple[int, int] = (6, 6), @@ -193,14 +204,21 @@ def visualize( md.preprocess() if md.image_ref is None or md.model is None or md.ctx is None: raise RuntimeError("Call .define_model(...) first.") + if md.dataset.shape[0] <= individual_row or md.dataset.shape[1] <= individual_col: + raise ValueError("individual row or column outside bounds of dataset") fig = plt.figure(figsize=(12, 7)) gs = gridspec.GridSpec(2, 1, height_ratios=[1, 2], hspace=0.3) ax_top = fig.add_subplot(gs[0]) - md.plot_losses(figax=(fig, ax_top), plot_lrs=True) + md.plot_losses(figax=(fig, ax_top), plot_lrs=True, plot_individual=plot_individual,individual_row=individual_row,individual_col=individual_col) - ref = np.asarray(md.image_ref, dtype=np.float32) - pred = md.render_current + if plot_individual: + ref = np.asarray(md.dataset.array[individual_row, individual_col], dtype=np.float32) + pred = md.render_indivdual_pattern(row=individual_row, col = individual_col) + else: + ref = np.asarray(md.image_ref, dtype=np.float32) + pred = md.render_current + refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) vmin = float(min(refp.min(), predp.min())) diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index a084b32dd..105d1d98e 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -1170,4 +1170,4 @@ def _refine_one(vec: NDArray) -> NDArray: return np.array((r_fit, c_fit, amp, sig, bg), dtype=float) - return _refine_one(u_rc), _refine_one(v_rc) + return _refine_one(u_rc), _refine_one(v_rc) \ No newline at end of file From b22d4cd4ed5d9c915fb7e8ef07bb485650867fa5 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Mon, 9 Mar 2026 18:29:00 -0700 Subject: [PATCH 045/113] adding normalization constraint, updating individual fitting to fit intensity per disk --- src/quantem/core/fitting/base.py | 3 ++- src/quantem/core/fitting/diffraction.py | 11 ++++++++++- src/quantem/diffraction/model_fitting.py | 12 ++++++------ .../diffraction/model_fitting_visualizations.py | 10 ++++++---- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index b21e1126b..d1df4d038 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -352,7 +352,8 @@ class FitBase(OptimizerMixin): def __init__(self): super().__init__() # Core wiring - self.loss_fn = torch.nn.MSELoss(reduction="mean") + self.loss_fn = torch.nn.L1Loss(reduction="mean") + # self.loss_fn = torch.nn.MSELoss(reduction="mean") self.model: AdditiveRenderModel | None = None self.ctx: RenderContext | None = None diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 30934bf54..bdd28f362 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -52,6 +52,7 @@ class DiskTemplate(RenderComponent): DEFAULT_HARD_CONSTRAINTS: dict[str, bool] = { "force_center": False, "force_positive": True, + "force_norm": True, # force range [0,1] } DEFAULT_SOFT_CONSTRAINTS: dict[str, float] = {"tv_weight": 0.0} @@ -238,12 +239,20 @@ def _enforce_positivity(self) -> None: with torch.no_grad(): self.template_raw.clamp_(min=0.0) self.intensity_raw.clamp_(min=0.0) + + def _enforce_norm(self) -> None: + with torch.no_grad(): + self.template_raw -= self.template_raw.min() + self.template_raw /= self.template_raw.max() def enforce_hard_constraints(self, ctx: RenderContext) -> None: if bool(self.hard_constraints.get("force_center", False)): self._center_disk() if bool(self.hard_constraints.get("force_positive", False)): self._enforce_positivity() + if bool(self.hard_constraints.get("force_norm", False)): # could be put in positivity + self._enforce_norm() + super().enforce_hard_constraints(ctx) def constraint_loss( @@ -291,7 +300,7 @@ def __init__( intensity_row_col: float | Sequence[float] = 0.0, per_disk_intensity: bool = False, per_disk_slopes: bool = True, - max_intensity_order: int | None = None, + max_intensity_order: int | None = 0, default_pattern_intensity_order: int | None = None, center_intensity_0: float | Sequence[float] | None = None, exclude_indices: Iterable[tuple[int, int]] | None = None, diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 9008ebbd6..0a163c829 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -487,6 +487,7 @@ def fit_mean_diffraction_pattern( raise ValueError("reset must be False, True, 'initialized', or 'mean_refined'.") self.fit_render( + # target=torch.tensor(self.dataset[30,30].array.astype("float32")), target=self.target_mean, n_steps=int(n_steps), constraint_weight=float(constraint_weight), @@ -578,10 +579,8 @@ def fit_individual_diffraction_pattern( rows = rows cols = cols - if isinstance(rows, int): - rows = [rows] - if isinstance(cols, int): - cols = [cols] + rows = np.asarray(rows) + cols = np.asarray(cols) self.state_individual_refined = np.full(shape=(scan_r, scan_c), fill_value=None, dtype=object) @@ -591,6 +590,7 @@ def fit_individual_diffraction_pattern( for r in rows: for c in cols: # print(self.dataset.array[r,c].shape) + self.reset(reset_to=cast(Literal["initialized", "mean_refined"], reset), reset_history=False) self.fit_render( target=torch.as_tensor(self.dataset.array[r,c],device=self.ctx.device,dtype=self.ctx.dtype), n_steps=int(n_steps), @@ -604,12 +604,12 @@ def fit_individual_diffraction_pattern( s_fit = self._get_model_state_dict_copy() self.state_individual_refined[r,c] = self._clone_state_dict(s_fit) - self.reset(reset_to=cast(Literal["initialized", "mean_refined"], reset), reset_history=False) - + # self.reset(reset_to=cast(Literal["initialized", "mean_refined"], reset), reset_history=False) if progress: pbar.update(1) if progress: pbar.close() + self.individual_refined=True return self diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index 8c8978339..63238eaa0 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -278,7 +278,7 @@ def plot_mean_model( overlay_on: Literal["model", "both"] = "model", origin_marker_kwargs: dict[str, Any] | None = None, disk_marker_kwargs: dict[str, Any] | None = None, - **_: Any, + **kwargs, ) -> tuple[Any, Any] | None: """ Plot reference and model mean diffraction images. @@ -325,22 +325,24 @@ def plot_mean_model( raise RuntimeError("Call .define_model(...) first.") ref = np.asarray(md.image_ref, dtype=np.float32) - pred = md.render_current + pred = md.render_mean_refined refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) vmin = float(min(refp.min(), predp.min())) vmax = float(max(refp.max(), predp.max())) + t1 = kwargs.pop("title", "") fig, ax = show_2d( [refp, predp], - title=["image_ref", "model"], + title=[t1 + " image_ref", t1 + " model"], cmap=config.get("viz.cmap"), - cbar=False, + cbar=True, returnfig=True, axsize=axsize, vmin=vmin, vmax=vmax, + **kwargs, ) if overlay: if overlay_on not in ("model", "both"): From 451ca80e98fdea903a2afc12314d7d70f217bcce Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Thu, 26 Mar 2026 14:05:12 -0700 Subject: [PATCH 046/113] independant pattern fitting and inital strain calculation --- src/quantem/core/fitting/diffraction.py | 98 +++++++- src/quantem/diffraction/model_fitting.py | 224 ++++++++++++++++-- .../model_fitting_visualizations.py | 40 +++- 3 files changed, 331 insertions(+), 31 deletions(-) diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index bdd28f362..b736c9705 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -53,8 +53,22 @@ class DiskTemplate(RenderComponent): "force_center": False, "force_positive": True, "force_norm": True, # force range [0,1] + "force_shrinkage": False, + "force_cutoff": False, + "force_circular_mask": True, + } + DEFAULT_SOFT_CONSTRAINTS: dict[str, float] = { + "tv_weight": 0.0, + "cutoff_weight": 0.0, + "circular_weight": 0.0, + } + DEFAULT_CONSTRAINT_CONFIG: dict[str, float] = { + "soft_cutoff_threshold": 0.0, + "soft_cutoff_target_ratio": 0.1, + "hard_cutoff_threshold": 0.35, + "shrinkage_amount": 0.25, + "circular_mask_radius_fraction": 1, } - DEFAULT_SOFT_CONSTRAINTS: dict[str, float] = {"tv_weight": 0.0} def __init__( self, @@ -67,6 +81,7 @@ def __init__( origin_key: str = "origin", intensity: float | Sequence[float] = 1.0, constraint_params: dict[str, Any] | None = None, + constraint_config: dict[str, Any] | None = None, ): """ Build a disk template renderer centered at the shared origin. @@ -126,10 +141,17 @@ def __init__( cc = cc.astype(np.float32) - (wt - 1) * 0.5 self.register_buffer("dr", torch.as_tensor(rr.ravel(), dtype=torch.float32)) self.register_buffer("dc", torch.as_tensor(cc.ravel(), dtype=torch.float32)) + + self.constraint_config = self.DEFAULT_CONSTRAINT_CONFIG.copy() + if constraint_config is not None: + self.constraint_config.update(constraint_config) + if constraint_params is not None: self.apply_constraint_params(constraint_params, strict=True) if bool(self.hard_constraints.get("force_positive", False)): self._enforce_positivity() + if bool(self.hard_constraints.get("force_shrinkage", False)) and bool(self.hard_constraints.get("force_positive", True)): + raise RuntimeWarning("Setting shrinkage true and positivity false might cause negative values in disk template") @classmethod def from_array( @@ -143,6 +165,8 @@ def from_array( origin_key: str = "origin", intensity: float | Sequence[float] = 1.0, constraint_params: dict[str, Any] | None = None, + constraint_config: dict[str, Any] | None = None, + ) -> "DiskTemplate": return cls( name=name, @@ -153,6 +177,8 @@ def from_array( origin_key=origin_key, intensity=intensity, constraint_params=constraint_params, + constraint_config=constraint_config, + ) def set_origin(self, origin: OriginND) -> None: @@ -240,14 +266,43 @@ def _enforce_positivity(self) -> None: self.template_raw.clamp_(min=0.0) self.intensity_raw.clamp_(min=0.0) - def _enforce_norm(self) -> None: + def _enforce_norm(self) -> None: # pick value and cut off 5 percent of mean, or every iteration shrinkage, every iteration just subtract a valye of 0.01 with torch.no_grad(): self.template_raw -= self.template_raw.min() self.template_raw /= self.template_raw.max() + + def _enforce_shrinkage(self) -> None: + with torch.no_grad(): + self.template_raw -= self.constraint_config["shrinkage_amount"] + + def _enforce_cutoff(self) -> None: + with torch.no_grad(): + mean_val = torch.max(self.template_raw) * self.constraint_config["hard_cutoff_threshold"] + self.template_raw[self.template_raw <= mean_val] = 0 + + def _enforce_circular_mask(self) -> None: + with torch.no_grad(): + h, w = self.template_raw.shape + radius = (min(h, w) / 2.0) * self.constraint_config["circular_mask_radius_fraction"] + + r = torch.arange(-h/2, h/2, device=self.template_raw.device, dtype=self.template_raw.dtype) + c = torch.arange(-w/2, w/2, device=self.template_raw.device, dtype=self.template_raw.dtype) + rr, cc = torch.meshgrid(r, c, indexing='ij') + circle_matrix = torch.sqrt(rr**2 + cc**2) + + mask = circle_matrix <= radius + self.template_raw[~mask] = 0.0 + def enforce_hard_constraints(self, ctx: RenderContext) -> None: if bool(self.hard_constraints.get("force_center", False)): self._center_disk() + if bool(self.hard_constraints.get("force_cutoff", False)): + self._enforce_cutoff() + if bool(self.hard_constraints.get("force_circular_mask", False)): + self._enforce_circular_mask() + if bool(self.hard_constraints.get("force_shrinkage", False)): + self._enforce_shrinkage() if bool(self.hard_constraints.get("force_positive", False)): self._enforce_positivity() if bool(self.hard_constraints.get("force_norm", False)): # could be put in positivity @@ -260,8 +315,17 @@ def constraint_loss( ) -> torch.Tensor: cfg = self.effective_soft_constraints(cast(dict[str, object] | None, params)) tv_weight = float(cfg.get("tv_weight", 0.0)) + cutoff_weight = float(cfg.get("cutoff_weight", 0.0)) + circular_weight = float(cfg.get("circular_weight", 0.0)) + if tv_weight <= 0.0: - return torch.zeros((), device=ctx.device, dtype=ctx.dtype) + tv_weight = 0.0 + if cutoff_weight <= 0.0: + cutoff_weight = 0.0 + if circular_weight <= 0.0: + circular_weight = 0.0 + + # tv loss calculation template = self.template_raw.to(device=ctx.device, dtype=ctx.dtype) tv_r = ( torch.mean(torch.abs(template[1:, :] - template[:-1, :])) @@ -273,7 +337,33 @@ def constraint_loss( if template.shape[1] > 1 else torch.zeros((), device=ctx.device, dtype=ctx.dtype) ) - return torch.as_tensor(tv_weight, device=ctx.device, dtype=ctx.dtype) * (tv_r + tv_c) + tv_loss = torch.as_tensor(tv_weight, device=ctx.device, dtype=ctx.dtype) * (tv_r + tv_c) + + # Cutoff loss calculation + num_px = torch.prod(torch.tensor(template.shape)) + px_under_threshold = torch.sum(template <= torch.mean(template)*self.constraint_config["soft_cutoff_threshold"])/num_px + cutoff_loss = torch.as_tensor(cutoff_weight, device=ctx.device, dtype=ctx.dtype) * torch.maximum( + px_under_threshold-self.constraint_config["soft_cutoff_target_ratio"], torch.as_tensor(0.0, device=ctx.device, dtype=ctx.dtype)) + + # circular loss calculation + h, w = template.shape + radius = (min(h, w) / 2.0) * self.constraint_config["circular_mask_radius_fraction"] + + r = torch.arange(h, device=ctx.device, dtype=ctx.dtype) - h / 2.0 + c = torch.arange(w, device=ctx.device, dtype=ctx.dtype) - w / 2.0 + rr, cc = torch.meshgrid(r, c, indexing='ij') + + circle_mask = torch.sqrt(rr**2 + cc**2) + + dist_from_radius = torch.abs(circle_mask - radius) + dist_from_radius = torch.relu(dist_from_radius) + circular_err = torch.mean(dist_from_radius * template) + + circular_loss = torch.as_tensor(circular_weight, device=ctx.device, dtype=ctx.dtype) * circular_err + + return cutoff_loss + tv_loss + circular_loss + + class SyntheticDiskLattice(RenderComponent): diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 0a163c829..c2ae4e8e7 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -9,7 +9,7 @@ from tqdm import tqdm from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem -from quantem.core.fitting.base import ( +from quantem.core.fitting.basev2 import ( AdditiveRenderModel, FitBase, OriginND, @@ -579,13 +579,20 @@ def fit_individual_diffraction_pattern( rows = rows cols = cols - rows = np.asarray(rows) - cols = np.asarray(cols) + if isinstance(rows, int): + rows = np.array([rows]).astype(int) + else: + rows = np.asarray(rows).astype(int) + + if isinstance(cols, int): + cols = np.array([cols]).astype(int) + else: + cols = np.asarray(cols).astype(int) self.state_individual_refined = np.full(shape=(scan_r, scan_c), fill_value=None, dtype=object) if progress: - pbar = tqdm(total=len(rows) * len(cols), desc="Fit individual") + pbar = tqdm(total=rows.shape[0] * cols.shape[0], desc="Fit individual") for r in rows: for c in cols: @@ -613,33 +620,33 @@ def fit_individual_diffraction_pattern( self.individual_refined=True return self - def get_individual_uv_vectors(self) ->tuple[np.ndarray, np.ndarray]: + def get_individual_uv_vectors(self) -> "ModelDiffraction": scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] # print(scan_r) - u_array = np.empty(shape=(scan_r, scan_c, 2)) - v_array = np.empty(shape=(scan_r, scan_c, 2)) + self.u_array = np.empty(shape=(scan_r, scan_c, 2)) + self.v_array = np.empty(shape=(scan_r, scan_c, 2)) if self.state_individual_refined is None: raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") for r in range(scan_r): for c in range(scan_c): pos_state = self.state_individual_refined[r,c] if pos_state is None: - u_array[r,c,:] = None - v_array[r,c,:] = None + self.u_array[r,c,:] = None + self.v_array[r,c,:] = None for key in pos_state.keys(): key_parts = key.split('.') if(key_parts[-1] == 'u_row'): - u_array[r,c,0] = pos_state[key] + self.u_array[r,c,0] = pos_state[key] if(key_parts[-1] == 'u_col'): - u_array[r,c,1] = pos_state[key] + self.u_array[r,c,1] = pos_state[key] if(key_parts[-1] == 'v_row'): - v_array[r,c,0] = pos_state[key] + self.v_array[r,c,0] = pos_state[key] if(key_parts[-1] == 'v_col'): - v_array[r,c,1] = pos_state[key] + self.v_array[r,c,1] = pos_state[key] - return u_array, v_array + return self def render_indivdual_pattern(self, row, col): if self.state_individual_refined is None: @@ -648,12 +655,201 @@ def render_indivdual_pattern(self, row, col): ) if self.dataset.shape[0] <= row or self.dataset.shape[1] <= col: raise ValueError("individual row or column outside bounds of dataset") + if row < 0 or col < 0: + raise ValueError("individual row or column outside bounds of dataset") if self.state_individual_refined[row, col] is None: raise RuntimeError( "individual_refined_state is not avalible for given row and column. Run fit_individual_diffraction_pattern(...) for that row and column." ) return self._render_state_array(self.state_individual_refined[row, col]) + def fit_strain(self) -> "ModelDiffraction": + u_fit = self.u_array + v_fit = self.v_array + u_ref = np.median(u_fit.reshape(-1, 2), axis=0) + v_ref = np.median(v_fit.reshape(-1, 2), axis=0) + + scan_r = self.dataset.shape[0] + scan_c = self.dataset.shape[1] + + Uref = np.stack((u_ref, v_ref), axis=1).astype(float) + det = np.linalg.det(Uref) + if not np.isfinite(det) or abs(det) < 1e-12: + Uref_inv = np.linalg.pinv(Uref) + else: + Uref_inv = np.linalg.inv(Uref) + + strain_trans = np.zeros((scan_r, scan_c, 2, 2)) + + for r in range(scan_r): + for c in range(scan_c): + U = np.stack((u_fit[r, c, :], v_fit[r, c, :]), axis=1) + strain_trans[r, c, :, :] = U @ Uref_inv + + self.strain_raw_err = Dataset2d.from_array( + strain_trans[:, :, 0, 0] - 1, + name="strain err", + signal_units="fractional", + ) + self.strain_raw_ecc = Dataset2d.from_array( + strain_trans[:, :, 1, 1] - 1, + name="strain ecc", + signal_units="fractional", + ) + self.strain_raw_erc = Dataset2d.from_array( + strain_trans[:, :, 1, 0] * 0.5 + strain_trans[:, :, 0, 1] * 0.5, + name="strain erc", + signal_units="fractional", + ) + self.strain_rotation = Dataset2d.from_array( + strain_trans[:, :, 1, 0] * -0.5 + strain_trans[:, :, 0, 1] * 0.5, + name="strain rotation", + signal_units="fractional", + ) + + return self + + + def plot_strain( + self, + rotation_angle=20, + strain_range_percent=(-3.0, 3.0), + rotation_range_degrees=(-2.0, 2.0), + plot_rotation=True, + cmap_strain="RdBu_r", + cmap_rotation=None, + layout="horizontal", + figsize=(6, 6), + ): + import matplotlib.pyplot as plt + + if cmap_rotation is None: + cmap_rotation = cmap_strain + + angle = rotation_angle + c = np.cos(angle) + s = np.sin(angle) + + err = self.strain_raw_err.array + ecc = self.strain_raw_ecc.array + erc = self.strain_raw_erc.array + + euu = err * (c * c) + 2.0 * erc * (c * s) + ecc * (s * s) + evv = err * (s * s) - 2.0 * erc * (c * s) + ecc * (c * c) + euv = (ecc - err) * (c * s) + erc * (c * c - s * s) + + strain_euu = self.strain_raw_err.copy() + strain_evv = self.strain_raw_ecc.copy() + strain_euv = self.strain_raw_erc.copy() + strain_euu.array[...] = euu + strain_evv.array[...] = evv + strain_euv.array[...] = euv + + alpha = None + good = None + alpha_im = None + + if layout != "horizontal": + raise ValueError("layout must be 'horizontal'") + + ncols = 4 if plot_rotation else 3 + fig, ax = plt.subplots(1, ncols, figsize=figsize) + + cm_strain = plt.get_cmap(cmap_strain).copy() + cm_strain.set_bad(color="black") + cm_rot = plt.get_cmap(cmap_rotation).copy() + cm_rot.set_bad(color="black") + + euu_pct = strain_euu.array * 100 + evv_pct = strain_evv.array * 100 + euv_pct = strain_euv.array * 100 + rot_deg = np.rad2deg(self.strain_rotation.array) + + if good is not None and np.any(good): + euu_m = np.ma.array(euu_pct, mask=~good) + evv_m = np.ma.array(evv_pct, mask=~good) + euv_m = np.ma.array(euv_pct, mask=~good) + rot_m = np.ma.array(rot_deg, mask=~good) + else: + euu_m = euu_pct + evv_m = evv_pct + euv_m = euv_pct + rot_m = rot_deg + + title_fs = 16 + im0 = ax[0].imshow( + euu_m, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cm_strain, + alpha=alpha_im, + ) + ax[1].imshow( + evv_m, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cm_strain, + alpha=alpha_im, + ) + ax[2].imshow( + euv_m, + vmin=strain_range_percent[0], + vmax=strain_range_percent[1], + cmap=cm_strain, + alpha=alpha_im, + ) + + ax[0].set_title(r"$\epsilon_{uu}$", fontsize=title_fs) + ax[1].set_title(r"$\epsilon_{vv}$", fontsize=title_fs) + ax[2].set_title(r"$\epsilon_{uv}$", fontsize=title_fs) + + if plot_rotation: + im3 = ax[3].imshow( + rot_m, + vmin=rotation_range_degrees[0], + vmax=rotation_range_degrees[1], + cmap=cm_rot, + alpha=alpha_im, + ) + ax[3].set_title("Rotation", fontsize=title_fs) + + for a in ax: + a.set_xticks([]) + a.set_yticks([]) + a.set_facecolor("black") + + fig.subplots_adjust(left=0.02, right=0.98, top=0.90, bottom=0.16, wspace=0.03) + + b0 = ax[0].get_position() + b2 = ax[2].get_position() + left = b0.x0 + right = b2.x1 + width = right - left + + b3 = ax[3].get_position() if plot_rotation else None + + cb_height = 0.04 + cb_pad = 0.03 + y = b0.y0 - cb_pad - cb_height + + cax1 = fig.add_axes([left, y, width, cb_height]) + cbar1 = fig.colorbar(im0, cax=cax1, orientation="horizontal") + cbar1.set_label("Strain (%)", fontsize=title_fs) + cbar1.ax.tick_params(labelsize=12) + + if plot_rotation: + left_r = b3.x0 + width_r = b3.x1 - b3.x0 + cax2 = fig.add_axes([left_r, y, width_r, cb_height]) + cbar2 = fig.colorbar(im3, cax=cax2, orientation="horizontal") + cbar2.set_label("Rotation (deg)", fontsize=title_fs) + cbar2.ax.tick_params(labelsize=12) + + for a in ax: + a.set_aspect("equal") + + return fig, ax + @property def render_mean_refined(self) -> np.ndarray: if self.state_mean_refined is None: diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index 63238eaa0..542d55925 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -149,9 +149,9 @@ def plot_losses( def visualize( self, *, - plot_individual: bool = False, - individual_row: int = 0, - individual_col: int = 0, + individual_loss: bool = False, + pattern_row: int = 0, + pattern_col: int = 0, power: float = 0.25, cbar: bool = False, axsize: tuple[int, int] = (6, 6), @@ -204,17 +204,17 @@ def visualize( md.preprocess() if md.image_ref is None or md.model is None or md.ctx is None: raise RuntimeError("Call .define_model(...) first.") - if md.dataset.shape[0] <= individual_row or md.dataset.shape[1] <= individual_col: + if md.dataset.shape[0] <= pattern_row or md.dataset.shape[1] <= pattern_col: raise ValueError("individual row or column outside bounds of dataset") fig = plt.figure(figsize=(12, 7)) gs = gridspec.GridSpec(2, 1, height_ratios=[1, 2], hspace=0.3) ax_top = fig.add_subplot(gs[0]) - md.plot_losses(figax=(fig, ax_top), plot_lrs=True, plot_individual=plot_individual,individual_row=individual_row,individual_col=individual_col) + md.plot_losses(figax=(fig, ax_top), plot_lrs=True, plot_individual=individual_loss,individual_row=pattern_row,individual_col=pattern_col) - if plot_individual: - ref = np.asarray(md.dataset.array[individual_row, individual_col], dtype=np.float32) - pred = md.render_indivdual_pattern(row=individual_row, col = individual_col) + if individual_loss: + pred = md.render_indivdual_pattern(row=pattern_row, col=pattern_col) + ref = np.asarray(md.dataset.array[pattern_row, pattern_col], dtype=np.float32) else: ref = np.asarray(md.image_ref, dtype=np.float32) pred = md.render_current @@ -257,6 +257,8 @@ def visualize( ) mean_hist = md.fit_history.get("mean") + if individual_loss: + mean_hist = md.fit_history.get(f"individual_{pattern_row}_{pattern_row}") if mean_hist is not None and len(mean_hist.losses) > 0: fig.suptitle( f"Final loss: {mean_hist.losses[-1]:.3e} | Iters: {len(mean_hist.losses)}", @@ -266,9 +268,13 @@ def visualize( plt.show() return fig, axs - def plot_mean_model( + def plot_model( self, *, + plot_mean_model: bool = False, + plot_individual_model: bool = False, + pattern_row: int = 0, + pattern_col: int= 0, power: float = 0.25, returnfig: bool = False, axsize: tuple[int, int] = (6, 6), @@ -281,7 +287,7 @@ def plot_mean_model( **kwargs, ) -> tuple[Any, Any] | None: """ - Plot reference and model mean diffraction images. + Plot reference and indiviual diffraction images. Parameters ---------- @@ -323,9 +329,17 @@ def plot_mean_model( md.preprocess() if md.image_ref is None or md.model is None or md.ctx is None: raise RuntimeError("Call .define_model(...) first.") - - ref = np.asarray(md.image_ref, dtype=np.float32) - pred = md.render_mean_refined + if plot_mean_model and plot_individual_model: + raise RuntimeError("can only plot mean or plot individual, not both") + if plot_individual_model: + pred = md.render_indivdual_pattern(pattern_row, pattern_col) + ref = np.asarray(md.dataset.array[pattern_row, pattern_col], dtype=np.float32) + elif plot_mean_model: + ref = np.asarray(md.image_ref, dtype=np.float32) + pred = md.render_mean_refined + else: + ref = np.asarray(md.image_ref, dtype=np.float32) + pred = md.render_current refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) From 9aff08f5a536f6d88af69f6c483bd17547a638bf Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Thu, 26 Mar 2026 14:07:32 -0700 Subject: [PATCH 047/113] adding independant optimizers --- src/quantem/core/fitting/base.py | 530 +++++++++++++++++++++---------- 1 file changed, 363 insertions(+), 167 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index d1df4d038..270383f3d 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -90,16 +90,36 @@ def __init__(self, *, ndim: int, init: Sequence[float]): self.coords = nn.Parameter(torch.as_tensor(init, dtype=torch.float32).reshape(self.ndim)) -class RenderComponent(nn.Module): +class RenderComponent(OptimizerMixin,nn.Module): DEFAULT_HARD_CONSTRAINTS: dict[str, Any] = {} DEFAULT_SOFT_CONSTRAINTS: dict[str, Any] = {} + DEFAULT_OPTIMIZER: str = "adam" + DEFAULT_LR: float = 1e-2 + DEFAULT_SCHEDULER_TYPE: str = "none" + def __init__(self) -> None: - super().__init__() + nn.Module.__init__(self) + + self._optimizer = None + self._scheduler = None + self._optimizer_params = {} + self._scheduler_params = {} self.hard_constraints: dict[str, Any] = dict(self.DEFAULT_HARD_CONSTRAINTS) self.soft_constraints: dict[str, Any] = dict(self.DEFAULT_SOFT_CONSTRAINTS) self.parameter_bounds: dict[str, tuple[float | None, float | None]] = {} + optimizer_params = { + "type": self.DEFAULT_OPTIMIZER, + "lr": self.DEFAULT_LR + } + self.optimizer_params = optimizer_params + + scheduler_params = { + "type": self.DEFAULT_SCHEDULER_TYPE + } + self.scheduler_params = scheduler_params + @staticmethod def parse_bounded_init( value: float | int | Sequence[float | int | None], *, name: str @@ -270,8 +290,82 @@ def constraint_loss( ) -> torch.Tensor: return torch.zeros((), device=ctx.device, dtype=ctx.dtype) + def get_optimization_parameters(self) -> Any: # make copy in synthetic disklattice which only refs lattice not disk lattice params, reoeat for origin byt in dusk teplate + return [p for p in self.parameters() if p.requires_grad] + + def initialize_optimizer(self, + optimizer_params: dict[str, Any] | None = None, + scheduler_params: dict[str, Any] | None = None, + num_iter: int | None = None, + ) -> None: + trainable_params = list(self.get_optimization_parameters()) + if not trainable_params: + self._optimizer = None + self._scheduler = None + return + if optimizer_params is not None: + self.optimizer_params = optimizer_params + else: + self.optimizer_params = { + "type": self.DEFAULT_OPTIMIZER, + "lr": self.DEFAULT_LR + } + self.set_optimizer(self.optimizer_params) -class AdditiveRenderModel(nn.Module): + if scheduler_params is not None: + self.scheduler_params = scheduler_params + else: + self.scheduler_params = { + "type": self.DEFAULT_SCHEDULER_TYPE + } + self.set_scheduler(self.scheduler_params, num_iter = num_iter) + return + + + def _infer_optimizer_rebuild_params(self) -> dict[str, Any]: + if self.optimizer_params: + return dict(self.optimizer_params) + if self.optimizer is not None: + opt_type: str | type[torch.optim.Optimizer] + if isinstance(self.optimizer, torch.optim.AdamW): + opt_type = "adamw" + elif isinstance(self.optimizer, torch.optim.Adam): + opt_type = "adam" + elif isinstance(self.optimizer, torch.optim.SGD): + opt_type = "sgd" + else: + opt_type = type(self.optimizer) + lr = float( + self.optimizer.param_groups[0].get( + "lr", getattr(self, "DEFAULT_LR", self.DEFAULT_LR) + ) + ) + return {"type": opt_type, "lr": lr} + return { + "type": getattr(self, "DEFAULT_OPTIMIZER_TYPE", self.DEFAULT_OPTIMIZER), + "lr": float(getattr(self, "DEFAULT_LR", self.DEFAULT_LR)), + } + + def _infer_scheduler_rebuild_params(self) -> dict[str, Any]: + if self.scheduler_params: + return dict(self.scheduler_params) + return { + "type": self.DEFAULT_SCHEDULER_TYPE, + } + + def _rebuild_optimizer_after_trainability_change(self) -> None: + trainable_params = list(self.get_optimization_parameters()) + if not trainable_params: + self._optimizer = None + self._scheduler = None + return + rebuild_params = self._infer_optimizer_rebuild_params() + rebuild_params_scheduler = self._infer_scheduler_rebuild_params() + self.set_optimizer(rebuild_params) + self.set_scheduler(rebuild_params_scheduler) + + +class AdditiveRenderModel(nn.Module): # step all otpimzers def __init__(self, *, origin: nn.Module, components: list[RenderComponent]): super().__init__() self.origin = origin @@ -335,71 +429,125 @@ def total_constraint_loss(self, ctx: RenderContext) -> torch.Tensor: loss = loss + component.constraint_loss(ctx) return loss + def initilize_independant_optimizers(self, + individual_optimizers: dict[str, dict[str, Any]] | None = None, + individual_schedulers: dict[str, dict[str, Any]] | None = None, + num_iter: int | None = None, + ) -> None: + for idx, module in enumerate(self.components): + component = cast(RenderComponent, module) + component_name = self._component_constraint_name(component, idx) + + component_optimizer_params = None + component_scheduler_params = None + if individual_optimizers is not None: + if component_name in individual_optimizers: + component_optimizer_params = individual_optimizers[component_name] + elif component.__class__.__name__ in individual_optimizers : + component_optimizer_params = individual_optimizers[component.__class__.__name__] + + if individual_schedulers is not None: + if component_name in individual_schedulers: + component_scheduler_params = individual_schedulers[component_name] + elif component.__class__.__name__ in individual_schedulers: + component_scheduler_params = individual_schedulers[component.__class__.__name__] + + component.initialize_optimizer(component_optimizer_params, component_scheduler_params, num_iter=num_iter) + + def set_independant_optimizer_params(self, + individual_optimizer_params: dict[str, dict[str, Any]], + individual_scheduler_params: dict[str, dict[str, Any]] | None = None, + num_iter: int | None = None, + ) -> None: + for component_name, param in individual_optimizer_params.items(): + scheduler_params = None + if individual_scheduler_params is not None and component_name in individual_scheduler_params: + scheduler_params = individual_scheduler_params[component_name] + + component = self._resolve_component_by_name(str(component_name)) + component.initialize_optimizer(param,scheduler_params,num_iter) + + def rebuild_independant_optimizers(self) -> None: + for module in self.components: + component = cast(RenderComponent, module) + component.initialize_optimizer() -@dataclass -class FitResult: - losses: list[float] - lrs: list[float] - final_loss: float - num_steps: int - metrics: dict[str, list[float]] = field(default_factory=dict) - - -class FitBase(OptimizerMixin): - DEFAULT_LR = 1e-2 - DEFAULT_OPTIMIZER_TYPE = "adam" - - def __init__(self): - super().__init__() - # Core wiring - self.loss_fn = torch.nn.L1Loss(reduction="mean") - # self.loss_fn = torch.nn.MSELoss(reduction="mean") - self.model: AdditiveRenderModel | None = None - self.ctx: RenderContext | None = None - - # State/checkpoints - self.state_initialized: dict[str, torch.Tensor] | None = None - - # Histories/results - self.fit_history: dict[str, FitResult] = {} - - def get_optimization_parameters(self) -> Any: - if self.model is None: - return [] - return [p for p in self.model.parameters() if p.requires_grad] - - @property - def state_current(self) -> dict[str, torch.Tensor] | None: - if self.model is None: - return None - return self._get_model_state_dict_copy() + def step_optimizers(self) -> None: + for module in self.components: + component = cast(RenderComponent, module) + component.step_optimizer() + + def step_schedulers(self, loss: float | None = None) -> None: + for module in self.components: + component = cast(RenderComponent, module) + if hasattr(component, 'step_scheduler'): + try: + component.step_scheduler(loss) + except (AttributeError, TypeError): + pass + + def zero_grad_optimizers(self) -> None: + for module in self.components: + component = cast(RenderComponent, module) + component.zero_optimizer_grad() + + def _iter_named_components(self) -> list[tuple[str, RenderComponent]]: + """ + Return canonical component names paired with components. - @property - def render_initialized(self) -> np.ndarray: - if self.state_initialized is None: - raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") - return self._render_state_array(self.state_initialized) + Returns + ------- + list[tuple[str, RenderComponent]] + ``(name, component)`` entries using the model's canonical naming + rule. Names fall back to class-name/index behavior when ``.name`` is + missing. - @property - def render_current(self) -> np.ndarray: - if self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - return self.model(self.ctx).detach().cpu().numpy() + Raises + ------ + RuntimeError + If the model is not defined. + """ + entries: list[tuple[str, RenderComponent]] = [] + for idx, module in enumerate(self.components): + component = cast(RenderComponent, module) + name = self._component_constraint_name(component, idx) + entries.append((name, component)) + return entries + + def _resolve_component_by_name(self, component_name: str) -> RenderComponent: + target = str(component_name) + for resolved_name, component in self._iter_named_components(): + if resolved_name == target: + return component + + for resolved_name, component in self._iter_named_components(): + if component.__class__.__name__ == target: + return component + + known = ", ".join(self.get_component_names()) + raise KeyError(f"Component not found: {target}. Known components: {known}") + + def get_component_names(self) -> list[str]: + """ + Return canonical component names. - def reset( - self, - reset_to: Literal["initialized"] = "initialized", - ) -> Self: - if reset_to != "initialized": - raise ValueError("FitBase.reset only supports reset_to='initialized'.") - if self.state_initialized is None: - raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") - self._load_model_state_dict_copy(self.state_initialized) - self._clear_fit_history_all() - return self + Returns + ------- + list[str] + Canonical component names. + """ + return [name for name, _ in self._iter_named_components()] + + def get_component_by_name(self, component_name: str) -> RenderComponent: + return self._resolve_component_by_name(component_name) + def set_component_trainable( - self, component_name: str, enabled: bool, rebuild_optimizer: bool = True + self, + component_name: str, + enabled: bool, + rebuild_optimizer: bool = True, + num_iter: int | None = None, ) -> None: """ Enable or disable optimization for all parameters in one component. @@ -435,7 +583,7 @@ def set_component_trainable( for _, param in component.named_parameters(recurse=True): param.requires_grad_(bool(enabled)) if rebuild_optimizer: - self._rebuild_optimizer_after_trainability_change() + component._rebuild_optimizer_after_trainability_change() def set_parameter_trainable( self, @@ -443,6 +591,7 @@ def set_parameter_trainable( parameter_name: str, enabled: bool, rebuild_optimizer: bool = True, + num_iter: int | None = None, ) -> None: """ Enable or disable optimization for one component parameter. @@ -484,7 +633,7 @@ def set_parameter_trainable( ) params[parameter_name].requires_grad_(bool(enabled)) if rebuild_optimizer: - self._rebuild_optimizer_after_trainability_change() + component._rebuild_optimizer_after_trainability_change() def set_parameters_trainable( self, @@ -492,6 +641,7 @@ def set_parameters_trainable( parameter_names: list[str], enabled: bool, rebuild_optimizer: bool = True, + num_iter: int | None = None, ) -> None: """ Enable or disable optimization for multiple component parameters. @@ -530,7 +680,7 @@ def set_parameters_trainable( for name in parameter_names: params[name].requires_grad_(bool(enabled)) if rebuild_optimizer: - self._rebuild_optimizer_after_trainability_change() + component._rebuild_optimizer_after_trainability_change() def get_component_trainable(self, component_name: str) -> dict[str, bool]: """ @@ -556,6 +706,77 @@ def get_component_trainable(self, component_name: str) -> dict[str, bool]: component = self._resolve_component_by_name(component_name) return {name: bool(param.requires_grad) for name, param in component.named_parameters()} + + + + + + + +@dataclass +class FitResult: + losses: list[float] + lrs: list[float] + final_loss: float + num_steps: int + metrics: dict[str, list[float]] = field(default_factory=dict) + + +class FitBase(OptimizerMixin): + + def __init__(self): + super().__init__() + # Core wiring + self.loss_fn = torch.nn.L1Loss(reduction="mean") + # self.loss_fn = torch.nn.MSELoss(reduction="mean") + self.model: AdditiveRenderModel | None = None + self.ctx: RenderContext | None = None + + # State/checkpoints + self.state_initialized: dict[str, torch.Tensor] | None = None + + # Histories/results + self.fit_history: dict[str, FitResult] = {} + + # self.multi_optimizers: dict[str, torch.optim.Optimizer] | None = None + # self.use_mutiple_optimizers: bool = False + + def get_optimization_parameters(self) -> Any: + if self.model is None: + return [] + + return [p for p in self.model.parameters() if p.requires_grad] + + @property + def state_current(self) -> dict[str, torch.Tensor] | None: + if self.model is None: + return None + return self._get_model_state_dict_copy() + + @property + def render_initialized(self) -> np.ndarray: + if self.state_initialized is None: + raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") + return self._render_state_array(self.state_initialized) + + @property + def render_current(self) -> np.ndarray: + if self.model is None or self.ctx is None: + raise RuntimeError("Call .define_model(...) first.") + return self.model(self.ctx).detach().cpu().numpy() + + def reset( + self, + reset_to: Literal["initialized"] = "initialized", + ) -> Self: + if reset_to != "initialized": + raise ValueError("FitBase.reset only supports reset_to='initialized'.") + if self.state_initialized is None: + raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") + self._load_model_state_dict_copy(self.state_initialized) + self._clear_fit_history_all() + return self + def fit_render( self, *, @@ -563,8 +784,8 @@ def fit_render( n_steps: int, constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, - optimizer_params: dict | None = None, - scheduler_params: dict | None = None, + optimizer_params: dict[str, dict[str, Any]] | None = None, + scheduler_params: dict[str, dict[str, Any]] | None = None, progress: bool = False, run_key: str = "default", **kwargs: Any, @@ -613,49 +834,41 @@ def fit_render( if constraint_params is not None: self.model.apply_constraint_params(constraint_params, strict=True) - optimizer_rebuilt = False - if optimizer_params is not None: - self.set_optimizer(optimizer_params) - optimizer_rebuilt = True - elif self.optimizer is None: - if self.optimizer_params: - self.set_optimizer(self.optimizer_params) - else: - self.set_optimizer( - { - "type": getattr(self, "DEFAULT_OPTIMIZER_TYPE", "adamw"), - "lr": float(getattr(self, "DEFAULT_LR", self.DEFAULT_LR)), - } - ) - optimizer_rebuilt = True - n_steps = int(n_steps) - if scheduler_params is not None: - self.set_scheduler(scheduler_params, num_iter=n_steps) - elif self.scheduler is None and self.scheduler_params: - self.set_scheduler(self.scheduler_params, num_iter=n_steps) - elif optimizer_rebuilt and self.scheduler is not None and self.optimizer is not None: - self.scheduler.optimizer = self.optimizer + self.model.initilize_independant_optimizers( + optimizer_params, + scheduler_params, + num_iter=n_steps + ) pbar = tqdm(range(n_steps), desc="Fit render", disable=not progress) losses: list[float] = [] lrs: list[float] = [] for _ in pbar: - self.zero_optimizer_grad() + self.model.zero_grad_optimizers() pred = self._forward_for_fit(target=target, **kwargs) data_loss = self._fidelity_loss(pred, target, **kwargs) constraint_loss = self._constraint_loss(pred, target, **kwargs) total_loss = data_loss + constraint_weight * constraint_loss total_loss.backward() - self.step_optimizer() + self.model.step_optimizers() if self.model is None or self.ctx is None: raise RuntimeError("Model and context are not defined for fitting.") self.model.apply_hard_constraints(self.ctx) total_loss_value = float(total_loss.detach().cpu()) - self.step_scheduler(total_loss_value) + self.model.step_schedulers(total_loss_value) losses.append(total_loss_value) - lrs.append(float(self.get_current_lr())) + + first_lr = 0.0 + if len(self.model.components) > 0: + first_comp = cast(RenderComponent, self.model.components[0]) + if hasattr(first_comp, 'get_current_lr'): + try: + first_lr = float(first_comp.get_current_lr()) + except (AttributeError, TypeError): + first_lr = 0.0 + lrs.append(first_lr) key = str(run_key) if key in self.fit_history: @@ -675,80 +888,6 @@ def fit_render( self.fit_history[key] = result return result - def _iter_named_components(self) -> list[tuple[str, RenderComponent]]: - """ - Return canonical component names paired with components. - - Returns - ------- - list[tuple[str, RenderComponent]] - ``(name, component)`` entries using the model's canonical naming - rule. Names fall back to class-name/index behavior when ``.name`` is - missing. - - Raises - ------ - RuntimeError - If the model is not defined. - """ - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - entries: list[tuple[str, RenderComponent]] = [] - for idx, module in enumerate(self.model.components): - component = cast(RenderComponent, module) - name = self.model._component_constraint_name(component, idx) - entries.append((name, component)) - return entries - - def get_component_names(self) -> list[str]: - """ - Return canonical component names. - - Returns - ------- - list[str] - Canonical component names. - """ - return [name for name, _ in self._iter_named_components()] - - def _resolve_component_by_name(self, component_name: str) -> RenderComponent: - target = str(component_name) - for resolved_name, component in self._iter_named_components(): - if resolved_name == target: - return component - known = ", ".join(self.get_component_names()) - raise KeyError(f"Component not found: {target}. Known components: {known}") - - def _infer_optimizer_rebuild_params(self) -> dict[str, Any]: - if self.optimizer_params: - return dict(self.optimizer_params) - if self.optimizer is not None: - opt_type: str | type[torch.optim.Optimizer] - if isinstance(self.optimizer, torch.optim.AdamW): - opt_type = "adamw" - elif isinstance(self.optimizer, torch.optim.Adam): - opt_type = "adam" - elif isinstance(self.optimizer, torch.optim.SGD): - opt_type = "sgd" - else: - opt_type = type(self.optimizer) - lr = float( - self.optimizer.param_groups[0].get( - "lr", getattr(self, "DEFAULT_LR", self.DEFAULT_LR) - ) - ) - return {"type": opt_type, "lr": lr} - return { - "type": getattr(self, "DEFAULT_OPTIMIZER_TYPE", self.DEFAULT_OPTIMIZER_TYPE), - "lr": float(getattr(self, "DEFAULT_LR", self.DEFAULT_LR)), - } - - def _rebuild_optimizer_after_trainability_change(self) -> None: - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - rebuild_params = self._infer_optimizer_rebuild_params() - self.set_optimizer(rebuild_params) - self.set_scheduler({"type": "none"}) def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: return {k: v.detach().clone() for k, v in state.items()} @@ -806,6 +945,63 @@ def _constraint_loss( raise RuntimeError("Model and context are not defined for fitting.") return self.model.total_constraint_loss(self.ctx) + def set_component_trainable( + self, + component_name: str, + enabled: bool, + rebuild_optimizer: bool = True, + num_iter: int | None = None + ) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + self.model.set_component_trainable(component_name, enabled, rebuild_optimizer, num_iter) + + def set_parameter_trainable( + self, + component_name: str, + parameter_name: str, + enabled: bool, + rebuild_optimizer: bool = True, + num_iter: int | None = None + ) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + self.model.set_parameter_trainable(component_name, parameter_name, enabled, rebuild_optimizer, num_iter) + + def set_parameters_trainable( + self, + component_name: str, + parameter_names: list[str], + enabled: bool, + rebuild_optimizer: bool = True, + num_iter: int | None = None + ) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + self.model.set_parameters_trainable(component_name, parameter_names, enabled, rebuild_optimizer, num_iter) + + def get_component_trainable(self, component_name: str) -> dict[str, bool]: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return self.model.get_component_trainable(component_name) + + def get_component_names(self) -> list[str]: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return self.model.get_component_names() + + def _iter_named_components(self) -> list[tuple[str, RenderComponent]]: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return self.model._iter_named_components() + + def _resolve_component_by_name(self, component_name: str) -> RenderComponent: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + return self.model._resolve_component_by_name(component_name) + + def get_component_by_name(self, component_name: str) -> RenderComponent: + return self._resolve_component_by_name(component_name) Component = RenderComponent ModelContext = RenderContext From 1f99fe513224c73df95d00399fccd3246f620af1 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Thu, 26 Mar 2026 15:13:32 -0700 Subject: [PATCH 048/113] updated strain plotting, fixed hard circular mask constraint, fixed bug for get_optimization_parameters() so it doesn't include subcomponents --- src/quantem/core/fitting/base.py | 8 +++- src/quantem/core/fitting/diffraction.py | 8 ++-- src/quantem/diffraction/model_fitting.py | 47 +++++++----------------- 3 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 270383f3d..68ee4b622 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -291,7 +291,7 @@ def constraint_loss( return torch.zeros((), device=ctx.device, dtype=ctx.dtype) def get_optimization_parameters(self) -> Any: # make copy in synthetic disklattice which only refs lattice not disk lattice params, reoeat for origin byt in dusk teplate - return [p for p in self.parameters() if p.requires_grad] + return [p for p in self.parameters(recurse=False) if p.requires_grad] def initialize_optimizer(self, optimizer_params: dict[str, Any] | None = None, @@ -1002,6 +1002,12 @@ def _resolve_component_by_name(self, component_name: str) -> RenderComponent: def get_component_by_name(self, component_name: str) -> RenderComponent: return self._resolve_component_by_name(component_name) + + def _rebuild_optimizer_after_trainability_change(self) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + self.model.rebuild_independant_optimizers() + Component = RenderComponent ModelContext = RenderContext diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index b736c9705..e8c9d3e89 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -67,7 +67,8 @@ class DiskTemplate(RenderComponent): "soft_cutoff_target_ratio": 0.1, "hard_cutoff_threshold": 0.35, "shrinkage_amount": 0.25, - "circular_mask_radius_fraction": 1, + "circular_mask_radius_fraction": 1, + "circular_mask_sharpness": 1.0, } def __init__( @@ -290,8 +291,9 @@ def _enforce_circular_mask(self) -> None: rr, cc = torch.meshgrid(r, c, indexing='ij') circle_matrix = torch.sqrt(rr**2 + cc**2) - mask = circle_matrix <= radius - self.template_raw[~mask] = 0.0 + # mask = circle_matrix <= radius + mask = torch.sigmoid(self.constraint_config["circular_mask_sharpness"]*(radius-circle_matrix)) + self.template_raw *= mask def enforce_hard_constraints(self, ctx: RenderContext) -> None: diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index c2ae4e8e7..45b6f5f8e 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -9,7 +9,7 @@ from tqdm import tqdm from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem -from quantem.core.fitting.basev2 import ( +from quantem.core.fitting.base import ( AdditiveRenderModel, FitBase, OriginND, @@ -673,18 +673,16 @@ def fit_strain(self) -> "ModelDiffraction": scan_c = self.dataset.shape[1] Uref = np.stack((u_ref, v_ref), axis=1).astype(float) - det = np.linalg.det(Uref) - if not np.isfinite(det) or abs(det) < 1e-12: - Uref_inv = np.linalg.pinv(Uref) - else: - Uref_inv = np.linalg.inv(Uref) - strain_trans = np.zeros((scan_r, scan_c, 2, 2)) - for r in range(scan_r): for c in range(scan_c): U = np.stack((u_fit[r, c, :], v_fit[r, c, :]), axis=1) - strain_trans[r, c, :, :] = U @ Uref_inv + det = np.linalg.det(U) + if not np.isfinite(det) or abs(det) < 1e-12: + U_inv = np.linalg.pinv(U) + else: + U_inv = np.linalg.inv(U) + strain_trans[r, c, :, :] = Uref @ U_inv self.strain_raw_err = Dataset2d.from_array( strain_trans[:, :, 0, 0] - 1, @@ -716,7 +714,7 @@ def plot_strain( strain_range_percent=(-3.0, 3.0), rotation_range_degrees=(-2.0, 2.0), plot_rotation=True, - cmap_strain="RdBu_r", + cmap_strain="RdBu", cmap_rotation=None, layout="horizontal", figsize=(6, 6), @@ -726,7 +724,7 @@ def plot_strain( if cmap_rotation is None: cmap_rotation = cmap_strain - angle = rotation_angle + angle = np.deg2rad(rotation_angle) c = np.cos(angle) s = np.sin(angle) @@ -745,10 +743,6 @@ def plot_strain( strain_evv.array[...] = evv strain_euv.array[...] = euv - alpha = None - good = None - alpha_im = None - if layout != "horizontal": raise ValueError("layout must be 'horizontal'") @@ -765,38 +759,24 @@ def plot_strain( euv_pct = strain_euv.array * 100 rot_deg = np.rad2deg(self.strain_rotation.array) - if good is not None and np.any(good): - euu_m = np.ma.array(euu_pct, mask=~good) - evv_m = np.ma.array(evv_pct, mask=~good) - euv_m = np.ma.array(euv_pct, mask=~good) - rot_m = np.ma.array(rot_deg, mask=~good) - else: - euu_m = euu_pct - evv_m = evv_pct - euv_m = euv_pct - rot_m = rot_deg - title_fs = 16 im0 = ax[0].imshow( - euu_m, + euu_pct, vmin=strain_range_percent[0], vmax=strain_range_percent[1], cmap=cm_strain, - alpha=alpha_im, ) ax[1].imshow( - evv_m, + evv_pct, vmin=strain_range_percent[0], vmax=strain_range_percent[1], cmap=cm_strain, - alpha=alpha_im, ) ax[2].imshow( - euv_m, + euv_pct, vmin=strain_range_percent[0], vmax=strain_range_percent[1], cmap=cm_strain, - alpha=alpha_im, ) ax[0].set_title(r"$\epsilon_{uu}$", fontsize=title_fs) @@ -805,11 +785,10 @@ def plot_strain( if plot_rotation: im3 = ax[3].imshow( - rot_m, + rot_deg, vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1], cmap=cm_rot, - alpha=alpha_im, ) ax[3].set_title("Rotation", fontsize=title_fs) From f993261950f8425c2703328a85ca032dcaa79072 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Fri, 27 Mar 2026 15:59:05 -0700 Subject: [PATCH 049/113] editing strain code for better cmaps, changing diffraction and base for allowing user setting of constraint parameters, fixing bug in get_parameters so it doesn't include submodules --- src/quantem/core/fitting/base.py | 45 ++++++++++++++++++++++-- src/quantem/core/fitting/diffraction.py | 25 ++++++++++--- src/quantem/diffraction/model_fitting.py | 6 ++-- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 68ee4b622..1c7d3b64b 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -93,6 +93,7 @@ def __init__(self, *, ndim: int, init: Sequence[float]): class RenderComponent(OptimizerMixin,nn.Module): DEFAULT_HARD_CONSTRAINTS: dict[str, Any] = {} DEFAULT_SOFT_CONSTRAINTS: dict[str, Any] = {} + DEFAULT_CONSTRAINT_CONFIG: dict[str, Any] = {} DEFAULT_OPTIMIZER: str = "adam" DEFAULT_LR: float = 1e-2 @@ -107,6 +108,7 @@ def __init__(self) -> None: self._scheduler_params = {} self.hard_constraints: dict[str, Any] = dict(self.DEFAULT_HARD_CONSTRAINTS) self.soft_constraints: dict[str, Any] = dict(self.DEFAULT_SOFT_CONSTRAINTS) + self.constraint_config: dict[str, Any] = dict(self.DEFAULT_CONSTRAINT_CONFIG) self.parameter_bounds: dict[str, tuple[float | None, float | None]] = {} optimizer_params = { @@ -290,8 +292,8 @@ def constraint_loss( ) -> torch.Tensor: return torch.zeros((), device=ctx.device, dtype=ctx.dtype) - def get_optimization_parameters(self) -> Any: # make copy in synthetic disklattice which only refs lattice not disk lattice params, reoeat for origin byt in dusk teplate - return [p for p in self.parameters(recurse=False) if p.requires_grad] + def get_optimization_parameters(self) -> Any: + return [p for p in self.parameters() if p.requires_grad] def initialize_optimizer(self, optimizer_params: dict[str, Any] | None = None, @@ -363,6 +365,28 @@ def _rebuild_optimizer_after_trainability_change(self) -> None: rebuild_params_scheduler = self._infer_scheduler_rebuild_params() self.set_optimizer(rebuild_params) self.set_scheduler(rebuild_params_scheduler) + + def initialize_constraint_config(self, config: dict[str, Any], strict: bool = True) -> None: + if not hasattr(self, 'constraint_config'): + if strict: + raise AttributeError( + f"{self.__class__.__name__} does not have constraint_config attribute" + ) + return + if not isinstance(config, dict): + raise TypeError("constraint config must be a dict.") + + unknown: dict[str, Any] = {} + for k, v in config.items(): + if k in self.DEFAULT_CONSTRAINT_CONFIG: + self.constraint_config[k] = v + else: + unknown[k] = v + + if unknown and strict: + keys = ", ".join(str(k) for k in unknown.keys()) + raise KeyError(f"Unknown constraint keys for {self.__class__.__name__}: {keys}") + return class AdditiveRenderModel(nn.Module): # step all otpimzers @@ -705,6 +729,13 @@ def get_component_trainable(self, component_name: str) -> dict[str, bool]: """ component = self._resolve_component_by_name(component_name) return {name: bool(param.requires_grad) for name, param in component.named_parameters()} + + def apply_constraint_configs( + self, constraint_configs: dict[str, Any], strict: bool = True + ) -> None: + for component_name, param in constraint_configs.items(): + component = self._resolve_component_by_name(str(component_name)) + component.initialize_constraint_config(param, strict=strict) @@ -784,6 +815,7 @@ def fit_render( n_steps: int, constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, + constraint_config_params: dict[str, Any] | None = None, optimizer_params: dict[str, dict[str, Any]] | None = None, scheduler_params: dict[str, dict[str, Any]] | None = None, progress: bool = False, @@ -833,6 +865,8 @@ def fit_render( raise RuntimeError("Model and context are not defined for fitting.") if constraint_params is not None: self.model.apply_constraint_params(constraint_params, strict=True) + if constraint_config_params is not None: + self.model.apply_constraint_configs(constraint_config_params, strict=True) n_steps = int(n_steps) self.model.initilize_independant_optimizers( @@ -1007,6 +1041,13 @@ def _rebuild_optimizer_after_trainability_change(self) -> None: if self.model is None: raise RuntimeError("Call .define_model(...) first.") self.model.rebuild_independant_optimizers() + + def apply_constraint_config_params( + self, constraint_configs: dict[str, Any], strict: bool = True + ) -> None: + if self.model is None: + raise RuntimeError("Call .define_model(...) first.") + self.model.apply_constraint_configs(constraint_configs, strict=strict) Component = RenderComponent diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index e8c9d3e89..78a151049 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -67,8 +67,9 @@ class DiskTemplate(RenderComponent): "soft_cutoff_target_ratio": 0.1, "hard_cutoff_threshold": 0.35, "shrinkage_amount": 0.25, - "circular_mask_radius_fraction": 1, - "circular_mask_sharpness": 1.0, + "circular_mask_radius_fraction": 0.95, + "circular_mask_sharpness": 0, + "soft_circular_mask": False, } def __init__( @@ -291,8 +292,10 @@ def _enforce_circular_mask(self) -> None: rr, cc = torch.meshgrid(r, c, indexing='ij') circle_matrix = torch.sqrt(rr**2 + cc**2) - # mask = circle_matrix <= radius - mask = torch.sigmoid(self.constraint_config["circular_mask_sharpness"]*(radius-circle_matrix)) + if self.constraint_config["soft_circular_mask"]: + mask = torch.sigmoid(self.constraint_config["circular_mask_sharpness"]*(radius-circle_matrix)) + else: + mask = circle_matrix <= radius self.template_raw *= mask @@ -364,6 +367,13 @@ def constraint_loss( circular_loss = torch.as_tensor(circular_weight, device=ctx.device, dtype=ctx.dtype) * circular_err return cutoff_loss + tv_loss + circular_loss + + def get_optimization_parameters(self) -> Any: + params = [] + for name, param in self.named_parameters(recurse=True): + if not name.startswith('origin.') and param.requires_grad: + params.append(param) + return params @@ -680,3 +690,10 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: self.disk.add_patch(out, r0=rr0, c0=cc0, scale=inten) return out + + def get_optimization_parameters(self) -> Any: + params = [] + for name, param in self.named_parameters(recurse=True): + if not name.startswith('disk.') and param.requires_grad: + params.append(param) + return params diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 45b6f5f8e..cdd8801ea 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -436,6 +436,7 @@ def fit_mean_diffraction_pattern( scheduler_params: dict | None = None, constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, + constraint_config_params: dict[str, Any] | None = None, progress: bool = True, ) -> "ModelDiffraction": """ @@ -492,6 +493,7 @@ def fit_mean_diffraction_pattern( n_steps=int(n_steps), constraint_weight=float(constraint_weight), constraint_params=constraint_params, + constraint_config_params=constraint_config_params, optimizer_params=optimizer_params, scheduler_params=scheduler_params, progress=bool(progress), @@ -714,8 +716,8 @@ def plot_strain( strain_range_percent=(-3.0, 3.0), rotation_range_degrees=(-2.0, 2.0), plot_rotation=True, - cmap_strain="RdBu", - cmap_rotation=None, + cmap_strain="RdBu_r", + cmap_rotation="PiYG", layout="horizontal", figsize=(6, 6), ): From 1b1c30eb4b3e5afcb2be8accda553993757ddc74 Mon Sep 17 00:00:00 2001 From: Miti Date: Wed, 15 Apr 2026 19:33:46 -0700 Subject: [PATCH 050/113] updated multiple optimizer --- src/quantem/core/fitting/base.py | 4 ++-- src/quantem/diffraction/model_fitting.py | 26 +++++++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 1c7d3b64b..f9c87b588 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -758,8 +758,8 @@ class FitBase(OptimizerMixin): def __init__(self): super().__init__() # Core wiring - self.loss_fn = torch.nn.L1Loss(reduction="mean") - # self.loss_fn = torch.nn.MSELoss(reduction="mean") + # self.loss_fn = torch.nn.L1Loss(reduction="mean") + self.loss_fn = torch.nn.MSELoss(reduction="mean") self.model: AdditiveRenderModel | None = None self.ctx: RenderContext | None = None diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index cdd8801ea..43090ef23 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -665,11 +665,31 @@ def render_indivdual_pattern(self, row, col): ) return self._render_state_array(self.state_individual_refined[row, col]) - def fit_strain(self) -> "ModelDiffraction": + def fit_strain(self, + mask_reference=None, + ) -> "ModelDiffraction": u_fit = self.u_array v_fit = self.v_array - u_ref = np.median(u_fit.reshape(-1, 2), axis=0) - v_ref = np.median(v_fit.reshape(-1, 2), axis=0) + + if mask_reference is None: + u_ref = np.median(u_fit.reshape(-1, 2), axis=0) + v_ref = np.median(v_fit.reshape(-1, 2), axis=0) + else: + m = np.asarray(mask_reference, dtype=bool) + u_ref = np.array( + ( + np.median(u_fit[m, 0]), + np.median(u_fit[m, 1]), + ), + dtype=float, + ) + v_ref = np.array( + ( + np.median(v_fit[m, 0]), + np.median(v_fit[m, 1]), + ), + dtype=float, + ) scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] From dce5546ada936b4ba6bb59a899f5ea266366d73c Mon Sep 17 00:00:00 2001 From: Miti Date: Wed, 15 Apr 2026 20:37:52 -0700 Subject: [PATCH 051/113] new loss function --- src/quantem/core/fitting/base.py | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index f9c87b588..23d5f9943 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -752,6 +752,38 @@ class FitResult: num_steps: int metrics: dict[str, list[float]] = field(default_factory=dict) +class BCEMSELoss(nn.Module): + + def __init__( + self, + gamma: float = 1, + percentile: float = 0.95, + ): + super().__init__() + self.gamma = gamma + self.percentile = percentile + + def forward(self, pred, target): + mse_loss = torch.mean((pred - target) ** 2) + + threshold_pred = torch.quantile(pred, self.percentile) + threshold_target = torch.quantile(target, self.percentile) + + # Create binary masks + target_binary = (target > threshold_target).float() + pred_binary = (pred > threshold_pred).float() + + epsilon = 1e-7 + pred_binary_clamped = torch.clamp(pred_binary, epsilon, 1.0 - epsilon) + bce_loss = -torch.mean( + target_binary * torch.log(pred_binary_clamped) + + (1 - target_binary) * torch.log(1 - pred_binary_clamped) + ) + + # Combined loss + total_loss = mse_loss + self.gamma ** bce_loss + + return total_loss class FitBase(OptimizerMixin): @@ -760,6 +792,7 @@ def __init__(self): # Core wiring # self.loss_fn = torch.nn.L1Loss(reduction="mean") self.loss_fn = torch.nn.MSELoss(reduction="mean") + self.loss_fn = BCEMSELoss() self.model: AdditiveRenderModel | None = None self.ctx: RenderContext | None = None From 496f86c6393a8c716068b45b961b87a1d8ae7b18 Mon Sep 17 00:00:00 2001 From: Miti Date: Wed, 15 Apr 2026 20:38:51 -0700 Subject: [PATCH 052/113] cosine optimizer --- src/quantem/core/ml/optimizer_mixin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index e2d2e89a6..033a05386 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -62,6 +62,7 @@ def scheduler_params(self, params: dict): "gamma", "linear", "none", + "cosine", ]: raise ValueError( f"Unknown scheduler type: {params['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'none']" @@ -163,7 +164,7 @@ def set_scheduler( min_lr=params.get("min_lr", base_LR / 20), cooldown=params.get("cooldown", 50), ) - elif sched_type in ["exp", "gamma", "exponential"]: + elif sched_type in ["exp", "gamma", "exponential","cosine"]: if "gamma" in params: gamma = params["gamma"] elif num_iter is not None: @@ -179,6 +180,11 @@ def set_scheduler( end_factor=params.get("end_factor", 1.0), total_iters=params.get("total_iters", num_iter), ) + elif sched_type == "cosine": + self._scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + T_max=params.get("t_max", 10), + ) else: raise ValueError(f"Unknown scheduler type: {sched_type}") From 9820b6bc390dfe7fa02d3d6cc3a2193b306b5af3 Mon Sep 17 00:00:00 2001 From: Miti Date: Thu, 16 Apr 2026 12:41:54 -0700 Subject: [PATCH 053/113] changing chosen loss function --- src/quantem/core/fitting/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 23d5f9943..f6fe429c2 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -792,7 +792,7 @@ def __init__(self): # Core wiring # self.loss_fn = torch.nn.L1Loss(reduction="mean") self.loss_fn = torch.nn.MSELoss(reduction="mean") - self.loss_fn = BCEMSELoss() + # self.loss_fn = BCEMSELoss() self.model: AdditiveRenderModel | None = None self.ctx: RenderContext | None = None From 5c33eade42172b13a39999e3478bed72daa92a4a Mon Sep 17 00:00:00 2001 From: Miti Date: Fri, 17 Apr 2026 09:18:05 -0700 Subject: [PATCH 054/113] adding preprocess square root of image --- src/quantem/diffraction/model_fitting.py | 32 ++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 43090ef23..a4b65dc38 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -315,10 +315,37 @@ def preprocess( upsample_factor: int = 32, max_shift: float | None = None, shift_order: int = 1, + gamma: float = 0.5, + mode: str = "linear", ) -> "ModelDiffraction": arr = np.asarray(self.dataset.array) if arr.ndim < 2: raise ValueError("dataset.array must have at least 2 dimensions.") + mode_in = mode.strip().lower() + if mode_in in {"linear", "patterson", "paterson", "acf", "autocorrelation"}: + mode_norm = "linear" + elif mode_in in {"log", "cepstrum", "cepstral"}: + mode_norm = "log" + elif mode_in in {"gamma", "power", "sqrt"}: + mode_norm = "gamma" + else: + raise ValueError( + "mode must be 'linear', 'log', or 'gamma' (aliases: 'patterson'->'linear', 'cepstrum'/'cepstral'->'log')." + ) + + self.metadata["mode"] = mode_norm + if mode_norm == "gamma": + self.metadata["gamma"] = gamma + + if mode_norm == "linear": + arr = arr + elif mode_norm == "log": + arr = np.log1p(arr) + elif mode_norm == "gamma": + arr = np.power(np.clip(arr, 0.0, None), self.metadata["gamma"]) + else: + raise RuntimeError("Unreachable: normalized mode mapping failed.") + h, w = arr.shape[-2], arr.shape[-1] self.index_shape = tuple(arr.shape[:-2]) @@ -665,8 +692,9 @@ def render_indivdual_pattern(self, row, col): ) return self._render_state_array(self.state_individual_refined[row, col]) - def fit_strain(self, - mask_reference=None, + def fit_strain( + self, + mask_reference=None, ) -> "ModelDiffraction": u_fit = self.u_array v_fit = self.v_array From 5ffe5055f525495689802ef981a9c1690286940a Mon Sep 17 00:00:00 2001 From: Miti Date: Sun, 19 Apr 2026 00:16:39 -0700 Subject: [PATCH 055/113] updated preprocessing --- src/quantem/diffraction/model_fitting.py | 2 +- src/quantem/diffraction/model_fitting_visualizations.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index a4b65dc38..e874cdec6 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -345,7 +345,7 @@ def preprocess( arr = np.power(np.clip(arr, 0.0, None), self.metadata["gamma"]) else: raise RuntimeError("Unreachable: normalized mode mapping failed.") - + self.dataset.array = np.asarray(arr) h, w = arr.shape[-2], arr.shape[-1] self.index_shape = tuple(arr.shape[:-2]) diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index 542d55925..b8cac881d 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -152,7 +152,7 @@ def visualize( individual_loss: bool = False, pattern_row: int = 0, pattern_col: int = 0, - power: float = 0.25, + power: float = 1, cbar: bool = False, axsize: tuple[int, int] = (6, 6), overlay: bool = True, From b74fa939a8cd5a6fdfb0644247bd0d3fb8832f45 Mon Sep 17 00:00:00 2001 From: Miti Date: Thu, 23 Apr 2026 19:08:49 -0700 Subject: [PATCH 056/113] new loss functions --- src/quantem/core/fitting/base.py | 41 ++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index f6fe429c2..5e4eedef1 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -785,14 +785,36 @@ def forward(self, pred, target): return total_loss +class SqrtMSELoss(nn.Module): + def __init__( + self, + gamma: float = 0.25, + ): + super().__init__() + self.gamma = gamma + self.mse_fn = torch.nn.MSELoss(reduction="mean") + + def forward(self, pred, target): + return self.mse_fn(pred ** self.gamma, target ** self.gamma) + +class LogMSELoss(nn.Module): + def __init__( + self, + ): + super().__init__() + self.mse_fn = torch.nn.MSELoss(reduction="mean") + + def forward(self, pred, target): + return self.mse_fn(torch.log(1+pred), torch.log(1+target)) + class FitBase(OptimizerMixin): def __init__(self): super().__init__() # Core wiring # self.loss_fn = torch.nn.L1Loss(reduction="mean") - self.loss_fn = torch.nn.MSELoss(reduction="mean") - # self.loss_fn = BCEMSELoss() + # self.loss_fn = torch.nn.MSELoss(reduction="mean") + self.loss_fn = SqrtMSELoss() self.model: AdditiveRenderModel | None = None self.ctx: RenderContext | None = None @@ -828,6 +850,21 @@ def render_current(self) -> np.ndarray: if self.model is None or self.ctx is None: raise RuntimeError("Call .define_model(...) first.") return self.model(self.ctx).detach().cpu().numpy() + + def set_loss(self, loss_fn: str = "sqrtmse", gamma: float = 0.25): + mode_in = loss_fn.strip().lower() + if mode_in in {"mse"}: + self.loss_fn = torch.nn.MSELoss(reduction="mean") + elif mode_in in {"sqrtmse"}: + self.loss_fn = SqrtMSELoss(gamma = gamma) + elif mode_in in {"logmse"}: + self.loss_fn = LogMSELoss() + elif mode_in in {"l1"}: + self.loss_fn = torch.nn.L1Loss(reduction="mean") + else: + raise ValueError( + "loss function must be mse, sqrtmse, logmse or l1" + ) def reset( self, From 2991a1aad2da467d3dfcaef16b0c5927fc56a1dc Mon Sep 17 00:00:00 2001 From: Miti Date: Fri, 24 Apr 2026 14:47:27 -0700 Subject: [PATCH 057/113] new loss functions --- src/quantem/core/fitting/base.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 5e4eedef1..f9421519a 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -795,7 +795,15 @@ def __init__( self.mse_fn = torch.nn.MSELoss(reduction="mean") def forward(self, pred, target): - return self.mse_fn(pred ** self.gamma, target ** self.gamma) + eps = 1 + pred_modified = (pred-torch.min(pred)+eps)**self.gamma + # pred_modified = pred_modified / torch.linalg.norm(pred_modified) + + target_modified = (target-torch.min(target)+eps)**self.gamma + # target_modified = target_modified / torch.linalg.norm(target_modified) + + loss = self.mse_fn(pred_modified, target_modified) + return loss class LogMSELoss(nn.Module): def __init__( From 712617453fdaae2936bbe43ced638e174bedded4 Mon Sep 17 00:00:00 2001 From: Miti Date: Thu, 30 Apr 2026 15:48:46 -0700 Subject: [PATCH 058/113] fixed sloped intensity peak fitting --- src/quantem/core/fitting/diffraction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 78a151049..d603e8716 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -402,7 +402,7 @@ def __init__( intensity_row_col: float | Sequence[float] = 0.0, per_disk_intensity: bool = False, per_disk_slopes: bool = True, - max_intensity_order: int | None = 0, + max_intensity_order: int | None = None, default_pattern_intensity_order: int | None = None, center_intensity_0: float | Sequence[float] | None = None, exclude_indices: Iterable[tuple[int, int]] | None = None, From 173c3ce096b55ed96390093fa427805278486eda Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Mon, 4 May 2026 14:48:56 -0700 Subject: [PATCH 059/113] changing synthetic disk lattice plotting and lr save to speed up model training --- src/quantem/core/fitting/base.py | 10 +- src/quantem/core/fitting/diffraction.py | 150 ++++++++++++++++++------ 2 files changed, 121 insertions(+), 39 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index f9421519a..c1c0f9cf6 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -1,6 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, field +from types import NoneType from typing import Any, Literal, Self, Sequence, cast import numpy as np @@ -954,10 +955,11 @@ def fit_render( ) pbar = tqdm(range(n_steps), desc="Fit render", disable=not progress) + loss_vals = torch.empty(n_steps, device=self.ctx.device) losses: list[float] = [] lrs: list[float] = [] - for _ in pbar: + for step in pbar: self.model.zero_grad_optimizers() pred = self._forward_for_fit(target=target, **kwargs) data_loss = self._fidelity_loss(pred, target, **kwargs) @@ -968,9 +970,10 @@ def fit_render( if self.model is None or self.ctx is None: raise RuntimeError("Model and context are not defined for fitting.") self.model.apply_hard_constraints(self.ctx) - total_loss_value = float(total_loss.detach().cpu()) + total_loss_value = (total_loss.detach()) self.model.step_schedulers(total_loss_value) - losses.append(total_loss_value) + # losses.append(total_loss_value) + loss_vals[step] = total_loss_value first_lr = 0.0 if len(self.model.components) > 0: @@ -981,6 +984,7 @@ def fit_render( except (AttributeError, TypeError): first_lr = 0.0 lrs.append(first_lr) + losses = loss_vals.cpu().tolist() key = str(run_key) if key in self.fit_history: diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index d603e8716..92e8ce60c 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -624,6 +624,73 @@ def enforce_hard_constraints(self, ctx: RenderContext) -> None: self.i0_raw[idx].clamp_(max=float(hi)) super().enforce_hard_constraints(ctx) + # def forward(self, ctx: RenderContext) -> torch.Tensor: + # if self.origin is None: + # raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") + + # out = torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) + # uv_indices = cast(torch.Tensor, self.uv_indices) + # if torch.numel(uv_indices) == 0: + # return out + + # uv = torch.as_tensor(uv_indices, device=ctx.device) + # u = uv[:, 0].to(dtype=ctx.dtype) + # v = uv[:, 1].to(dtype=ctx.dtype) + # r0, c0 = self.origin.coords[0], self.origin.coords[1] + # centers_r = r0 + u * self.u_row + v * self.v_row + # centers_c = c0 + u * self.u_col + v * self.v_col + + # b = torch.as_tensor(self.boundary_px, device=ctx.device, dtype=ctx.dtype) + # keep = (centers_r >= b) & (centers_r <= (ctx.shape[0] - 1) - b) + # keep = keep & (centers_c >= b) & (centers_c <= (ctx.shape[1] - 1) - b) + # keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) + # if keep_idx.numel() == 0: + # return out + + # active_order = int( + # ctx.fields.get( + # "lattice_intensity_order_override", self.default_pattern_intensity_order + # ) + # ) + # active_order = max(0, min(active_order, self.max_intensity_order)) + + # dr, dc = self.disk.patch_offsets() + # dr = dr.to(device=ctx.device, dtype=ctx.dtype) + # dc = dc.to(device=ctx.device, dtype=ctx.dtype) + # dr2 = dr * dr + # dc2 = dc * dc + # drdc = dr * dc + + # for j in keep_idx: + # rr0 = centers_r[j] + # cc0 = centers_c[j] + + # if self.per_disk_intensity: + # inten = self.i0_raw[j] + # if active_order >= 1 and self.ir is not None and self.ic is not None: + # inten = inten + self.ir[j] * dr + self.ic[j] * dc + # if ( + # active_order >= 2 + # and self.irr is not None + # and self.icc is not None + # and self.irc is not None + # ): + # inten = inten + self.irr[j] * dr2 + self.icc[j] * dc2 + self.irc[j] * drdc + # else: + # inten = self.i0_raw + # if active_order >= 1: + # assert self.ir is not None and self.ic is not None + # inten = inten + self.ir * rr0 + self.ic * cc0 + # if active_order >= 2: + # assert self.irr is not None and self.icc is not None and self.irc is not None + # inten = ( + # inten + self.irr * rr0 * rr0 + self.icc * cc0 * cc0 + self.irc * rr0 * cc0 + # ) + + # self.disk.add_patch(out, r0=rr0, c0=cc0, scale=inten) + + # return out + def forward(self, ctx: RenderContext) -> torch.Tensor: if self.origin is None: raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") @@ -643,9 +710,13 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: b = torch.as_tensor(self.boundary_px, device=ctx.device, dtype=ctx.dtype) keep = (centers_r >= b) & (centers_r <= (ctx.shape[0] - 1) - b) keep = keep & (centers_c >= b) & (centers_c <= (ctx.shape[1] - 1) - b) - keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) - if keep_idx.numel() == 0: + if not torch.any(keep): return out + + centers_r = centers_r[keep] + centers_c = centers_c[keep] + keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) + num_disks = centers_r.shape[0] active_order = int( ctx.fields.get( @@ -654,41 +725,48 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: ) active_order = max(0, min(active_order, self.max_intensity_order)) - dr, dc = self.disk.patch_offsets() - dr = dr.to(device=ctx.device, dtype=ctx.dtype) - dc = dc.to(device=ctx.device, dtype=ctx.dtype) - dr2 = dr * dr - dc2 = dc * dc - drdc = dr * dc - - for j in keep_idx: - rr0 = centers_r[j] - cc0 = centers_c[j] - - if self.per_disk_intensity: - inten = self.i0_raw[j] - if active_order >= 1 and self.ir is not None and self.ic is not None: - inten = inten + self.ir[j] * dr + self.ic[j] * dc - if ( - active_order >= 2 - and self.irr is not None - and self.icc is not None - and self.irc is not None - ): - inten = inten + self.irr[j] * dr2 + self.icc[j] * dc2 + self.irc[j] * drdc - else: - inten = self.i0_raw - if active_order >= 1: - assert self.ir is not None and self.ic is not None - inten = inten + self.ir * rr0 + self.ic * cc0 - if active_order >= 2: - assert self.irr is not None and self.icc is not None and self.irc is not None - inten = ( - inten + self.irr * rr0 * rr0 + self.icc * cc0 * cc0 + self.irc * rr0 * cc0 - ) - - self.disk.add_patch(out, r0=rr0, c0=cc0, scale=inten) + dr = cast(torch.Tensor, self.disk.dr).to(device=ctx.device, dtype=ctx.dtype) + dc = cast(torch.Tensor, self.disk.dc).to(device=ctx.device, dtype=ctx.dtype) + patch_vals = self.disk.patch_values().to(device=ctx.device, dtype=ctx.dtype) + num_pixels = patch_vals.shape[0] + if self.per_disk_intensity: + i0 = self.i0_raw[keep_idx][:, None] + inten = i0.expand(-1, num_pixels) + + if active_order >= 1 and self.ir is not None: + ir = self.ir[keep_idx][:, None] + ic = self.ic[keep_idx][:, None] + inten = inten + ir * dr[None, :] + ic * dc[None, :] + + if active_order >= 2 and self.irr is not None: + irr = self.irr[keep_idx][:, None] + icc = self.icc[keep_idx][:, None] + irc = self.irc[keep_idx][:, None] + inten = inten + irr * (dr*dr)[None, :] + icc * (dc*dc)[None, :] + irc * (dr*dc)[None, :] + else: + inten = self.i0_raw if isinstance(self.i0_raw, torch.Tensor) else self.i0_raw + if active_order >= 1: + inten = inten + self.ir * centers_r + self.ic * centers_c + if active_order >= 2: + inten = inten + self.irr * centers_r**2 + self.icc * centers_c**2 + self.irc * centers_r * centers_c + + inten = inten[:, None].expand(-1, num_pixels) if inten.ndim == 1 else inten.expand(num_disks, num_pixels) + total_pixels = num_disks * num_pixels + r0_all = centers_r[:, None].expand(-1, num_pixels).reshape(total_pixels) + c0_all = centers_c[:, None].expand(-1, num_pixels).reshape(total_pixels) + dr_all = dr[None, :].expand(num_disks, -1).reshape(total_pixels) + dc_all = dc[None, :].expand(num_disks, -1).reshape(total_pixels) + vals_all = (patch_vals[None, :] * inten).reshape(total_pixels) + _splat_patch( + out, + r0=r0_all, + c0=c0_all, + patch_vals=vals_all, + dr=dr_all, + dc=dc_all, + scale=torch.ones_like(vals_all) + ) return out def get_optimization_parameters(self) -> Any: From 77e4715c93bc1e5d9cc4f7d276ed2b2353fa16dd Mon Sep 17 00:00:00 2001 From: Miti Date: Tue, 5 May 2026 13:53:25 -0700 Subject: [PATCH 060/113] updated multi optimizer to match upstream --- src/quantem/core/fitting/base.py | 250 ------------------------------- 1 file changed, 250 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 339262bff..8f1b3e5a3 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -1,20 +1,12 @@ from __future__ import annotations from dataclasses import dataclass, field -<<<<<<< HEAD from types import NoneType -======= ->>>>>>> upstream/fitting_models_clean from typing import Any, Literal, Self, Sequence, cast import numpy as np import torch from torch import nn -<<<<<<< HEAD -from tqdm.auto import tqdm - -from quantem.core.ml.optimizer_mixin import OptimizerMixin -======= from tqdm import tqdm from quantem.core.ml.optimizer_mixin import ( @@ -23,7 +15,6 @@ OptimizerType, SchedulerType, ) ->>>>>>> upstream/fitting_models_clean def parse_bounded_init( @@ -105,7 +96,6 @@ def __init__(self, *, ndim: int, init: Sequence[float]): self.coords = nn.Parameter(torch.as_tensor(init, dtype=torch.float32).reshape(self.ndim)) -<<<<<<< HEAD class RenderComponent(OptimizerMixin,nn.Module): DEFAULT_HARD_CONSTRAINTS: dict[str, Any] = {} DEFAULT_SOFT_CONSTRAINTS: dict[str, Any] = {} @@ -138,18 +128,6 @@ def __init__(self) -> None: } self.scheduler_params = scheduler_params -======= -class RenderComponent(nn.Module): - DEFAULT_HARD_CONSTRAINTS: dict[str, Any] = {} - DEFAULT_SOFT_CONSTRAINTS: dict[str, Any] = {} - - def __init__(self) -> None: - super().__init__() - self.hard_constraints: dict[str, Any] = dict(self.DEFAULT_HARD_CONSTRAINTS) - self.soft_constraints: dict[str, Any] = dict(self.DEFAULT_SOFT_CONSTRAINTS) - self.parameter_bounds: dict[str, tuple[float | None, float | None]] = {} - ->>>>>>> upstream/fitting_models_clean @staticmethod def parse_bounded_init( value: float | int | Sequence[float | int | None], *, name: str @@ -320,7 +298,6 @@ def constraint_loss( ) -> torch.Tensor: return torch.zeros((), device=ctx.device, dtype=ctx.dtype) -<<<<<<< HEAD def get_optimization_parameters(self) -> Any: return [p for p in self.parameters() if p.requires_grad] @@ -419,10 +396,6 @@ def initialize_constraint_config(self, config: dict[str, Any], strict: bool = Tr class AdditiveRenderModel(nn.Module): # step all otpimzers -======= - -class AdditiveRenderModel(nn.Module): ->>>>>>> upstream/fitting_models_clean def __init__(self, *, origin: nn.Module, components: list[RenderComponent]): super().__init__() self.origin = origin @@ -486,7 +459,6 @@ def total_constraint_loss(self, ctx: RenderContext) -> torch.Tensor: loss = loss + component.constraint_loss(ctx) return loss -<<<<<<< HEAD def initilize_independant_optimizers(self, individual_optimizers: dict[str, dict[str, Any]] | None = None, individual_schedulers: dict[str, dict[str, Any]] | None = None, @@ -606,72 +578,6 @@ def set_component_trainable( enabled: bool, rebuild_optimizer: bool = True, num_iter: int | None = None, -======= - -@dataclass -class FitResult: - losses: list[float] - lrs: list[float] - final_loss: float - num_steps: int - metrics: dict[str, list[float]] = field(default_factory=dict) - - -class FitBase(OptimizerMixin): - DEFAULT_LR = 1e-2 - DEFAULT_OPTIMIZER_TYPE = "adam" - - def __init__(self): - super().__init__() - # Core wiring - self.loss_fn = torch.nn.MSELoss(reduction="mean") - self.model: AdditiveRenderModel | None = None - self.ctx: RenderContext | None = None - - # State/checkpoints - self.state_initialized: dict[str, torch.Tensor] | None = None - - # Histories/results - self.fit_history: dict[str, FitResult] = {} - - def get_optimization_parameters(self) -> Any: - if self.model is None: - return [] - return [p for p in self.model.parameters() if p.requires_grad] - - @property - def state_current(self) -> dict[str, torch.Tensor] | None: - if self.model is None: - return None - return self._get_model_state_dict_copy() - - @property - def render_initialized(self) -> np.ndarray: - if self.state_initialized is None: - raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") - return self._render_state_array(self.state_initialized) - - @property - def render_current(self) -> np.ndarray: - if self.model is None or self.ctx is None: - raise RuntimeError("Call .define_model(...) first.") - return self.model(self.ctx).detach().cpu().numpy() - - def reset( - self, - reset_to: Literal["initialized"] = "initialized", - ) -> Self: - if reset_to != "initialized": - raise ValueError("FitBase.reset only supports reset_to='initialized'.") - if self.state_initialized is None: - raise RuntimeError("initialized state is unavailable. Call .define_model(...) first.") - self._load_model_state_dict_copy(self.state_initialized) - self._clear_fit_history_all() - return self - - def set_component_trainable( - self, component_name: str, enabled: bool, rebuild_optimizer: bool = True ->>>>>>> upstream/fitting_models_clean ) -> None: """ Enable or disable optimization for all parameters in one component. @@ -707,11 +613,7 @@ def set_component_trainable( for _, param in component.named_parameters(recurse=True): param.requires_grad_(bool(enabled)) if rebuild_optimizer: -<<<<<<< HEAD component._rebuild_optimizer_after_trainability_change() -======= - self._rebuild_optimizer_after_trainability_change() ->>>>>>> upstream/fitting_models_clean def set_parameter_trainable( self, @@ -719,10 +621,7 @@ def set_parameter_trainable( parameter_name: str, enabled: bool, rebuild_optimizer: bool = True, -<<<<<<< HEAD num_iter: int | None = None, -======= ->>>>>>> upstream/fitting_models_clean ) -> None: """ Enable or disable optimization for one component parameter. @@ -764,11 +663,7 @@ def set_parameter_trainable( ) params[parameter_name].requires_grad_(bool(enabled)) if rebuild_optimizer: -<<<<<<< HEAD component._rebuild_optimizer_after_trainability_change() -======= - self._rebuild_optimizer_after_trainability_change() ->>>>>>> upstream/fitting_models_clean def set_parameters_trainable( self, @@ -776,10 +671,7 @@ def set_parameters_trainable( parameter_names: list[str], enabled: bool, rebuild_optimizer: bool = True, -<<<<<<< HEAD num_iter: int | None = None, -======= ->>>>>>> upstream/fitting_models_clean ) -> None: """ Enable or disable optimization for multiple component parameters. @@ -818,11 +710,7 @@ def set_parameters_trainable( for name in parameter_names: params[name].requires_grad_(bool(enabled)) if rebuild_optimizer: -<<<<<<< HEAD component._rebuild_optimizer_after_trainability_change() -======= - self._rebuild_optimizer_after_trainability_change() ->>>>>>> upstream/fitting_models_clean def get_component_trainable(self, component_name: str) -> dict[str, bool]: """ @@ -847,7 +735,6 @@ def get_component_trainable(self, component_name: str) -> dict[str, bool]: """ component = self._resolve_component_by_name(component_name) return {name: bool(param.requires_grad) for name, param in component.named_parameters()} -<<<<<<< HEAD def apply_constraint_configs( self, constraint_configs: dict[str, Any], strict: bool = True @@ -1004,8 +891,6 @@ def reset( self._load_model_state_dict_copy(self.state_initialized) self._clear_fit_history_all() return self -======= ->>>>>>> upstream/fitting_models_clean def fit_render( self, @@ -1014,14 +899,9 @@ def fit_render( n_steps: int, constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, -<<<<<<< HEAD constraint_config_params: dict[str, Any] | None = None, optimizer_params: dict[str, dict[str, Any]] | None = None, scheduler_params: dict[str, dict[str, Any]] | None = None, -======= - optimizer_params: OptimizerType | dict | None = None, - scheduler_params: SchedulerType | dict | None = None, ->>>>>>> upstream/fitting_models_clean progress: bool = False, run_key: str = "default", **kwargs: Any, @@ -1069,7 +949,6 @@ def fit_render( raise RuntimeError("Model and context are not defined for fitting.") if constraint_params is not None: self.model.apply_constraint_params(constraint_params, strict=True) -<<<<<<< HEAD if constraint_config_params is not None: self.model.apply_constraint_configs(constraint_config_params, strict=True) @@ -1087,45 +966,11 @@ def fit_render( lrs: list[float] = [] for step in pbar: self.model.zero_grad_optimizers() -======= - - optimizer_rebuilt = False - if optimizer_params is not None: - self.set_optimizer(optimizer_params) - optimizer_rebuilt = True - elif self.optimizer is None: - if self.optimizer_params: - self.set_optimizer(self.optimizer_params) - else: - self.set_optimizer( - { - "type": getattr(self, "DEFAULT_OPTIMIZER_TYPE", "adamw"), - "lr": float(getattr(self, "DEFAULT_LR", self.DEFAULT_LR)), - } - ) - optimizer_rebuilt = True - - n_steps = int(n_steps) - if scheduler_params is not None: - self.set_scheduler(scheduler_params, num_iter=n_steps) - elif self.scheduler is None and self.scheduler_params: - self.set_scheduler(self.scheduler_params, num_iter=n_steps) - elif optimizer_rebuilt and self.scheduler is not None and self.optimizer is not None: - self.scheduler.optimizer = self.optimizer - - pbar = tqdm(range(n_steps), desc="Fit render", disable=not progress) - - losses: list[float] = [] - lrs: list[float] = [] - for _ in pbar: - self.zero_optimizer_grad() ->>>>>>> upstream/fitting_models_clean pred = self._forward_for_fit(target=target, **kwargs) data_loss = self._fidelity_loss(pred, target, **kwargs) constraint_loss = self._constraint_loss(pred, target, **kwargs) total_loss = data_loss + constraint_weight * constraint_loss total_loss.backward() -<<<<<<< HEAD self.model.step_optimizers() if self.model is None or self.ctx is None: raise RuntimeError("Model and context are not defined for fitting.") @@ -1145,16 +990,6 @@ def fit_render( first_lr = 0.0 lrs.append(first_lr) losses = loss_vals.cpu().tolist() -======= - self.step_optimizer() - if self.model is None or self.ctx is None: - raise RuntimeError("Model and context are not defined for fitting.") - self.model.apply_hard_constraints(self.ctx) - total_loss_value = float(total_loss.detach().cpu()) - self.step_scheduler(total_loss_value) - losses.append(total_loss_value) - lrs.append(float(self.get_current_lr())) ->>>>>>> upstream/fitting_models_clean key = str(run_key) if key in self.fit_history: @@ -1174,88 +1009,6 @@ def fit_render( self.fit_history[key] = result return result -<<<<<<< HEAD -======= - def _iter_named_components(self) -> list[tuple[str, RenderComponent]]: - """ - Return canonical component names paired with components. - - Returns - ------- - list[tuple[str, RenderComponent]] - ``(name, component)`` entries using the model's canonical naming - rule. Names fall back to class-name/index behavior when ``.name`` is - missing. - - Raises - ------ - RuntimeError - If the model is not defined. - """ - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - entries: list[tuple[str, RenderComponent]] = [] - for idx, module in enumerate(self.model.components): - component = cast(RenderComponent, module) - name = self.model._component_constraint_name(component, idx) - entries.append((name, component)) - return entries - - def get_component_names(self) -> list[str]: - """ - Return canonical component names. - - Returns - ------- - list[str] - Canonical component names. - """ - return [name for name, _ in self._iter_named_components()] - - def _resolve_component_by_name(self, component_name: str) -> RenderComponent: - target = str(component_name) - for resolved_name, component in self._iter_named_components(): - if resolved_name == target: - return component - known = ", ".join(self.get_component_names()) - raise KeyError(f"Component not found: {target}. Known components: {known}") - - def _infer_optimizer_rebuild_params(self) -> dict[str, Any]: - if self.optimizer_params: - op = self.optimizer_params - if isinstance(op, OptimizerParams.NoneOptimizer): - return {"type": "none"} - out: dict[str, Any] = dict(op.params()) - out["type"] = op._name - return out - if self.optimizer is not None: - opt_type: str | type[torch.optim.Optimizer] - if isinstance(self.optimizer, torch.optim.AdamW): - opt_type = "adamw" - elif isinstance(self.optimizer, torch.optim.Adam): - opt_type = "adam" - elif isinstance(self.optimizer, torch.optim.SGD): - opt_type = "sgd" - else: - opt_type = type(self.optimizer) - lr = float( - self.optimizer.param_groups[0].get( - "lr", getattr(self, "DEFAULT_LR", self.DEFAULT_LR) - ) - ) - return {"type": opt_type, "lr": lr} - return { - "type": getattr(self, "DEFAULT_OPTIMIZER_TYPE", self.DEFAULT_OPTIMIZER_TYPE), - "lr": float(getattr(self, "DEFAULT_LR", self.DEFAULT_LR)), - } - - def _rebuild_optimizer_after_trainability_change(self) -> None: - if self.model is None: - raise RuntimeError("Call .define_model(...) first.") - rebuild_params = self._infer_optimizer_rebuild_params() - self.set_optimizer(rebuild_params) - self.set_scheduler({"type": "none"}) ->>>>>>> upstream/fitting_models_clean def _clone_state_dict(self, state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: return {k: v.detach().clone() for k, v in state.items()} @@ -1313,7 +1066,6 @@ def _constraint_loss( raise RuntimeError("Model and context are not defined for fitting.") return self.model.total_constraint_loss(self.ctx) -<<<<<<< HEAD def set_component_trainable( self, component_name: str, @@ -1384,8 +1136,6 @@ def apply_constraint_config_params( raise RuntimeError("Call .define_model(...) first.") self.model.apply_constraint_configs(constraint_configs, strict=strict) -======= ->>>>>>> upstream/fitting_models_clean Component = RenderComponent ModelContext = RenderContext From 9db1d9cb0401ddd4bb6de5c8a64c248f42eda98e Mon Sep 17 00:00:00 2001 From: Miti Date: Tue, 5 May 2026 14:02:26 -0700 Subject: [PATCH 061/113] post-merge cleanup --- pyproject.toml | 5 -- src/quantem/core/fitting/diffraction.py | 85 ------------------------ src/quantem/diffraction/__init__.py | 3 - src/quantem/diffraction/model_fitting.py | 48 ------------- 4 files changed, 141 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 753f5c461..5ca6787b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,9 +82,4 @@ dev = [ "pre-commit>=4.2.0", "ruff>=0.11.5", "tomli>=2.2.1", -<<<<<<< HEAD - "hdf5plugin>=6.0.0", ] -======= -] ->>>>>>> upstream/fitting_models_clean diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 01eceb0d1..92e8ce60c 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -52,7 +52,6 @@ class DiskTemplate(RenderComponent): DEFAULT_HARD_CONSTRAINTS: dict[str, bool] = { "force_center": False, "force_positive": True, -<<<<<<< HEAD "force_norm": True, # force range [0,1] "force_shrinkage": False, "force_cutoff": False, @@ -72,10 +71,6 @@ class DiskTemplate(RenderComponent): "circular_mask_sharpness": 0, "soft_circular_mask": False, } -======= - } - DEFAULT_SOFT_CONSTRAINTS: dict[str, float] = {"tv_weight": 0.0} ->>>>>>> upstream/fitting_models_clean def __init__( self, @@ -88,10 +83,7 @@ def __init__( origin_key: str = "origin", intensity: float | Sequence[float] = 1.0, constraint_params: dict[str, Any] | None = None, -<<<<<<< HEAD constraint_config: dict[str, Any] | None = None, -======= ->>>>>>> upstream/fitting_models_clean ): """ Build a disk template renderer centered at the shared origin. @@ -151,23 +143,17 @@ def __init__( cc = cc.astype(np.float32) - (wt - 1) * 0.5 self.register_buffer("dr", torch.as_tensor(rr.ravel(), dtype=torch.float32)) self.register_buffer("dc", torch.as_tensor(cc.ravel(), dtype=torch.float32)) -<<<<<<< HEAD self.constraint_config = self.DEFAULT_CONSTRAINT_CONFIG.copy() if constraint_config is not None: self.constraint_config.update(constraint_config) -======= ->>>>>>> upstream/fitting_models_clean if constraint_params is not None: self.apply_constraint_params(constraint_params, strict=True) if bool(self.hard_constraints.get("force_positive", False)): self._enforce_positivity() -<<<<<<< HEAD if bool(self.hard_constraints.get("force_shrinkage", False)) and bool(self.hard_constraints.get("force_positive", True)): raise RuntimeWarning("Setting shrinkage true and positivity false might cause negative values in disk template") -======= ->>>>>>> upstream/fitting_models_clean @classmethod def from_array( @@ -181,11 +167,8 @@ def from_array( origin_key: str = "origin", intensity: float | Sequence[float] = 1.0, constraint_params: dict[str, Any] | None = None, -<<<<<<< HEAD constraint_config: dict[str, Any] | None = None, -======= ->>>>>>> upstream/fitting_models_clean ) -> "DiskTemplate": return cls( name=name, @@ -196,11 +179,8 @@ def from_array( origin_key=origin_key, intensity=intensity, constraint_params=constraint_params, -<<<<<<< HEAD constraint_config=constraint_config, -======= ->>>>>>> upstream/fitting_models_clean ) def set_origin(self, origin: OriginND) -> None: @@ -287,7 +267,6 @@ def _enforce_positivity(self) -> None: with torch.no_grad(): self.template_raw.clamp_(min=0.0) self.intensity_raw.clamp_(min=0.0) -<<<<<<< HEAD def _enforce_norm(self) -> None: # pick value and cut off 5 percent of mean, or every iteration shrinkage, every iteration just subtract a valye of 0.01 with torch.no_grad(): @@ -319,13 +298,10 @@ def _enforce_circular_mask(self) -> None: mask = circle_matrix <= radius self.template_raw *= mask -======= ->>>>>>> upstream/fitting_models_clean def enforce_hard_constraints(self, ctx: RenderContext) -> None: if bool(self.hard_constraints.get("force_center", False)): self._center_disk() -<<<<<<< HEAD if bool(self.hard_constraints.get("force_cutoff", False)): self._enforce_cutoff() if bool(self.hard_constraints.get("force_circular_mask", False)): @@ -337,10 +313,6 @@ def enforce_hard_constraints(self, ctx: RenderContext) -> None: if bool(self.hard_constraints.get("force_norm", False)): # could be put in positivity self._enforce_norm() -======= - if bool(self.hard_constraints.get("force_positive", False)): - self._enforce_positivity() ->>>>>>> upstream/fitting_models_clean super().enforce_hard_constraints(ctx) def constraint_loss( @@ -348,7 +320,6 @@ def constraint_loss( ) -> torch.Tensor: cfg = self.effective_soft_constraints(cast(dict[str, object] | None, params)) tv_weight = float(cfg.get("tv_weight", 0.0)) -<<<<<<< HEAD cutoff_weight = float(cfg.get("cutoff_weight", 0.0)) circular_weight = float(cfg.get("circular_weight", 0.0)) @@ -360,10 +331,6 @@ def constraint_loss( circular_weight = 0.0 # tv loss calculation -======= - if tv_weight <= 0.0: - return torch.zeros((), device=ctx.device, dtype=ctx.dtype) ->>>>>>> upstream/fitting_models_clean template = self.template_raw.to(device=ctx.device, dtype=ctx.dtype) tv_r = ( torch.mean(torch.abs(template[1:, :] - template[:-1, :])) @@ -375,7 +342,6 @@ def constraint_loss( if template.shape[1] > 1 else torch.zeros((), device=ctx.device, dtype=ctx.dtype) ) -<<<<<<< HEAD tv_loss = torch.as_tensor(tv_weight, device=ctx.device, dtype=ctx.dtype) * (tv_r + tv_c) # Cutoff loss calculation @@ -410,9 +376,6 @@ def get_optimization_parameters(self) -> Any: return params -======= - return torch.as_tensor(tv_weight, device=ctx.device, dtype=ctx.dtype) * (tv_r + tv_c) ->>>>>>> upstream/fitting_models_clean class SyntheticDiskLattice(RenderComponent): @@ -661,7 +624,6 @@ def enforce_hard_constraints(self, ctx: RenderContext) -> None: self.i0_raw[idx].clamp_(max=float(hi)) super().enforce_hard_constraints(ctx) -<<<<<<< HEAD # def forward(self, ctx: RenderContext) -> torch.Tensor: # if self.origin is None: # raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") @@ -729,8 +691,6 @@ def enforce_hard_constraints(self, ctx: RenderContext) -> None: # return out -======= ->>>>>>> upstream/fitting_models_clean def forward(self, ctx: RenderContext) -> torch.Tensor: if self.origin is None: raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") @@ -750,7 +710,6 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: b = torch.as_tensor(self.boundary_px, device=ctx.device, dtype=ctx.dtype) keep = (centers_r >= b) & (centers_r <= (ctx.shape[0] - 1) - b) keep = keep & (centers_c >= b) & (centers_c <= (ctx.shape[1] - 1) - b) -<<<<<<< HEAD if not torch.any(keep): return out @@ -758,11 +717,6 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: centers_c = centers_c[keep] keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) num_disks = centers_r.shape[0] -======= - keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) - if keep_idx.numel() == 0: - return out ->>>>>>> upstream/fitting_models_clean active_order = int( ctx.fields.get( @@ -771,7 +725,6 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: ) active_order = max(0, min(active_order, self.max_intensity_order)) -<<<<<<< HEAD dr = cast(torch.Tensor, self.disk.dr).to(device=ctx.device, dtype=ctx.dtype) dc = cast(torch.Tensor, self.disk.dc).to(device=ctx.device, dtype=ctx.dtype) patch_vals = self.disk.patch_values().to(device=ctx.device, dtype=ctx.dtype) @@ -822,41 +775,3 @@ def get_optimization_parameters(self) -> Any: if not name.startswith('disk.') and param.requires_grad: params.append(param) return params -======= - dr, dc = self.disk.patch_offsets() - dr = dr.to(device=ctx.device, dtype=ctx.dtype) - dc = dc.to(device=ctx.device, dtype=ctx.dtype) - dr2 = dr * dr - dc2 = dc * dc - drdc = dr * dc - - for j in keep_idx: - rr0 = centers_r[j] - cc0 = centers_c[j] - - if self.per_disk_intensity: - inten = self.i0_raw[j] - if active_order >= 1 and self.ir is not None and self.ic is not None: - inten = inten + self.ir[j] * dr + self.ic[j] * dc - if ( - active_order >= 2 - and self.irr is not None - and self.icc is not None - and self.irc is not None - ): - inten = inten + self.irr[j] * dr2 + self.icc[j] * dc2 + self.irc[j] * drdc - else: - inten = self.i0_raw - if active_order >= 1: - assert self.ir is not None and self.ic is not None - inten = inten + self.ir * rr0 + self.ic * cc0 - if active_order >= 2: - assert self.irr is not None and self.icc is not None and self.irc is not None - inten = ( - inten + self.irr * rr0 * rr0 + self.icc * cc0 * cc0 + self.irc * rr0 * cc0 - ) - - self.disk.add_patch(out, r0=rr0, c0=cc0, scale=inten) - - return out ->>>>>>> upstream/fitting_models_clean diff --git a/src/quantem/diffraction/__init__.py b/src/quantem/diffraction/__init__.py index 4b6bf2c25..a64a357e8 100644 --- a/src/quantem/diffraction/__init__.py +++ b/src/quantem/diffraction/__init__.py @@ -1,7 +1,4 @@ -<<<<<<< HEAD from quantem.diffraction.polar import RDF as RDF from quantem.diffraction.strain_autocorrelation import StrainMapAutocorrelation as StrainMapAutocorrelation from quantem.diffraction.maped import MAPED as MAPED -======= ->>>>>>> upstream/fitting_models_clean from quantem.diffraction.model_fitting import ModelDiffraction as ModelDiffraction diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 5c4e8748b..174a0ea07 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -6,10 +6,7 @@ import torch from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey -<<<<<<< HEAD from tqdm import tqdm -======= ->>>>>>> upstream/fitting_models_clean from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem from quantem.core.fitting.base import ( @@ -21,10 +18,7 @@ ) from quantem.core.fitting.diffraction import DiskTemplate, SyntheticDiskLattice from quantem.core.io.serialize import AutoSerialize -<<<<<<< HEAD -======= from quantem.core.ml.optimizer_mixin import OptimizerType, SchedulerType ->>>>>>> upstream/fitting_models_clean from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations @@ -61,12 +55,9 @@ def __init__(self, dataset: Any, _token: object | None = None): self.state_mean_refined: dict[str, torch.Tensor] | None = None self.mean_refined: bool = False -<<<<<<< HEAD self.state_individual_refined: np.ndarray | None = None self.individual_refined: bool = False -======= ->>>>>>> upstream/fitting_models_clean # Misc metadata self.metadata: dict[str, Any] = {} @@ -325,16 +316,12 @@ def preprocess( upsample_factor: int = 32, max_shift: float | None = None, shift_order: int = 1, -<<<<<<< HEAD gamma: float = 0.5, mode: str = "linear", -======= ->>>>>>> upstream/fitting_models_clean ) -> "ModelDiffraction": arr = np.asarray(self.dataset.array) if arr.ndim < 2: raise ValueError("dataset.array must have at least 2 dimensions.") -<<<<<<< HEAD mode_in = mode.strip().lower() if mode_in in {"linear", "patterson", "paterson", "acf", "autocorrelation"}: mode_norm = "linear" @@ -360,8 +347,6 @@ def preprocess( else: raise RuntimeError("Unreachable: normalized mode mapping failed.") self.dataset.array = np.asarray(arr) -======= ->>>>>>> upstream/fitting_models_clean h, w = arr.shape[-2], arr.shape[-1] self.index_shape = tuple(arr.shape[:-2]) @@ -475,18 +460,11 @@ def fit_mean_diffraction_pattern( *, n_steps: int = 200, reset: bool | Literal["initialized", "mean_refined"] = False, -<<<<<<< HEAD optimizer_params: dict | None = None, scheduler_params: dict | None = None, constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, constraint_config_params: dict[str, Any] | None = None, -======= - optimizer_params: OptimizerType | dict | None = None, - scheduler_params: SchedulerType | dict | None = None, - constraint_weight: float = 1.0, - constraint_params: dict[str, Any] | None = None, ->>>>>>> upstream/fitting_models_clean progress: bool = True, ) -> "ModelDiffraction": """ @@ -538,18 +516,12 @@ def fit_mean_diffraction_pattern( raise ValueError("reset must be False, True, 'initialized', or 'mean_refined'.") self.fit_render( -<<<<<<< HEAD # target=torch.tensor(self.dataset[30,30].array.astype("float32")), -======= ->>>>>>> upstream/fitting_models_clean target=self.target_mean, n_steps=int(n_steps), constraint_weight=float(constraint_weight), constraint_params=constraint_params, -<<<<<<< HEAD constraint_config_params=constraint_config_params, -======= ->>>>>>> upstream/fitting_models_clean optimizer_params=optimizer_params, scheduler_params=scheduler_params, progress=bool(progress), @@ -563,14 +535,10 @@ def fit_mean_diffraction_pattern( def reset( self, -<<<<<<< HEAD reset_to: Literal["initialized", "mean_refined", "individual"] = "mean_refined", reset_history: bool = True, individual_row: int = 0, individual_col: int = 0, -======= - reset_to: Literal["initialized", "mean_refined"] = "mean_refined", ->>>>>>> upstream/fitting_models_clean ) -> "ModelDiffraction": if reset_to == "initialized": state = self.state_initialized @@ -578,19 +546,14 @@ def reset( raise RuntimeError( "initialized state is unavailable. Call .define_model(...) first." ) -<<<<<<< HEAD if reset_history: self._clear_fit_history_all() -======= - self._clear_fit_history_all() ->>>>>>> upstream/fitting_models_clean elif reset_to == "mean_refined": state = self.state_mean_refined if state is None: raise RuntimeError( "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." ) -<<<<<<< HEAD if reset_history: mean_hist = self.fit_history.get("mean") self._clear_fit_history_all() @@ -916,17 +879,6 @@ def plot_strain( a.set_aspect("equal") return fig, ax -======= - mean_hist = self.fit_history.get("mean") - self._clear_fit_history_all() - if mean_hist is not None: - self.fit_history["mean"] = mean_hist - else: - raise ValueError("reset_to must be 'initialized' or 'mean_refined'.") - - self._load_model_state_dict_copy(state) - return self ->>>>>>> upstream/fitting_models_clean @property def render_mean_refined(self) -> np.ndarray: From 7efc8457a6975f00300bbfe9876367b6ebf82e78 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 11 May 2026 15:06:05 -0700 Subject: [PATCH 062/113] Add batched single-GPU per-pattern fit path Vectorizes ModelDiffraction.fit_individual_diffraction_pattern over the scan dimension via explicit batched forwards plus a per-sample Adam over stacked (B, *param_shape) tensors. Adds forward_batched on DiskTemplate, SyntheticDiskLattice, DCBackground, GaussianBackground, a functional _splat_patch_batched (scatter_add_ on a fresh buffer to avoid the in-place index_put_ that blocked torch.func.vmap), and a _BatchedPlan that builds stacked params, runs the batched forward, applies hard constraints, and unstacks per-pattern state dicts. The serial path is unchanged; the batched path is dispatched by passing batch_size= to fit_individual_diffraction_pattern. Measured ~33-200x speedup on single GPU with loss decreasing per pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/quantem/core/fitting/background.py | 29 ++ src/quantem/core/fitting/diffraction.py | 158 ++++++- src/quantem/diffraction/model_fitting.py | 516 ++++++++++++++++++++++- 3 files changed, 700 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/fitting/background.py b/src/quantem/core/fitting/background.py index 000d4a01a..6d6784276 100644 --- a/src/quantem/core/fitting/background.py +++ b/src/quantem/core/fitting/background.py @@ -48,6 +48,17 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: inten = self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype) return torch.ones(ctx.shape, device=ctx.device, dtype=ctx.dtype) * inten + def forward_batched( + self, + ctx: RenderContext, + *, + intensity_raw_b: torch.Tensor, + ) -> torch.Tensor: + B = intensity_raw_b.shape[0] + return intensity_raw_b.view(B, 1, 1).expand(B, ctx.shape[0], ctx.shape[1]).to( + device=ctx.device, dtype=ctx.dtype + ) + class GaussianBackground(RenderComponent): # TODO this should be N dimensional by default def __init__( @@ -110,3 +121,21 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: inten = self.intensity_raw.to(device=ctx.device, dtype=ctx.dtype) r2 = (rr - r0) ** 2 + (cc - c0) ** 2 return inten * torch.exp(-0.5 * r2 / (sigma * sigma)) + + def forward_batched( + self, + ctx: RenderContext, + *, + sigma_raw_b: torch.Tensor, + intensity_raw_b: torch.Tensor, + origin_coords_b: torch.Tensor, + ) -> torch.Tensor: + B = sigma_raw_b.shape[0] + rr = torch.arange(ctx.shape[0], device=ctx.device, dtype=ctx.dtype).view(1, ctx.shape[0], 1) + cc = torch.arange(ctx.shape[1], device=ctx.device, dtype=ctx.dtype).view(1, 1, ctx.shape[1]) + r0 = origin_coords_b[:, 0].view(B, 1, 1) + c0 = origin_coords_b[:, 1].view(B, 1, 1) + sigma = sigma_raw_b.view(B, 1, 1) + inten = intensity_raw_b.view(B, 1, 1) + r2 = (rr - r0) ** 2 + (cc - c0) ** 2 + return inten * torch.exp(-0.5 * r2 / (sigma * sigma)) diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 92e8ce60c..c03cdb3aa 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -48,6 +48,49 @@ def put(rr: torch.Tensor, cc: torch.Tensor, ww: torch.Tensor) -> None: put(r0i + 1, c0i + 1, w11) +def _splat_patch_batched( + shape: tuple[int, int], + *, + r0: torch.Tensor, + c0: torch.Tensor, + vals: torch.Tensor, + device: torch.device, + dtype: torch.dtype, +) -> torch.Tensor: + h, w = int(shape[0]), int(shape[1]) + B, N = r0.shape + + r_base = torch.floor(r0) + c_base = torch.floor(c0) + fr = r0 - r_base + fc = c0 - c_base + r0i = r_base.to(torch.long) + c0i = c_base.to(torch.long) + + w00 = (1.0 - fr) * (1.0 - fc) + w01 = (1.0 - fr) * fc + w10 = fr * (1.0 - fc) + w11 = fr * fc + + rr = torch.stack([r0i, r0i, r0i + 1, r0i + 1], dim=0) + cc = torch.stack([c0i, c0i + 1, c0i, c0i + 1], dim=0) + ww = torch.stack([w00, w01, w10, w11], dim=0) + + keep = (rr >= 0) & (rr < h) & (cc >= 0) & (cc < w) + weighted = ww * vals.unsqueeze(0) * keep.to(dtype) + + rr_c = rr.clamp(0, h - 1) + cc_c = cc.clamp(0, w - 1) + flat_idx = rr_c * w + cc_c + + flat_idx_b = flat_idx.permute(1, 0, 2).reshape(B, -1) + weighted_b = weighted.permute(1, 0, 2).reshape(B, -1) + + out_flat = torch.zeros(B, h * w, device=device, dtype=dtype) + out_flat.scatter_add_(1, flat_idx_b, weighted_b) + return out_flat.reshape(B, h, w) + + class DiskTemplate(RenderComponent): DEFAULT_HARD_CONSTRAINTS: dict[str, bool] = { "force_center": False, @@ -227,6 +270,25 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: self.add_patch(out, r0=r0, c0=c0, scale=scale) return out + def forward_batched( + self, + ctx: RenderContext, + *, + template_raw_b: torch.Tensor, + intensity_raw_b: torch.Tensor, + origin_coords_b: torch.Tensor, + ) -> torch.Tensor: + B = template_raw_b.shape[0] + N = int(cast(torch.Tensor, self.dr).numel()) + dr = cast(torch.Tensor, self.dr).to(device=ctx.device, dtype=ctx.dtype) + dc = cast(torch.Tensor, self.dc).to(device=ctx.device, dtype=ctx.dtype) + r0 = origin_coords_b[:, 0:1] + dr.unsqueeze(0) + c0 = origin_coords_b[:, 1:2] + dc.unsqueeze(0) + vals = template_raw_b.reshape(B, N) * intensity_raw_b.view(B, 1) + return _splat_patch_batched( + ctx.shape, r0=r0, c0=c0, vals=vals, device=ctx.device, dtype=ctx.dtype + ) + def _center_disk(self) -> None: with torch.no_grad(): template = self.template_raw @@ -768,8 +830,100 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: scale=torch.ones_like(vals_all) ) return out - - def get_optimization_parameters(self) -> Any: + + def forward_batched( + self, + ctx: RenderContext, + *, + u_row_b: torch.Tensor, + u_col_b: torch.Tensor, + v_row_b: torch.Tensor, + v_col_b: torch.Tensor, + i0_raw_b: torch.Tensor, + ir_b: torch.Tensor | None, + ic_b: torch.Tensor | None, + irr_b: torch.Tensor | None, + icc_b: torch.Tensor | None, + irc_b: torch.Tensor | None, + template_raw_b: torch.Tensor, + origin_coords_b: torch.Tensor, + ) -> torch.Tensor: + if self.origin is None: + raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") + + B = u_row_b.shape[0] + uv = cast(torch.Tensor, self.uv_indices).to(device=ctx.device) + K = int(uv.shape[0]) + if K == 0: + return torch.zeros(B, ctx.shape[0], ctx.shape[1], device=ctx.device, dtype=ctx.dtype) + + u = uv[:, 0].to(dtype=ctx.dtype) + v = uv[:, 1].to(dtype=ctx.dtype) + + r0_kb = ( + origin_coords_b[:, 0:1] + + u.unsqueeze(0) * u_row_b.unsqueeze(1) + + v.unsqueeze(0) * v_row_b.unsqueeze(1) + ) + c0_kb = ( + origin_coords_b[:, 1:2] + + u.unsqueeze(0) * u_col_b.unsqueeze(1) + + v.unsqueeze(0) * v_col_b.unsqueeze(1) + ) + + bb = torch.as_tensor(self.boundary_px, device=ctx.device, dtype=ctx.dtype) + keep = (r0_kb >= bb) & (r0_kb <= (ctx.shape[0] - 1) - bb) + keep = keep & (c0_kb >= bb) & (c0_kb <= (ctx.shape[1] - 1) - bb) + keep_f = keep.to(dtype=ctx.dtype) + + active_order = int( + ctx.fields.get( + "lattice_intensity_order_override", self.default_pattern_intensity_order + ) + ) + active_order = max(0, min(active_order, self.max_intensity_order)) + + dr = cast(torch.Tensor, self.disk.dr).to(device=ctx.device, dtype=ctx.dtype) + dc = cast(torch.Tensor, self.disk.dc).to(device=ctx.device, dtype=ctx.dtype) + N_pix = int(dr.numel()) + patch_vals = template_raw_b.reshape(B, N_pix) + + if self.per_disk_intensity: + inten = i0_raw_b.unsqueeze(2).expand(B, K, N_pix) + if active_order >= 1 and ir_b is not None and ic_b is not None: + inten = inten + ir_b.unsqueeze(2) * dr.view(1, 1, N_pix) + ic_b.unsqueeze(2) * dc.view(1, 1, N_pix) + if active_order >= 2 and irr_b is not None and icc_b is not None and irc_b is not None: + inten = ( + inten + + irr_b.unsqueeze(2) * (dr * dr).view(1, 1, N_pix) + + icc_b.unsqueeze(2) * (dc * dc).view(1, 1, N_pix) + + irc_b.unsqueeze(2) * (dr * dc).view(1, 1, N_pix) + ) + else: + inten = i0_raw_b.view(B, 1, 1).expand(B, K, N_pix).clone() + if active_order >= 1 and ir_b is not None and ic_b is not None: + inten = inten + ir_b.view(B, 1, 1) * r0_kb.unsqueeze(2) + ic_b.view(B, 1, 1) * c0_kb.unsqueeze(2) + if active_order >= 2 and irr_b is not None and icc_b is not None and irc_b is not None: + inten = ( + inten + + irr_b.view(B, 1, 1) * (r0_kb * r0_kb).unsqueeze(2) + + icc_b.view(B, 1, 1) * (c0_kb * c0_kb).unsqueeze(2) + + irc_b.view(B, 1, 1) * (r0_kb * c0_kb).unsqueeze(2) + ) + + inten = inten * keep_f.unsqueeze(2) + vals = patch_vals.unsqueeze(1) * inten + + r0_full = (r0_kb.unsqueeze(2) + dr.view(1, 1, N_pix)).reshape(B, K * N_pix) + c0_full = (c0_kb.unsqueeze(2) + dc.view(1, 1, N_pix)).reshape(B, K * N_pix) + vals_full = vals.reshape(B, K * N_pix) + + return _splat_patch_batched( + ctx.shape, r0=r0_full, c0=c0_full, vals=vals_full, + device=ctx.device, dtype=ctx.dtype, + ) + + def get_optimization_parameters(self) -> Any: params = [] for name, param in self.named_parameters(recurse=True): if not name.startswith('disk.') and param.requires_grad: diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 174a0ea07..c65480c39 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -4,6 +4,7 @@ import numpy as np import torch +import torch.nn.functional as F from scipy.ndimage import shift as ndi_shift from scipy.signal.windows import tukey from tqdm import tqdm @@ -16,6 +17,7 @@ RenderComponent, RenderContext, ) +from quantem.core.fitting.background import DCBackground, GaussianBackground from quantem.core.fitting.diffraction import DiskTemplate, SyntheticDiskLattice from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.optimizer_mixin import OptimizerType, SchedulerType @@ -585,7 +587,20 @@ def fit_individual_diffraction_pattern( constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, progress: bool = True, + batch_size: int | None = None, + **_compat_kwargs: Any, ) -> "ModelDiffraction": + if batch_size is not None: + return self.fit_individual_diffraction_pattern_batched( + rows=rows, + cols=cols, + batch_size=int(batch_size), + n_steps=int(n_steps), + reset=cast(Literal["initialized", "mean_refined"], reset), + optimizer_params=optimizer_params, + constraint_params=constraint_params, + progress=progress, + ) if self.model is None or self.ctx is None or self.target_mean is None: raise RuntimeError("Call .define_model(...) first.") @@ -649,7 +664,130 @@ def fit_individual_diffraction_pattern( self.individual_refined=True return self - + + def fit_individual_diffraction_pattern_batched( + self, + *, + rows: Any = None, + cols: Any = None, + batch_size: int = 16, + n_steps: int = 200, + reset: Literal["initialized", "mean_refined"] = "mean_refined", + optimizer_params: dict | None = None, + constraint_params: dict[str, Any] | None = None, + progress: bool = True, + ) -> "ModelDiffraction": + """ + Per-pattern fit, vectorized across a batch dimension on a single GPU. + + See ``fit_individual_diffraction_pattern`` for argument semantics. The + batched version runs ``batch_size`` patterns in parallel per optimizer + step, with per-sample stacked parameters and per-sample Adam moments. + Soft constraint losses are not yet supported; only hard constraints + and parameter bounds are enforced between steps. + """ + if self.model is None or self.ctx is None or self.target_mean is None: + raise RuntimeError("Call .define_model(...) first.") + if not isinstance(self.dataset, Dataset4d): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + if reset not in ("initialized", "mean_refined"): + raise ValueError("reset must be 'initialized' or 'mean_refined'.") + + # Choose initialization state and load into the live model so we can + # read current parameter values + constraint settings off the modules. + if reset == "mean_refined": + if self.state_mean_refined is None: + raise RuntimeError("mean_refined state is unavailable. Run fit_mean_diffraction_pattern first.") + init_state = self._clone_state_dict(self.state_mean_refined) + else: + if self.state_initialized is None: + raise RuntimeError("initialized state is unavailable. Call define_model first.") + init_state = self._clone_state_dict(self.state_initialized) + self._load_model_state_dict_copy(init_state) + + if constraint_params is not None: + self.model.apply_constraint_params(constraint_params, strict=True) + + scan_r = int(self.dataset.shape[0]) + scan_c = int(self.dataset.shape[1]) + rows_arr, cols_arr = _resolve_rows_cols_for_batched(rows, cols, scan_r, scan_c) + positions: list[tuple[int, int]] = [(int(r), int(c)) for r in rows_arr for c in cols_arr] + if len(positions) == 0: + return self + + if self.state_individual_refined is None or self.state_individual_refined.shape != (scan_r, scan_c): + self.state_individual_refined = np.full(shape=(scan_r, scan_c), fill_value=None, dtype=object) + + ctx = self.ctx + components_list = list(self.model.components) + plan = _BatchedPlan.from_model(self.model, components_list, optimizer_params or {}) + + loss_fn = self.loss_fn + + total_steps = len(positions) * n_steps + pbar = tqdm(total=total_steps, desc="Fit individual (batched)", disable=not progress) + + for start in range(0, len(positions), int(batch_size)): + chunk = positions[start:start + int(batch_size)] + B = len(chunk) + + targets = torch.stack( + [ + torch.as_tensor(self.dataset.array[r, c], device=ctx.device, dtype=ctx.dtype) + for (r, c) in chunk + ], + dim=0, + ) + + stacked = plan.build_stacked_params(B) + + adam_state: dict[str, dict[str, torch.Tensor]] = { + name: { + "m": torch.zeros_like(p.detach()), + "v": torch.zeros_like(p.detach()), + } + for name, p in stacked.items() + } + + for step in range(int(n_steps)): + pred = plan.batched_forward(ctx, stacked) + # Per-sample fidelity loss summed → scalar with per-sample grads + diff2 = (pred.float() - targets.float()) + # Match SqrtMSELoss behavior approximately when loss_fn is SqrtMSELoss: + # gamma-power transform of (x - min(x) + 1), per-sample independently. + from quantem.core.fitting.base import SqrtMSELoss, LogMSELoss + if isinstance(loss_fn, SqrtMSELoss): + gamma = float(loss_fn.gamma) + eps = 1.0 + pred_min = pred.amin(dim=(1, 2), keepdim=True) + tgt_min = targets.amin(dim=(1, 2), keepdim=True) + pred_mod = (pred - pred_min + eps) ** gamma + tgt_mod = (targets - tgt_min + eps) ** gamma + per_sample_loss = ((pred_mod - tgt_mod) ** 2).mean(dim=(1, 2)) + elif isinstance(loss_fn, LogMSELoss): + per_sample_loss = ((torch.log1p(pred) - torch.log1p(targets)) ** 2).mean(dim=(1, 2)) + else: + per_sample_loss = (diff2 * diff2).mean(dim=(1, 2)) + total_loss = per_sample_loss.sum() + + grads = torch.autograd.grad(total_loss, list(stacked.values())) + t = step + 1 + _adam_step_inplace(stacked, grads, adam_state, plan.lrs, t) + + with torch.no_grad(): + plan.apply_hard_constraints(stacked) + + pbar.update(B) + + # Unstack each sample back into a state_dict and store + for b, (r, c) in enumerate(chunk): + sample_state = plan.build_sample_state_dict(init_state, stacked, b) + self.state_individual_refined[r, c] = sample_state + + pbar.close() + self.individual_refined = True + return self + def get_individual_uv_vectors(self) -> "ModelDiffraction": scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] @@ -887,3 +1025,379 @@ def render_mean_refined(self) -> np.ndarray: "mean_refined state is unavailable. Run .fit_mean_diffraction_pattern(...) first." ) return self._render_state_array(self.state_mean_refined) + + +def _resolve_rows_cols_for_batched( + rows: Any, cols: Any, scan_r: int, scan_c: int +) -> tuple[np.ndarray, np.ndarray]: + if rows is None: + rows = range(scan_r) + if cols is None: + cols = range(scan_c) + if isinstance(rows, int): + rows_arr = np.array([rows], dtype=int) + else: + rows_arr = np.asarray(list(rows), dtype=int) + if isinstance(cols, int): + cols_arr = np.array([cols], dtype=int) + else: + cols_arr = np.asarray(list(cols), dtype=int) + return rows_arr, cols_arr + + +def _lr_for_component( + component: RenderComponent, + component_idx: int, + optimizer_params: dict[str, Any], + model: AdditiveRenderModel, +) -> float: + name = model._component_constraint_name(component, component_idx) + if name in optimizer_params: + return float(optimizer_params[name].get("lr", component.DEFAULT_LR)) + class_name = component.__class__.__name__ + if class_name in optimizer_params: + return float(optimizer_params[class_name].get("lr", component.DEFAULT_LR)) + return float(component.DEFAULT_LR) + + +class _BatchedPlan: + """Resolved layout for the batched per-pattern fit: component refs, lrs, and helpers.""" + + def __init__(self) -> None: + self.origin: OriginND | None = None + self.disk: DiskTemplate | None = None + self.dcbg: DCBackground | None = None + self.gaussbg: GaussianBackground | None = None + self.lat: SyntheticDiskLattice | None = None + self.disk_idx: int | None = None + self.dcbg_idx: int | None = None + self.gaussbg_idx: int | None = None + self.lat_idx: int | None = None + self.lrs: dict[str, float] = {} + + @classmethod + def from_model( + cls, + model: AdditiveRenderModel, + components_list: list[Any], + optimizer_params: dict[str, Any], + ) -> "_BatchedPlan": + self = cls() + self.origin = cast(OriginND, model.origin) + + for idx, comp in enumerate(components_list): + if isinstance(comp, DiskTemplate) and self.disk is None: + self.disk = comp + self.disk_idx = idx + elif isinstance(comp, DCBackground) and self.dcbg is None: + self.dcbg = comp + self.dcbg_idx = idx + elif isinstance(comp, GaussianBackground) and self.gaussbg is None: + self.gaussbg = comp + self.gaussbg_idx = idx + elif isinstance(comp, SyntheticDiskLattice) and self.lat is None: + self.lat = comp + self.lat_idx = idx + else: + raise TypeError( + f"Batched fit does not yet support component type {type(comp).__name__} " + f"at index {idx} (or duplicate of an already-handled type)." + ) + + # Per-component LRs (with fallback to the component's DEFAULT_LR). + if self.disk is not None and self.disk_idx is not None: + self.lrs["disk.template_raw"] = _lr_for_component(self.disk, self.disk_idx, optimizer_params, model) + self.lrs["disk.intensity_raw"] = self.lrs["disk.template_raw"] + if self.dcbg is not None and self.dcbg_idx is not None: + self.lrs["dcbg.intensity_raw"] = _lr_for_component(self.dcbg, self.dcbg_idx, optimizer_params, model) + if self.gaussbg is not None and self.gaussbg_idx is not None: + lr = _lr_for_component(self.gaussbg, self.gaussbg_idx, optimizer_params, model) + self.lrs["gaussbg.sigma_raw"] = lr + self.lrs["gaussbg.intensity_raw"] = lr + if self.lat is not None and self.lat_idx is not None: + lr = _lr_for_component(self.lat, self.lat_idx, optimizer_params, model) + for k in ("u_row", "u_col", "v_row", "v_col", "i0_raw", "ir", "ic", "irr", "icc", "irc"): + self.lrs[f"lat.{k}"] = lr + # Origin uses the lattice's LR (lattice is the most common owner of origin). + self.lrs["origin.coords"] = lr + elif self.gaussbg is not None and self.gaussbg_idx is not None: + self.lrs["origin.coords"] = self.lrs["gaussbg.intensity_raw"] + else: + self.lrs["origin.coords"] = 1e-2 + return self + + def build_stacked_params(self, B: int) -> dict[str, torch.Tensor]: + out: dict[str, torch.Tensor] = {} + assert self.origin is not None + out["origin.coords"] = self._stack(self.origin.coords, B) + + if self.disk is not None: + out["disk.template_raw"] = self._stack(self.disk.template_raw, B) + out["disk.intensity_raw"] = self._stack(self.disk.intensity_raw, B) + if self.dcbg is not None: + out["dcbg.intensity_raw"] = self._stack(self.dcbg.intensity_raw, B) + if self.gaussbg is not None: + out["gaussbg.sigma_raw"] = self._stack(self.gaussbg.sigma_raw, B) + out["gaussbg.intensity_raw"] = self._stack(self.gaussbg.intensity_raw, B) + if self.lat is not None: + for attr in ("u_row", "u_col", "v_row", "v_col", "i0_raw"): + t = getattr(self.lat, attr) + if t is not None: + out[f"lat.{attr}"] = self._stack(t, B) + for attr in ("ir", "ic", "irr", "icc", "irc"): + t = getattr(self.lat, attr, None) + if t is not None: + out[f"lat.{attr}"] = self._stack(t, B) + return out + + @staticmethod + def _stack(p: torch.Tensor, B: int) -> torch.Tensor: + x = p.detach().clone().unsqueeze(0).expand(B, *p.shape).contiguous() + x.requires_grad_(True) + return x + + def batched_forward( + self, ctx: RenderContext, stacked: dict[str, torch.Tensor] + ) -> torch.Tensor: + B = stacked["origin.coords"].shape[0] + origin_b = stacked["origin.coords"] + + pred = torch.zeros(B, ctx.shape[0], ctx.shape[1], device=ctx.device, dtype=ctx.dtype) + + if self.disk is not None: + pred = pred + self.disk.forward_batched( + ctx, + template_raw_b=stacked["disk.template_raw"], + intensity_raw_b=stacked["disk.intensity_raw"], + origin_coords_b=origin_b, + ) + if self.dcbg is not None: + pred = pred + self.dcbg.forward_batched( + ctx, intensity_raw_b=stacked["dcbg.intensity_raw"] + ) + if self.gaussbg is not None: + pred = pred + self.gaussbg.forward_batched( + ctx, + sigma_raw_b=stacked["gaussbg.sigma_raw"], + intensity_raw_b=stacked["gaussbg.intensity_raw"], + origin_coords_b=origin_b, + ) + if self.lat is not None: + pred = pred + self.lat.forward_batched( + ctx, + u_row_b=stacked["lat.u_row"], + u_col_b=stacked["lat.u_col"], + v_row_b=stacked["lat.v_row"], + v_col_b=stacked["lat.v_col"], + i0_raw_b=stacked["lat.i0_raw"], + ir_b=stacked.get("lat.ir"), + ic_b=stacked.get("lat.ic"), + irr_b=stacked.get("lat.irr"), + icc_b=stacked.get("lat.icc"), + irc_b=stacked.get("lat.irc"), + template_raw_b=stacked["disk.template_raw"], + origin_coords_b=origin_b, + ) + return pred + + def apply_hard_constraints(self, stacked: dict[str, torch.Tensor]) -> None: + """Apply batched analogues of each component's hard constraints to stacked params (in-place).""" + # Parameter bounds (always elementwise; safe on any shape). + if self.disk is not None: + for pname, (lo, hi) in self.disk.parameter_bounds.items(): + key = f"disk.{pname}" + if key in stacked: + self._clamp_bounds_inplace(stacked[key], lo, hi) + if self.dcbg is not None: + for pname, (lo, hi) in self.dcbg.parameter_bounds.items(): + key = f"dcbg.{pname}" + if key in stacked: + self._clamp_bounds_inplace(stacked[key], lo, hi) + if self.gaussbg is not None: + for pname, (lo, hi) in self.gaussbg.parameter_bounds.items(): + key = f"gaussbg.{pname}" + if key in stacked: + self._clamp_bounds_inplace(stacked[key], lo, hi) + if self.lat is not None: + for pname, (lo, hi) in self.lat.parameter_bounds.items(): + key = f"lat.{pname}" + if key in stacked: + self._clamp_bounds_inplace(stacked[key], lo, hi) + + # DiskTemplate composite hard constraints. + if self.disk is not None: + template = stacked.get("disk.template_raw") + intensity = stacked.get("disk.intensity_raw") + cfg = self.disk.constraint_config + if template is not None and intensity is not None: + if bool(self.disk.hard_constraints.get("force_center", False)): + self._batched_center_disk(template) + if bool(self.disk.hard_constraints.get("force_cutoff", False)): + self._batched_enforce_cutoff(template, cfg) + if bool(self.disk.hard_constraints.get("force_circular_mask", False)): + self._batched_enforce_circular_mask(template, cfg) + if bool(self.disk.hard_constraints.get("force_shrinkage", False)): + template.sub_(float(cfg.get("shrinkage_amount", 0.25))) + if bool(self.disk.hard_constraints.get("force_positive", False)): + template.clamp_(min=0.0) + intensity.clamp_(min=0.0) + if bool(self.disk.hard_constraints.get("force_norm", False)): + self._batched_enforce_norm(template) + + # SyntheticDiskLattice.force_positive_intensity. + if self.lat is not None and bool( + self.lat.hard_constraints.get("force_positive_intensity", False) + ): + i0 = stacked.get("lat.i0_raw") + if i0 is not None: + i0.clamp_(min=0.0) + + @staticmethod + def _clamp_bounds_inplace(t: torch.Tensor, lo: float | None, hi: float | None) -> None: + if lo is None and hi is None: + return + if lo is None: + t.clamp_(max=float(hi)) # type: ignore[arg-type] + elif hi is None: + t.clamp_(min=float(lo)) + else: + t.clamp_(min=float(lo), max=float(hi)) + + @staticmethod + def _batched_center_disk(template_b: torch.Tensor) -> None: + # template_b: (B, H_t, W_t) + B, h, w = template_b.shape + weights = template_b.clamp(min=0.0) + mass = weights.sum(dim=(1, 2)) + if not torch.any(mass > 1e-12): + return + rr = torch.arange(h, device=template_b.device, dtype=template_b.dtype).view(1, h, 1) + cc = torch.arange(w, device=template_b.device, dtype=template_b.dtype).view(1, 1, w) + safe_mass = mass.clamp(min=1e-12) + com_r = (weights * rr).sum(dim=(1, 2)) / safe_mass + com_c = (weights * cc).sum(dim=(1, 2)) / safe_mass + target_r = (h - 1) * 0.5 + target_c = (w - 1) * 0.5 + shift_r = target_r - com_r # (B,) + shift_c = target_c - com_c + denom_h = max(h - 1, 1) + denom_w = max(w - 1, 1) + ty = -2.0 * shift_r / float(denom_h) + tx = -2.0 * shift_c / float(denom_w) + zeros = torch.zeros_like(tx) + ones = torch.ones_like(tx) + theta = torch.stack( + [ + torch.stack([ones, zeros, tx], dim=1), + torch.stack([zeros, ones, ty], dim=1), + ], + dim=1, + ) # (B, 2, 3) + src = template_b.unsqueeze(1) # (B, 1, H, W) + grid = F.affine_grid(theta, [B, 1, h, w], align_corners=True) + shifted = F.grid_sample(src, grid, mode="bilinear", padding_mode="zeros", align_corners=True)[:, 0] + # Only shift samples with nonzero mass; others left as-is. + do_shift = (mass > 1e-12).view(B, 1, 1) + template_b.copy_(torch.where(do_shift, shifted, template_b)) + + @staticmethod + def _batched_enforce_norm(template_b: torch.Tensor) -> None: + mins = template_b.amin(dim=(1, 2), keepdim=True) + template_b.sub_(mins) + maxs = template_b.amax(dim=(1, 2), keepdim=True).clamp(min=1e-12) + template_b.div_(maxs) + + @staticmethod + def _batched_enforce_cutoff(template_b: torch.Tensor, cfg: dict[str, Any]) -> None: + thresh_ratio = float(cfg.get("hard_cutoff_threshold", 0.35)) + maxs = template_b.amax(dim=(1, 2), keepdim=True) + thresh = maxs * thresh_ratio + mask = template_b <= thresh + template_b.masked_fill_(mask, 0.0) + + @staticmethod + def _batched_enforce_circular_mask(template_b: torch.Tensor, cfg: dict[str, Any]) -> None: + B, h, w = template_b.shape + radius = (min(h, w) / 2.0) * float(cfg.get("circular_mask_radius_fraction", 0.95)) + r = torch.arange(-h / 2, h / 2, device=template_b.device, dtype=template_b.dtype) + c = torch.arange(-w / 2, w / 2, device=template_b.device, dtype=template_b.dtype) + rr, cc = torch.meshgrid(r, c, indexing="ij") + circle = torch.sqrt(rr * rr + cc * cc) + if bool(cfg.get("soft_circular_mask", False)): + sharpness = float(cfg.get("circular_mask_sharpness", 0)) + mask2d = torch.sigmoid(sharpness * (radius - circle)) + else: + mask2d = (circle <= radius).to(dtype=template_b.dtype) + template_b.mul_(mask2d.view(1, h, w)) + + def build_sample_state_dict( + self, + init_state: dict[str, torch.Tensor], + stacked: dict[str, torch.Tensor], + b: int, + ) -> dict[str, torch.Tensor]: + out = {k: v.detach().clone() for k, v in init_state.items()} + + # Origin: top-level key plus any 'components.X.origin.coords' or + # 'components.X.disk.origin.coords' that PyTorch state_dict registers. + origin_val = stacked["origin.coords"][b].detach().clone() + for key in list(out.keys()): + if key == "origin.coords" or key.endswith(".origin.coords"): + out[key] = origin_val.clone() + + # DiskTemplate (shared with lat.disk). + if self.disk is not None and self.disk_idx is not None: + t_val = stacked["disk.template_raw"][b].detach().clone() + i_val = stacked["disk.intensity_raw"][b].detach().clone() + for key in list(out.keys()): + if key.endswith(".template_raw"): + out[key] = t_val.clone() + for key in ( + f"components.{self.disk_idx}.intensity_raw", + f"components.{self.lat_idx}.disk.intensity_raw" if self.lat_idx is not None else None, + ): + if key is not None and key in out: + out[key] = i_val.clone() + + # DCBackground. + if self.dcbg is not None and self.dcbg_idx is not None: + out[f"components.{self.dcbg_idx}.intensity_raw"] = stacked["dcbg.intensity_raw"][b].detach().clone() + + # GaussianBackground. + if self.gaussbg is not None and self.gaussbg_idx is not None: + out[f"components.{self.gaussbg_idx}.sigma_raw"] = stacked["gaussbg.sigma_raw"][b].detach().clone() + out[f"components.{self.gaussbg_idx}.intensity_raw"] = stacked["gaussbg.intensity_raw"][b].detach().clone() + + # SyntheticDiskLattice scalar + tensor params. + if self.lat is not None and self.lat_idx is not None: + for attr in ("u_row", "u_col", "v_row", "v_col", "i0_raw", "ir", "ic", "irr", "icc", "irc"): + key = f"components.{self.lat_idx}.{attr}" + stacked_key = f"lat.{attr}" + if key in out and stacked_key in stacked: + out[key] = stacked[stacked_key][b].detach().clone() + return out + + +def _adam_step_inplace( + stacked: dict[str, torch.Tensor], + grads: tuple[torch.Tensor, ...], + adam_state: dict[str, dict[str, torch.Tensor]], + lrs: dict[str, float], + t: int, + beta1: float = 0.9, + beta2: float = 0.999, + eps: float = 1e-8, +) -> None: + bias1 = 1.0 - beta1 ** t + bias2 = 1.0 - beta2 ** t + with torch.no_grad(): + for (name, p), g in zip(stacked.items(), grads): + if g is None: + continue + st = adam_state[name] + st["m"].mul_(beta1).add_(g, alpha=1.0 - beta1) + st["v"].mul_(beta2).addcmul_(g, g, value=1.0 - beta2) + m_hat = st["m"] / bias1 + v_hat = st["v"] / bias2 + lr = float(lrs.get(name, 1e-2)) + p.data.addcdiv_(m_hat, v_hat.sqrt().add_(eps), value=-lr) From 40052b95c233e6fe2a93656ecad86e9315bf898e Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 11 May 2026 15:30:48 -0700 Subject: [PATCH 063/113] Additiona Claude updates to add soft/schedulers/hard-constraints back into the reconstruction loop --- src/quantem/core/fitting/diffraction.py | 63 ++++- src/quantem/diffraction/model_fitting.py | 311 +++++++++++++++++++++-- 2 files changed, 357 insertions(+), 17 deletions(-) diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index c03cdb3aa..31ee422f2 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -429,8 +429,67 @@ def constraint_loss( circular_loss = torch.as_tensor(circular_weight, device=ctx.device, dtype=ctx.dtype) * circular_err return cutoff_loss + tv_loss + circular_loss - - def get_optimization_parameters(self) -> Any: + + def constraint_loss_batched( + self, + ctx: RenderContext, + *, + template_raw_b: torch.Tensor, + params: dict[str, object] | None = None, + ) -> torch.Tensor: + """ + Per-sample analogue of ``constraint_loss`` for stacked templates. + + Parameters + ---------- + template_raw_b : torch.Tensor + Stacked templates with shape ``(B, H_t, W_t)``. + + Returns + ------- + torch.Tensor + Per-sample soft-constraint losses with shape ``(B,)``. Identical + semantics to ``constraint_loss`` on each slice, but reductions are + taken over ``dim=(1, 2)`` only. + """ + cfg = self.effective_soft_constraints(cast(dict[str, object] | None, params)) + tv_weight = max(float(cfg.get("tv_weight", 0.0)), 0.0) + cutoff_weight = max(float(cfg.get("cutoff_weight", 0.0)), 0.0) + circular_weight = max(float(cfg.get("circular_weight", 0.0)), 0.0) + + template = template_raw_b.to(device=ctx.device, dtype=ctx.dtype) + B, h, w = template.shape + + if h > 1: + tv_r = torch.mean(torch.abs(template[:, 1:, :] - template[:, :-1, :]), dim=(1, 2)) + else: + tv_r = torch.zeros(B, device=ctx.device, dtype=ctx.dtype) + if w > 1: + tv_c = torch.mean(torch.abs(template[:, :, 1:] - template[:, :, :-1]), dim=(1, 2)) + else: + tv_c = torch.zeros(B, device=ctx.device, dtype=ctx.dtype) + tv_loss = tv_weight * (tv_r + tv_c) + + # Per-sample cutoff (note: hard `<=` is non-differentiable; matches serial). + per_sample_mean = template.mean(dim=(1, 2), keepdim=True) + thresh = per_sample_mean * float(self.constraint_config["soft_cutoff_threshold"]) + frac_under = (template <= thresh).to(dtype=ctx.dtype).mean(dim=(1, 2)) + target_ratio = float(self.constraint_config["soft_cutoff_target_ratio"]) + cutoff_loss = cutoff_weight * torch.relu(frac_under - target_ratio) + + # Per-sample circular: mask depends only on (H, W), so build once. + radius = (min(h, w) / 2.0) * float(self.constraint_config["circular_mask_radius_fraction"]) + r = torch.arange(h, device=ctx.device, dtype=ctx.dtype) - h / 2.0 + c = torch.arange(w, device=ctx.device, dtype=ctx.dtype) - w / 2.0 + rr, cc = torch.meshgrid(r, c, indexing="ij") + circle_mask = torch.sqrt(rr * rr + cc * cc) + dist_from_radius = torch.relu(torch.abs(circle_mask - radius)) # (H, W) + circular_err = (dist_from_radius.unsqueeze(0) * template).mean(dim=(1, 2)) + circular_loss = circular_weight * circular_err + + return tv_loss + cutoff_loss + circular_loss + + def get_optimization_parameters(self) -> Any: params = [] for name, param in self.named_parameters(recurse=True): if not name.startswith('origin.') and param.requires_grad: diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index c65480c39..4ecf443fa 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -586,8 +586,11 @@ def fit_individual_diffraction_pattern( scheduler_params: dict | None = None, constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, + constraint_config_params: dict[str, Any] | None = None, progress: bool = True, batch_size: int | None = None, + frozen_components: list[str] | str | None = None, + sample_trainability: dict[str, Any] | None = None, **_compat_kwargs: Any, ) -> "ModelDiffraction": if batch_size is not None: @@ -598,7 +601,12 @@ def fit_individual_diffraction_pattern( n_steps=int(n_steps), reset=cast(Literal["initialized", "mean_refined"], reset), optimizer_params=optimizer_params, + scheduler_params=scheduler_params, + constraint_weight=float(constraint_weight), constraint_params=constraint_params, + constraint_config_params=constraint_config_params, + frozen_components=frozen_components, + sample_trainability=sample_trainability, progress=progress, ) @@ -674,7 +682,12 @@ def fit_individual_diffraction_pattern_batched( n_steps: int = 200, reset: Literal["initialized", "mean_refined"] = "mean_refined", optimizer_params: dict | None = None, + scheduler_params: dict | None = None, + constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, + constraint_config_params: dict[str, Any] | None = None, + frozen_components: list[str] | str | None = None, + sample_trainability: dict[str, Any] | None = None, progress: bool = True, ) -> "ModelDiffraction": """ @@ -683,8 +696,33 @@ def fit_individual_diffraction_pattern_batched( See ``fit_individual_diffraction_pattern`` for argument semantics. The batched version runs ``batch_size`` patterns in parallel per optimizer step, with per-sample stacked parameters and per-sample Adam moments. - Soft constraint losses are not yet supported; only hard constraints - and parameter bounds are enforced between steps. + + Soft constraint losses are added per-sample via + ``model.total_constraint_loss``-style accumulation (currently only + ``DiskTemplate`` contributes). Hard constraints and parameter bounds + are still enforced between steps. + + Parameters + ---------- + scheduler_params : dict | None, optional + Either a single spec dict (``{"type": "cosine", "t_max": N}``) + applied to every parameter group, or a dict keyed by component + name/class. Supported types: ``none``, ``cosine`` (a.k.a. + ``cosine_annealing``), ``linear``, ``exponential``. ``plateau`` + and ``cyclic`` fall back to constant LR with a warning. + constraint_weight : float + Global multiplier on the per-sample soft-constraint loss. + constraint_config_params : dict | None + Applied to the model once before the fit (``apply_constraint_configs``). + frozen_components : list[str] | str | None + Component name(s) whose parameters are frozen across all samples. + Resolved via ``AdditiveRenderModel._component_constraint_name``; + both instance names (``"disk0"``) and class names (``"DiskTemplate"``) + work. + sample_trainability : dict[str, ArrayLike] | None + Per-canonical-key (e.g. ``"disk.template_raw"``) boolean array of + length ``len(positions)``; ``False`` entries freeze that parameter + for the matching scan position. """ if self.model is None or self.ctx is None or self.target_mean is None: raise RuntimeError("Call .define_model(...) first.") @@ -707,6 +745,8 @@ def fit_individual_diffraction_pattern_batched( if constraint_params is not None: self.model.apply_constraint_params(constraint_params, strict=True) + if constraint_config_params is not None: + self.model.apply_constraint_configs(constraint_config_params, strict=True) scan_r = int(self.dataset.shape[0]) scan_c = int(self.dataset.shape[1]) @@ -721,6 +761,32 @@ def fit_individual_diffraction_pattern_batched( ctx = self.ctx components_list = list(self.model.components) plan = _BatchedPlan.from_model(self.model, components_list, optimizer_params or {}) + plan.set_scheduler_params(scheduler_params) + + # Build per-position trainability arrays (default True everywhere). + n_positions = len(positions) + is_trainable_pos: dict[str, np.ndarray] = { + key: np.ones(n_positions, dtype=bool) for key in plan.lrs + } + frozen_keys = plan.resolve_component_keys(frozen_components) + for key in frozen_keys: + is_trainable_pos[key][:] = False + # Keys that are frozen for ALL samples skip hard constraints too. + hard_skip_keys: set[str] = set(frozen_keys) + if sample_trainability is not None: + for key, arr in sample_trainability.items(): + if key not in is_trainable_pos: + raise KeyError( + f"sample_trainability key '{key}' is not a known stacked-param. " + f"Known: {sorted(is_trainable_pos)}" + ) + mask = np.asarray(arr, dtype=bool).reshape(-1) + if mask.shape != (n_positions,): + raise ValueError( + f"sample_trainability['{key}'] has shape {mask.shape}, " + f"expected ({n_positions},)." + ) + is_trainable_pos[key] = mask loss_fn = self.loss_fn @@ -749,6 +815,23 @@ def fit_individual_diffraction_pattern_batched( for name, p in stacked.items() } + # Per-chunk trainability slice as (B,) bool tensors on device. + chunk_slice = slice(start, start + B) + chunk_trainable: dict[str, torch.Tensor] = {} + mixed_trainable_keys: list[str] = [] + init_snapshots: dict[str, torch.Tensor] = {} + for key in stacked: + if key in is_trainable_pos: + mask_np = is_trainable_pos[key][chunk_slice] + mask = torch.as_tensor(mask_np, device=ctx.device, dtype=torch.bool) + chunk_trainable[key] = mask + # A "mixed" key has some frozen samples and some trainable. + # We snapshot the init value so frozen samples can be + # restored after hard constraints / Adam. + if not bool(mask.all()) and not bool((~mask).all()): + mixed_trainable_keys.append(key) + init_snapshots[key] = stacked[key].detach().clone() + for step in range(int(n_steps)): pred = plan.batched_forward(ctx, stacked) # Per-sample fidelity loss summed → scalar with per-sample grads @@ -768,14 +851,45 @@ def fit_individual_diffraction_pattern_batched( per_sample_loss = ((torch.log1p(pred) - torch.log1p(targets)) ** 2).mean(dim=(1, 2)) else: per_sample_loss = (diff2 * diff2).mean(dim=(1, 2)) + + # Add per-sample soft constraint loss. + if float(constraint_weight) != 0.0: + constraint_per_sample = plan.batched_constraint_loss(ctx, stacked) + per_sample_loss = per_sample_loss + float(constraint_weight) * constraint_per_sample + total_loss = per_sample_loss.sum() - grads = torch.autograd.grad(total_loss, list(stacked.values())) + grads_list = list(torch.autograd.grad(total_loss, list(stacked.values()))) + + # Apply trainability masks: zero out per-sample gradient entries + # for frozen samples/params before the Adam step. + for i, (name, g) in enumerate(zip(stacked.keys(), grads_list)): + if g is None: + continue + mask_b = chunk_trainable.get(name) + if mask_b is None: + continue + if bool(mask_b.all()): + continue + # Broadcast (B,) over remaining dims. + view_shape = (g.shape[0],) + (1,) * (g.ndim - 1) + grads_list[i] = g * mask_b.view(view_shape).to(dtype=g.dtype) + t = step + 1 - _adam_step_inplace(stacked, grads, adam_state, plan.lrs, t) + current_lrs = plan.lr_at_step(t, int(n_steps)) + _adam_step_inplace(stacked, tuple(grads_list), adam_state, current_lrs, t) with torch.no_grad(): - plan.apply_hard_constraints(stacked) + plan.apply_hard_constraints(stacked, skip_keys=hard_skip_keys) + # For mixed-trainability keys, restore frozen-sample values + # from the init snapshot so they stay at their starting state. + for key in mixed_trainable_keys: + mask = chunk_trainable[key] + view_shape = (mask.shape[0],) + (1,) * (stacked[key].ndim - 1) + keep = mask.view(view_shape) + stacked[key].data.copy_( + torch.where(keep, stacked[key].data, init_snapshots[key]) + ) pbar.update(B) @@ -1074,6 +1188,11 @@ def __init__(self) -> None: self.gaussbg_idx: int | None = None self.lat_idx: int | None = None self.lrs: dict[str, float] = {} + # Per-param normalized scheduler spec; keys match self.lrs. + # Each value: {"type": "none|cosine|linear|exponential", ...numerics}. + self.scheduler_specs: dict[str, dict[str, Any]] = {} + # Mapping from canonical component name -> stacked-param keys. + self.component_keys: dict[str, list[str]] = {} @classmethod def from_model( @@ -1108,24 +1227,171 @@ def from_model( if self.disk is not None and self.disk_idx is not None: self.lrs["disk.template_raw"] = _lr_for_component(self.disk, self.disk_idx, optimizer_params, model) self.lrs["disk.intensity_raw"] = self.lrs["disk.template_raw"] + name = model._component_constraint_name(self.disk, self.disk_idx) + self.component_keys[name] = ["disk.template_raw", "disk.intensity_raw"] + self.component_keys[self.disk.__class__.__name__] = list(self.component_keys[name]) if self.dcbg is not None and self.dcbg_idx is not None: self.lrs["dcbg.intensity_raw"] = _lr_for_component(self.dcbg, self.dcbg_idx, optimizer_params, model) + name = model._component_constraint_name(self.dcbg, self.dcbg_idx) + self.component_keys[name] = ["dcbg.intensity_raw"] + self.component_keys[self.dcbg.__class__.__name__] = list(self.component_keys[name]) if self.gaussbg is not None and self.gaussbg_idx is not None: lr = _lr_for_component(self.gaussbg, self.gaussbg_idx, optimizer_params, model) self.lrs["gaussbg.sigma_raw"] = lr self.lrs["gaussbg.intensity_raw"] = lr + name = model._component_constraint_name(self.gaussbg, self.gaussbg_idx) + self.component_keys[name] = ["gaussbg.sigma_raw", "gaussbg.intensity_raw"] + self.component_keys[self.gaussbg.__class__.__name__] = list(self.component_keys[name]) if self.lat is not None and self.lat_idx is not None: lr = _lr_for_component(self.lat, self.lat_idx, optimizer_params, model) + lat_keys = [] for k in ("u_row", "u_col", "v_row", "v_col", "i0_raw", "ir", "ic", "irr", "icc", "irc"): self.lrs[f"lat.{k}"] = lr + lat_keys.append(f"lat.{k}") # Origin uses the lattice's LR (lattice is the most common owner of origin). self.lrs["origin.coords"] = lr + name = model._component_constraint_name(self.lat, self.lat_idx) + self.component_keys[name] = lat_keys + self.component_keys[self.lat.__class__.__name__] = list(lat_keys) elif self.gaussbg is not None and self.gaussbg_idx is not None: self.lrs["origin.coords"] = self.lrs["gaussbg.intensity_raw"] else: self.lrs["origin.coords"] = 1e-2 + + # Default schedulers: constant LR for every key. + self.scheduler_specs = {k: {"type": "none"} for k in self.lrs} return self + def set_scheduler_params(self, scheduler_params: Any) -> None: + """ + Configure per-key scheduler specs. + + Accepts either a single spec dict (applied to all params) or a dict + keyed by component name / class name (matching ``optimizer_params``). + Unknown scheduler types fall back to constant LR. + """ + if scheduler_params is None: + return + if not isinstance(scheduler_params, dict): + return + + def _normalize(spec: dict[str, Any]) -> dict[str, Any]: + out = dict(spec) + t = str(out.pop("type", out.pop("name", "none"))).lower() + # Aliases. + if t in ("cosine", "cosineannealing"): + t = "cosine_annealing" + if t == "cosine_annealing": + # Normalize T_max alias and cast numeric strings. + if "t_max" in out: + out["T_max"] = out.pop("t_max") + if "T_max" in out and out["T_max"] is not None: + out["T_max"] = int(float(out["T_max"])) + if "eta_min" in out: + out["eta_min"] = float(out["eta_min"]) + elif t == "exponential": + if "gamma" in out: + out["gamma"] = float(out["gamma"]) + elif t == "linear": + for k in ("start_factor", "end_factor", "total_iters"): + if k in out and out[k] is not None: + out[k] = float(out[k]) if k != "total_iters" else int(float(out[k])) + out["type"] = t + return out + + # Single-spec form: {"type": ..., ...}. + if "type" in scheduler_params or "name" in scheduler_params: + spec = _normalize(cast(dict[str, Any], scheduler_params)) + for k in self.scheduler_specs: + self.scheduler_specs[k] = dict(spec) + return + + # Per-component form: {component_name_or_class: spec_dict}. + for comp_name, spec in scheduler_params.items(): + if not isinstance(spec, dict): + continue + norm = _normalize(spec) + param_keys = self.component_keys.get(str(comp_name), []) + for k in param_keys: + if k in self.scheduler_specs: + self.scheduler_specs[k] = dict(norm) + + def lr_at_step(self, step: int, n_steps: int) -> dict[str, float]: + """ + Evaluate the analytic LR schedule for each stacked-param key at + ``step`` (1-indexed, matches the bias correction). + """ + out: dict[str, float] = {} + for key, base_lr in self.lrs.items(): + spec = self.scheduler_specs.get(key, {"type": "none"}) + t = str(spec.get("type", "none")).lower() + if t == "cosine_annealing": + T_max = int(spec.get("T_max") or n_steps) + if T_max < 1: + T_max = 1 + eta_min = float(spec.get("eta_min", 1e-7)) + # PyTorch CosineAnnealingLR formula at step s (0-indexed): + # lr(s) = eta_min + 0.5*(base - eta_min)*(1 + cos(pi*s/T_max)) + s = max(step - 1, 0) + import math + lr = eta_min + 0.5 * (base_lr - eta_min) * (1.0 + math.cos(math.pi * s / T_max)) + out[key] = float(lr) + elif t == "exponential": + gamma = float(spec.get("gamma", 1.0)) + out[key] = float(base_lr * (gamma ** max(step - 1, 0))) + elif t == "linear": + start = float(spec.get("start_factor", 1.0 / 3.0)) + end = float(spec.get("end_factor", 1.0)) + total = int(spec.get("total_iters", n_steps) or n_steps) + if total < 1: + total = 1 + s = min(max(step - 1, 0), total) + factor = start + (end - start) * (s / total) + out[key] = float(base_lr * factor) + else: + # none / plateau / cyclic / unknown -> constant LR. + out[key] = float(base_lr) + return out + + def batched_constraint_loss( + self, + ctx: RenderContext, + stacked: dict[str, torch.Tensor], + ) -> torch.Tensor: + """ + Per-sample soft-constraint loss summed across components. + + Returns a tensor of shape ``(B,)``. Only ``DiskTemplate`` contributes + today; other components inherit the zero-returning base + ``constraint_loss``. + """ + B = stacked["origin.coords"].shape[0] + out = torch.zeros(B, device=ctx.device, dtype=ctx.dtype) + if self.disk is not None: + template_b = stacked.get("disk.template_raw") + if template_b is not None: + out = out + self.disk.constraint_loss_batched(ctx, template_raw_b=template_b) + return out + + def resolve_component_keys(self, components: Any) -> list[str]: + """Resolve a name/list of component names to the stacked-param keys they own.""" + if components is None: + return [] + if isinstance(components, str): + components = [components] + keys: list[str] = [] + for name in components: + ks = self.component_keys.get(str(name)) + if ks is None: + raise KeyError( + f"Unknown component name '{name}' for batched fit. " + f"Known: {sorted(self.component_keys)}" + ) + for k in ks: + if k not in keys: + keys.append(k) + return keys + def build_stacked_params(self, B: int) -> dict[str, torch.Tensor]: out: dict[str, torch.Tensor] = {} assert self.origin is not None @@ -1200,32 +1466,44 @@ def batched_forward( ) return pred - def apply_hard_constraints(self, stacked: dict[str, torch.Tensor]) -> None: - """Apply batched analogues of each component's hard constraints to stacked params (in-place).""" + def apply_hard_constraints( + self, + stacked: dict[str, torch.Tensor], + skip_keys: set[str] | None = None, + ) -> None: + """Apply batched analogues of each component's hard constraints to stacked params (in-place). + + ``skip_keys`` is a set of stacked-param keys that should not be mutated + by any hard-constraint op (used for fully-frozen components). + """ + skip_keys = skip_keys or set() # Parameter bounds (always elementwise; safe on any shape). if self.disk is not None: for pname, (lo, hi) in self.disk.parameter_bounds.items(): key = f"disk.{pname}" - if key in stacked: + if key in stacked and key not in skip_keys: self._clamp_bounds_inplace(stacked[key], lo, hi) if self.dcbg is not None: for pname, (lo, hi) in self.dcbg.parameter_bounds.items(): key = f"dcbg.{pname}" - if key in stacked: + if key in stacked and key not in skip_keys: self._clamp_bounds_inplace(stacked[key], lo, hi) if self.gaussbg is not None: for pname, (lo, hi) in self.gaussbg.parameter_bounds.items(): key = f"gaussbg.{pname}" - if key in stacked: + if key in stacked and key not in skip_keys: self._clamp_bounds_inplace(stacked[key], lo, hi) if self.lat is not None: for pname, (lo, hi) in self.lat.parameter_bounds.items(): key = f"lat.{pname}" - if key in stacked: + if key in stacked and key not in skip_keys: self._clamp_bounds_inplace(stacked[key], lo, hi) + disk_template_frozen = "disk.template_raw" in skip_keys + disk_intensity_frozen = "disk.intensity_raw" in skip_keys + # DiskTemplate composite hard constraints. - if self.disk is not None: + if self.disk is not None and not disk_template_frozen: template = stacked.get("disk.template_raw") intensity = stacked.get("disk.intensity_raw") cfg = self.disk.constraint_config @@ -1240,13 +1518,16 @@ def apply_hard_constraints(self, stacked: dict[str, torch.Tensor]) -> None: template.sub_(float(cfg.get("shrinkage_amount", 0.25))) if bool(self.disk.hard_constraints.get("force_positive", False)): template.clamp_(min=0.0) - intensity.clamp_(min=0.0) + if not disk_intensity_frozen: + intensity.clamp_(min=0.0) if bool(self.disk.hard_constraints.get("force_norm", False)): self._batched_enforce_norm(template) # SyntheticDiskLattice.force_positive_intensity. - if self.lat is not None and bool( - self.lat.hard_constraints.get("force_positive_intensity", False) + if ( + self.lat is not None + and "lat.i0_raw" not in skip_keys + and bool(self.lat.hard_constraints.get("force_positive_intensity", False)) ): i0 = stacked.get("lat.i0_raw") if i0 is not None: From 2f4e69e569e8aea6da0d28129bbc440549066a74 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Mon, 11 May 2026 15:58:22 -0700 Subject: [PATCH 064/113] updating base to match with new optimizer mixin --- src/quantem/core/fitting/base.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 8f1b3e5a3..8a64cb3bb 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -330,9 +330,9 @@ def initialize_optimizer(self, return - def _infer_optimizer_rebuild_params(self) -> dict[str, Any]: + def _infer_optimizer_rebuild_params(self) -> Any: if self.optimizer_params: - return dict(self.optimizer_params) + return self.optimizer_params if self.optimizer is not None: opt_type: str | type[torch.optim.Optimizer] if isinstance(self.optimizer, torch.optim.AdamW): @@ -354,9 +354,9 @@ def _infer_optimizer_rebuild_params(self) -> dict[str, Any]: "lr": float(getattr(self, "DEFAULT_LR", self.DEFAULT_LR)), } - def _infer_scheduler_rebuild_params(self) -> dict[str, Any]: + def _infer_scheduler_rebuild_params(self) -> Any: if self.scheduler_params: - return dict(self.scheduler_params) + return self.scheduler_params return { "type": self.DEFAULT_SCHEDULER_TYPE, } @@ -367,10 +367,10 @@ def _rebuild_optimizer_after_trainability_change(self) -> None: self._optimizer = None self._scheduler = None return - rebuild_params = self._infer_optimizer_rebuild_params() - rebuild_params_scheduler = self._infer_scheduler_rebuild_params() - self.set_optimizer(rebuild_params) - self.set_scheduler(rebuild_params_scheduler) + # rebuild_params = self._infer_optimizer_rebuild_params() + # rebuild_params_scheduler = self._infer_scheduler_rebuild_params() + self.set_optimizer(self.optimizer_params) + self.set_scheduler(self.scheduler_params) def initialize_constraint_config(self, config: dict[str, Any], strict: bool = True) -> None: if not hasattr(self, 'constraint_config'): From f69e0bf59cae6803bb0347ff27b75ec21ac814c7 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Wed, 13 May 2026 17:14:19 -0700 Subject: [PATCH 065/113] adding windowing for mean pattern calculation --- src/quantem/diffraction/model_fitting.py | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 4ecf443fa..05272eed6 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -320,6 +320,8 @@ def preprocess( shift_order: int = 1, gamma: float = 0.5, mode: str = "linear", + rows=None, + cols = None, ) -> "ModelDiffraction": arr = np.asarray(self.dataset.array) if arr.ndim < 2: @@ -349,6 +351,30 @@ def preprocess( else: raise RuntimeError("Unreachable: normalized mode mapping failed.") self.dataset.array = np.asarray(arr) + + if rows is None and cols is None: + rows = range(self.dataset.shape[0]) + cols = range(self.dataset.shape[1]) + elif rows is not None and cols is None: + cols = range(self.dataset.shape[1]) + elif rows is None and cols is not None: + rows = range(self.dataset.shape[0]) + else: + rows = rows + cols = cols + + if isinstance(rows, int): + rows = np.array([rows]).astype(int) + else: + rows = np.asarray(rows).astype(int) + + if isinstance(cols, int): + cols = np.array([cols]).astype(int) + else: + cols = np.asarray(cols).astype(int) + + arr = arr[rows, cols] + h, w = arr.shape[-2], arr.shape[-1] self.index_shape = tuple(arr.shape[:-2]) From 2067546efc487304a2672e0897569e4dd4490b2a Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Thu, 14 May 2026 19:31:42 -0700 Subject: [PATCH 066/113] adding plot masking --- src/quantem/diffraction/model_fitting.py | 88 ++++++++++++++++++------ 1 file changed, 68 insertions(+), 20 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 05272eed6..111c93fde 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -324,6 +324,8 @@ def preprocess( cols = None, ) -> "ModelDiffraction": arr = np.asarray(self.dataset.array) + self.mask = np.ones(arr.shape[:2]) + if arr.ndim < 2: raise ValueError("dataset.array must have at least 2 dimensions.") mode_in = mode.strip().lower() @@ -373,7 +375,7 @@ def preprocess( else: cols = np.asarray(cols).astype(int) - arr = arr[rows, cols] + arr = arr[np.ix_(rows, cols)] h, w = arr.shape[-2], arr.shape[-1] self.index_shape = tuple(arr.shape[:-2]) @@ -1035,6 +1037,54 @@ def fit_strain( ) return self + + def create_mask( + self, + min_threshold: float = 0.4, + max_threshold: float = 0.6, + smooth: bool = True, + + ): + if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + scan_r = self.dataset.shape[0] + scan_c = self.dataset.shape[1] + self.mask = np.zeros(self.dataset.shape[:2]) + + self.i0_sum_array = np.empty(shape=(scan_r, scan_c)) + + if self.state_individual_refined is None: + raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") + + for r in range(scan_r): + for c in range(scan_c): + pos_state = self.state_individual_refined[r, c] + if pos_state is None: + self.i0_sum_array[r, c] = 0.0 + continue + + i0_raw = None + uv_indices = None + for key in pos_state.keys(): + if key.endswith('i0_raw'): + i0_raw = pos_state[key].cpu().numpy() + if key.endswith('uv_indices'): + uv_indices = pos_state[key].cpu().numpy() + + if i0_raw is None or uv_indices is None: + self.i0_sum_array[r, c] = 0.0 + continue + + is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) + self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) + max_intensity = np.max(self.i0_sum_array) + if max_intensity == 0: + return np.zeros_like(self.i0_sum_array) + self.mask = self.i0_sum_array / max_intensity + self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) + if smooth: + self.mask = np.sin(np.pi / 2 * self.mask) ** 2 + return self def plot_strain( @@ -1088,24 +1138,21 @@ def plot_strain( euv_pct = strain_euv.array * 100 rot_deg = np.rad2deg(self.strain_rotation.array) + from matplotlib.colors import Normalize + norm_strain = Normalize(vmin=strain_range_percent[0], vmax=strain_range_percent[1]) + euu_rgb = cm_strain(norm_strain(euu_pct))[:, :, :3] + evv_rgb = cm_strain(norm_strain(evv_pct))[:, :, :3] + euv_rgb = cm_strain(norm_strain(euv_pct))[:, :, :3] + title_fs = 16 im0 = ax[0].imshow( - euu_pct, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cm_strain, + euu_rgb * self.mask[:, :, np.newaxis], ) ax[1].imshow( - evv_pct, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cm_strain, + evv_rgb * self.mask[:, :, np.newaxis], ) ax[2].imshow( - euv_pct, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cm_strain, + euv_rgb * self.mask[:, :, np.newaxis], ) ax[0].set_title(r"$\epsilon_{uu}$", fontsize=title_fs) @@ -1113,11 +1160,10 @@ def plot_strain( ax[2].set_title(r"$\epsilon_{uv}$", fontsize=title_fs) if plot_rotation: + norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) + rot_rgb = cm_rot(norm_rot(rot_deg))[:, :, :3] im3 = ax[3].imshow( - rot_deg, - vmin=rotation_range_degrees[0], - vmax=rotation_range_degrees[1], - cmap=cm_rot, + rot_rgb * self.mask[:, :, np.newaxis], ) ax[3].set_title("Rotation", fontsize=title_fs) @@ -1139,9 +1185,10 @@ def plot_strain( cb_height = 0.04 cb_pad = 0.03 y = b0.y0 - cb_pad - cb_height - + from matplotlib.cm import ScalarMappable cax1 = fig.add_axes([left, y, width, cb_height]) - cbar1 = fig.colorbar(im0, cax=cax1, orientation="horizontal") + sm_strain = ScalarMappable(norm=norm_strain, cmap=cm_strain) + cbar1 = fig.colorbar(sm_strain, cax=cax1, orientation="horizontal") cbar1.set_label("Strain (%)", fontsize=title_fs) cbar1.ax.tick_params(labelsize=12) @@ -1149,7 +1196,8 @@ def plot_strain( left_r = b3.x0 width_r = b3.x1 - b3.x0 cax2 = fig.add_axes([left_r, y, width_r, cb_height]) - cbar2 = fig.colorbar(im3, cax=cax2, orientation="horizontal") + sm_rot = ScalarMappable(norm=norm_rot, cmap=cm_rot) + cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation="horizontal") cbar2.set_label("Rotation (deg)", fontsize=title_fs) cbar2.ax.tick_params(labelsize=12) From 04e91bdedbd1ef075e219211583182df3c62390e Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Tue, 19 May 2026 16:09:38 -0700 Subject: [PATCH 067/113] changing plotting of strain map and labeling --- src/quantem/diffraction/model_fitting.py | 26 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 111c93fde..acf4f2aaf 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -63,6 +63,9 @@ def __init__(self, dataset: Any, _token: object | None = None): # Misc metadata self.metadata: dict[str, Any] = {} + self.u_ref: np.ndarray | None = None + self.v_ref: np.ndarray | None = None + @classmethod def from_dataset( cls, dataset: Dataset2d | Dataset3d | Dataset4d | Dataset4dstem | Any @@ -999,6 +1002,8 @@ def fit_strain( ), dtype=float, ) + self.u_ref = u_ref + self.v_ref = v_ref scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] @@ -1084,7 +1089,7 @@ def create_mask( self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) if smooth: self.mask = np.sin(np.pi / 2 * self.mask) ** 2 - return self + return self def plot_strain( @@ -1155,9 +1160,9 @@ def plot_strain( euv_rgb * self.mask[:, :, np.newaxis], ) - ax[0].set_title(r"$\epsilon_{uu}$", fontsize=title_fs) - ax[1].set_title(r"$\epsilon_{vv}$", fontsize=title_fs) - ax[2].set_title(r"$\epsilon_{uv}$", fontsize=title_fs) + ax[0].set_title(r"$\epsilon_{uu}$ $\updownarrow$", fontsize=title_fs) + ax[1].set_title(r"$\epsilon_{vv}$ $\leftrightarrow$", fontsize=title_fs) + ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow$", fontsize=title_fs) if plot_rotation: norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) @@ -1165,14 +1170,17 @@ def plot_strain( im3 = ax[3].imshow( rot_rgb * self.mask[:, :, np.newaxis], ) - ax[3].set_title("Rotation", fontsize=title_fs) + ax[3].set_title(r"Rotation $\circlearrowleft$", fontsize=title_fs) for a in ax: a.set_xticks([]) a.set_yticks([]) a.set_facecolor("black") - fig.subplots_adjust(left=0.02, right=0.98, top=0.90, bottom=0.16, wspace=0.03) + fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) + if plot_rotation: + pos3 = ax[3].get_position() + ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) b0 = ax[0].get_position() b2 = ax[2].get_position() @@ -1182,8 +1190,8 @@ def plot_strain( b3 = ax[3].get_position() if plot_rotation else None - cb_height = 0.04 - cb_pad = 0.03 + cb_height = 0.02 + cb_pad = 0.02 y = b0.y0 - cb_pad - cb_height from matplotlib.cm import ScalarMappable cax1 = fig.add_axes([left, y, width, cb_height]) @@ -1200,7 +1208,7 @@ def plot_strain( cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation="horizontal") cbar2.set_label("Rotation (deg)", fontsize=title_fs) cbar2.ax.tick_params(labelsize=12) - + for a in ax: a.set_aspect("equal") From df7729952553f09e2139321481064c93e10ec7b7 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Tue, 19 May 2026 22:51:57 -0700 Subject: [PATCH 068/113] adding radial masking function and scalebar option --- src/quantem/diffraction/model_fitting.py | 174 +++++++++++++++++------ 1 file changed, 131 insertions(+), 43 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index acf4f2aaf..5d86fc15e 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -10,6 +10,7 @@ from tqdm import tqdm from quantem.core.datastructures import Dataset2d, Dataset3d, Dataset4d, Dataset4dstem +from quantem.core.fitting.background import DCBackground, GaussianBackground from quantem.core.fitting.base import ( AdditiveRenderModel, FitBase, @@ -17,11 +18,11 @@ RenderComponent, RenderContext, ) -from quantem.core.fitting.background import DCBackground, GaussianBackground from quantem.core.fitting.diffraction import DiskTemplate, SyntheticDiskLattice from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.optimizer_mixin import OptimizerType, SchedulerType from quantem.core.utils.imaging_utils import cross_correlation_shift +from quantem.core.visualization.visualization_utils import ScalebarConfig, add_scalebar_to_ax from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations @@ -1045,13 +1046,12 @@ def fit_strain( def create_mask( self, + use_radial_method: bool = False, min_threshold: float = 0.4, max_threshold: float = 0.6, + exclusion_radius_fraction: float = 0.1, smooth: bool = True, - ): - if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): - raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] self.mask = np.zeros(self.dataset.shape[:2]) @@ -1060,28 +1060,41 @@ def create_mask( if self.state_individual_refined is None: raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") - - for r in range(scan_r): - for c in range(scan_c): - pos_state = self.state_individual_refined[r, c] - if pos_state is None: - self.i0_sum_array[r, c] = 0.0 - continue + if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + + if use_radial_method: + center_y, center_x = np.array(self.dataset.shape[:-2]) / 2 + y, x = np.ogrid[:self.dataset.shape[-2], :self.dataset.shape[-1]] + radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) + exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] + for r in range(scan_r): + for c in range(scan_c): + dp = self.dataset.array[r, c] + outside_mask = radius_map > exclusion_radius + self.i0_sum_array[r, c] = np.sum(dp[outside_mask]) + else: + for r in range(scan_r): + for c in range(scan_c): + pos_state = self.state_individual_refined[r, c] + if pos_state is None: + self.i0_sum_array[r, c] = 0.0 + continue - i0_raw = None - uv_indices = None - for key in pos_state.keys(): - if key.endswith('i0_raw'): - i0_raw = pos_state[key].cpu().numpy() - if key.endswith('uv_indices'): - uv_indices = pos_state[key].cpu().numpy() - - if i0_raw is None or uv_indices is None: - self.i0_sum_array[r, c] = 0.0 - continue - - is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) - self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) + i0_raw = None + uv_indices = None + for key in pos_state.keys(): + if key.endswith('i0_raw'): + i0_raw = pos_state[key].cpu().numpy() + if key.endswith('uv_indices'): + uv_indices = pos_state[key].cpu().numpy() + + if i0_raw is None or uv_indices is None: + self.i0_sum_array[r, c] = 0.0 + continue + + is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) + self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) max_intensity = np.max(self.i0_sum_array) if max_intensity == 0: return np.zeros_like(self.i0_sum_array) @@ -1098,10 +1111,13 @@ def plot_strain( strain_range_percent=(-3.0, 3.0), rotation_range_degrees=(-2.0, 2.0), plot_rotation=True, + plot_gvecs=True, + plot_scalebar=True, cmap_strain="RdBu_r", cmap_rotation="PiYG", layout="horizontal", figsize=(6, 6), + **scalebar_kwargs, ): import matplotlib.pyplot as plt @@ -1150,44 +1166,76 @@ def plot_strain( euv_rgb = cm_strain(norm_strain(euv_pct))[:, :, :3] title_fs = 16 - im0 = ax[0].imshow( - euu_rgb * self.mask[:, :, np.newaxis], - ) - ax[1].imshow( - evv_rgb * self.mask[:, :, np.newaxis], - ) - ax[2].imshow( - euv_rgb * self.mask[:, :, np.newaxis], - ) + ax[0].imshow(euu_rgb * self.mask[:, :, np.newaxis],) + ax[1].imshow(evv_rgb * self.mask[:, :, np.newaxis],) + ax[2].imshow(euv_rgb * self.mask[:, :, np.newaxis],) ax[0].set_title(r"$\epsilon_{uu}$ $\updownarrow$", fontsize=title_fs) ax[1].set_title(r"$\epsilon_{vv}$ $\leftrightarrow$", fontsize=title_fs) - ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow$", fontsize=title_fs) + ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow\!\swarrow$", fontsize=title_fs) if plot_rotation: norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) rot_rgb = cm_rot(norm_rot(rot_deg))[:, :, :3] - im3 = ax[3].imshow( - rot_rgb * self.mask[:, :, np.newaxis], - ) + ax[3].imshow(rot_rgb * self.mask[:, :, np.newaxis],) ax[3].set_title(r"Rotation $\circlearrowleft$", fontsize=title_fs) for a in ax: a.set_xticks([]) a.set_yticks([]) a.set_facecolor("black") + a.set_aspect("equal") + + if plot_scalebar and isinstance(self.dataset, Dataset4dstem): + default_sampling = 1.0 + default_units = 'pixels' + + if hasattr(self.dataset, 'units'): + if isinstance(self.dataset.units, (tuple, list)): + default_units = str(self.dataset.units[0]) + else: + default_units = str(self.dataset.units) + if hasattr(self.dataset, 'sampling'): + if isinstance(self.dataset.sampling, (tuple, list, np.ndarray)): + default_sampling = float(self.dataset.sampling[0]) + else: + default_sampling = float(self.dataset.sampling) + scalebar_defaults = { + 'sampling': default_sampling, + 'units': default_units, + 'length': None, + 'width_px': 1, + 'pad_px': 0.5, + 'color': 'black', + 'loc': 'lower left', + 'fontsize': 12, + 'bold': True, + } + scalebar_defaults.update(scalebar_kwargs) + scalebar_config = ScalebarConfig(**scalebar_defaults) + add_scalebar_to_ax( + ax[0], + array_size=int(self.dataset.shape[0]), + sampling=scalebar_config.sampling, + length_units=scalebar_config.length, + units=scalebar_config.units, + width_px=scalebar_config.width_px, + pad_px=scalebar_config.pad_px, + color=scalebar_config.color, + loc=scalebar_config.loc, + fontsize=scalebar_config.fontsize, + bold=scalebar_config.bold, + ) fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) if plot_rotation: pos3 = ax[3].get_position() ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) - b0 = ax[0].get_position() b2 = ax[2].get_position() left = b0.x0 right = b2.x1 width = right - left - b3 = ax[3].get_position() if plot_rotation else None cb_height = 0.02 @@ -1208,9 +1256,49 @@ def plot_strain( cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation="horizontal") cbar2.set_label("Rotation (deg)", fontsize=title_fs) cbar2.ax.tick_params(labelsize=12) - - for a in ax: - a.set_aspect("equal") + + if plot_gvecs: + from matplotlib.patches import FancyArrowPatch + if not hasattr(self, 'u_ref') or not hasattr(self, 'v_ref'): + print("Warning: u_ref and v_ref not found. Call fit_strain() first.") + return fig, ax + + last_pos = b3 if plot_rotation else b2 + + ref_width = last_pos.width * 0.8 + ref_left = last_pos.x1 - 0.035 + + ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) + ref_ax.set_xlim(-1.5, 1.5) + ref_ax.set_ylim(-1.5, 1.5) + ref_ax.set_aspect('equal') + ref_ax.axis('off') + u_norm = self.u_ref / np.linalg.norm(self.u_ref) + v_norm = self.v_ref / np.linalg.norm(self.v_ref) + + u_row, u_col = u_norm + v_row, v_col = v_norm + arrow_props_ref = dict(arrowstyle='->', lw=3, mutation_scale=25) + + u_arrow = FancyArrowPatch( + (0, 0), (u_col, -u_row), + color='darkred', **arrow_props_ref + ) + ref_ax.add_patch(u_arrow) + + v_arrow = FancyArrowPatch( + (0, 0), (v_col, -v_row), + color='darkblue', **arrow_props_ref + ) + ref_ax.add_patch(v_arrow) + ref_ax.text(u_col * 1.3, -u_row * 1.3, r'$\mathbf{u}_{ref}$', + fontsize=14, fontweight='bold', color='darkred', + ha='center', va='center') + + ref_ax.text(v_col * 1.3, -v_row * 1.3, r'$\mathbf{v}_{ref}$', + fontsize=14, fontweight='bold', color='darkblue', + ha='center', va='center') + return fig, ax From 4a27332a9bed909b7149aa6bad08ea24967121cd Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Wed, 20 May 2026 11:14:16 -0700 Subject: [PATCH 069/113] updated scalebar plotting --- src/quantem/diffraction/model_fitting.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 5d86fc15e..e14465a57 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -1117,7 +1117,7 @@ def plot_strain( cmap_rotation="PiYG", layout="horizontal", figsize=(6, 6), - **scalebar_kwargs, + **kwargs, ): import matplotlib.pyplot as plt @@ -1189,6 +1189,11 @@ def plot_strain( if plot_scalebar and isinstance(self.dataset, Dataset4dstem): default_sampling = 1.0 default_units = 'pixels' + scalebar_kwargs = {} + for key, value in kwargs.items(): + if key.startswith('scalebar_'): + scalebar_key = key[len('scalebar_'):] + scalebar_kwargs[scalebar_key] = value if hasattr(self.dataset, 'units'): if isinstance(self.dataset.units, (tuple, list)): From 945ee2d3f197f65f6012036eff1d0de71f999c87 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Wed, 20 May 2026 11:40:27 -0700 Subject: [PATCH 070/113] updated dev merge --- src/quantem/core/io/file_readers.py | 6 ------ uv.lock | 18 ------------------ 2 files changed, 24 deletions(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 543d1d626..6e9701720 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -16,13 +16,7 @@ def read_4dstem( file_path: str | PathLike, file_type: str | None = None, dataset_index: int | None = None, -<<<<<<< HEAD - scan_length: int | None = None, - scan_axis: int = 0, - transpose_scan_axes: bool = False, -======= hot_pixel_filter: bool = False, ->>>>>>> dev **kwargs, ) -> Dataset4dstem: """ diff --git a/uv.lock b/uv.lock index 71a8b52ee..cad9aa3bb 100644 --- a/uv.lock +++ b/uv.lock @@ -1467,15 +1467,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -<<<<<<< HEAD -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 }, -======= 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/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" }, ->>>>>>> dev ] [[package]] @@ -1866,15 +1860,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -<<<<<<< HEAD -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 }, -======= 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/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" }, ->>>>>>> dev ] [[package]] @@ -3720,15 +3708,9 @@ wheels = [ name = "traitlets" version = "5.15.0" source = { registry = "https://pypi.org/simple" } -<<<<<<< HEAD -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 }, -======= 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/da/98/a9937a969d018a23badfea0b381f66783649d48e0ea6c41923265c3cbeb3/traitlets-5.15.0-py3-none-any.whl", hash = "sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40", size = 85877, upload-time = "2026-05-06T08:05:55.853Z" }, ->>>>>>> dev ] [[package]] From 3887cca5ba2c2323025e6e73222d0753f99a7993 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Fri, 22 May 2026 10:58:35 -0700 Subject: [PATCH 071/113] adding cepstral multi peak implementation --- .../diffraction/strain_autocorrelation.py | 238 ++++++++++++++++-- 1 file changed, 213 insertions(+), 25 deletions(-) diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 105d1d98e..a62e63189 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt import numpy as np +import torch from numpy.typing import NDArray from scipy.ndimage import distance_transform_edt @@ -41,6 +42,8 @@ def __init__( self.u: NDArray | None = None self.v: NDArray | None = None + self.mean_img_peaks: NDArray | None = None + self.mean_img_weights: NDArray | None = None self.u_fit: Dataset3d | None = None self.v_fit: Dataset3d | None = None @@ -292,6 +295,81 @@ def plot_transform( _apply_center_crop_limits(a, self.transform.shape, cropping_factor) return fig, ax + + def plot_single_transform( + self, + row: int = 0, + col: int = 0, + cropping_factor: float = 0.25, + scalebar_fraction: float = 0.25, + **plot_kwargs: Any, + ): + if self.transform is None or self.transform_rotated is None: + raise ValueError("Run preprocess() first to compute transform images.") + + sampling = np.mean(self.metadata["sampling_real"]) + units = self.metadata.get("real_units", r"$\mathrm{\AA}$") + + W = self.transform.shape[1] + view_w_px = W * cropping_factor + target_units = scalebar_fraction * view_w_px * sampling + sb_len = _nice_length_units(target_units) + + kr = (np.arange(self.transform.shape[0], dtype=float) - self.transform.shape[0] // 2)[:, None] + kc = (np.arange(self.transform.shape[1], dtype=float) - self.transform.shape[1] // 2)[None, :] + qmag = np.sqrt(kr * kr + kc * kc) + im0 = self.transform.array + tmp = im0 * qmag + i0 = np.unravel_index(np.nanargmax(tmp), tmp.shape) + vmin = 0.0 + vmax = im0[i0] + + defaults = dict( + vmin=vmin, + vmax=vmax, + title=(f"Row={row} Col={col} Transform"), + scalebar=ScalebarConfig( + sampling=sampling, + units=units, + length=sb_len if sb_len > 0 else None, + ), + ) + defaults.update(plot_kwargs) + if row >= 0 and row < self.dataset.array.shape[0] and col >= 0 and col < self.dataset.array.shape[1]: + dp = self.dataset.array[row, col] * self.mask_diffraction + self.mask_diffraction_inv + else: + raise ValueError("row or column value out of bounds") + mode = self.metadata.get("mode", "linear").lower() + if mode == "gamma": + g = self.metadata["gamma"] + + + if mode == "linear": + im = np.fft.fftshift(np.abs(np.fft.fft2(dp))) + elif mode == "log": + im = np.fft.fftshift(np.abs(np.fft.fft2(np.log1p(dp)))) + elif mode == "gamma": + im = np.fft.fftshift(np.abs(np.fft.fft2(np.power(np.clip(dp, 0.0, None), g)))) + else: + raise ValueError("metadata['mode'] must be 'linear', 'log', or 'gamma'") + + + fig, ax = show_2d(im, **defaults) + rot_ccw = self.metadata["q_to_r_rotation_ccw_deg"] + q_transpose = self.metadata["q_transpose"] + + _overlay_lattice_vectors( + ax=ax, + shape=self.transform.shape, + u_rc= self.u_fit.array[row, col, :], + v_rc=self.v_fit.array[row, col, :], + rot_ccw_deg=rot_ccw, + q_transpose=q_transpose, + peaks_plot=self.mean_img_peaks, + ) + + for a in _flatten_axes(ax): + _apply_center_crop_limits(a, self.transform.shape, cropping_factor) def choose_lattice_vector( self, @@ -301,9 +379,12 @@ def choose_lattice_vector( define_in_rotated: bool = False, refine_gaussian: bool = True, refine_dft: bool = False, + refine_all_peaks: bool = False, refine_radius_px: float = 2.0, upsample: int = 16, gaussian_maxfev: int = 100, + threshold_percentile: float = 0.9975, + min_peak_spacing: float = 0, plot: bool = True, cropping_factor: float = 0.25, **plot_kwargs: Any, @@ -321,29 +402,36 @@ def choose_lattice_vector( u_rc = _display_vec_to_raw(u_rc, rotation_ccw_deg=rot_ccw, transpose=q_transpose) v_rc = _display_vec_to_raw(v_rc, rotation_ccw_deg=rot_ccw, transpose=q_transpose) - u_fit_abs, v_fit_abs = _refine_lattice_vectors( + u_fit_abs, v_fit_abs, peaks, weights = _refine_lattice_vectors( self.transform.array, u_rc=u_rc, v_rc=v_rc, radius_px=refine_radius_px, refine_gaussian=refine_gaussian, refine_dft=refine_dft, + refine_all_peaks=refine_all_peaks, + peaks=None, + weights=None, upsample=upsample, maxfev=gaussian_maxfev, + threshold_percentile=threshold_percentile, + min_peak_spacing = min_peak_spacing, ) - H, W = self.transform.array.shape - center = np.array((H // 2, W // 2), dtype=float) - - self.u = u_fit_abs[:2] - center - self.v = v_fit_abs[:2] - center + self.u = u_fit_abs[:2] + self.v = v_fit_abs[:2] + if refine_all_peaks: + self.mean_img_peaks = peaks + self.mean_img_weights = weights self.metadata["choose_define_in_rotated"] = define_in_rotated self.metadata["choose_refine_gaussian"] = refine_gaussian self.metadata["choose_refine_dft"] = refine_dft + self.metadata["choose_refine_all_peaks"] = refine_all_peaks self.metadata["choose_refine_radius_px"] = refine_radius_px self.metadata["choose_upsample"] = upsample self.metadata["choose_gaussian_maxfev"] = gaussian_maxfev + self.metadata["choose_threshold_percentile"] = threshold_percentile if plot: fig, ax = self.plot_transform(cropping_factor=cropping_factor, **plot_kwargs) @@ -354,6 +442,7 @@ def choose_lattice_vector( v_rc=self.v, rot_ccw_deg=rot_ccw, q_transpose=q_transpose, + peaks_plot=self.mean_img_peaks, ) return self @@ -363,6 +452,7 @@ def fit_lattice_vectors( self, refine_gaussian: bool = True, refine_dft: bool = False, + refine_all_peaks: bool = False, refine_radius_px: float = 2.0, upsample: int = 16, gaussian_maxfev: int = 100, @@ -370,7 +460,10 @@ def fit_lattice_vectors( ) -> "StrainMapAutocorrelation": if self.u is None or self.v is None: raise ValueError("Run choose_lattice_vector() first to set initial lattice vectors (self.u, self.v).") - + if refine_all_peaks: + if self.mean_img_peaks is None or self.mean_img_weights is None: + raise ValueError("Run choose_lattice_vector() with refine_all_peaks=True to determine which peaks to fit") + scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] @@ -428,13 +521,16 @@ def fit_lattice_vectors( else: raise ValueError("metadata['mode'] must be 'linear', 'log', or 'gamma'") - u_fit_abs, v_fit_abs = _refine_lattice_vectors( + u_fit_abs, v_fit_abs, _, _ = _refine_lattice_vectors( im, u_rc=u0, v_rc=v0, radius_px=refine_radius_px, refine_gaussian=refine_gaussian, refine_dft=refine_dft, + refine_all_peaks=refine_all_peaks, + peaks=self.mean_img_peaks, + weights=self.mean_img_weights, upsample=upsample, maxfev=gaussian_maxfev, ) @@ -442,13 +538,14 @@ def fit_lattice_vectors( self.u_peak_fit.array[r, c, :] = u_fit_abs self.v_peak_fit.array[r, c, :] = v_fit_abs - self.u_fit.array[r, c, 0] = u_fit_abs[0] - r_center - self.u_fit.array[r, c, 1] = u_fit_abs[1] - c_center - self.v_fit.array[r, c, 0] = v_fit_abs[0] - r_center - self.v_fit.array[r, c, 1] = v_fit_abs[1] - c_center + self.u_fit.array[r, c, 0] = u_fit_abs[0] + self.u_fit.array[r, c, 1] = u_fit_abs[1] + self.v_fit.array[r, c, 0] = v_fit_abs[0] + self.v_fit.array[r, c, 1] = v_fit_abs[1] self.metadata["fit_refine_gaussian"] = refine_gaussian self.metadata["fit_refine_dft"] = refine_dft + self.metadata["fit_refine_all_peaks"] = refine_all_peaks self.metadata["fit_refine_radius_px"] = refine_radius_px self.metadata["fit_upsample"] = upsample self.metadata["fit_gaussian_maxfev"] = gaussian_maxfev @@ -622,27 +719,30 @@ def fit_strain( def plot_strain( self, ref_u_v=(1.0, 0.0), - ref_angle_degrees=None, + rotation_angle=None, strain_range_percent=(-3.0, 3.0), rotation_range_degrees=(-2.0, 2.0), plot_rotation=True, + plot_scalebar=True, + plot_gvecs=False, cmap_strain="RdBu_r", cmap_rotation=None, layout="horizontal", figsize=(6, 6), max_shift: tuple[float, float] | None = None, amp_range: tuple[float, float] | None = None, + **kwargs, ): import matplotlib.pyplot as plt if cmap_rotation is None: cmap_rotation = cmap_strain - if ref_angle_degrees is None: + if rotation_angle is None: ref_vec = self.u_ref * ref_u_v[0] + self.v_ref * ref_u_v[1] ref_angle = np.arctan2(ref_vec[1], ref_vec[0]) else: - ref_angle = np.deg2rad(ref_angle_degrees) + ref_angle = np.deg2rad(rotation_angle) angle = ref_angle + np.deg2rad(self.metadata["q_to_r_rotation_ccw_deg"]) c = np.cos(angle) @@ -897,6 +997,15 @@ def _draw(vec: NDArray, label: str, color: tuple[float, float, float]) -> None: _draw(np.asarray(u_rc, dtype=float).reshape(2), "u", (1.0, 0.0, 0.0)) _draw(np.asarray(v_rc, dtype=float).reshape(2), "v", (0.0, 0.7, 1.0)) +def _plot_peaks(ax: Any, center_rc: tuple[float, float], peaks_plot: NDArray) -> None: + r0, c0 = center_rc + + def _draw(vec: NDArray, color: tuple[float, float, float]) -> None: + dr, dc = vec[0], vec[1] + ax.plot([c0 + dc], [r0 + dr], marker="o", markersize=6.0, color=color) + + for p in peaks_plot: + _draw(np.asarray(p, dtype=float).reshape(2), (0.0, 1.0, 0.0)) def _overlay_lattice_vectors( *, @@ -906,6 +1015,7 @@ def _overlay_lattice_vectors( v_rc: NDArray, rot_ccw_deg: float, q_transpose: bool, + peaks_plot: NDArray | None = None, ) -> None: axs = _flatten_axes(ax) if not axs: @@ -915,6 +1025,8 @@ def _overlay_lattice_vectors( center_rc = (H // 2, W // 2) _plot_lattice_vectors(axs[0], center_rc, u_rc, v_rc) + if peaks_plot is not None: + _plot_peaks(axs[0], center_rc, peaks_plot) if len(axs) >= 2: u_disp = _raw_vec_to_display(u_rc, rotation_ccw_deg=rot_ccw_deg, transpose=q_transpose) @@ -985,12 +1097,15 @@ def _refine_peak_subpixel_dft( return r0, c0 im = np.asarray(im, dtype=float) - F = np.fft.fft2(im) + if torch.is_tensor(r0): + r0 = float(r0.item()) + if torch.is_tensor(c0): + c0 = float(c0.item()) + F = np.fft.fft2(np.fft.fftshift(im)) up = upsample - du = int(np.ceil(1.5 * up)) - - patch = dft_upsample(F, up=up, shift=(r0, c0), device="cpu") + du = int(np.fix(np.ceil(1.5 * up))) + patch = np.abs(dft_upsample(F, up=up, shift=(r0, c0), device="cpu")) patch = np.asarray(patch, dtype=float) i0, j0 = np.unravel_index(np.argmax(patch), patch.shape) @@ -1006,9 +1121,9 @@ def _refine_peak_subpixel_dft( dj = _parabolic_vertex_delta(row[0], row[1], row[2]) else: dj = 0.0 - - dr = (i0 - du + di) / up - dc = (j0 - du + dj) / up + M, N = im.shape + dr = ((float(i0) - du + di)) / up + dc = ((float(j0) - du + dj)) / up return r0 + dr, c0 + dc @@ -1021,9 +1136,14 @@ def _refine_lattice_vectors( radius_px: float = 2.0, refine_gaussian: bool = True, refine_dft: bool = False, + refine_all_peaks: bool = False, + peaks: NDArray | None = None, + weights: NDArray | None = None, upsample: int = 16, maxfev: int = 100, -) -> tuple[NDArray, NDArray]: + threshold_percentile: float = 0.9975, + min_peak_spacing: float = 0, +) -> tuple[NDArray, NDArray, NDArray, NDArray]: from scipy.optimize import curve_fit im = np.asarray(im, dtype=float) @@ -1168,6 +1288,74 @@ def _refine_one(vec: NDArray) -> NDArray: ) r_fit, c_fit = r_dft, c_dft - return np.array((r_fit, c_fit, amp, sig, bg), dtype=float) + return np.array((r_fit - r_center, c_fit - c_center, amp, sig, bg), dtype=float) + + def _find_initial_peaks_weights(initial_peaks: NDArray) -> tuple[NDArray, NDArray, NDArray]: + if initial_peaks is None: + threshold = np.percentile(im, threshold_percentile*100) + p = np.logical_and.reduce(( + im>np.roll(im,(-1,-1),axis = (0,1)), + im>np.roll(im,(-1, 0),axis = (0,1)), + im>np.roll(im,(-1, 1),axis = (0,1)), + im>np.roll(im,( 0,-1),axis = (0,1)), + im>np.roll(im,( 0, 1),axis = (0,1)), + im>np.roll(im,( 1,-1),axis = (0,1)), + im>np.roll(im,( 1, 0),axis = (0,1)), + im>np.roll(im,( 1, 1),axis = (0,1)), + im>threshold, + ) ) + initial_peaks = np.argwhere(p) + r0 = (r_center, c_center) + initial_peaks = initial_peaks - r0 + if min_peak_spacing > 0: + intensities = im[initial_peaks[:, 0] + r_center, initial_peaks[:, 1] + c_center] + sorted_indices = np.argsort(intensities)[::-1] + initial_peaks_sorted = initial_peaks[sorted_indices] + accepted_peaks = [] + + for peak in initial_peaks_sorted: + if len(accepted_peaks) == 0: + accepted_peaks.append(peak) + else: + distances = np.sqrt(np.sum((np.array(accepted_peaks) - peak)**2, axis=1)) + if np.all(distances >= min_peak_spacing): + accepted_peaks.append(peak) + + initial_peaks = np.array(accepted_peaks) + + peak_sp = np.zeros(initial_peaks.shape) + weights = np.zeros((initial_peaks.shape[0], 1)) + it = 0 + for peak in initial_peaks: + refined_peak = _refine_one(peak) + peak_sp[it, :] = refined_peak[:2] + weights[it] = refined_peak[2] + it += 1 + return peak_sp, weights + + + if refine_all_peaks: + if peaks is None or weights is None: + pts, weights = _find_initial_peaks_weights(None) + else: + pts,_ = _find_initial_peaks_weights(peaks) + + A = np.column_stack((u_rc, v_rc)) + ab0_float = np.linalg.lstsq(A, pts.T, rcond=None)[0] + ab0 = (np.round(ab0_float)).T + + weights /= weights.sum() + A = np.ones((pts.shape[0], 3)) + A[:,:2] = ab0 + pts_weighted = pts * np.sqrt(weights) + A_weighted = A * np.sqrt(weights) + uvr0 = np.linalg.lstsq(A_weighted, pts_weighted, rcond=None)[0] + u_refined = uvr0[0,:] + v_refined = uvr0[1,:] + _,_, amp_par_u = _parabolic_peak_rc_amp(r_guess=u_refined[0], c_guess=u_refined[1]) + _,_, amp_par_v = _parabolic_peak_rc_amp(r_guess=v_refined[0], c_guess=v_refined[1]) + + return np.array((u_refined[0], u_refined[1], amp_par_u, 0, 0), dtype=float), np.array((v_refined[0], v_refined[1], amp_par_v, 0, 0), dtype=float), pts, weights + + return _refine_one(u_rc), _refine_one(v_rc), None, None - return _refine_one(u_rc), _refine_one(v_rc) \ No newline at end of file From 864bd571496d6bfc1488fec30420e941c98cf997 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Sun, 24 May 2026 17:46:25 -0700 Subject: [PATCH 072/113] creating strain fitting mixin for plotting and calculation --- src/quantem/diffraction/model_fitting.py | 383 +++-------------- .../diffraction/strain_autocorrelation.py | 310 +------------- src/quantem/diffraction/strain_fitting.py | 400 ++++++++++++++++++ 3 files changed, 478 insertions(+), 615 deletions(-) create mode 100644 src/quantem/diffraction/strain_fitting.py diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index e14465a57..5f46a3794 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -66,6 +66,10 @@ def __init__(self, dataset: Any, _token: object | None = None): self.u_ref: np.ndarray | None = None self.v_ref: np.ndarray | None = None + self.u_array: np.ndarray | None = None + self.v_array: np.ndarray | None = None + + self.real_space = False @classmethod def from_dataset( @@ -977,335 +981,66 @@ def render_indivdual_pattern(self, row, col): ) return self._render_state_array(self.state_individual_refined[row, col]) - def fit_strain( - self, - mask_reference=None, - ) -> "ModelDiffraction": - u_fit = self.u_array - v_fit = self.v_array - - if mask_reference is None: - u_ref = np.median(u_fit.reshape(-1, 2), axis=0) - v_ref = np.median(v_fit.reshape(-1, 2), axis=0) - else: - m = np.asarray(mask_reference, dtype=bool) - u_ref = np.array( - ( - np.median(u_fit[m, 0]), - np.median(u_fit[m, 1]), - ), - dtype=float, - ) - v_ref = np.array( - ( - np.median(v_fit[m, 0]), - np.median(v_fit[m, 1]), - ), - dtype=float, - ) - self.u_ref = u_ref - self.v_ref = v_ref - - scan_r = self.dataset.shape[0] - scan_c = self.dataset.shape[1] - - Uref = np.stack((u_ref, v_ref), axis=1).astype(float) - strain_trans = np.zeros((scan_r, scan_c, 2, 2)) - for r in range(scan_r): - for c in range(scan_c): - U = np.stack((u_fit[r, c, :], v_fit[r, c, :]), axis=1) - det = np.linalg.det(U) - if not np.isfinite(det) or abs(det) < 1e-12: - U_inv = np.linalg.pinv(U) - else: - U_inv = np.linalg.inv(U) - strain_trans[r, c, :, :] = Uref @ U_inv - - self.strain_raw_err = Dataset2d.from_array( - strain_trans[:, :, 0, 0] - 1, - name="strain err", - signal_units="fractional", - ) - self.strain_raw_ecc = Dataset2d.from_array( - strain_trans[:, :, 1, 1] - 1, - name="strain ecc", - signal_units="fractional", - ) - self.strain_raw_erc = Dataset2d.from_array( - strain_trans[:, :, 1, 0] * 0.5 + strain_trans[:, :, 0, 1] * 0.5, - name="strain erc", - signal_units="fractional", - ) - self.strain_rotation = Dataset2d.from_array( - strain_trans[:, :, 1, 0] * -0.5 + strain_trans[:, :, 0, 1] * 0.5, - name="strain rotation", - signal_units="fractional", - ) - - return self - def create_mask( - self, - use_radial_method: bool = False, - min_threshold: float = 0.4, - max_threshold: float = 0.6, - exclusion_radius_fraction: float = 0.1, - smooth: bool = True, - ): - scan_r = self.dataset.shape[0] - scan_c = self.dataset.shape[1] - self.mask = np.zeros(self.dataset.shape[:2]) + # def create_mask( + # self, + # use_radial_method: bool = False, + # min_threshold: float = 0.4, + # max_threshold: float = 0.6, + # exclusion_radius_fraction: float = 0.1, + # smooth: bool = True, + # ): + # scan_r = self.dataset.shape[0] + # scan_c = self.dataset.shape[1] + # self.mask = np.zeros(self.dataset.shape[:2]) - self.i0_sum_array = np.empty(shape=(scan_r, scan_c)) + # self.i0_sum_array = np.empty(shape=(scan_r, scan_c)) - if self.state_individual_refined is None: - raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") - if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): - raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - - if use_radial_method: - center_y, center_x = np.array(self.dataset.shape[:-2]) / 2 - y, x = np.ogrid[:self.dataset.shape[-2], :self.dataset.shape[-1]] - radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) - exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] - for r in range(scan_r): - for c in range(scan_c): - dp = self.dataset.array[r, c] - outside_mask = radius_map > exclusion_radius - self.i0_sum_array[r, c] = np.sum(dp[outside_mask]) - else: - for r in range(scan_r): - for c in range(scan_c): - pos_state = self.state_individual_refined[r, c] - if pos_state is None: - self.i0_sum_array[r, c] = 0.0 - continue - - i0_raw = None - uv_indices = None - for key in pos_state.keys(): - if key.endswith('i0_raw'): - i0_raw = pos_state[key].cpu().numpy() - if key.endswith('uv_indices'): - uv_indices = pos_state[key].cpu().numpy() + # if self.state_individual_refined is None: + # raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") + # if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): + # raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + + # if use_radial_method: + # center_y, center_x = np.array(self.dataset.shape[:-2]) / 2 + # y, x = np.ogrid[:self.dataset.shape[-2], :self.dataset.shape[-1]] + # radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) + # exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] + # for r in range(scan_r): + # for c in range(scan_c): + # dp = self.dataset.array[r, c] + # outside_mask = radius_map > exclusion_radius + # self.i0_sum_array[r, c] = np.sum(dp[outside_mask]) + # else: + # for r in range(scan_r): + # for c in range(scan_c): + # pos_state = self.state_individual_refined[r, c] + # if pos_state is None: + # self.i0_sum_array[r, c] = 0.0 + # continue + + # i0_raw = None + # uv_indices = None + # for key in pos_state.keys(): + # if key.endswith('i0_raw'): + # i0_raw = pos_state[key].cpu().numpy() + # if key.endswith('uv_indices'): + # uv_indices = pos_state[key].cpu().numpy() - if i0_raw is None or uv_indices is None: - self.i0_sum_array[r, c] = 0.0 - continue + # if i0_raw is None or uv_indices is None: + # self.i0_sum_array[r, c] = 0.0 + # continue - is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) - self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) - max_intensity = np.max(self.i0_sum_array) - if max_intensity == 0: - return np.zeros_like(self.i0_sum_array) - self.mask = self.i0_sum_array / max_intensity - self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) - if smooth: - self.mask = np.sin(np.pi / 2 * self.mask) ** 2 - return self - - - def plot_strain( - self, - rotation_angle=20, - strain_range_percent=(-3.0, 3.0), - rotation_range_degrees=(-2.0, 2.0), - plot_rotation=True, - plot_gvecs=True, - plot_scalebar=True, - cmap_strain="RdBu_r", - cmap_rotation="PiYG", - layout="horizontal", - figsize=(6, 6), - **kwargs, - ): - import matplotlib.pyplot as plt - - if cmap_rotation is None: - cmap_rotation = cmap_strain - - angle = np.deg2rad(rotation_angle) - c = np.cos(angle) - s = np.sin(angle) - - err = self.strain_raw_err.array - ecc = self.strain_raw_ecc.array - erc = self.strain_raw_erc.array - - euu = err * (c * c) + 2.0 * erc * (c * s) + ecc * (s * s) - evv = err * (s * s) - 2.0 * erc * (c * s) + ecc * (c * c) - euv = (ecc - err) * (c * s) + erc * (c * c - s * s) - - strain_euu = self.strain_raw_err.copy() - strain_evv = self.strain_raw_ecc.copy() - strain_euv = self.strain_raw_erc.copy() - strain_euu.array[...] = euu - strain_evv.array[...] = evv - strain_euv.array[...] = euv - - if layout != "horizontal": - raise ValueError("layout must be 'horizontal'") - - ncols = 4 if plot_rotation else 3 - fig, ax = plt.subplots(1, ncols, figsize=figsize) - - cm_strain = plt.get_cmap(cmap_strain).copy() - cm_strain.set_bad(color="black") - cm_rot = plt.get_cmap(cmap_rotation).copy() - cm_rot.set_bad(color="black") - - euu_pct = strain_euu.array * 100 - evv_pct = strain_evv.array * 100 - euv_pct = strain_euv.array * 100 - rot_deg = np.rad2deg(self.strain_rotation.array) - - from matplotlib.colors import Normalize - norm_strain = Normalize(vmin=strain_range_percent[0], vmax=strain_range_percent[1]) - euu_rgb = cm_strain(norm_strain(euu_pct))[:, :, :3] - evv_rgb = cm_strain(norm_strain(evv_pct))[:, :, :3] - euv_rgb = cm_strain(norm_strain(euv_pct))[:, :, :3] - - title_fs = 16 - ax[0].imshow(euu_rgb * self.mask[:, :, np.newaxis],) - ax[1].imshow(evv_rgb * self.mask[:, :, np.newaxis],) - ax[2].imshow(euv_rgb * self.mask[:, :, np.newaxis],) - - ax[0].set_title(r"$\epsilon_{uu}$ $\updownarrow$", fontsize=title_fs) - ax[1].set_title(r"$\epsilon_{vv}$ $\leftrightarrow$", fontsize=title_fs) - ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow\!\swarrow$", fontsize=title_fs) - - if plot_rotation: - norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) - rot_rgb = cm_rot(norm_rot(rot_deg))[:, :, :3] - ax[3].imshow(rot_rgb * self.mask[:, :, np.newaxis],) - ax[3].set_title(r"Rotation $\circlearrowleft$", fontsize=title_fs) - - for a in ax: - a.set_xticks([]) - a.set_yticks([]) - a.set_facecolor("black") - a.set_aspect("equal") - - if plot_scalebar and isinstance(self.dataset, Dataset4dstem): - default_sampling = 1.0 - default_units = 'pixels' - scalebar_kwargs = {} - for key, value in kwargs.items(): - if key.startswith('scalebar_'): - scalebar_key = key[len('scalebar_'):] - scalebar_kwargs[scalebar_key] = value - - if hasattr(self.dataset, 'units'): - if isinstance(self.dataset.units, (tuple, list)): - default_units = str(self.dataset.units[0]) - else: - default_units = str(self.dataset.units) - if hasattr(self.dataset, 'sampling'): - if isinstance(self.dataset.sampling, (tuple, list, np.ndarray)): - default_sampling = float(self.dataset.sampling[0]) - else: - default_sampling = float(self.dataset.sampling) - scalebar_defaults = { - 'sampling': default_sampling, - 'units': default_units, - 'length': None, - 'width_px': 1, - 'pad_px': 0.5, - 'color': 'black', - 'loc': 'lower left', - 'fontsize': 12, - 'bold': True, - } - scalebar_defaults.update(scalebar_kwargs) - scalebar_config = ScalebarConfig(**scalebar_defaults) - add_scalebar_to_ax( - ax[0], - array_size=int(self.dataset.shape[0]), - sampling=scalebar_config.sampling, - length_units=scalebar_config.length, - units=scalebar_config.units, - width_px=scalebar_config.width_px, - pad_px=scalebar_config.pad_px, - color=scalebar_config.color, - loc=scalebar_config.loc, - fontsize=scalebar_config.fontsize, - bold=scalebar_config.bold, - ) - - fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) - if plot_rotation: - pos3 = ax[3].get_position() - ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) - b0 = ax[0].get_position() - b2 = ax[2].get_position() - left = b0.x0 - right = b2.x1 - width = right - left - b3 = ax[3].get_position() if plot_rotation else None - - cb_height = 0.02 - cb_pad = 0.02 - y = b0.y0 - cb_pad - cb_height - from matplotlib.cm import ScalarMappable - cax1 = fig.add_axes([left, y, width, cb_height]) - sm_strain = ScalarMappable(norm=norm_strain, cmap=cm_strain) - cbar1 = fig.colorbar(sm_strain, cax=cax1, orientation="horizontal") - cbar1.set_label("Strain (%)", fontsize=title_fs) - cbar1.ax.tick_params(labelsize=12) - - if plot_rotation: - left_r = b3.x0 - width_r = b3.x1 - b3.x0 - cax2 = fig.add_axes([left_r, y, width_r, cb_height]) - sm_rot = ScalarMappable(norm=norm_rot, cmap=cm_rot) - cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation="horizontal") - cbar2.set_label("Rotation (deg)", fontsize=title_fs) - cbar2.ax.tick_params(labelsize=12) - - if plot_gvecs: - from matplotlib.patches import FancyArrowPatch - if not hasattr(self, 'u_ref') or not hasattr(self, 'v_ref'): - print("Warning: u_ref and v_ref not found. Call fit_strain() first.") - return fig, ax - - last_pos = b3 if plot_rotation else b2 - - ref_width = last_pos.width * 0.8 - ref_left = last_pos.x1 - 0.035 - - ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) - ref_ax.set_xlim(-1.5, 1.5) - ref_ax.set_ylim(-1.5, 1.5) - ref_ax.set_aspect('equal') - ref_ax.axis('off') - u_norm = self.u_ref / np.linalg.norm(self.u_ref) - v_norm = self.v_ref / np.linalg.norm(self.v_ref) - - u_row, u_col = u_norm - v_row, v_col = v_norm - arrow_props_ref = dict(arrowstyle='->', lw=3, mutation_scale=25) - - u_arrow = FancyArrowPatch( - (0, 0), (u_col, -u_row), - color='darkred', **arrow_props_ref - ) - ref_ax.add_patch(u_arrow) - - v_arrow = FancyArrowPatch( - (0, 0), (v_col, -v_row), - color='darkblue', **arrow_props_ref - ) - ref_ax.add_patch(v_arrow) - ref_ax.text(u_col * 1.3, -u_row * 1.3, r'$\mathbf{u}_{ref}$', - fontsize=14, fontweight='bold', color='darkred', - ha='center', va='center') - - ref_ax.text(v_col * 1.3, -v_row * 1.3, r'$\mathbf{v}_{ref}$', - fontsize=14, fontweight='bold', color='darkblue', - ha='center', va='center') - - - return fig, ax + # is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) + # self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) + # max_intensity = np.max(self.i0_sum_array) + # if max_intensity == 0: + # return np.zeros_like(self.i0_sum_array) + # self.mask = self.i0_sum_array / max_intensity + # self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) + # if smooth: + # self.mask = np.sin(np.pi / 2 * self.mask) ** 2 + # return self @property def render_mean_refined(self) -> np.ndarray: diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index a62e63189..e42d7723f 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -40,19 +40,19 @@ def __init__( self.transform: Dataset2d | None = None self.transform_rotated: Dataset2d | None = None - self.u: NDArray | None = None - self.v: NDArray | None = None self.mean_img_peaks: NDArray | None = None self.mean_img_weights: NDArray | None = None - self.u_fit: Dataset3d | None = None - self.v_fit: Dataset3d | None = None - self.u_peak_fit: Dataset3d | None = None - self.v_peak_fit: Dataset3d | None = None - self.mask_diffraction = np.ones(self.dataset.array.shape[2:]) self.mask_diffraction_inv = np.zeros(self.dataset.array.shape[2:]) + self.u_ref: np.ndarray | None = None + self.v_ref: np.ndarray | None = None + self.u_array: np.ndarray | None = None + self.v_array: np.ndarray | None = None + + self.real_space = True + @classmethod def from_dataset(cls, dataset: Dataset4dstem, *, name: str | None = None) -> "StrainMapAutocorrelation": if not isinstance(dataset, Dataset4dstem): @@ -478,16 +478,8 @@ def fit_lattice_vectors( signal_units="mixed", ) - self.u_fit = Dataset3d.from_shape( - (scan_r, scan_c, 2), - name="u_fit", - signal_units="pixels", - ) - self.v_fit = Dataset3d.from_shape( - (scan_r, scan_c, 2), - name="v_fit", - signal_units="pixels", - ) + self.u_array = np.zeros((scan_r, scan_c, 2)) + self.v_array = np.zeros((scan_r, scan_c, 2)) mode = self.metadata.get("mode", "linear").lower() if mode == "gamma": @@ -505,9 +497,6 @@ def fit_lattice_vectors( u0 = np.asarray(self.u, dtype=float).reshape(2) v0 = np.asarray(self.v, dtype=float).reshape(2) - dp_shape = self.dataset.array.shape[2:] - r_center = dp_shape[0] // 2 - c_center = dp_shape[1] // 2 for r, c in it: dp = self.dataset.array[r, c] * self.mask_diffraction + self.mask_diffraction_inv @@ -538,10 +527,10 @@ def fit_lattice_vectors( self.u_peak_fit.array[r, c, :] = u_fit_abs self.v_peak_fit.array[r, c, :] = v_fit_abs - self.u_fit.array[r, c, 0] = u_fit_abs[0] - self.u_fit.array[r, c, 1] = u_fit_abs[1] - self.v_fit.array[r, c, 0] = v_fit_abs[0] - self.v_fit.array[r, c, 1] = v_fit_abs[1] + self.u_array[r, c, 0] = u_fit_abs[0] + self.u_array[r, c, 1] = u_fit_abs[1] + self.v_array[r, c, 0] = v_fit_abs[0] + self.v_array[r, c, 1] = v_fit_abs[1] self.metadata["fit_refine_gaussian"] = refine_gaussian self.metadata["fit_refine_dft"] = refine_dft @@ -561,15 +550,15 @@ def plot_lattice_vectors( figsize: tuple[float, float] | None = None, **imshow_kwargs: Any, ): - if self.u_fit is None or self.v_fit is None: - raise ValueError("Run fit_lattice_vectors() first to compute u_fit and v_fit.") + if self.u_array is None or self.v_array is None: + raise ValueError("Run fit_lattice_vectors() first to compute u_array and v_array.") if self.u is None or self.v is None: raise ValueError("Run choose_lattice_vector() first to set self.u and self.v.") - im0 = self.u_fit.array[:, :, 0] - im1 = self.u_fit.array[:, :, 1] - im2 = self.v_fit.array[:, :, 0] - im3 = self.v_fit.array[:, :, 1] + im0 = self.u_array[:, :, 0] + im1 = self.u_array[:, :, 1] + im2 = self.v_array[:, :, 0] + im3 = self.v_array[:, :, 1] du0 = im0 - self.u[0] du1 = im1 - self.u[1] @@ -642,267 +631,6 @@ def plot_lattice_vectors( return fig, ax - def fit_strain( - self, - mask_reference=None, - plot_strain=True, - ): - if self.u_fit is None or self.v_fit is None: - raise ValueError("Run fit_lattice_vectors() first to compute u_fit and v_fit.") - - u_fit = self.u_fit.array - v_fit = self.v_fit.array - scan_r, scan_c = u_fit.shape[0], u_fit.shape[1] - - if mask_reference is None: - self.u_ref = np.median(u_fit.reshape(-1, 2), axis=0) - self.v_ref = np.median(v_fit.reshape(-1, 2), axis=0) - else: - m = np.asarray(mask_reference, dtype=bool) - self.u_ref = np.array( - ( - np.median(u_fit[m, 0]), - np.median(u_fit[m, 1]), - ), - dtype=float, - ) - self.v_ref = np.array( - ( - np.median(v_fit[m, 0]), - np.median(v_fit[m, 1]), - ), - dtype=float, - ) - - Uref = np.stack((self.u_ref, self.v_ref), axis=1).astype(float) - det = np.linalg.det(Uref) - if not np.isfinite(det) or abs(det) < 1e-12: - Uref_inv = np.linalg.pinv(Uref) - else: - Uref_inv = np.linalg.inv(Uref) - - self.strain_trans = Dataset4d.from_shape( - (scan_r, scan_c, 2, 2), - name="transformation matrix", - signal_units="fractional", - ) - - for r in range(scan_r): - for c in range(scan_c): - U = np.stack((u_fit[r, c, :], v_fit[r, c, :]), axis=1) - self.strain_trans.array[r, c, :, :] = U @ Uref_inv - - self.strain_raw_err = Dataset2d.from_array( - self.strain_trans.array[:, :, 0, 0] - 1, - name="strain err", - signal_units="fractional", - ) - self.strain_raw_ecc = Dataset2d.from_array( - self.strain_trans.array[:, :, 1, 1] - 1, - name="strain ecc", - signal_units="fractional", - ) - self.strain_raw_erc = Dataset2d.from_array( - self.strain_trans.array[:, :, 1, 0] * 0.5 + self.strain_trans.array[:, :, 0, 1] * 0.5, - name="strain erc", - signal_units="fractional", - ) - self.strain_rotation = Dataset2d.from_array( - self.strain_trans.array[:, :, 1, 0] * -0.5 + self.strain_trans.array[:, :, 0, 1] * 0.5, - name="strain rotation", - signal_units="fractional", - ) - - return self - - - def plot_strain( - self, - ref_u_v=(1.0, 0.0), - rotation_angle=None, - strain_range_percent=(-3.0, 3.0), - rotation_range_degrees=(-2.0, 2.0), - plot_rotation=True, - plot_scalebar=True, - plot_gvecs=False, - cmap_strain="RdBu_r", - cmap_rotation=None, - layout="horizontal", - figsize=(6, 6), - max_shift: tuple[float, float] | None = None, - amp_range: tuple[float, float] | None = None, - **kwargs, - ): - import matplotlib.pyplot as plt - - if cmap_rotation is None: - cmap_rotation = cmap_strain - - if rotation_angle is None: - ref_vec = self.u_ref * ref_u_v[0] + self.v_ref * ref_u_v[1] - ref_angle = np.arctan2(ref_vec[1], ref_vec[0]) - else: - ref_angle = np.deg2rad(rotation_angle) - - angle = ref_angle + np.deg2rad(self.metadata["q_to_r_rotation_ccw_deg"]) - c = np.cos(angle) - s = np.sin(angle) - - err = self.strain_raw_err.array - ecc = self.strain_raw_ecc.array - erc = self.strain_raw_erc.array - - euu = err * (c * c) + 2.0 * erc * (c * s) + ecc * (s * s) - evv = err * (s * s) - 2.0 * erc * (c * s) + ecc * (c * c) - euv = (ecc - err) * (c * s) + erc * (c * c - s * s) - - self.strain_euu = self.strain_raw_err.copy() - self.strain_evv = self.strain_raw_ecc.copy() - self.strain_euv = self.strain_raw_erc.copy() - self.strain_euu.array[...] = euu - self.strain_evv.array[...] = evv - self.strain_euv.array[...] = euv - - alpha = None - if max_shift is not None: - if self.u_fit is None or self.v_fit is None or self.u is None or self.v is None: - raise ValueError("max_shift masking requires u_fit, v_fit, u, v to be available.") - - ur = self.u_fit.array[:, :, 0] - uc = self.u_fit.array[:, :, 1] - vr = self.v_fit.array[:, :, 0] - vc = self.v_fit.array[:, :, 1] - - du0 = ur - self.u[0] - du1 = uc - self.u[1] - dv0 = vr - self.v[0] - dv1 = vc - self.v[1] - - su = du0 * du0 + du1 * du1 - sv = dv0 * dv0 + dv1 * dv1 - sdist2 = 0.5 * (su + sv) - - smin, smax = max_shift - mask = np.clip((sdist2 - smin) / (smax - smin), 0.0, 1.0) - alpha = 1.0 - mask - - if amp_range is not None: - if self.u_peak_fit is None or self.v_peak_fit is None: - raise ValueError("amp_range masking requires u_peak_fit and v_peak_fit to be available.") - a = 0.5 * (self.u_peak_fit.array[:, :, 2] + self.v_peak_fit.array[:, :, 2]) - amin, amax = amp_range - a_mask = np.clip((a - amin) / (amax - amin), 0.0, 1.0) - alpha = a_mask if alpha is None else alpha * a_mask - - if alpha is not None: - alpha = np.asarray(alpha, dtype=float) - good = alpha > 0 - alpha_im = np.where(good, alpha, 1.0) - else: - good = None - alpha_im = None - - if layout != "horizontal": - raise ValueError("layout must be 'horizontal'") - - ncols = 4 if plot_rotation else 3 - fig, ax = plt.subplots(1, ncols, figsize=figsize) - - cm_strain = plt.get_cmap(cmap_strain).copy() - cm_strain.set_bad(color="black") - cm_rot = plt.get_cmap(cmap_rotation).copy() - cm_rot.set_bad(color="black") - - euu_pct = self.strain_euu.array * 100 - evv_pct = self.strain_evv.array * 100 - euv_pct = self.strain_euv.array * 100 - rot_deg = np.rad2deg(self.strain_rotation.array) - - if good is not None and np.any(good): - euu_m = np.ma.array(euu_pct, mask=~good) - evv_m = np.ma.array(evv_pct, mask=~good) - euv_m = np.ma.array(euv_pct, mask=~good) - rot_m = np.ma.array(rot_deg, mask=~good) - else: - euu_m = euu_pct - evv_m = evv_pct - euv_m = euv_pct - rot_m = rot_deg - - title_fs = 16 - im0 = ax[0].imshow( - euu_m, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cm_strain, - alpha=alpha_im, - ) - ax[1].imshow( - evv_m, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cm_strain, - alpha=alpha_im, - ) - ax[2].imshow( - euv_m, - vmin=strain_range_percent[0], - vmax=strain_range_percent[1], - cmap=cm_strain, - alpha=alpha_im, - ) - - ax[0].set_title(r"$\epsilon_{uu}$", fontsize=title_fs) - ax[1].set_title(r"$\epsilon_{vv}$", fontsize=title_fs) - ax[2].set_title(r"$\epsilon_{uv}$", fontsize=title_fs) - - if plot_rotation: - im3 = ax[3].imshow( - rot_m, - vmin=rotation_range_degrees[0], - vmax=rotation_range_degrees[1], - cmap=cm_rot, - alpha=alpha_im, - ) - ax[3].set_title("Rotation", fontsize=title_fs) - - for a in ax: - a.set_xticks([]) - a.set_yticks([]) - a.set_facecolor("black") - - fig.subplots_adjust(left=0.02, right=0.98, top=0.90, bottom=0.16, wspace=0.03) - - b0 = ax[0].get_position() - b2 = ax[2].get_position() - left = b0.x0 - right = b2.x1 - width = right - left - - b3 = ax[3].get_position() if plot_rotation else None - - cb_height = 0.04 - cb_pad = 0.03 - y = b0.y0 - cb_pad - cb_height - - cax1 = fig.add_axes([left, y, width, cb_height]) - cbar1 = fig.colorbar(im0, cax=cax1, orientation="horizontal") - cbar1.set_label("Strain (%)", fontsize=title_fs) - cbar1.ax.tick_params(labelsize=12) - - if plot_rotation: - left_r = b3.x0 - width_r = b3.x1 - b3.x0 - cax2 = fig.add_axes([left_r, y, width_r, cb_height]) - cbar2 = fig.colorbar(im3, cax=cax2, orientation="horizontal") - cbar2.set_label("Rotation (deg)", fontsize=title_fs) - cbar2.ax.tick_params(labelsize=12) - - for a in ax: - a.set_aspect("equal") - - return fig, ax - def _nice_length_units(target: float) -> float: if not np.isfinite(target) or target <= 0: diff --git a/src/quantem/diffraction/strain_fitting.py b/src/quantem/diffraction/strain_fitting.py new file mode 100644 index 000000000..5610abb9a --- /dev/null +++ b/src/quantem/diffraction/strain_fitting.py @@ -0,0 +1,400 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, TypeVar, cast + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.cm import ScalarMappable +from matplotlib.colors import Normalize +from matplotlib.patches import FancyArrowPatch + +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.datastructures.dataset4d import Dataset4d +from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from quantem.core.io.serialize import AutoSerialize +from quantem.core.visualization.visualization_utils import ScalebarConfig, add_scalebar_to_ax + +if TYPE_CHECKING: + from quantem.diffraction.model_fitting import ModelDiffraction + from quantem.diffraction.strain_autocorrelation import StrainMapAutocorrelation + +StrainParent = TypeVar('StrainParent', bound='StrainFittingMixin') + +class StrainFittingMixin(AutoSerialize): + mask: np.ndarray | None = None + i0_sum_array: np.ndarray + strain_raw_err: Dataset2d + strain_raw_ecc: Dataset2d + strain_raw_erc: Dataset2d + strain_rotation: Dataset2d + + def fit_strain( + self, + ) -> "StrainFittingMixin": + parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) + + if not hasattr(parent, 'u_array') or parent.u_array is None: + raise RuntimeError("u_array not available. Set u_array before calling fit_strain().") + if not hasattr(parent, 'v_array') or parent.v_array is None: + raise RuntimeError("v_array not available. Set v_array before calling fit_strain().") + if not hasattr(parent, 'dataset'): + raise RuntimeError("Parent must have 'dataset' attribute.") + + u_fit = parent.u_array + v_fit = parent.v_array + + if self.mask is None: + u_ref = np.median(u_fit.reshape(-1, 2), axis=0) + v_ref = np.median(v_fit.reshape(-1, 2), axis=0) + else: + m = np.asarray(self.mask, dtype=bool) + u_ref = np.array( + ( + np.median(u_fit[m, 0]), + np.median(u_fit[m, 1]), + ), + dtype=float, + ) + v_ref = np.array( + ( + np.median(v_fit[m, 0]), + np.median(v_fit[m, 1]), + ), + dtype=float, + ) + self.u_ref = u_ref + self.v_ref = v_ref + + scan_r = parent.dataset.shape[0] + scan_c = parent.dataset.shape[1] + + Uref = np.stack((u_ref, v_ref), axis=1).astype(float) + strain_trans = np.zeros((scan_r, scan_c, 2, 2)) + for r in range(scan_r): + for c in range(scan_c): + U = np.stack((u_fit[r, c, :], v_fit[r, c, :]), axis=1) + det = np.linalg.det(U) + if not np.isfinite(det) or abs(det) < 1e-12: + U_inv = np.linalg.pinv(U) + else: + U_inv = np.linalg.inv(U) + strain_trans[r, c, :, :] = Uref @ U_inv + const = 1 + if parent.real_space is False: + const = -1 + + self.strain_raw_err = Dataset2d.from_array( + strain_trans[:, :, 0, 0] - 1, + name="strain err", + signal_units="fractional", + ) + self.strain_raw_ecc = Dataset2d.from_array( + strain_trans[:, :, 1, 1] - 1, + name="strain ecc", + signal_units="fractional", + ) + self.strain_raw_erc = Dataset2d.from_array( + strain_trans[:, :, 1, 0] * 0.5 * const + strain_trans[:, :, 0, 1] * 0.5 * const, + name="strain erc", + signal_units="fractional", + ) + self.strain_rotation = Dataset2d.from_array( + strain_trans[:, :, 1, 0] * -0.5 * const + strain_trans[:, :, 0, 1] * 0.5 * const, + name="strain rotation", + signal_units="fractional", + ) + + return self + + def create_mask( + self, + use_radial_method: bool = False, + min_threshold: float = 0.4, + max_threshold: float = 0.6, + exclusion_radius_fraction: float = 0.1, + smooth: bool = True, + peak_intensity_array: np.ndarray | None = None + ): + parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) + + if not hasattr(parent, 'dataset'): + raise RuntimeError("Parent must have 'dataset' attribute.") + + if not isinstance(parent.dataset, (Dataset4d, Dataset4dstem)): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + + scan_r = parent.dataset.shape[0] + scan_c = parent.dataset.shape[1] + self.mask = np.zeros(parent.dataset.shape[:2]) + + i0_sum_array = np.empty(shape=(scan_r, scan_c)) + + if not isinstance(parent.dataset, ("Dataset4dstem")): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + + if use_radial_method: + center_y, center_x = np.array(parent.dataset.shape[:-2]) / 2 + y, x = np.ogrid[:parent.dataset.shape[-2], :parent.dataset.shape[-1]] + radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) + exclusion_radius = exclusion_radius_fraction * parent.dataset.shape[-1] + for r in range(scan_r): + for c in range(scan_c): + dp = parent.dataset.array[r, c] + outside_mask = radius_map > exclusion_radius + i0_sum_array[r, c] = np.sum(dp[outside_mask]) + else: + md = cast('ModelDiffraction', parent) + if not hasattr(md, 'state_individual_refined') or md.state_individual_refined is None: + raise RuntimeError( + "For non-radial masking, ModelDiffraction must have state_individual_refined. " + "Call fit_individual_diffraction_pattern() first, or use use_radial_method=True." + ) + + for r in range(scan_r): + for c in range(scan_c): + pos_state = md.state_individual_refined[r, c] + if pos_state is None: + i0_sum_array[r, c] = 0.0 + continue + + i0_raw = None + uv_indices = None + for key in pos_state.keys(): + if key.endswith('i0_raw'): + i0_raw = pos_state[key].cpu().numpy() + if key.endswith('uv_indices'): + uv_indices = pos_state[key].cpu().numpy() + + if i0_raw is None or uv_indices is None: + i0_sum_array[r, c] = 0.0 + continue + + is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) + i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) + self.i0_sum_array = i0_sum_array + max_intensity = np.max(i0_sum_array) + if max_intensity == 0: + return np.zeros_like(i0_sum_array) + self.mask = i0_sum_array / max_intensity + self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) + if smooth: + self.mask = np.sin(np.pi / 2 * self.mask) ** 2 + return self + + + def plot_strain( + self, + rotation_angle=20, + strain_range_percent=(-3.0, 3.0), + rotation_range_degrees=(-2.0, 2.0), + plot_rotation=True, + plot_gvecs=True, + plot_scalebar=False, + cmap_strain="RdBu_r", + cmap_rotation="PiYG", + layout="horizontal", + figsize=(6, 6), + **kwargs, + ): + parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) + if not hasattr(self, 'strain_raw_err') or self.strain_raw_err is None: + raise RuntimeError("Call fit_strain() first.") + if not hasattr(self, 'strain_raw_ecc') or self.strain_raw_ecc is None: + raise RuntimeError("Call fit_strain() first.") + if not hasattr(self, 'strain_raw_erc') or self.strain_raw_erc is None: + raise RuntimeError("Call fit_strain() first.") + if not hasattr(self, 'strain_rotation') or self.strain_rotation is None: + raise RuntimeError("Call fit_strain() first.") + + if self.mask is None: + self.mask = np.zeros(parent.dataset.shape[:2]) + + if cmap_rotation is None: + cmap_rotation = cmap_strain + + angle = np.deg2rad(rotation_angle) + c = np.cos(angle) + s = np.sin(angle) + + err = self.strain_raw_err.array + ecc = self.strain_raw_ecc.array + erc = self.strain_raw_erc.array + + euu = err * (c * c) + 2.0 * erc * (c * s) + ecc * (s * s) + evv = err * (s * s) - 2.0 * erc * (c * s) + ecc * (c * c) + euv = (ecc - err) * (c * s) + erc * (c * c - s * s) + + strain_euu = self.strain_raw_err.copy() + strain_evv = self.strain_raw_ecc.copy() + strain_euv = self.strain_raw_erc.copy() + strain_euu.array[...] = euu + strain_evv.array[...] = evv + strain_euv.array[...] = euv + + if layout != "horizontal": + raise ValueError("layout must be 'horizontal'") + + ncols = 4 if plot_rotation else 3 + fig, ax = plt.subplots(1, ncols, figsize=figsize) + + cm_strain = plt.get_cmap(cmap_strain).copy() + cm_strain.set_bad(color="black") + cm_rot = plt.get_cmap(cmap_rotation).copy() + cm_rot.set_bad(color="black") + + euu_pct = strain_euu.array * 100 + evv_pct = strain_evv.array * 100 + euv_pct = strain_euv.array * 100 + rot_deg = np.rad2deg(self.strain_rotation.array) + + from matplotlib.colors import Normalize + norm_strain = Normalize(vmin=strain_range_percent[0], vmax=strain_range_percent[1]) + euu_rgb = cm_strain(norm_strain(euu_pct))[:, :, :3] + evv_rgb = cm_strain(norm_strain(evv_pct))[:, :, :3] + euv_rgb = cm_strain(norm_strain(euv_pct))[:, :, :3] + + title_fs = 16 + ax[0].imshow(euu_rgb * self.mask[:, :, np.newaxis],) + ax[1].imshow(evv_rgb * self.mask[:, :, np.newaxis],) + ax[2].imshow(euv_rgb * self.mask[:, :, np.newaxis],) + + ax[0].set_title(r"$\epsilon_{uu}$ $\updownarrow$", fontsize=title_fs) + ax[1].set_title(r"$\epsilon_{vv}$ $\leftrightarrow$", fontsize=title_fs) + ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow\!\swarrow$", fontsize=title_fs) + + if plot_rotation: + norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) + rot_rgb = cm_rot(norm_rot(rot_deg))[:, :, :3] + ax[3].imshow(rot_rgb * self.mask[:, :, np.newaxis],) + ax[3].set_title(r"Rotation $\circlearrowleft$", fontsize=title_fs) + + for a in ax: + a.set_xticks([]) + a.set_yticks([]) + a.set_facecolor("black") + a.set_aspect("equal") + + if plot_scalebar and isinstance(parent.dataset, Dataset4dstem): + default_sampling = 1.0 + default_units = 'pixels' + scalebar_kwargs = {} + for key, value in kwargs.items(): + if key.startswith('scalebar_'): + scalebar_key = key[len('scalebar_'):] + scalebar_kwargs[scalebar_key] = value + + if hasattr(parent.dataset, 'units'): + if isinstance(parent.dataset.units, (tuple, list)): + default_units = str(parent.dataset.units[0]) + else: + default_units = str(parent.dataset.units) + if hasattr(parent.dataset, 'sampling'): + if isinstance(parent.dataset.sampling, (tuple, list, np.ndarray)): + default_sampling = float(parent.dataset.sampling[0]) + else: + default_sampling = float(parent.dataset.sampling) + scalebar_defaults = { + 'sampling': default_sampling, + 'units': default_units, + 'length': None, + 'width_px': 1, + 'pad_px': 0.5, + 'color': 'black', + 'loc': 'lower left', + 'fontsize': 12, + 'bold': True, + } + scalebar_defaults.update(scalebar_kwargs) + scalebar_config = ScalebarConfig(**scalebar_defaults) + add_scalebar_to_ax( + ax[0], + array_size=int(parent.dataset.shape[0]), + sampling=scalebar_config.sampling, + length_units=scalebar_config.length, + units=scalebar_config.units, + width_px=scalebar_config.width_px, + pad_px=scalebar_config.pad_px, + color=scalebar_config.color, + loc=scalebar_config.loc, + fontsize=scalebar_config.fontsize, + bold=scalebar_config.bold, + ) + + fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) + if plot_rotation: + pos3 = ax[3].get_position() + ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) + b0 = ax[0].get_position() + b2 = ax[2].get_position() + left = b0.x0 + right = b2.x1 + width = right - left + b3 = ax[3].get_position() if plot_rotation else None + + cb_height = 0.02 + cb_pad = 0.02 + y = b0.y0 - cb_pad - cb_height + from matplotlib.cm import ScalarMappable + cax1 = fig.add_axes([left, y, width, cb_height]) + sm_strain = ScalarMappable(norm=norm_strain, cmap=cm_strain) + cbar1 = fig.colorbar(sm_strain, cax=cax1, orientation="horizontal") + cbar1.set_label("Strain (%)", fontsize=title_fs) + cbar1.ax.tick_params(labelsize=12) + + if plot_rotation: + left_r = b3.x0 + width_r = b3.x1 - b3.x0 + cax2 = fig.add_axes([left_r, y, width_r, cb_height]) + sm_rot = ScalarMappable(norm=norm_rot, cmap=cm_rot) + cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation="horizontal") + cbar2.set_label("Rotation (deg)", fontsize=title_fs) + cbar2.ax.tick_params(labelsize=12) + + if plot_gvecs: + from matplotlib.patches import FancyArrowPatch + if not hasattr(parent, 'u_ref') or not hasattr(parent, 'v_ref'): + print("Warning: u_ref and v_ref not found. Call fit_strain() first.") + return fig, ax + if parent.u_ref is None or parent.v_ref is None: + print("Warning: u_ref and v_ref not found. Call fit_strain() first.") + return fig, ax + + last_pos = b3 if plot_rotation else b2 + + ref_width = last_pos.width * 0.8 + ref_left = last_pos.x1 - 0.035 + + ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) + ref_ax.set_xlim(-1.5, 1.5) + ref_ax.set_ylim(-1.5, 1.5) + ref_ax.set_aspect('equal') + ref_ax.axis('off') + u_norm = parent.u_ref / np.linalg.norm(parent.u_ref) + v_norm = parent.v_ref / np.linalg.norm(parent.v_ref) + + u_row, u_col = u_norm + v_row, v_col = v_norm + arrow_props_ref = dict(arrowstyle='->', lw=3, mutation_scale=25) + + u_arrow = FancyArrowPatch( + (0, 0), (u_col, -u_row), + color='darkred', **arrow_props_ref + ) + ref_ax.add_patch(u_arrow) + + v_arrow = FancyArrowPatch( + (0, 0), (v_col, -v_row), + color='darkblue', **arrow_props_ref + ) + ref_ax.add_patch(v_arrow) + ref_ax.text(u_col * 1.3, -u_row * 1.3, r'$\mathbf{u}_{ref}$', + fontsize=14, fontweight='bold', color='darkred', + ha='center', va='center') + + ref_ax.text(v_col * 1.3, -v_row * 1.3, r'$\mathbf{v}_{ref}$', + fontsize=14, fontweight='bold', color='darkblue', + ha='center', va='center') + + + return fig, ax + From 3062f150eb7203171241590bd5f934d914b66132 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Mon, 25 May 2026 21:29:30 -0700 Subject: [PATCH 073/113] strain fitting class, updated cepstral and plotting --- src/quantem/diffraction/model_fitting.py | 4 +- .../diffraction/strain_autocorrelation.py | 9 +- ...ain_fitting.py => strain_fitting_mixin.py} | 113 +++++++++++------- 3 files changed, 81 insertions(+), 45 deletions(-) rename src/quantem/diffraction/{strain_fitting.py => strain_fitting_mixin.py} (83%) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 5f46a3794..66197ead9 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -22,8 +22,8 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.optimizer_mixin import OptimizerType, SchedulerType from quantem.core.utils.imaging_utils import cross_correlation_shift -from quantem.core.visualization.visualization_utils import ScalebarConfig, add_scalebar_to_ax from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations +from quantem.diffraction.strain_fitting_mixin import StrainFittingMixin def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: @@ -36,7 +36,7 @@ def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) return float(cast(float | int, value)) -class ModelDiffraction(ModelDiffractionVisualizations, FitBase, AutoSerialize): +class ModelDiffraction(ModelDiffractionVisualizations, StrainFittingMixin, FitBase, AutoSerialize): _token = object() DEFAULT_LR = 5e-2 DEFAULT_OPTIMIZER_TYPE = "adam" diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index e42d7723f..bbe1294ac 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -17,9 +17,10 @@ from quantem.core.utils.utils import electron_wavelength_angstrom from quantem.core.utils.validators import ensure_valid_array from quantem.core.visualization import ScalebarConfig, show_2d +from quantem.diffraction.strain_fitting_mixin import StrainFittingMixin -class StrainMapAutocorrelation(AutoSerialize): +class StrainMapAutocorrelation(AutoSerialize, StrainFittingMixin): _token = object() def __init__( @@ -306,6 +307,8 @@ def plot_single_transform( ): if self.transform is None or self.transform_rotated is None: raise ValueError("Run preprocess() first to compute transform images.") + if self.u_peak_fit is None or self.v_peak_fit is None: + raise ValueError("Run fit_lattice_vectors() first.") sampling = np.mean(self.metadata["sampling_real"]) units = self.metadata.get("real_units", r"$\mathrm{\AA}$") @@ -361,8 +364,8 @@ def plot_single_transform( _overlay_lattice_vectors( ax=ax, shape=self.transform.shape, - u_rc= self.u_fit.array[row, col, :], - v_rc=self.v_fit.array[row, col, :], + u_rc= self.u_peak_fit.array[row, col, :2], + v_rc=self.v_peak_fit.array[row, col, :2], rot_ccw_deg=rot_ccw, q_transpose=q_transpose, peaks_plot=self.mean_img_peaks, diff --git a/src/quantem/diffraction/strain_fitting.py b/src/quantem/diffraction/strain_fitting_mixin.py similarity index 83% rename from src/quantem/diffraction/strain_fitting.py rename to src/quantem/diffraction/strain_fitting_mixin.py index 5610abb9a..32300e899 100644 --- a/src/quantem/diffraction/strain_fitting.py +++ b/src/quantem/diffraction/strain_fitting_mixin.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, TypeVar, cast +from typing import TYPE_CHECKING, TypeVar, cast import matplotlib.pyplot as plt import numpy as np @@ -11,7 +11,6 @@ from quantem.core.datastructures.dataset2d import Dataset2d from quantem.core.datastructures.dataset4d import Dataset4d from quantem.core.datastructures.dataset4dstem import Dataset4dstem -from quantem.core.io.serialize import AutoSerialize from quantem.core.visualization.visualization_utils import ScalebarConfig, add_scalebar_to_ax if TYPE_CHECKING: @@ -20,7 +19,7 @@ StrainParent = TypeVar('StrainParent', bound='StrainFittingMixin') -class StrainFittingMixin(AutoSerialize): +class StrainFittingMixin: mask: np.ndarray | None = None i0_sum_array: np.ndarray strain_raw_err: Dataset2d @@ -30,6 +29,7 @@ class StrainFittingMixin(AutoSerialize): def fit_strain( self, + plot_strain = False, ) -> "StrainFittingMixin": parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) @@ -103,7 +103,8 @@ def fit_strain( name="strain rotation", signal_units="fractional", ) - + if plot_strain: + self.plot_strain() return self def create_mask( @@ -113,7 +114,6 @@ def create_mask( max_threshold: float = 0.6, exclusion_radius_fraction: float = 0.1, smooth: bool = True, - peak_intensity_array: np.ndarray | None = None ): parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) @@ -193,7 +193,7 @@ def plot_strain( cmap_strain="RdBu_r", cmap_rotation="PiYG", layout="horizontal", - figsize=(6, 6), + figsize=None, **kwargs, ): parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) @@ -212,6 +212,9 @@ def plot_strain( if cmap_rotation is None: cmap_rotation = cmap_strain + if layout not in ["horizontal", "vertical"]: + raise ValueError("layout must be 'horizontal' or 'vertical'") + angle = np.deg2rad(rotation_angle) c = np.cos(angle) s = np.sin(angle) @@ -231,11 +234,16 @@ def plot_strain( strain_evv.array[...] = evv strain_euv.array[...] = euv - if layout != "horizontal": - raise ValueError("layout must be 'horizontal'") - ncols = 4 if plot_rotation else 3 - fig, ax = plt.subplots(1, ncols, figsize=figsize) + is_horizontal = layout == "horizontal" + + if figsize is None: + figsize = (6, 6) if is_horizontal else (6, 8) + + if is_horizontal: + fig, ax = plt.subplots(1, ncols, figsize=figsize) + else: + fig, ax = plt.subplots(ncols, 1, figsize=figsize) cm_strain = plt.get_cmap(cmap_strain).copy() cm_strain.set_bad(color="black") @@ -247,7 +255,6 @@ def plot_strain( euv_pct = strain_euv.array * 100 rot_deg = np.rad2deg(self.strain_rotation.array) - from matplotlib.colors import Normalize norm_strain = Normalize(vmin=strain_range_percent[0], vmax=strain_range_percent[1]) euu_rgb = cm_strain(norm_strain(euu_pct))[:, :, :3] evv_rgb = cm_strain(norm_strain(evv_pct))[:, :, :3] @@ -260,7 +267,7 @@ def plot_strain( ax[0].set_title(r"$\epsilon_{uu}$ $\updownarrow$", fontsize=title_fs) ax[1].set_title(r"$\epsilon_{vv}$ $\leftrightarrow$", fontsize=title_fs) - ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow\!\swarrow$", fontsize=title_fs) + ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow\!\!\!\!\swarrow$", fontsize=title_fs) if plot_rotation: norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) @@ -320,38 +327,61 @@ def plot_strain( bold=scalebar_config.bold, ) - fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) - if plot_rotation: - pos3 = ax[3].get_position() - ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) - b0 = ax[0].get_position() - b2 = ax[2].get_position() - left = b0.x0 - right = b2.x1 - width = right - left - b3 = ax[3].get_position() if plot_rotation else None - - cb_height = 0.02 - cb_pad = 0.02 - y = b0.y0 - cb_pad - cb_height - from matplotlib.cm import ScalarMappable - cax1 = fig.add_axes([left, y, width, cb_height]) + if is_horizontal: + fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) + if plot_rotation: + pos3 = ax[3].get_position() + ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) + + cb_orientation = "horizontal" + cb_size = 0.02 + cb_pad = 0.02 + + b0 = ax[0].get_position() + b2 = ax[2].get_position() + strain_cb_pos = [b0.x0, b0.y0 - cb_pad - cb_size, b2.x1 - b0.x0, cb_size] + + if plot_rotation: + b3 = ax[3].get_position() + rot_cb_pos = [b3.x0, b0.y0 - cb_pad - cb_size, b3.x1 - b3.x0, cb_size] + last_pos = b3 + else: + rot_cb_pos = None + last_pos = b2 + + else: + fig.subplots_adjust(left=0.04, right=0.80, top=0.98, bottom=0.04, hspace=0.15) + + cb_orientation = "vertical" + cb_size = 0.02 + cb_pad = 0.02 + + b0 = ax[0].get_position() + b2 = ax[2].get_position() + strain_cb_pos = [b0.x1 + cb_pad, b2.y0, cb_size, b0.y1 - b2.y0] + + if plot_rotation: + b3 = ax[3].get_position() + rot_cb_pos = [b0.x1 + cb_pad, b3.y0, cb_size, b3.y1 - b3.y0] + last_pos = b3 + else: + rot_cb_pos = None + last_pos = b2 + + cax1 = fig.add_axes(strain_cb_pos) sm_strain = ScalarMappable(norm=norm_strain, cmap=cm_strain) - cbar1 = fig.colorbar(sm_strain, cax=cax1, orientation="horizontal") + cbar1 = fig.colorbar(sm_strain, cax=cax1, orientation=cb_orientation) cbar1.set_label("Strain (%)", fontsize=title_fs) cbar1.ax.tick_params(labelsize=12) - if plot_rotation: - left_r = b3.x0 - width_r = b3.x1 - b3.x0 - cax2 = fig.add_axes([left_r, y, width_r, cb_height]) + if plot_rotation and rot_cb_pos is not None: + cax2 = fig.add_axes(rot_cb_pos) sm_rot = ScalarMappable(norm=norm_rot, cmap=cm_rot) - cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation="horizontal") + cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation=cb_orientation) cbar2.set_label("Rotation (deg)", fontsize=title_fs) cbar2.ax.tick_params(labelsize=12) if plot_gvecs: - from matplotlib.patches import FancyArrowPatch if not hasattr(parent, 'u_ref') or not hasattr(parent, 'v_ref'): print("Warning: u_ref and v_ref not found. Call fit_strain() first.") return fig, ax @@ -359,12 +389,15 @@ def plot_strain( print("Warning: u_ref and v_ref not found. Call fit_strain() first.") return fig, ax - last_pos = b3 if plot_rotation else b2 - - ref_width = last_pos.width * 0.8 - ref_left = last_pos.x1 - 0.035 + if is_horizontal: + ref_width = last_pos.width * 0.8 + ref_left = last_pos.x1 - 0.035 + ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) + else: + ref_height = last_pos.height * 0.8 + ref_bottom = last_pos.y0 + 0.02 + ref_ax = fig.add_axes([last_pos.x0, ref_bottom, last_pos.width, ref_height]) - ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) ref_ax.set_xlim(-1.5, 1.5) ref_ax.set_ylim(-1.5, 1.5) ref_ax.set_aspect('equal') From a9df33596f776e24d1193093c0336c410a342eb3 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Tue, 26 May 2026 23:31:42 -0700 Subject: [PATCH 074/113] updated strain class and implementation in model_fitting --- src/quantem/diffraction/model_fitting.py | 162 ++++++++----- .../diffraction/strain_autocorrelation.py | 96 +++++++- ...tting_mixin.py => strain_visualization.py} | 226 +++++++----------- 3 files changed, 287 insertions(+), 197 deletions(-) rename src/quantem/diffraction/{strain_fitting_mixin.py => strain_visualization.py} (62%) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 66197ead9..1600f4217 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -20,10 +20,9 @@ ) from quantem.core.fitting.diffraction import DiskTemplate, SyntheticDiskLattice from quantem.core.io.serialize import AutoSerialize -from quantem.core.ml.optimizer_mixin import OptimizerType, SchedulerType from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations -from quantem.diffraction.strain_fitting_mixin import StrainFittingMixin +from quantem.diffraction.strain_visualization import StrainFitting def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: @@ -36,7 +35,7 @@ def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) return float(cast(float | int, value)) -class ModelDiffraction(ModelDiffractionVisualizations, StrainFittingMixin, FitBase, AutoSerialize): +class ModelDiffraction(ModelDiffractionVisualizations, FitBase, AutoSerialize): _token = object() DEFAULT_LR = 5e-2 DEFAULT_OPTIMIZER_TYPE = "adam" @@ -982,65 +981,112 @@ def render_indivdual_pattern(self, row, col): return self._render_state_array(self.state_individual_refined[row, col]) - # def create_mask( - # self, - # use_radial_method: bool = False, - # min_threshold: float = 0.4, - # max_threshold: float = 0.6, - # exclusion_radius_fraction: float = 0.1, - # smooth: bool = True, - # ): - # scan_r = self.dataset.shape[0] - # scan_c = self.dataset.shape[1] - # self.mask = np.zeros(self.dataset.shape[:2]) + def create_mask( + self, + use_radial_method: bool = False, + min_threshold: float = 0.4, + max_threshold: float = 0.6, + exclusion_radius_fraction: float = 0.1, + smooth: bool = True, + ): + scan_r = self.dataset.shape[0] + scan_c = self.dataset.shape[1] + self.mask = np.zeros(self.dataset.shape[:2]) - # self.i0_sum_array = np.empty(shape=(scan_r, scan_c)) + self.i0_sum_array = np.empty(shape=(scan_r, scan_c)) - # if self.state_individual_refined is None: - # raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") - # if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): - # raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - - # if use_radial_method: - # center_y, center_x = np.array(self.dataset.shape[:-2]) / 2 - # y, x = np.ogrid[:self.dataset.shape[-2], :self.dataset.shape[-1]] - # radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) - # exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] - # for r in range(scan_r): - # for c in range(scan_c): - # dp = self.dataset.array[r, c] - # outside_mask = radius_map > exclusion_radius - # self.i0_sum_array[r, c] = np.sum(dp[outside_mask]) - # else: - # for r in range(scan_r): - # for c in range(scan_c): - # pos_state = self.state_individual_refined[r, c] - # if pos_state is None: - # self.i0_sum_array[r, c] = 0.0 - # continue - - # i0_raw = None - # uv_indices = None - # for key in pos_state.keys(): - # if key.endswith('i0_raw'): - # i0_raw = pos_state[key].cpu().numpy() - # if key.endswith('uv_indices'): - # uv_indices = pos_state[key].cpu().numpy() + if self.state_individual_refined is None: + raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") + if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + + if use_radial_method: + center_y, center_x = np.array(self.dataset.shape[:-2]) / 2 + y, x = np.ogrid[:self.dataset.shape[-2], :self.dataset.shape[-1]] + radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) + exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] + for r in range(scan_r): + for c in range(scan_c): + dp = self.dataset.array[r, c] + outside_mask = radius_map > exclusion_radius + self.i0_sum_array[r, c] = np.sum(dp[outside_mask]) + else: + for r in range(scan_r): + for c in range(scan_c): + pos_state = self.state_individual_refined[r, c] + if pos_state is None: + self.i0_sum_array[r, c] = 0.0 + continue + + i0_raw = None + uv_indices = None + for key in pos_state.keys(): + if key.endswith('i0_raw'): + i0_raw = pos_state[key].cpu().numpy() + if key.endswith('uv_indices'): + uv_indices = pos_state[key].cpu().numpy() - # if i0_raw is None or uv_indices is None: - # self.i0_sum_array[r, c] = 0.0 - # continue + if i0_raw is None or uv_indices is None: + self.i0_sum_array[r, c] = 0.0 + continue - # is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) - # self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) - # max_intensity = np.max(self.i0_sum_array) - # if max_intensity == 0: - # return np.zeros_like(self.i0_sum_array) - # self.mask = self.i0_sum_array / max_intensity - # self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) - # if smooth: - # self.mask = np.sin(np.pi / 2 * self.mask) ** 2 - # return self + is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) + self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) + max_intensity = np.max(self.i0_sum_array) + if max_intensity == 0: + return np.zeros_like(self.i0_sum_array) + self.mask = self.i0_sum_array / max_intensity + self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) + if smooth: + self.mask = np.sin(np.pi / 2 * self.mask) ** 2 + return self + + def initilize_strain_class( + self, + u_ref: np.ndarray | None = None, + v_ref: np.ndarray | None = None, + )->StrainFitting: + if self.u_array is None or self.v_array is None: + raise RuntimeWarning("Need to run fit_lattice_vectors before initilizing strain class") + if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + + default_units = None + default_sampling = None + if hasattr(self.dataset, 'units'): + if isinstance(self.dataset.units, (tuple, list)): + default_units = str(self.dataset.units[0]) + else: + default_units = str(self.dataset.units) + if hasattr(self.dataset, 'sampling'): + if isinstance(self.dataset.sampling, (tuple, list, np.ndarray)): + default_sampling = float(self.dataset.sampling[0]) + else: + default_sampling = float(self.dataset.sampling) + + if self.mask is not None: + return StrainFitting( + u_array = self.u_array, + v_array = self.v_array, + ds_shape = self.dataset.shape, + real_space = self.real_space, + u_ref = u_ref, + v_ref = v_ref, + mask = self.mask, + ds_sampling=default_sampling, + ds_units = default_units, + ) + else: + return StrainFitting( + u_array = self.u_array, + v_array = self.v_array, + ds_shape = self.dataset.shape, + real_space = self.real_space, + u_ref = u_ref, + v_ref = v_ref, + ds_sampling=default_sampling, + ds_units = default_units, + ) @property def render_mean_refined(self) -> np.ndarray: diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index bbe1294ac..79fa4de0e 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -17,10 +17,10 @@ from quantem.core.utils.utils import electron_wavelength_angstrom from quantem.core.utils.validators import ensure_valid_array from quantem.core.visualization import ScalebarConfig, show_2d -from quantem.diffraction.strain_fitting_mixin import StrainFittingMixin +from quantem.diffraction.strain_visualization import StrainFitting -class StrainMapAutocorrelation(AutoSerialize, StrainFittingMixin): +class StrainMapAutocorrelation(AutoSerialize): _token = object() def __init__( @@ -51,6 +51,7 @@ def __init__( self.v_ref: np.ndarray | None = None self.u_array: np.ndarray | None = None self.v_array: np.ndarray | None = None + self.mask: np.ndarray | None = None self.real_space = True @@ -544,6 +545,97 @@ def fit_lattice_vectors( return self + def create_mask( + self, + use_radial_method: bool = False, + min_threshold: float = 0.4, + max_threshold: float = 0.6, + exclusion_radius_fraction: float = 0.1, + smooth: bool = True, + ): + if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + + scan_r = self.dataset.shape[0] + scan_c = self.dataset.shape[1] + self.mask = np.ones(self.dataset.shape[:2]) + + i0_sum_array = np.empty(shape=(scan_r, scan_c)) + + if use_radial_method: + center_y, center_x = np.array(self.dataset.shape[:-2]) / 2 + y, x = np.ogrid[:self.dataset.shape[-2], :self.dataset.shape[-1]] + radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) + exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] + for r in range(scan_r): + for c in range(scan_c): + dp = self.dataset.array[r, c] + outside_mask = radius_map > exclusion_radius + i0_sum_array[r, c] = np.sum(dp[outside_mask]) + else: + if self.u_peak_fit is None or self.v_peak_fit is None: + raise RuntimeError("For intensity-based masking, run fit_lattice_vectors() first.") + u_amplitudes = self.u_peak_fit.array[:, :, 2] + v_amplitudes = self.v_peak_fit.array[:, :, 2] + i0_sum_array = (u_amplitudes + v_amplitudes) / 2.0 + + max_intensity = np.max(i0_sum_array) + if max_intensity == 0: + return np.ones_like(i0_sum_array) + self.mask = i0_sum_array / max_intensity + self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) + if smooth: + self.mask = np.sin(np.pi / 2 * self.mask) ** 2 + return self + + + def initilize_strain_class( + self, + u_ref: np.ndarray | None = None, + v_ref: np.ndarray | None = None, + )->StrainFitting: + if self.u_array is None or self.v_array is None: + raise RuntimeWarning("Need to run fit_lattice_vectors before initilizing strain class") + if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + + default_units = None + default_sampling = None + if hasattr(self.dataset, 'units'): + if isinstance(self.dataset.units, (tuple, list)): + default_units = str(self.dataset.units[0]) + else: + default_units = str(self.dataset.units) + if hasattr(self.dataset, 'sampling'): + if isinstance(self.dataset.sampling, (tuple, list, np.ndarray)): + default_sampling = float(self.dataset.sampling[0]) + else: + default_sampling = float(self.dataset.sampling) + + if self.mask is not None: + return StrainFitting( + u_array = self.u_array, + v_array = self.v_array, + ds_shape = self.dataset.shape, + real_space = self.real_space, + u_ref = u_ref, + v_ref = v_ref, + mask = self.mask, + ds_sampling=default_sampling, + ds_units = default_units, + ) + else: + return StrainFitting( + u_array = self.u_array, + v_array = self.v_array, + ds_shape = self.dataset.shape, + real_space = self.real_space, + u_ref = u_ref, + v_ref = v_ref, + ds_sampling=default_sampling, + ds_units = default_units, + ) + def plot_lattice_vectors( self, subtract_mean: bool = True, diff --git a/src/quantem/diffraction/strain_fitting_mixin.py b/src/quantem/diffraction/strain_visualization.py similarity index 62% rename from src/quantem/diffraction/strain_fitting_mixin.py rename to src/quantem/diffraction/strain_visualization.py index 32300e899..f5c7ee35c 100644 --- a/src/quantem/diffraction/strain_fitting_mixin.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar, cast - import matplotlib.pyplot as plt import numpy as np from matplotlib.cm import ScalarMappable @@ -9,64 +7,106 @@ from matplotlib.patches import FancyArrowPatch from quantem.core.datastructures.dataset2d import Dataset2d -from quantem.core.datastructures.dataset4d import Dataset4d -from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from quantem.core.io.serialize import AutoSerialize from quantem.core.visualization.visualization_utils import ScalebarConfig, add_scalebar_to_ax -if TYPE_CHECKING: - from quantem.diffraction.model_fitting import ModelDiffraction - from quantem.diffraction.strain_autocorrelation import StrainMapAutocorrelation - -StrainParent = TypeVar('StrainParent', bound='StrainFittingMixin') -class StrainFittingMixin: +class StrainFitting(AutoSerialize): mask: np.ndarray | None = None - i0_sum_array: np.ndarray + real_space: bool = False + strain_raw_err: Dataset2d strain_raw_ecc: Dataset2d strain_raw_erc: Dataset2d strain_rotation: Dataset2d + u_ref: np.ndarray | None = None + v_ref: np.ndarray | None = None + u_array: np.ndarray + v_array: np.ndarray + + ds_sampling = 1.0 + ds_units = 'pixels' + ds_shape: tuple[int, ...] + + + def __init__( + self, + u_array: np.ndarray, + v_array: np.ndarray, + ds_shape: tuple[int, ...], + real_space: bool, + + u_ref: np.ndarray | None = None, + v_ref: np.ndarray | None = None, + mask: np.ndarray | None = None, + + ds_sampling: float | None = None, + ds_units: str | None = None, + ): + super().__init__() + self.u_array = u_array + self.v_array = v_array + + self.ds_shape = ds_shape + self.real_space = real_space + + self.ds_sampling = 1.0 if ds_sampling is None else ds_sampling + self.ds_units = 'pixels' if ds_units is None else ds_units + + self.mask = np.ones(ds_shape[:2]) if mask is None else mask + self.mask = (self.mask - np.min(self.mask))/np.linalg.norm(self.mask) + + self.u_ref = u_ref if u_ref is not None else None + self.v_ref = v_ref if v_ref is not None else None + + + def fit_strain( self, plot_strain = False, - ) -> "StrainFittingMixin": - parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) + strain_mask: np.ndarray | None = None, + ) -> "StrainFitting": - if not hasattr(parent, 'u_array') or parent.u_array is None: + if self.u_array is None: raise RuntimeError("u_array not available. Set u_array before calling fit_strain().") - if not hasattr(parent, 'v_array') or parent.v_array is None: + if self.v_array is None: raise RuntimeError("v_array not available. Set v_array before calling fit_strain().") - if not hasattr(parent, 'dataset'): - raise RuntimeError("Parent must have 'dataset' attribute.") - u_fit = parent.u_array - v_fit = parent.v_array + u_fit = self.u_array + v_fit = self.v_array - if self.mask is None: + if strain_mask is not None: + m = np.asarray((strain_mask==1), dtype=bool) + u_ref = np.array( + (np.median(u_fit[m, 0]), np.median(u_fit[m, 1])), + dtype=float, + ) + v_ref = np.array( + (np.median(v_fit[m, 0]), np.median(v_fit[m, 1])), + dtype=float, + ) + elif self.mask is None: u_ref = np.median(u_fit.reshape(-1, 2), axis=0) v_ref = np.median(v_fit.reshape(-1, 2), axis=0) else: - m = np.asarray(self.mask, dtype=bool) + m = np.asarray((self.mask==1), dtype=bool) u_ref = np.array( - ( - np.median(u_fit[m, 0]), - np.median(u_fit[m, 1]), - ), + (np.median(u_fit[m, 0]), np.median(u_fit[m, 1])), dtype=float, ) v_ref = np.array( - ( - np.median(v_fit[m, 0]), - np.median(v_fit[m, 1]), - ), + (np.median(v_fit[m, 0]), np.median(v_fit[m, 1])), dtype=float, ) + if self.u_ref is not None: + u_ref = self.u_ref + if self.v_ref is not None: + v_ref = self.v_ref self.u_ref = u_ref self.v_ref = v_ref - - scan_r = parent.dataset.shape[0] - scan_c = parent.dataset.shape[1] + scan_r = self.ds_shape[0] + scan_c = self.ds_shape[1] Uref = np.stack((u_ref, v_ref), axis=1).astype(float) strain_trans = np.zeros((scan_r, scan_c, 2, 2)) @@ -80,7 +120,7 @@ def fit_strain( U_inv = np.linalg.inv(U) strain_trans[r, c, :, :] = Uref @ U_inv const = 1 - if parent.real_space is False: + if self.real_space is False: const = -1 self.strain_raw_err = Dataset2d.from_array( @@ -106,80 +146,6 @@ def fit_strain( if plot_strain: self.plot_strain() return self - - def create_mask( - self, - use_radial_method: bool = False, - min_threshold: float = 0.4, - max_threshold: float = 0.6, - exclusion_radius_fraction: float = 0.1, - smooth: bool = True, - ): - parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) - - if not hasattr(parent, 'dataset'): - raise RuntimeError("Parent must have 'dataset' attribute.") - - if not isinstance(parent.dataset, (Dataset4d, Dataset4dstem)): - raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - - scan_r = parent.dataset.shape[0] - scan_c = parent.dataset.shape[1] - self.mask = np.zeros(parent.dataset.shape[:2]) - - i0_sum_array = np.empty(shape=(scan_r, scan_c)) - - if not isinstance(parent.dataset, ("Dataset4dstem")): - raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - - if use_radial_method: - center_y, center_x = np.array(parent.dataset.shape[:-2]) / 2 - y, x = np.ogrid[:parent.dataset.shape[-2], :parent.dataset.shape[-1]] - radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) - exclusion_radius = exclusion_radius_fraction * parent.dataset.shape[-1] - for r in range(scan_r): - for c in range(scan_c): - dp = parent.dataset.array[r, c] - outside_mask = radius_map > exclusion_radius - i0_sum_array[r, c] = np.sum(dp[outside_mask]) - else: - md = cast('ModelDiffraction', parent) - if not hasattr(md, 'state_individual_refined') or md.state_individual_refined is None: - raise RuntimeError( - "For non-radial masking, ModelDiffraction must have state_individual_refined. " - "Call fit_individual_diffraction_pattern() first, or use use_radial_method=True." - ) - - for r in range(scan_r): - for c in range(scan_c): - pos_state = md.state_individual_refined[r, c] - if pos_state is None: - i0_sum_array[r, c] = 0.0 - continue - - i0_raw = None - uv_indices = None - for key in pos_state.keys(): - if key.endswith('i0_raw'): - i0_raw = pos_state[key].cpu().numpy() - if key.endswith('uv_indices'): - uv_indices = pos_state[key].cpu().numpy() - - if i0_raw is None or uv_indices is None: - i0_sum_array[r, c] = 0.0 - continue - - is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) - i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) - self.i0_sum_array = i0_sum_array - max_intensity = np.max(i0_sum_array) - if max_intensity == 0: - return np.zeros_like(i0_sum_array) - self.mask = i0_sum_array / max_intensity - self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) - if smooth: - self.mask = np.sin(np.pi / 2 * self.mask) ** 2 - return self def plot_strain( @@ -196,7 +162,6 @@ def plot_strain( figsize=None, **kwargs, ): - parent = cast('ModelDiffraction | StrainMapAutocorrelation', self) if not hasattr(self, 'strain_raw_err') or self.strain_raw_err is None: raise RuntimeError("Call fit_strain() first.") if not hasattr(self, 'strain_raw_ecc') or self.strain_raw_ecc is None: @@ -207,7 +172,7 @@ def plot_strain( raise RuntimeError("Call fit_strain() first.") if self.mask is None: - self.mask = np.zeros(parent.dataset.shape[:2]) + self.mask = np.zeros(self.ds_shape[:2]) if cmap_rotation is None: cmap_rotation = cmap_strain @@ -267,7 +232,7 @@ def plot_strain( ax[0].set_title(r"$\epsilon_{uu}$ $\updownarrow$", fontsize=title_fs) ax[1].set_title(r"$\epsilon_{vv}$ $\leftrightarrow$", fontsize=title_fs) - ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow\!\!\!\!\swarrow$", fontsize=title_fs) + ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow\!\!\!\!\!\!\!\!\!\:\swarrow$", fontsize=title_fs) if plot_rotation: norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) @@ -281,28 +246,16 @@ def plot_strain( a.set_facecolor("black") a.set_aspect("equal") - if plot_scalebar and isinstance(parent.dataset, Dataset4dstem): - default_sampling = 1.0 - default_units = 'pixels' + if plot_scalebar: scalebar_kwargs = {} for key, value in kwargs.items(): if key.startswith('scalebar_'): scalebar_key = key[len('scalebar_'):] scalebar_kwargs[scalebar_key] = value - if hasattr(parent.dataset, 'units'): - if isinstance(parent.dataset.units, (tuple, list)): - default_units = str(parent.dataset.units[0]) - else: - default_units = str(parent.dataset.units) - if hasattr(parent.dataset, 'sampling'): - if isinstance(parent.dataset.sampling, (tuple, list, np.ndarray)): - default_sampling = float(parent.dataset.sampling[0]) - else: - default_sampling = float(parent.dataset.sampling) scalebar_defaults = { - 'sampling': default_sampling, - 'units': default_units, + 'sampling': self.ds_sampling, + 'units': self.ds_units, 'length': None, 'width_px': 1, 'pad_px': 0.5, @@ -315,7 +268,7 @@ def plot_strain( scalebar_config = ScalebarConfig(**scalebar_defaults) add_scalebar_to_ax( ax[0], - array_size=int(parent.dataset.shape[0]), + array_size=int(self.ds_shape[0]), sampling=scalebar_config.sampling, length_units=scalebar_config.length, units=scalebar_config.units, @@ -382,10 +335,7 @@ def plot_strain( cbar2.ax.tick_params(labelsize=12) if plot_gvecs: - if not hasattr(parent, 'u_ref') or not hasattr(parent, 'v_ref'): - print("Warning: u_ref and v_ref not found. Call fit_strain() first.") - return fig, ax - if parent.u_ref is None or parent.v_ref is None: + if self.u_ref is None or self.v_ref is None: print("Warning: u_ref and v_ref not found. Call fit_strain() first.") return fig, ax @@ -394,16 +344,18 @@ def plot_strain( ref_left = last_pos.x1 - 0.035 ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) else: - ref_height = last_pos.height * 0.8 - ref_bottom = last_pos.y0 + 0.02 - ref_ax = fig.add_axes([last_pos.x0, ref_bottom, last_pos.width, ref_height]) + ref_height = last_pos.height * 0.5 + ref_bottom = last_pos.y0 - ref_height - 0.05 + ref_width = last_pos.width * 0.8 + ref_left = last_pos.x0 + (last_pos.width - ref_width) / 2 # Center it + ref_ax = fig.add_axes([ref_left, ref_bottom, ref_width, ref_height]) ref_ax.set_xlim(-1.5, 1.5) ref_ax.set_ylim(-1.5, 1.5) ref_ax.set_aspect('equal') ref_ax.axis('off') - u_norm = parent.u_ref / np.linalg.norm(parent.u_ref) - v_norm = parent.v_ref / np.linalg.norm(parent.v_ref) + u_norm = self.u_ref / np.linalg.norm(self.u_ref) + v_norm = self.v_ref / np.linalg.norm(self.v_ref) u_row, u_col = u_norm v_row, v_col = v_norm @@ -420,11 +372,11 @@ def plot_strain( color='darkblue', **arrow_props_ref ) ref_ax.add_patch(v_arrow) - ref_ax.text(u_col * 1.3, -u_row * 1.3, r'$\mathbf{u}_{ref}$', + ref_ax.text(u_col * 1.3, -u_row * 1.3, r'$\mathbf{g}_{1}$', fontsize=14, fontweight='bold', color='darkred', ha='center', va='center') - ref_ax.text(v_col * 1.3, -v_row * 1.3, r'$\mathbf{v}_{ref}$', + ref_ax.text(v_col * 1.3, -v_row * 1.3, r'$\mathbf{g}_{2}$', fontsize=14, fontweight='bold', color='darkblue', ha='center', va='center') From a3d9a25498b65604f63acfe7c34c365c92bc6e72 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Wed, 27 May 2026 11:58:21 -0700 Subject: [PATCH 075/113] editing model fitting to match with strain visualization class --- src/quantem/diffraction/model_fitting.py | 39 +++++++------------ .../diffraction/strain_visualization.py | 2 +- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 1600f4217..a5ccb4ed0 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -940,12 +940,11 @@ def fit_individual_diffraction_pattern_batched( def get_individual_uv_vectors(self) -> "ModelDiffraction": scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] - # print(scan_r) self.u_array = np.empty(shape=(scan_r, scan_c, 2)) self.v_array = np.empty(shape=(scan_r, scan_c, 2)) if self.state_individual_refined is None: - raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") + raise RuntimeError("Call .fit_individual_diffraction_pattern(...) on all patterns first.") for r in range(scan_r): for c in range(scan_c): pos_state = self.state_individual_refined[r,c] @@ -1047,7 +1046,7 @@ def initilize_strain_class( v_ref: np.ndarray | None = None, )->StrainFitting: if self.u_array is None or self.v_array is None: - raise RuntimeWarning("Need to run fit_lattice_vectors before initilizing strain class") + self.get_individual_uv_vectors() if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") @@ -1064,29 +1063,17 @@ def initilize_strain_class( else: default_sampling = float(self.dataset.sampling) - if self.mask is not None: - return StrainFitting( - u_array = self.u_array, - v_array = self.v_array, - ds_shape = self.dataset.shape, - real_space = self.real_space, - u_ref = u_ref, - v_ref = v_ref, - mask = self.mask, - ds_sampling=default_sampling, - ds_units = default_units, - ) - else: - return StrainFitting( - u_array = self.u_array, - v_array = self.v_array, - ds_shape = self.dataset.shape, - real_space = self.real_space, - u_ref = u_ref, - v_ref = v_ref, - ds_sampling=default_sampling, - ds_units = default_units, - ) + return StrainFitting( + u_array = self.u_array, + v_array = self.v_array, + ds_shape = self.dataset.shape, + real_space = self.real_space, + u_ref = u_ref, + v_ref = v_ref, + mask = self.mask, + ds_sampling=default_sampling, + ds_units = default_units, + ) @property def render_mean_refined(self) -> np.ndarray: diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index f5c7ee35c..b080f3d36 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -55,7 +55,7 @@ def __init__( self.ds_units = 'pixels' if ds_units is None else ds_units self.mask = np.ones(ds_shape[:2]) if mask is None else mask - self.mask = (self.mask - np.min(self.mask))/np.linalg.norm(self.mask) + self.mask = (self.mask - np.min(self.mask))/np.max(self.mask) self.u_ref = u_ref if u_ref is not None else None self.v_ref = v_ref if v_ref is not None else None From be4cf85437ec1be32d6404485da7319a13714929 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Wed, 27 May 2026 16:15:09 -0700 Subject: [PATCH 076/113] merge with dev --- uv.lock | 6 ------ 1 file changed, 6 deletions(-) diff --git a/uv.lock b/uv.lock index 8c7e72e85..05cf8378b 100644 --- a/uv.lock +++ b/uv.lock @@ -764,15 +764,9 @@ wheels = [ name = "decorator" version = "5.3.1" source = { registry = "https://pypi.org/simple" } -<<<<<<< HEAD -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 }, -======= 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/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, ->>>>>>> dev ] [[package]] From 5e6a74cf8afa86fb4761defdf56f3f767dfcf7d4 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Thu, 28 May 2026 23:04:04 -0700 Subject: [PATCH 077/113] fixing typos --- src/quantem/diffraction/model_fitting.py | 6 +++--- src/quantem/diffraction/model_fitting_visualizations.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index a5ccb4ed0..b5d5ea0df 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -964,7 +964,7 @@ def get_individual_uv_vectors(self) -> "ModelDiffraction": return self - def render_indivdual_pattern(self, row, col): + def render_individual_pattern(self, row, col): if self.state_individual_refined is None: raise RuntimeError( "individual_refined_state is unavalible. Run fit_individual_diffraction_pattern(...) first." @@ -1040,7 +1040,7 @@ def create_mask( self.mask = np.sin(np.pi / 2 * self.mask) ** 2 return self - def initilize_strain_class( + def initialize_strain_class( self, u_ref: np.ndarray | None = None, v_ref: np.ndarray | None = None, @@ -1624,4 +1624,4 @@ def _adam_step_inplace( m_hat = st["m"] / bias1 v_hat = st["v"] / bias2 lr = float(lrs.get(name, 1e-2)) - p.data.addcdiv_(m_hat, v_hat.sqrt().add_(eps), value=-lr) + p.data.addcdiv_(m_hat, v_hat.sqrt().add_(eps), value=-lr) \ No newline at end of file diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index b8cac881d..39de26b58 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -213,7 +213,7 @@ def visualize( md.plot_losses(figax=(fig, ax_top), plot_lrs=True, plot_individual=individual_loss,individual_row=pattern_row,individual_col=pattern_col) if individual_loss: - pred = md.render_indivdual_pattern(row=pattern_row, col=pattern_col) + pred = md.render_individual_pattern(row=pattern_row, col=pattern_col) ref = np.asarray(md.dataset.array[pattern_row, pattern_col], dtype=np.float32) else: ref = np.asarray(md.image_ref, dtype=np.float32) @@ -332,7 +332,7 @@ def plot_model( if plot_mean_model and plot_individual_model: raise RuntimeError("can only plot mean or plot individual, not both") if plot_individual_model: - pred = md.render_indivdual_pattern(pattern_row, pattern_col) + pred = md.render_individual_pattern(pattern_row, pattern_col) ref = np.asarray(md.dataset.array[pattern_row, pattern_col], dtype=np.float32) elif plot_mean_model: ref = np.asarray(md.image_ref, dtype=np.float32) From 925b44cbb637cf8e719743ead39f725415a25d0b Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Fri, 29 May 2026 00:02:31 -0700 Subject: [PATCH 078/113] updated model fitting so it doesn't train parameters where grad=False --- src/quantem/diffraction/model_fitting.py | 640 +++++++++++------- .../diffraction/strain_visualization.py | 2 +- 2 files changed, 404 insertions(+), 238 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index b5d5ea0df..af673043a 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -912,14 +912,18 @@ def fit_individual_diffraction_pattern_batched( t = step + 1 current_lrs = plan.lr_at_step(t, int(n_steps)) - _adam_step_inplace(stacked, tuple(grads_list), adam_state, current_lrs, t) + _adam_step_inplace(stacked, tuple(grads_list), adam_state, current_lrs, chunk_trainable, t) with torch.no_grad(): plan.apply_hard_constraints(stacked, skip_keys=hard_skip_keys) - # For mixed-trainability keys, restore frozen-sample values - # from the init snapshot so they stay at their starting state. - for key in mixed_trainable_keys: + for key in stacked: + if key not in chunk_trainable: + continue mask = chunk_trainable[key] + if bool(mask.all()): + continue + if key not in init_snapshots: + init_snapshots[key] = stacked[key].detach().clone() view_shape = (mask.shape[0],) + (1,) * (stacked[key].ndim - 1) keep = mask.view(view_shape) stacked[key].data.copy_( @@ -1116,6 +1120,51 @@ def _lr_for_component( return float(optimizer_params[class_name].get("lr", component.DEFAULT_LR)) return float(component.DEFAULT_LR) +def _adam_step_inplace( + stacked: dict[str, torch.Tensor], + grads: tuple[torch.Tensor, ...], + adam_state: dict[str, dict[str, torch.Tensor]], + lrs: dict[str, float], + chunk_trainable: dict[str, torch.Tensor], # NEW parameter + t: int, + beta1: float = 0.9, + beta2: float = 0.999, + eps: float = 1e-8, +) -> None: + """ + In-place Adam step with per-sample trainability support. + + Frozen samples (mask=False) will: + 1. Not accumulate gradient statistics in moments + 2. Not receive parameter updates + """ + bias1 = 1.0 - beta1 ** t + bias2 = 1.0 - beta2 ** t + + with torch.no_grad(): + for (name, p), g in zip(stacked.items(), grads): + if g is None: + continue + + st = adam_state[name] + mask_b = chunk_trainable.get(name) + + if mask_b is not None and not bool(mask_b.all()): + view_shape = (g.shape[0],) + (1,) * (g.ndim - 1) + mask_expanded = mask_b.view(view_shape).to(dtype=g.dtype) + g_masked = g * mask_expanded + + st["m"].mul_(beta1).add_(g_masked, alpha=1.0 - beta1) + st["v"].mul_(beta2).addcmul_(g_masked, g_masked, value=1.0 - beta2) + else: + st["m"].mul_(beta1).add_(g, alpha=1.0 - beta1) + st["v"].mul_(beta2).addcmul_(g, g, value=1.0 - beta2) + + m_hat = st["m"] / bias1 + v_hat = st["v"] / bias2 + + lr = float(lrs.get(name, 1e-2)) + p.data.addcdiv_(m_hat, v_hat.sqrt().add_(eps), value=-lr) class _BatchedPlan: """Resolved layout for the batched per-pattern fit: component refs, lrs, and helpers.""" @@ -1131,11 +1180,11 @@ def __init__(self) -> None: self.gaussbg_idx: int | None = None self.lat_idx: int | None = None self.lrs: dict[str, float] = {} - # Per-param normalized scheduler spec; keys match self.lrs. - # Each value: {"type": "none|cosine|linear|exponential", ...numerics}. self.scheduler_specs: dict[str, dict[str, Any]] = {} - # Mapping from canonical component name -> stacked-param keys. self.component_keys: dict[str, list[str]] = {} + + # NEW: Track which params are trainable + self._trainable_flags: dict[str, bool] = {} @classmethod def from_model( @@ -1166,197 +1215,122 @@ def from_model( f"at index {idx} (or duplicate of an already-handled type)." ) - # Per-component LRs (with fallback to the component's DEFAULT_LR). + # Track trainability for each parameter if self.disk is not None and self.disk_idx is not None: - self.lrs["disk.template_raw"] = _lr_for_component(self.disk, self.disk_idx, optimizer_params, model) - self.lrs["disk.intensity_raw"] = self.lrs["disk.template_raw"] + lr = _lr_for_component(self.disk, self.disk_idx, optimizer_params, model) name = model._component_constraint_name(self.disk, self.disk_idx) + + # Check if parameters are trainable + self._trainable_flags["disk.template_raw"] = self.disk.template_raw.requires_grad + self._trainable_flags["disk.intensity_raw"] = self.disk.intensity_raw.requires_grad + + # Only add to lrs if trainable + if self._trainable_flags["disk.template_raw"]: + self.lrs["disk.template_raw"] = lr + if self._trainable_flags["disk.intensity_raw"]: + self.lrs["disk.intensity_raw"] = lr + self.component_keys[name] = ["disk.template_raw", "disk.intensity_raw"] self.component_keys[self.disk.__class__.__name__] = list(self.component_keys[name]) + if self.dcbg is not None and self.dcbg_idx is not None: - self.lrs["dcbg.intensity_raw"] = _lr_for_component(self.dcbg, self.dcbg_idx, optimizer_params, model) + lr = _lr_for_component(self.dcbg, self.dcbg_idx, optimizer_params, model) name = model._component_constraint_name(self.dcbg, self.dcbg_idx) + + self._trainable_flags["dcbg.intensity_raw"] = self.dcbg.intensity_raw.requires_grad + if self._trainable_flags["dcbg.intensity_raw"]: + self.lrs["dcbg.intensity_raw"] = lr + self.component_keys[name] = ["dcbg.intensity_raw"] self.component_keys[self.dcbg.__class__.__name__] = list(self.component_keys[name]) + if self.gaussbg is not None and self.gaussbg_idx is not None: lr = _lr_for_component(self.gaussbg, self.gaussbg_idx, optimizer_params, model) - self.lrs["gaussbg.sigma_raw"] = lr - self.lrs["gaussbg.intensity_raw"] = lr name = model._component_constraint_name(self.gaussbg, self.gaussbg_idx) + + self._trainable_flags["gaussbg.sigma_raw"] = self.gaussbg.sigma_raw.requires_grad + self._trainable_flags["gaussbg.intensity_raw"] = self.gaussbg.intensity_raw.requires_grad + + if self._trainable_flags["gaussbg.sigma_raw"]: + self.lrs["gaussbg.sigma_raw"] = lr + if self._trainable_flags["gaussbg.intensity_raw"]: + self.lrs["gaussbg.intensity_raw"] = lr + self.component_keys[name] = ["gaussbg.sigma_raw", "gaussbg.intensity_raw"] self.component_keys[self.gaussbg.__class__.__name__] = list(self.component_keys[name]) + if self.lat is not None and self.lat_idx is not None: lr = _lr_for_component(self.lat, self.lat_idx, optimizer_params, model) + name = model._component_constraint_name(self.lat, self.lat_idx) lat_keys = [] + for k in ("u_row", "u_col", "v_row", "v_col", "i0_raw", "ir", "ic", "irr", "icc", "irc"): - self.lrs[f"lat.{k}"] = lr - lat_keys.append(f"lat.{k}") - # Origin uses the lattice's LR (lattice is the most common owner of origin). - self.lrs["origin.coords"] = lr - name = model._component_constraint_name(self.lat, self.lat_idx) + full_key = f"lat.{k}" + param = getattr(self.lat, k, None) + if param is not None: + is_trainable = param.requires_grad if isinstance(param, torch.nn.Parameter) else False + self._trainable_flags[full_key] = is_trainable + if is_trainable: + self.lrs[full_key] = lr + lat_keys.append(full_key) + + # Origin uses the lattice's LR + self._trainable_flags["origin.coords"] = self.origin.coords.requires_grad + if self._trainable_flags["origin.coords"]: + self.lrs["origin.coords"] = lr + self.component_keys[name] = lat_keys self.component_keys[self.lat.__class__.__name__] = list(lat_keys) - elif self.gaussbg is not None and self.gaussbg_idx is not None: - self.lrs["origin.coords"] = self.lrs["gaussbg.intensity_raw"] + elif self.gaussbg is not None: + if self._trainable_flags.get("gaussbg.intensity_raw", False): + self.lrs["origin.coords"] = self.lrs["gaussbg.intensity_raw"] else: - self.lrs["origin.coords"] = 1e-2 + if self.origin.coords.requires_grad: + self.lrs["origin.coords"] = 1e-2 - # Default schedulers: constant LR for every key. + # Default schedulers: constant LR for every key self.scheduler_specs = {k: {"type": "none"} for k in self.lrs} return self - def set_scheduler_params(self, scheduler_params: Any) -> None: - """ - Configure per-key scheduler specs. - - Accepts either a single spec dict (applied to all params) or a dict - keyed by component name / class name (matching ``optimizer_params``). - Unknown scheduler types fall back to constant LR. - """ - if scheduler_params is None: - return - if not isinstance(scheduler_params, dict): - return - - def _normalize(spec: dict[str, Any]) -> dict[str, Any]: - out = dict(spec) - t = str(out.pop("type", out.pop("name", "none"))).lower() - # Aliases. - if t in ("cosine", "cosineannealing"): - t = "cosine_annealing" - if t == "cosine_annealing": - # Normalize T_max alias and cast numeric strings. - if "t_max" in out: - out["T_max"] = out.pop("t_max") - if "T_max" in out and out["T_max"] is not None: - out["T_max"] = int(float(out["T_max"])) - if "eta_min" in out: - out["eta_min"] = float(out["eta_min"]) - elif t == "exponential": - if "gamma" in out: - out["gamma"] = float(out["gamma"]) - elif t == "linear": - for k in ("start_factor", "end_factor", "total_iters"): - if k in out and out[k] is not None: - out[k] = float(out[k]) if k != "total_iters" else int(float(out[k])) - out["type"] = t - return out - - # Single-spec form: {"type": ..., ...}. - if "type" in scheduler_params or "name" in scheduler_params: - spec = _normalize(cast(dict[str, Any], scheduler_params)) - for k in self.scheduler_specs: - self.scheduler_specs[k] = dict(spec) - return - - # Per-component form: {component_name_or_class: spec_dict}. - for comp_name, spec in scheduler_params.items(): - if not isinstance(spec, dict): - continue - norm = _normalize(spec) - param_keys = self.component_keys.get(str(comp_name), []) - for k in param_keys: - if k in self.scheduler_specs: - self.scheduler_specs[k] = dict(norm) - - def lr_at_step(self, step: int, n_steps: int) -> dict[str, float]: - """ - Evaluate the analytic LR schedule for each stacked-param key at - ``step`` (1-indexed, matches the bias correction). - """ - out: dict[str, float] = {} - for key, base_lr in self.lrs.items(): - spec = self.scheduler_specs.get(key, {"type": "none"}) - t = str(spec.get("type", "none")).lower() - if t == "cosine_annealing": - T_max = int(spec.get("T_max") or n_steps) - if T_max < 1: - T_max = 1 - eta_min = float(spec.get("eta_min", 1e-7)) - # PyTorch CosineAnnealingLR formula at step s (0-indexed): - # lr(s) = eta_min + 0.5*(base - eta_min)*(1 + cos(pi*s/T_max)) - s = max(step - 1, 0) - import math - lr = eta_min + 0.5 * (base_lr - eta_min) * (1.0 + math.cos(math.pi * s / T_max)) - out[key] = float(lr) - elif t == "exponential": - gamma = float(spec.get("gamma", 1.0)) - out[key] = float(base_lr * (gamma ** max(step - 1, 0))) - elif t == "linear": - start = float(spec.get("start_factor", 1.0 / 3.0)) - end = float(spec.get("end_factor", 1.0)) - total = int(spec.get("total_iters", n_steps) or n_steps) - if total < 1: - total = 1 - s = min(max(step - 1, 0), total) - factor = start + (end - start) * (s / total) - out[key] = float(base_lr * factor) - else: - # none / plateau / cyclic / unknown -> constant LR. - out[key] = float(base_lr) - return out - - def batched_constraint_loss( - self, - ctx: RenderContext, - stacked: dict[str, torch.Tensor], - ) -> torch.Tensor: - """ - Per-sample soft-constraint loss summed across components. - - Returns a tensor of shape ``(B,)``. Only ``DiskTemplate`` contributes - today; other components inherit the zero-returning base - ``constraint_loss``. - """ - B = stacked["origin.coords"].shape[0] - out = torch.zeros(B, device=ctx.device, dtype=ctx.dtype) - if self.disk is not None: - template_b = stacked.get("disk.template_raw") - if template_b is not None: - out = out + self.disk.constraint_loss_batched(ctx, template_raw_b=template_b) - return out - - def resolve_component_keys(self, components: Any) -> list[str]: - """Resolve a name/list of component names to the stacked-param keys they own.""" - if components is None: - return [] - if isinstance(components, str): - components = [components] - keys: list[str] = [] - for name in components: - ks = self.component_keys.get(str(name)) - if ks is None: - raise KeyError( - f"Unknown component name '{name}' for batched fit. " - f"Known: {sorted(self.component_keys)}" - ) - for k in ks: - if k not in keys: - keys.append(k) - return keys + # ... (keep set_scheduler_params, lr_at_step, batched_constraint_loss, resolve_component_keys as-is) def build_stacked_params(self, B: int) -> dict[str, torch.Tensor]: + """Build stacked params only for trainable parameters, expand frozen ones as needed.""" out: dict[str, torch.Tensor] = {} assert self.origin is not None - out["origin.coords"] = self._stack(self.origin.coords, B) + + # Only stack trainable params + if self._trainable_flags.get("origin.coords", False): + out["origin.coords"] = self._stack(self.origin.coords, B) if self.disk is not None: - out["disk.template_raw"] = self._stack(self.disk.template_raw, B) - out["disk.intensity_raw"] = self._stack(self.disk.intensity_raw, B) + if self._trainable_flags.get("disk.template_raw", False): + out["disk.template_raw"] = self._stack(self.disk.template_raw, B) + if self._trainable_flags.get("disk.intensity_raw", False): + out["disk.intensity_raw"] = self._stack(self.disk.intensity_raw, B) + if self.dcbg is not None: - out["dcbg.intensity_raw"] = self._stack(self.dcbg.intensity_raw, B) + if self._trainable_flags.get("dcbg.intensity_raw", False): + out["dcbg.intensity_raw"] = self._stack(self.dcbg.intensity_raw, B) + if self.gaussbg is not None: - out["gaussbg.sigma_raw"] = self._stack(self.gaussbg.sigma_raw, B) - out["gaussbg.intensity_raw"] = self._stack(self.gaussbg.intensity_raw, B) + if self._trainable_flags.get("gaussbg.sigma_raw", False): + out["gaussbg.sigma_raw"] = self._stack(self.gaussbg.sigma_raw, B) + if self._trainable_flags.get("gaussbg.intensity_raw", False): + out["gaussbg.intensity_raw"] = self._stack(self.gaussbg.intensity_raw, B) + if self.lat is not None: for attr in ("u_row", "u_col", "v_row", "v_col", "i0_raw"): t = getattr(self.lat, attr) - if t is not None: - out[f"lat.{attr}"] = self._stack(t, B) + key = f"lat.{attr}" + if t is not None and self._trainable_flags.get(key, False): + out[key] = self._stack(t, B) for attr in ("ir", "ic", "irr", "icc", "irc"): t = getattr(self.lat, attr, None) - if t is not None: - out[f"lat.{attr}"] = self._stack(t, B) + key = f"lat.{attr}" + if t is not None and self._trainable_flags.get(key, False): + out[key] = self._stack(t, B) + return out @staticmethod @@ -1368,43 +1342,117 @@ def _stack(p: torch.Tensor, B: int) -> torch.Tensor: def batched_forward( self, ctx: RenderContext, stacked: dict[str, torch.Tensor] ) -> torch.Tensor: - B = stacked["origin.coords"].shape[0] - origin_b = stacked["origin.coords"] + """Batched forward with frozen parameter support.""" + B = next(iter(stacked.values())).shape[0] if stacked else 1 + + # Get origin (might be frozen) + origin_b = stacked.get("origin.coords") + if origin_b is None: + # Origin is frozen - expand from component + origin_b = self.origin.coords.unsqueeze(0).expand(B, *self.origin.coords.shape).clone() pred = torch.zeros(B, ctx.shape[0], ctx.shape[1], device=ctx.device, dtype=ctx.dtype) if self.disk is not None: + # Handle potentially frozen disk params + template_b = stacked.get("disk.template_raw") + if template_b is None: + template_b = self.disk.template_raw.unsqueeze(0).expand(B, *self.disk.template_raw.shape).clone() + + intensity_b = stacked.get("disk.intensity_raw") + if intensity_b is None: + intensity_b = self.disk.intensity_raw.unsqueeze(0).expand(B).clone() + pred = pred + self.disk.forward_batched( ctx, - template_raw_b=stacked["disk.template_raw"], - intensity_raw_b=stacked["disk.intensity_raw"], + template_raw_b=template_b, + intensity_raw_b=intensity_b, origin_coords_b=origin_b, ) + if self.dcbg is not None: - pred = pred + self.dcbg.forward_batched( - ctx, intensity_raw_b=stacked["dcbg.intensity_raw"] - ) + intensity_b = stacked.get("dcbg.intensity_raw") + if intensity_b is None: + intensity_b = self.dcbg.intensity_raw.unsqueeze(0).expand(B).clone() + pred = pred + self.dcbg.forward_batched(ctx, intensity_raw_b=intensity_b) + if self.gaussbg is not None: + sigma_b = stacked.get("gaussbg.sigma_raw") + if sigma_b is None: + sigma_b = self.gaussbg.sigma_raw.unsqueeze(0).expand(B).clone() + + intensity_b = stacked.get("gaussbg.intensity_raw") + if intensity_b is None: + intensity_b = self.gaussbg.intensity_raw.unsqueeze(0).expand(B).clone() + pred = pred + self.gaussbg.forward_batched( ctx, - sigma_raw_b=stacked["gaussbg.sigma_raw"], - intensity_raw_b=stacked["gaussbg.intensity_raw"], + sigma_raw_b=sigma_b, + intensity_raw_b=intensity_b, origin_coords_b=origin_b, ) + if self.lat is not None: + # Lattice requires all params - expand frozen ones + u_row_b = stacked.get("lat.u_row") + if u_row_b is None: + u_row_b = self.lat.u_row.unsqueeze(0).expand(B).clone() + + u_col_b = stacked.get("lat.u_col") + if u_col_b is None: + u_col_b = self.lat.u_col.unsqueeze(0).expand(B).clone() + + v_row_b = stacked.get("lat.v_row") + if v_row_b is None: + v_row_b = self.lat.v_row.unsqueeze(0).expand(B).clone() + + v_col_b = stacked.get("lat.v_col") + if v_col_b is None: + v_col_b = self.lat.v_col.unsqueeze(0).expand(B).clone() + + i0_raw_b = stacked.get("lat.i0_raw") + if i0_raw_b is None: + i0_raw_b = self.lat.i0_raw.unsqueeze(0).expand(B, *self.lat.i0_raw.shape).clone() + + # Optional params + ir_b = stacked.get("lat.ir") + if ir_b is None and self.lat.ir is not None: + ir_b = self.lat.ir.unsqueeze(0).expand(B, *self.lat.ir.shape).clone() + + ic_b = stacked.get("lat.ic") + if ic_b is None and self.lat.ic is not None: + ic_b = self.lat.ic.unsqueeze(0).expand(B, *self.lat.ic.shape).clone() + + irr_b = stacked.get("lat.irr") + if irr_b is None and self.lat.irr is not None: + irr_b = self.lat.irr.unsqueeze(0).expand(B, *self.lat.irr.shape).clone() + + icc_b = stacked.get("lat.icc") + if icc_b is None and self.lat.icc is not None: + icc_b = self.lat.icc.unsqueeze(0).expand(B, *self.lat.icc.shape).clone() + + irc_b = stacked.get("lat.irc") + if irc_b is None and self.lat.irc is not None: + irc_b = self.lat.irc.unsqueeze(0).expand(B, *self.lat.irc.shape).clone() + + # Template (from disk) + template_b = stacked.get("disk.template_raw") + if template_b is None and self.disk is not None: + template_b = self.disk.template_raw.unsqueeze(0).expand(B, *self.disk.template_raw.shape).clone() + pred = pred + self.lat.forward_batched( ctx, - u_row_b=stacked["lat.u_row"], - u_col_b=stacked["lat.u_col"], - v_row_b=stacked["lat.v_row"], - v_col_b=stacked["lat.v_col"], - i0_raw_b=stacked["lat.i0_raw"], - ir_b=stacked.get("lat.ir"), - ic_b=stacked.get("lat.ic"), - irr_b=stacked.get("lat.irr"), - icc_b=stacked.get("lat.icc"), - irc_b=stacked.get("lat.irc"), - template_raw_b=stacked["disk.template_raw"], + u_row_b=u_row_b, + u_col_b=u_col_b, + v_row_b=v_row_b, + v_col_b=v_col_b, + i0_raw_b=i0_raw_b, + ir_b=ir_b, + ic_b=ic_b, + irr_b=irr_b, + icc_b=icc_b, + irc_b=irc_b, + template_raw_b=template_b, origin_coords_b=origin_b, ) return pred @@ -1414,13 +1462,14 @@ def apply_hard_constraints( stacked: dict[str, torch.Tensor], skip_keys: set[str] | None = None, ) -> None: - """Apply batched analogues of each component's hard constraints to stacked params (in-place). - - ``skip_keys`` is a set of stacked-param keys that should not be mutated - by any hard-constraint op (used for fully-frozen components). - """ + """Apply batched hard constraints, respecting frozen parameters.""" skip_keys = skip_keys or set() - # Parameter bounds (always elementwise; safe on any shape). + + # Add frozen params to skip_keys + frozen_keys = {k for k, trainable in self._trainable_flags.items() if not trainable} + skip_keys = skip_keys | frozen_keys + + # Parameter bounds (always elementwise; safe on any shape) if self.disk is not None: for pname, (lo, hi) in self.disk.parameter_bounds.items(): key = f"disk.{pname}" @@ -1445,7 +1494,7 @@ def apply_hard_constraints( disk_template_frozen = "disk.template_raw" in skip_keys disk_intensity_frozen = "disk.intensity_raw" in skip_keys - # DiskTemplate composite hard constraints. + # DiskTemplate composite hard constraints if self.disk is not None and not disk_template_frozen: template = stacked.get("disk.template_raw") intensity = stacked.get("disk.intensity_raw") @@ -1461,12 +1510,12 @@ def apply_hard_constraints( template.sub_(float(cfg.get("shrinkage_amount", 0.25))) if bool(self.disk.hard_constraints.get("force_positive", False)): template.clamp_(min=0.0) - if not disk_intensity_frozen: + if not disk_intensity_frozen and intensity is not None: intensity.clamp_(min=0.0) if bool(self.disk.hard_constraints.get("force_norm", False)): self._batched_enforce_norm(template) - # SyntheticDiskLattice.force_positive_intensity. + # SyntheticDiskLattice.force_positive_intensity if ( self.lat is not None and "lat.i0_raw" not in skip_keys @@ -1520,7 +1569,7 @@ def _batched_center_disk(template_b: torch.Tensor) -> None: src = template_b.unsqueeze(1) # (B, 1, H, W) grid = F.affine_grid(theta, [B, 1, h, w], align_corners=True) shifted = F.grid_sample(src, grid, mode="bilinear", padding_mode="zeros", align_corners=True)[:, 0] - # Only shift samples with nonzero mass; others left as-is. + # Only shift samples with nonzero mass; others left as-is do_shift = (mass > 1e-12).view(B, 1, 1) template_b.copy_(torch.where(do_shift, shifted, template_b)) @@ -1560,68 +1609,185 @@ def build_sample_state_dict( stacked: dict[str, torch.Tensor], b: int, ) -> dict[str, torch.Tensor]: + """Extract sample b from stacked params, handling frozen params.""" out = {k: v.detach().clone() for k, v in init_state.items()} # Origin: top-level key plus any 'components.X.origin.coords' or - # 'components.X.disk.origin.coords' that PyTorch state_dict registers. - origin_val = stacked["origin.coords"][b].detach().clone() - for key in list(out.keys()): - if key == "origin.coords" or key.endswith(".origin.coords"): - out[key] = origin_val.clone() + # 'components.X.disk.origin.coords' that PyTorch state_dict registers + if "origin.coords" in stacked: + origin_val = stacked["origin.coords"][b].detach().clone() + else: + # Origin was frozen - use the init value + origin_val = init_state.get("origin.coords") + if origin_val is not None: + origin_val = origin_val.detach().clone() + + if origin_val is not None: + for key in list(out.keys()): + if key == "origin.coords" or key.endswith(".origin.coords"): + out[key] = origin_val.clone() - # DiskTemplate (shared with lat.disk). + # DiskTemplate (shared with lat.disk) if self.disk is not None and self.disk_idx is not None: - t_val = stacked["disk.template_raw"][b].detach().clone() - i_val = stacked["disk.intensity_raw"][b].detach().clone() - for key in list(out.keys()): - if key.endswith(".template_raw"): - out[key] = t_val.clone() - for key in ( - f"components.{self.disk_idx}.intensity_raw", - f"components.{self.lat_idx}.disk.intensity_raw" if self.lat_idx is not None else None, - ): - if key is not None and key in out: - out[key] = i_val.clone() - - # DCBackground. + if "disk.template_raw" in stacked: + t_val = stacked["disk.template_raw"][b].detach().clone() + else: + # Template was frozen + t_val = init_state.get(f"components.{self.disk_idx}.template_raw") + if t_val is not None: + t_val = t_val.detach().clone() + + if "disk.intensity_raw" in stacked: + i_val = stacked["disk.intensity_raw"][b].detach().clone() + else: + # Intensity was frozen + i_val = init_state.get(f"components.{self.disk_idx}.intensity_raw") + if i_val is not None: + i_val = i_val.detach().clone() + + if t_val is not None: + for key in list(out.keys()): + if key.endswith(".template_raw"): + out[key] = t_val.clone() + + if i_val is not None: + for key in ( + f"components.{self.disk_idx}.intensity_raw", + f"components.{self.lat_idx}.disk.intensity_raw" if self.lat_idx is not None else None, + ): + if key is not None and key in out: + out[key] = i_val.clone() + + # DCBackground if self.dcbg is not None and self.dcbg_idx is not None: - out[f"components.{self.dcbg_idx}.intensity_raw"] = stacked["dcbg.intensity_raw"][b].detach().clone() + if "dcbg.intensity_raw" in stacked: + out[f"components.{self.dcbg_idx}.intensity_raw"] = stacked["dcbg.intensity_raw"][b].detach().clone() + else: + # Was frozen - keep init value (already in out) + pass - # GaussianBackground. + # GaussianBackground if self.gaussbg is not None and self.gaussbg_idx is not None: - out[f"components.{self.gaussbg_idx}.sigma_raw"] = stacked["gaussbg.sigma_raw"][b].detach().clone() - out[f"components.{self.gaussbg_idx}.intensity_raw"] = stacked["gaussbg.intensity_raw"][b].detach().clone() + if "gaussbg.sigma_raw" in stacked: + out[f"components.{self.gaussbg_idx}.sigma_raw"] = stacked["gaussbg.sigma_raw"][b].detach().clone() + if "gaussbg.intensity_raw" in stacked: + out[f"components.{self.gaussbg_idx}.intensity_raw"] = stacked["gaussbg.intensity_raw"][b].detach().clone() - # SyntheticDiskLattice scalar + tensor params. + # SyntheticDiskLattice scalar + tensor params if self.lat is not None and self.lat_idx is not None: for attr in ("u_row", "u_col", "v_row", "v_col", "i0_raw", "ir", "ic", "irr", "icc", "irc"): key = f"components.{self.lat_idx}.{attr}" stacked_key = f"lat.{attr}" if key in out and stacked_key in stacked: out[key] = stacked[stacked_key][b].detach().clone() + # else: was frozen, keep init value + return out + + def set_scheduler_params(self, scheduler_params: Any) -> None: + """Configure per-key scheduler specs.""" + if scheduler_params is None: + return + if not isinstance(scheduler_params, dict): + return + + def _normalize(spec: dict[str, Any]) -> dict[str, Any]: + out = dict(spec) + t = str(out.pop("type", out.pop("name", "none"))).lower() + if t in ("cosine", "cosineannealing"): + t = "cosine_annealing" + if t == "cosine_annealing": + if "t_max" in out: + out["T_max"] = out.pop("t_max") + if "T_max" in out and out["T_max"] is not None: + out["T_max"] = int(float(out["T_max"])) + if "eta_min" in out: + out["eta_min"] = float(out["eta_min"]) + elif t == "exponential": + if "gamma" in out: + out["gamma"] = float(out["gamma"]) + elif t == "linear": + for k in ("start_factor", "end_factor", "total_iters"): + if k in out and out[k] is not None: + out[k] = float(out[k]) if k != "total_iters" else int(float(out[k])) + out["type"] = t + return out + if "type" in scheduler_params or "name" in scheduler_params: + spec = _normalize(cast(dict[str, Any], scheduler_params)) + for k in self.scheduler_specs: + self.scheduler_specs[k] = dict(spec) + return -def _adam_step_inplace( - stacked: dict[str, torch.Tensor], - grads: tuple[torch.Tensor, ...], - adam_state: dict[str, dict[str, torch.Tensor]], - lrs: dict[str, float], - t: int, - beta1: float = 0.9, - beta2: float = 0.999, - eps: float = 1e-8, -) -> None: - bias1 = 1.0 - beta1 ** t - bias2 = 1.0 - beta2 ** t - with torch.no_grad(): - for (name, p), g in zip(stacked.items(), grads): - if g is None: + for comp_name, spec in scheduler_params.items(): + if not isinstance(spec, dict): continue - st = adam_state[name] - st["m"].mul_(beta1).add_(g, alpha=1.0 - beta1) - st["v"].mul_(beta2).addcmul_(g, g, value=1.0 - beta2) - m_hat = st["m"] / bias1 - v_hat = st["v"] / bias2 - lr = float(lrs.get(name, 1e-2)) - p.data.addcdiv_(m_hat, v_hat.sqrt().add_(eps), value=-lr) \ No newline at end of file + norm = _normalize(spec) + param_keys = self.component_keys.get(str(comp_name), []) + for k in param_keys: + if k in self.scheduler_specs: + self.scheduler_specs[k] = dict(norm) + + def lr_at_step(self, step: int, n_steps: int) -> dict[str, float]: + """Evaluate the analytic LR schedule for each stacked-param key at step.""" + out: dict[str, float] = {} + for key, base_lr in self.lrs.items(): + spec = self.scheduler_specs.get(key, {"type": "none"}) + t = str(spec.get("type", "none")).lower() + if t == "cosine_annealing": + T_max = int(spec.get("T_max") or n_steps) + if T_max < 1: + T_max = 1 + eta_min = float(spec.get("eta_min", 1e-7)) + s = max(step - 1, 0) + import math + lr = eta_min + 0.5 * (base_lr - eta_min) * (1.0 + math.cos(math.pi * s / T_max)) + out[key] = float(lr) + elif t == "exponential": + gamma = float(spec.get("gamma", 1.0)) + out[key] = float(base_lr * (gamma ** max(step - 1, 0))) + elif t == "linear": + start = float(spec.get("start_factor", 1.0 / 3.0)) + end = float(spec.get("end_factor", 1.0)) + total = int(spec.get("total_iters", n_steps) or n_steps) + if total < 1: + total = 1 + s = min(max(step - 1, 0), total) + factor = start + (end - start) * (s / total) + out[key] = float(base_lr * factor) + else: + out[key] = float(base_lr) + return out + + def batched_constraint_loss( + self, + ctx: RenderContext, + stacked: dict[str, torch.Tensor], + ) -> torch.Tensor: + """Per-sample soft-constraint loss summed across components.""" + B = next(iter(stacked.values())).shape[0] if stacked else 1 + out = torch.zeros(B, device=ctx.device, dtype=ctx.dtype) + if self.disk is not None: + template_b = stacked.get("disk.template_raw") + if template_b is not None: + out = out + self.disk.constraint_loss_batched(ctx, template_raw_b=template_b) + return out + + def resolve_component_keys(self, components: Any) -> list[str]: + """Resolve a name/list of component names to the stacked-param keys they own.""" + if components is None: + return [] + if isinstance(components, str): + components = [components] + keys: list[str] = [] + for name in components: + ks = self.component_keys.get(str(name)) + if ks is None: + raise KeyError( + f"Unknown component name '{name}' for batched fit. " + f"Known: {sorted(self.component_keys)}" + ) + for k in ks: + if k not in keys: + keys.append(k) + return keys \ No newline at end of file diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index b080f3d36..e5b02d500 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -203,7 +203,7 @@ def plot_strain( is_horizontal = layout == "horizontal" if figsize is None: - figsize = (6, 6) if is_horizontal else (6, 8) + figsize = (6, 6) if is_horizontal else (6, 6) if is_horizontal: fig, ax = plt.subplots(1, ncols, figsize=figsize) From 85de2fdefaf4fa540f81c07f69f04305b046ff9c Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Fri, 29 May 2026 15:02:23 -0700 Subject: [PATCH 079/113] remote additional comments --- src/quantem/core/fitting/diffraction.py | 66 ------------------------ src/quantem/diffraction/model_fitting.py | 3 -- 2 files changed, 69 deletions(-) diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 31ee422f2..2da095d76 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -745,72 +745,6 @@ def enforce_hard_constraints(self, ctx: RenderContext) -> None: self.i0_raw[idx].clamp_(max=float(hi)) super().enforce_hard_constraints(ctx) - # def forward(self, ctx: RenderContext) -> torch.Tensor: - # if self.origin is None: - # raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") - - # out = torch.zeros(ctx.shape, device=ctx.device, dtype=ctx.dtype) - # uv_indices = cast(torch.Tensor, self.uv_indices) - # if torch.numel(uv_indices) == 0: - # return out - - # uv = torch.as_tensor(uv_indices, device=ctx.device) - # u = uv[:, 0].to(dtype=ctx.dtype) - # v = uv[:, 1].to(dtype=ctx.dtype) - # r0, c0 = self.origin.coords[0], self.origin.coords[1] - # centers_r = r0 + u * self.u_row + v * self.v_row - # centers_c = c0 + u * self.u_col + v * self.v_col - - # b = torch.as_tensor(self.boundary_px, device=ctx.device, dtype=ctx.dtype) - # keep = (centers_r >= b) & (centers_r <= (ctx.shape[0] - 1) - b) - # keep = keep & (centers_c >= b) & (centers_c <= (ctx.shape[1] - 1) - b) - # keep_idx = torch.nonzero(keep, as_tuple=False).reshape(-1) - # if keep_idx.numel() == 0: - # return out - - # active_order = int( - # ctx.fields.get( - # "lattice_intensity_order_override", self.default_pattern_intensity_order - # ) - # ) - # active_order = max(0, min(active_order, self.max_intensity_order)) - - # dr, dc = self.disk.patch_offsets() - # dr = dr.to(device=ctx.device, dtype=ctx.dtype) - # dc = dc.to(device=ctx.device, dtype=ctx.dtype) - # dr2 = dr * dr - # dc2 = dc * dc - # drdc = dr * dc - - # for j in keep_idx: - # rr0 = centers_r[j] - # cc0 = centers_c[j] - - # if self.per_disk_intensity: - # inten = self.i0_raw[j] - # if active_order >= 1 and self.ir is not None and self.ic is not None: - # inten = inten + self.ir[j] * dr + self.ic[j] * dc - # if ( - # active_order >= 2 - # and self.irr is not None - # and self.icc is not None - # and self.irc is not None - # ): - # inten = inten + self.irr[j] * dr2 + self.icc[j] * dc2 + self.irc[j] * drdc - # else: - # inten = self.i0_raw - # if active_order >= 1: - # assert self.ir is not None and self.ic is not None - # inten = inten + self.ir * rr0 + self.ic * cc0 - # if active_order >= 2: - # assert self.irr is not None and self.icc is not None and self.irc is not None - # inten = ( - # inten + self.irr * rr0 * rr0 + self.icc * cc0 * cc0 + self.irc * rr0 * cc0 - # ) - - # self.disk.add_patch(out, r0=rr0, c0=cc0, scale=inten) - - # return out def forward(self, ctx: RenderContext) -> torch.Tensor: if self.origin is None: diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index af673043a..185154ed9 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -806,7 +806,6 @@ def fit_individual_diffraction_pattern_batched( frozen_keys = plan.resolve_component_keys(frozen_components) for key in frozen_keys: is_trainable_pos[key][:] = False - # Keys that are frozen for ALL samples skip hard constraints too. hard_skip_keys: set[str] = set(frozen_keys) if sample_trainability is not None: for key, arr in sample_trainability.items(): @@ -1183,7 +1182,6 @@ def __init__(self) -> None: self.scheduler_specs: dict[str, dict[str, Any]] = {} self.component_keys: dict[str, list[str]] = {} - # NEW: Track which params are trainable self._trainable_flags: dict[str, bool] = {} @classmethod @@ -1515,7 +1513,6 @@ def apply_hard_constraints( if bool(self.disk.hard_constraints.get("force_norm", False)): self._batched_enforce_norm(template) - # SyntheticDiskLattice.force_positive_intensity if ( self.lat is not None and "lat.i0_raw" not in skip_keys From 703e631e14a6d20526d0dc5fedbb4aae490099ed Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 1 Jun 2026 09:44:18 +0200 Subject: [PATCH 080/113] current strain --- src/quantem/diffraction/__init__.py | 2 + src/quantem/diffraction/bragg_vectors.py | 1394 +++++++++++++++++ .../bragg_vectors_visualization.py | 768 +++++++++ src/quantem/diffraction/disk_detection.py | 1093 +++++++++++++ src/quantem/diffraction/model_fitting.py | 6 +- src/quantem/diffraction/strain.py | 762 +++++++++ .../diffraction/strain_autocorrelation.py | 46 +- .../diffraction/strain_visualization.py | 664 ++++---- 8 files changed, 4342 insertions(+), 393 deletions(-) create mode 100644 src/quantem/diffraction/bragg_vectors.py create mode 100644 src/quantem/diffraction/bragg_vectors_visualization.py create mode 100644 src/quantem/diffraction/disk_detection.py create mode 100644 src/quantem/diffraction/strain.py diff --git a/src/quantem/diffraction/__init__.py b/src/quantem/diffraction/__init__.py index a64a357e8..fed057f1d 100644 --- a/src/quantem/diffraction/__init__.py +++ b/src/quantem/diffraction/__init__.py @@ -1,4 +1,6 @@ from quantem.diffraction.polar import RDF as RDF +from quantem.diffraction.bragg_vectors import BraggVectors as BraggVectors +from quantem.diffraction.strain import StrainMap as StrainMap from quantem.diffraction.strain_autocorrelation import StrainMapAutocorrelation as StrainMapAutocorrelation from quantem.diffraction.maped import MAPED as MAPED from quantem.diffraction.model_fitting import ModelDiffraction as ModelDiffraction diff --git a/src/quantem/diffraction/bragg_vectors.py b/src/quantem/diffraction/bragg_vectors.py new file mode 100644 index 000000000..ed36ede40 --- /dev/null +++ b/src/quantem/diffraction/bragg_vectors.py @@ -0,0 +1,1394 @@ +from __future__ import annotations + +from typing import Any + +import numpy as np +import torch +from numpy.typing import NDArray + +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.datastructures.dataset4dstem import Dataset4dstem +from quantem.core.datastructures.vector import Vector +from quantem.core.io.serialize import AutoSerialize +from quantem.diffraction.bragg_vectors_visualization import ( + plot_basis_vectors, + plot_bvm, + plot_detection, + plot_diffraction_grid, + plot_lattice_fit, + plot_reference_lattice, + plot_template, +) +from quantem.diffraction.disk_detection import ( + cross_correlation, + detect_disks_batch, + estimate_central_beam, + make_template, + probe_centroid, + synthetic_probe, + template_fourier, +) +from quantem.diffraction.strain import StrainMap + +PEAK_FIELDS = ("q_row", "q_col", "intensity") + + +class BraggVectors(AutoSerialize): + """Correlation-based Bragg disk detection and lattice fitting for 4D-STEM. + + Workflow (each step writes state consumed by the next): + + 1. ``make_template_*`` – build a cross-correlation template, either from a + synthetic soft disk (:meth:`make_template_synthetic`), by averaging data + over an ROI (:meth:`make_template_from_data`), or from an explicit probe + image (:meth:`make_template_from_probe`). + 2. :meth:`detect_disks` – template-match every scan position; detected peaks + are stored in :attr:`peaks` (a :class:`Vector` of ``[q_row, q_col, + intensity]``, numpy-backed) and accumulated into the Bragg vector map + :attr:`bvm`. + 3. :meth:`choose_basis_vectors` – pick the lattice basis ``(origin, g1, g2)`` + from the BVM, automatically or by hand; also stores the numbered candidate + peaks. + 4. :meth:`index_peaks` – quick, lightweight: index just the picked candidate + peaks into a reference lattice (``reference_ab``/``reference_qpos``). + 5. :meth:`fit_lattice` – the heavy step: at every scan position, match the + detections to the reference within ``max_peak_shift``, intensity-weighted + least-squares fit the lattice vectors into ``u_array``/``v_array`` of shape + ``(scan_row, scan_col, 2)``, and compute the per-position ``mask_weight``. + 6. :meth:`calculate_strain_map` – hand the lattice vectors (and + ``mask_weight``) to a :class:`~quantem.diffraction.strain.StrainMap`. + + Detection runs in torch (CPU now, CUDA later); the ragged peak table is held + in a numpy-backed :class:`Vector`. The detector→scan rotation is read from + the parent dataset metadata (``q_to_r_rotation_ccw_deg`` + ``q_transpose``), + the single source of truth shared with the DPC/CoM workflow. + + Use :meth:`from_dataset` to construct an instance. + + Parameters + ---------- + dataset : Dataset4dstem + The 4D-STEM dataset to analyze. + device : str, default="cpu" + Torch device used for detection (e.g. ``"cpu"`` or ``"cuda"``). + """ + + _token = object() + + # nanobeam lattice vectors are measured in reciprocal space -> sign flip + real_space: bool = False + + def __init__( + self, + dataset: Dataset4dstem, + device: str = "cpu", + _token: object | None = None, + ): + if _token is not self._token: + raise RuntimeError("Use BraggVectors.from_dataset() to instantiate this class.") + super().__init__() + self.dataset = dataset + self.device = device + + self.peaks: Vector | None = None + self.bvm: Dataset2d | None = None + + self.origin: np.ndarray | None = None + self.g1: np.ndarray | None = None + self.g2: np.ndarray | None = None + + # candidate peaks picked from the BVM in choose_basis_vectors(), and the + # reference lattice (those candidates indexed) set by index_peaks(). + self.candidates_rc: np.ndarray | None = None + self.candidates_intensity: np.ndarray | None = None + self.reference_ab: np.ndarray | None = None + self.reference_qpos: np.ndarray | None = None + self.reference_intensity: np.ndarray | None = None + + self.u_array: np.ndarray | None = None + self.v_array: np.ndarray | None = None + # per-position diagnostics from fit_lattice() + self.mask_weight: np.ndarray | None = None + self.fit_error: np.ndarray | None = None + + self._template: torch.Tensor | None = None + self._template_ft: torch.Tensor | None = None + self.metadata: dict[str, Any] = {} + + @classmethod + def from_dataset( + cls, dataset: Dataset4dstem, *, device: str = "cpu", name: str | None = None + ) -> "BraggVectors": + """Create a BraggVectors workflow bound to a 4D-STEM dataset. + + Parameters + ---------- + dataset : Dataset4dstem + The 4D-STEM dataset to analyze. + device : str, default="cpu" + Torch device used for detection (e.g. ``"cpu"`` or ``"cuda"``). + name : str, optional + If given, sets ``dataset.name``. + + Returns + ------- + BraggVectors + A new workflow instance bound to ``dataset``. + """ + if not isinstance(dataset, Dataset4dstem): + raise TypeError("BraggVectors.from_dataset expects a Dataset4dstem instance.") + if name is not None: + dataset.name = name + return cls(dataset=dataset, device=device, _token=cls._token) + + # ---- main methods ---- + + def make_template_synthetic( + self, + radius: float | None = None, + edge: float = 1.0, + center: tuple[float, float] | None = None, + subtract_mean: bool = True, + ) -> "BraggVectors": + """Build the template from a synthetic soft-edged disk. + + Parameters + ---------- + radius : float, optional + Disk radius in pixels. Defaults to a rough estimate from the mean + diffraction pattern + (:func:`~quantem.diffraction.disk_detection.estimate_central_beam`); + pass it explicitly when several disks share comparable intensity. + edge : float, default=1.0 + Width in pixels of the ``tanh`` edge falloff. + center : tuple of float, optional + ``(row, col)`` disk center; defaults to the detector center + ``(H // 2, W // 2)``. + subtract_mean : bool, default=True + If ``True``, make the template zero-sum — a band-pass kernel that + suppresses uniform background in the correlation. + + Returns + ------- + BraggVectors + ``self``, for method chaining. + """ + H, W = int(self.dataset.shape[-2]), int(self.dataset.shape[-1]) + if radius is None: + dp_mean = torch.as_tensor( + np.asarray(self.dataset.dp_mean.array), dtype=torch.float, device=self.device + ) + _, radius = estimate_central_beam(dp_mean) + if center is None: + center = (H // 2, W // 2) + probe = synthetic_probe((H, W), float(radius), edge=edge, center=center) + self._set_template(probe, center=center, subtract_mean=subtract_mean) + self.metadata["template"] = { + "kind": "synthetic", + "radius": float(radius), + "edge": float(edge), + "center": (float(center[0]), float(center[1])), + } + return self + + def make_template_from_data( + self, + roi: NDArray | None = None, + subtract_mean: bool = True, + center: tuple[float, float] | None = None, + ) -> "BraggVectors": + """Build the template by averaging diffraction patterns from the data. + + Parameters + ---------- + roi : np.ndarray, optional + ``(scan_row, scan_col)`` mask selecting scan positions to average — + ideally a vacuum / single-disk region so the unscattered probe is + isolated. ``None`` (default) averages the whole scan (the mean + diffraction pattern). + subtract_mean : bool, default=True + If ``True``, make the template zero-sum — a band-pass kernel that + suppresses uniform background in the correlation. + center : tuple of float, optional + ``(row, col)`` probe center rolled to the origin; defaults to the + probe's intensity centroid. + + Returns + ------- + BraggVectors + ``self``, for method chaining. + """ + data = torch.as_tensor( + np.asarray(self.dataset.array), dtype=torch.float, device=self.device + ) + if roi is None: + probe = data.mean(dim=(0, 1)) + else: + m = torch.as_tensor(np.asarray(roi) > 0, device=self.device) + if not bool(m.any()): + raise ValueError("roi selects no scan positions.") + probe = data[m].mean(dim=0) + if center is None: + center = probe_centroid(probe) + self._set_template(probe, center=center, subtract_mean=subtract_mean) + self.metadata["template"] = { + "kind": "from_data", + "roi": roi is not None, + "center": (float(center[0]), float(center[1])), + } + return self + + def make_template_from_probe( + self, + probe: NDArray | torch.Tensor, + center: tuple[float, float] | None = None, + subtract_mean: bool = True, + ) -> "BraggVectors": + """Build the template from an explicit probe image (e.g. a measured vacuum probe). + + Parameters + ---------- + probe : np.ndarray or torch.Tensor + Probe image; must match the diffraction-pattern shape. + center : tuple of float, optional + ``(row, col)`` probe center rolled to the origin; defaults to the + probe's intensity centroid. + subtract_mean : bool, default=True + If ``True``, make the template zero-sum — a band-pass kernel that + suppresses uniform background in the correlation. + + Returns + ------- + BraggVectors + ``self``, for method chaining. + """ + probe_t = torch.as_tensor(probe, dtype=torch.float, device=self.device) + if center is None and tuple(probe_t.shape) == tuple(self.dataset.shape[-2:]): + center = probe_centroid(probe_t) + self._set_template(probe_t, center=center, subtract_mean=subtract_mean) + self.metadata["template"] = { + "kind": "from_probe", + "center": (float(center[0]), float(center[1])), + } + return self + + @property + def template(self) -> np.ndarray | None: + """The correlation template, fftshifted to the image center for display (numpy). + + The ``make_template_*`` methods store the template corner-shifted (center + at the ``[0, 0]`` FFT origin) so correlation peaks land at absolute disk + positions; this property shifts it back to the center for plotting. + + Returns + ------- + np.ndarray or None + ``(H, W)`` center-shifted template, or ``None`` if no template has been + built yet. + """ + if self._template is None: + return None + return torch.fft.fftshift(self._template).detach().cpu().numpy() + + def correlation_map(self, row: int, col: int) -> np.ndarray: + """Cross-correlation map of one diffraction pattern with the template (numpy). + + Peaks in the returned map sit at absolute disk positions (no fftshift + needed), matching what :meth:`detect_disks` searches. + + Parameters + ---------- + row : int + Scan row of the diffraction pattern to correlate. + col : int + Scan column of the diffraction pattern to correlate. + + Returns + ------- + np.ndarray + ``(H, W)`` real-space correlation map. + """ + if self._template_ft is None: + raise ValueError("Run a make_template_* method before correlation_map().") + dp = torch.as_tensor( + np.asarray(self.dataset.array[row, col]), dtype=torch.float, device=self.device + ) + corr, _ = cross_correlation(dp, self._template_ft) + return corr.detach().cpu().numpy() + + def detect_disks( + self, + *, + positions: list[tuple[int, int]] | None = None, + min_abs_intensity: float = 0.0, + min_spacing: float = 0.0, + edge_boundary: int = 1, + subpixel: str = "upsample", + upsample_factor: int = 16, + max_num_peaks: int = 1000, + batch_size: int | None = None, + progressbar: bool = True, + ) -> Vector: + """Detect Bragg disks at every scan position (or a subset for testing). + + Pass ``positions`` to test detection hyperparameters on a handful of + patterns without scanning the full grid; the returned :class:`Vector` then + has shape ``(len(positions),)`` and the workflow state is left untouched. + With ``positions=None`` the full scan is processed, :attr:`peaks` and + :attr:`bvm` are populated, and the same Vector is returned. Patterns are + processed in batches (the FFTs and subpixel refinement run together across + the batch, which is far faster on a GPU); results are identical to detecting + each pattern on its own. + + Parameters + ---------- + positions : list of tuple of int, optional + ``(row, col)`` scan positions to test on. ``None`` (default) processes + the full scan and updates the workflow state. + min_abs_intensity : float, default=0.0 + Drop correlation peaks below this absolute intensity. + min_spacing : float, default=0.0 + Minimum spacing in pixels between kept peaks; closer / dimmer peaks are + suppressed. + edge_boundary : int, default=1 + Width in pixels of the border in which peaks are ignored. + subpixel : {"none", "parabolic", "upsample"}, default="upsample" + Subpixel refinement mode; see + :func:`~quantem.diffraction.disk_detection.detect_disks`. + upsample_factor : int, default=16 + Upsampling factor for the ``"upsample"`` subpixel refinement. + max_num_peaks : int, default=1000 + Maximum number of peaks to keep per pattern. + batch_size : int, optional + Number of patterns per batch. ``None`` (default) picks a size from the + detector dimensions. + progressbar : bool, default=True + If ``True``, show a tqdm progress bar over the full-scan detection. + + Returns + ------- + Vector + Detected peaks (``[q_row, q_col, intensity]``): shape ``(scan_row, + scan_col)`` for the full scan, or ``(len(positions),)`` for a test run. + """ + if self._template_ft is None: + raise ValueError("Run a make_template_* method before detect_disks().") + + detect_kwargs = dict( + min_abs_intensity=min_abs_intensity, + min_spacing=min_spacing, + edge_boundary=edge_boundary, + subpixel=subpixel, + upsample_factor=upsample_factor, + max_num_peaks=max_num_peaks, + ) + + if positions is not None: + if len(positions) == 0: + raise ValueError("positions must contain at least one (row, col) to test on.") + coords = [(int(r), int(c)) for r, c in positions] + results = self._detect_positions(coords, detect_kwargs, batch_size, progressbar=False) + return Vector.from_data(results, fields=PEAK_FIELDS, name="bragg_peaks_test") + + scan_r, scan_c = int(self.dataset.shape[0]), int(self.dataset.shape[1]) + coords = list(np.ndindex(scan_r, scan_c)) + results = self._detect_positions( + coords, detect_kwargs, batch_size, progressbar=progressbar + ) + # Store every cell in a single bulk pass. Per-cell assignment + # (peaks[r, c] = arr) re-concatenates the entire backing buffer on every + # write -- O(N^2) over the scan, which is the stall at the end of + # detection. from_data stacks all cells with one _replace_cells call. + nested = [results[r * scan_c : (r + 1) * scan_c] for r in range(scan_r)] + peaks = Vector.from_data(nested, fields=PEAK_FIELDS, name="bragg_peaks") + + self.peaks = peaks + self.metadata["detect"] = detect_kwargs + self.compute_bvm() + return peaks + + def compute_bvm(self, sampling: float = 1.0) -> Dataset2d: + """Accumulate all detected peaks into a Bragg vector map (intensity histogram). + + Parameters + ---------- + sampling : float, default=1.0 + Reciprocal-space sampling (per pixel) stored on the returned dataset. + + Returns + ------- + Dataset2d + ``(H, W)`` Bragg vector map, also stored on :attr:`bvm`. + """ + if self.peaks is None: + raise ValueError("Run detect_disks() before compute_bvm().") + H, W = (int(self.dataset.shape[-2]), int(self.dataset.shape[-1])) + flat = self.peaks.select_fields("q_row", "q_col", "intensity").flatten() + + bvm = np.zeros((H, W), dtype=float) + if flat.shape[0] > 0: + rows = np.clip(np.round(flat[:, 0]).astype(int), 0, H - 1) + cols = np.clip(np.round(flat[:, 1]).astype(int), 0, W - 1) + np.add.at(bvm, (rows, cols), flat[:, 2]) + + self.bvm = Dataset2d.from_array( + bvm, name="bragg_vector_map", sampling=(sampling, sampling), signal_units="intensity" + ) + return self.bvm + + def choose_basis_vectors( + self, + origin: int | tuple[float, float] | NDArray | None = None, + g1: int | tuple[float, float] | NDArray | None = None, + g2: int | tuple[float, float] | NDArray | None = None, + *, + num_candidates: int = 100, + min_spacing: float = 2.0, + min_abs_intensity: float = 0.0, + plot: bool = True, + returnfig: bool = False, + **show_kwargs, + ): + """Select the lattice basis ``(origin, g1, g2)`` from the Bragg vector map. + + Any of ``origin``/``g1``/``g2`` may be given explicitly; the rest are picked + automatically. The origin is the **brightest** candidate peak (the + unscattered central beam). With ``quality = intensity / distance`` rewarding + short, bright vectors, ``g1`` is then the highest-``quality`` peak and ``g2`` + is the highest ``quality * sin^2(theta)`` peak, where ``theta`` is its angle + to ``g1`` (the ``sin^2`` factor vanishes for peaks collinear with ``g1``). + + Each override accepts **either** form, told apart by shape: + + - a scalar **candidate index** (an ``int``) picks one of the numbered + candidate peaks drawn on the plot; + - a **``(row, col)`` vector** is taken literally — an absolute position for + ``origin``, an offset *from the origin* for ``g1``/``g2``. + + With ``plot=True`` (default) the Bragg vector map is shown with the + candidate peaks numbered and the chosen basis overlaid, so the index to + pass back here can be read straight off the figure. + + Parameters + ---------- + origin : int or tuple of float or np.ndarray, optional + Candidate index, or absolute ``(row, col)`` lattice origin. Picked + automatically if omitted. + g1 : int or tuple of float or np.ndarray, optional + Candidate index (vector taken as ``peak - origin``), or a ``(row, col)`` + offset *from the origin*. Picked automatically if omitted. + g2 : int or tuple of float or np.ndarray, optional + Candidate index (vector taken as ``peak - origin``), or a ``(row, col)`` + offset *from the origin*. Picked automatically if omitted. + num_candidates : int, default=100 + Number of brightest candidate peaks to consider (and to number on the + plot). + min_spacing : float, default=2.0 + Minimum spacing in pixels between candidate peaks. + min_abs_intensity : float, default=0.0 + Drop candidate peaks below this absolute intensity. + plot : bool, default=True + If ``True``, show the Bragg vector map with the numbered candidates and + the chosen basis overlaid via + :func:`~quantem.diffraction.bragg_vectors_visualization.plot_basis_vectors`. + returnfig : bool, default=False + If ``True``, return ``(fig, ax)`` from the overlay plot instead of + ``self`` (implies ``plot=True``). + **show_kwargs + Display-scaling options forwarded to the overlay plot's + :func:`~quantem.core.visualization.show_2d` call (e.g. ``norm``, + ``vmin``, ``vmax``, ``cmap``, ``lower_quantile``, ``upper_quantile``). + + Returns + ------- + BraggVectors or tuple + ``self`` for method chaining; or ``(fig, ax)`` when ``returnfig=True``. + """ + if self.bvm is None: + raise ValueError("Run detect_disks()/compute_bvm() before choosing basis vectors.") + + cand_rc, cand_int = self._bvm_candidates(num_candidates, min_spacing, min_abs_intensity) + if cand_rc.shape[0] == 0: + raise RuntimeError("No candidate peaks found in the Bragg vector map.") + # remember the numbered candidates so index_peaks() reuses this exact set. + self.candidates_rc = cand_rc + self.candidates_intensity = cand_int + + def _candidate(idx: int) -> NDArray: + i = int(idx) + if not -cand_rc.shape[0] <= i < cand_rc.shape[0]: + raise IndexError( + f"candidate index {i} out of range for {cand_rc.shape[0]} candidates " + f"(increase num_candidates or loosen min_spacing/min_abs_intensity)." + ) + return cand_rc[i] + + if origin is None: + # candidates are returned brightest-first, so [0] is the central beam. + origin_rc = cand_rc[0] + elif np.ndim(origin) == 0: + origin_rc = _candidate(origin) + else: + origin_rc = np.asarray(origin, dtype=float).reshape(2) + + rel = cand_rc - origin_rc + dist = np.linalg.norm(rel, axis=1) + valid = dist > max(1e-6, min_spacing) + # "shortest/brightest": reward bright peaks close to the origin. + quality = cand_int / (dist + 1e-12) + + if g1 is None: + if not valid.any(): + raise RuntimeError("Could not find a g1 candidate distinct from the origin.") + g1_rc = rel[int(np.argmax(np.where(valid, quality, -np.inf)))] + elif np.ndim(g1) == 0: + g1_rc = _candidate(g1) - origin_rc + else: + g1_rc = np.asarray(g1, dtype=float).reshape(2) + + if g2 is None: + # g2 = highest quality * sin^2(theta) where theta is the angle to g1; + # sin^2 = 1 - cos^2 vanishes for peaks collinear with g1. + g1n = g1_rc / (np.linalg.norm(g1_rc) + 1e-12) + cos = rel @ g1n / (dist + 1e-12) + sin2 = np.clip(1.0 - cos**2, 0.0, 1.0) + g2_score = np.where(valid, quality * sin2, -np.inf) + if g2_score.max() <= 0.0: + raise RuntimeError("Could not find a g2 candidate non-collinear with g1.") + g2_rc = rel[int(np.argmax(g2_score))] + elif np.ndim(g2) == 0: + g2_rc = _candidate(g2) - origin_rc + else: + g2_rc = np.asarray(g2, dtype=float).reshape(2) + + self.origin = np.asarray(origin_rc, dtype=float).reshape(2) + self.g1 = np.asarray(g1_rc, dtype=float).reshape(2) + self.g2 = np.asarray(g2_rc, dtype=float).reshape(2) + + if plot or returnfig: + fig, ax = plot_basis_vectors( + np.asarray(self.bvm.array), + cand_rc, + cand_int, + self.origin, + self.g1, + self.g2, + **show_kwargs, + ) + if returnfig: + return fig, ax + return self + + def index_peaks( + self, + *, + plot: bool = True, + returnfig: bool = False, + **show_kwargs, + ): + """Index the chosen candidate peaks into a reference lattice. + + Assigns integer Miller indices ``(a, b)`` to the numbered candidate peaks + picked in :meth:`choose_basis_vectors` — not the full per-position + detections; that heavy lifting happens in :meth:`fit_lattice`. Each + candidate gets ``[a, b] = round(B^-1 (q - origin))`` with ``B`` columns + ``g1, g2``; when two candidates round to the same ``(a, b)`` only the + brightest is kept. The result is a compact reference lattice stored on + :attr:`reference_ab` / :attr:`reference_qpos` / :attr:`reference_intensity` + which :meth:`fit_lattice` matches against at every scan position. This step + is deliberately quick and lightweight. + + With ``plot=True`` (default) the reference lattice is drawn over the Bragg + vector map, each site ringed and labelled with its ``(a, b)`` index. The ring + color encodes how far the picked candidate sits from its ideal lattice site + ``origin + a*g1 + b*g2``, so a mis-picked or duplicate candidate (which rings + far from zero offset) is easy to spot. + + Parameters + ---------- + plot : bool, default=True + If ``True``, show the reference lattice via + :func:`~quantem.diffraction.bragg_vectors_visualization.plot_reference_lattice`. + returnfig : bool, default=False + If ``True``, return ``(fig, ax)`` instead of ``self`` (implies ``plot``). + **show_kwargs + Display-scaling options forwarded to the plot's + :func:`~quantem.core.visualization.show_2d` call (e.g. ``norm``, + ``vmin``, ``vmax``, ``cmap``). + + Returns + ------- + BraggVectors or tuple + ``self`` for method chaining; or ``(fig, ax)`` when ``returnfig=True``. + """ + if self.candidates_rc is None or self.candidates_intensity is None: + raise ValueError("Run choose_basis_vectors() before index_peaks().") + if self.origin is None or self.g1 is None or self.g2 is None: + raise ValueError("Run choose_basis_vectors() before index_peaks().") + + cand_rc = self.candidates_rc + cand_int = self.candidates_intensity + ab = _index_directions(cand_rc, self.origin, self.g1, self.g2) + + # Dedupe by (a, b): candidates are brightest-first, so the first occurrence + # of each index is the brightest -- keep it, drop the dimmer duplicates. + seen: dict[tuple[int, int], int] = {} + for i, (a, b) in enumerate(ab): + key = (int(a), int(b)) + if key not in seen: + seen[key] = i + keep = np.array(sorted(seen.values()), dtype=int) + + self.reference_ab = ab[keep] + self.reference_qpos = cand_rc[keep] + self.reference_intensity = cand_int[keep] + + if not (plot or returnfig): + return self + + fig, ax = plot_reference_lattice( + np.asarray(self.bvm.array), + self.reference_qpos, + self.reference_ab, + self.origin, + self.g1, + self.g2, + **show_kwargs, + ) + if returnfig: + return fig, ax + return self + + def fit_lattice( + self, + min_num_peaks: int = 5, + max_peak_shift: float | None = None, + *, + progressbar: bool = True, + plot: bool = True, + returnfig: bool = False, + ): + """Per-position weighted least-squares fit of the lattice vectors. + + This is the heavy step. At every scan position the detected peaks are + matched to the *ideal* lattice sites from :meth:`index_peaks` — + ``origin + a*g1 + b*g2`` for each reference ``(a, b)`` — keeping a peak only + when it lands within ``max_peak_shift`` of its nearest ideal site (not the + measured candidate position), then ``q = x0 + a*g1 + b*g2`` is fit by + intensity-weighted least squares over the matched peaks. The fitted ``g1``/``g2`` go into :attr:`u_array`/ + :attr:`v_array` (shape ``(scan_row, scan_col, 2)``, row/col components); + positions with fewer than ``min_num_peaks`` matched peaks are left ``nan``. + + Two diagnostics are stored per position. :attr:`fit_error` is the RMS fit + residual over the *matched* peaks, in pixels. :attr:`mask_weight` is a lattice + *order parameter* in ``0``–``1``: every detected peak is snapped to the + nearest site of the just-fitted lattice and the intensity-weighted RMS of + those displacements (the zero beam excluded) is normalized by + ``sqrt(|g1 x g2| / 2*pi)`` — the RMS displacement expected from intensity + scattered at random with no lattice — as ``1 - RMS_all / rms_rand`` clipped to + ``[0, 1]``. Because it weighs *all* detected intensity against the lattice + (not just the matched peaks, as :attr:`fit_error` does), a clean single + crystal approaches ``1`` while positions with strong off-lattice intensity (a + second grain, a mis-index, many spurious peaks) fall toward ``0``; weak false + positives carry little intensity and barely move it. Positions with no valid + fit (vacuum, fewer than ``min_num_peaks``) are ``0``. It is the default + reference weighting handed to :meth:`calculate_strain_map`. + + Parameters + ---------- + min_num_peaks : int, default=5 + Minimum number of matched peaks required to fit a position; positions + with fewer are left ``nan``. + max_peak_shift : float, optional + Inclusion radius in pixels: a detected peak is kept only if it lands + within this distance of its nearest *ideal* lattice site + (``origin + a*g1 + b*g2``), excluding peaks that stray too far from where + the best-fit lattice predicts. Defaults to ``0.5 * min(|g1|, |g2|)`` — + half the shorter lattice spacing. + progressbar : bool, default=True + If ``True``, show a tqdm progress bar over the scan positions. + plot : bool, default=True + If ``True``, show the fit diagnostics (mask weight + RMS error) via + :func:`~quantem.diffraction.bragg_vectors_visualization.plot_lattice_fit`. + returnfig : bool, default=False + If ``True``, return ``(fig, ax)`` instead of ``self`` (implies ``plot``). + + Returns + ------- + BraggVectors or tuple + ``self`` for method chaining; or ``(fig, ax)`` when ``returnfig=True``. + """ + if self.peaks is None: + raise ValueError("Run detect_disks() before fit_lattice().") + if self.reference_ab is None or self.reference_qpos is None: + raise ValueError("Run index_peaks() before fit_lattice().") + + ref_ab = self.reference_ab.astype(float) + + # Ideal lattice sites for the reference (a, b) set: origin + a*g1 + b*g2. + # Per-position detections are matched to these *ideal* points (not the measured + # candidate positions), so a peak is included only when it lands within + # max_peak_shift of where the best-fit lattice predicts it should be. + o = np.asarray(self.origin, dtype=float).reshape(2) + g1 = np.asarray(self.g1, dtype=float).reshape(2) + g2 = np.asarray(self.g2, dtype=float).reshape(2) + ideal_qpos = o[None, :] + ref_ab[:, 0:1] * g1[None, :] + ref_ab[:, 1:2] * g2[None, :] + + if max_peak_shift is None: + max_peak_shift = 0.5 * float(min(np.linalg.norm(g1), np.linalg.norm(g2))) + + # Normalization scale for the mask weight: the intensity-weighted RMS + # peak-to-nearest-site displacement expected from intensity scattered at + # random (uniformly over a unit cell of area |g1 x g2|) -- i.e. with no + # lattice order at all. sqrt(A / 2pi) is the equal-area-disk value; the mask + # weight is then 1 - RMS_all / rms_rand, a lattice order parameter in [0, 1]. + cell_area = abs(float(g1[0] * g2[1] - g1[1] * g2[0])) + rms_rand = float(np.sqrt(cell_area / (2.0 * np.pi))) if cell_area > 0 else 1.0 + + scan_r, scan_c = int(self.dataset.shape[0]), int(self.dataset.shape[1]) + u_array = np.full((scan_r, scan_c, 2), np.nan, dtype=float) + v_array = np.full((scan_r, scan_c, 2), np.nan, dtype=float) + mask_weight = np.zeros((scan_r, scan_c), dtype=float) + fit_error = np.full((scan_r, scan_c), np.nan, dtype=float) + + fields = self.peaks.fields + i_qr, i_qc = fields.index("q_row"), fields.index("q_col") + i_int = fields.index("intensity") + + coords: Any = list(np.ndindex(scan_r, scan_c)) + if progressbar: + try: + from tqdm.auto import tqdm + + coords = tqdm(coords, desc="fit_lattice", leave=True) + except Exception: + pass + + for r, c in coords: + cell = self.peaks[r, c].array + if cell.shape[0] == 0: + continue + qpos = cell[:, [i_qr, i_qc]] + inten = cell[:, i_int] + + # nearest *ideal* lattice site for each detected peak, kept if close enough + d = np.linalg.norm(qpos[:, None, :] - ideal_qpos[None, :, :], axis=2) + nearest = np.argmin(d, axis=1) + matched = d[np.arange(d.shape[0]), nearest] <= max_peak_shift + + if int(matched.sum()) < min_num_peaks: + continue + + beta, rms = _fit_lattice_vectors( + qpos[matched, 0], + qpos[matched, 1], + ref_ab[nearest[matched], 0], + ref_ab[nearest[matched], 1], + inten[matched], + ) + if beta is None: + continue + u_array[r, c] = beta[1] + v_array[r, c] = beta[2] + fit_error[r, c] = rms + + # mask weight = lattice "order parameter": snap EVERY detected peak to the + # nearest site of the just-fitted lattice (beta = [x0, g1, g2]) and take + # the intensity-weighted RMS of those displacements, excluding the zero + # beam. Unlike fit_error (matched peaks only), this sees OFF-lattice + # intensity -- a second grain, a mis-index, or many spurious peaks drive + # it up -- while weak false positives, carrying little intensity, barely + # move it. Normalized by rms_rand: 1 (all intensity on the lattice) down + # to 0 (scattered as if there were no lattice). + x0 = beta[0] + mat = np.stack([beta[1], beta[2]]) # (2, 2): rows g1, g2 + ab = np.rint((qpos - x0[None, :]) @ np.linalg.inv(mat)) + sites = x0[None, :] + ab @ mat + disp = np.linalg.norm(qpos - sites, axis=1) + nonzero = ~np.all(ab == 0, axis=1) # drop the central (zero) beam + w = np.clip(inten[nonzero], 0.0, None) + wsum = float(w.sum()) + if wsum > 0: + rms_all = float(np.sqrt(np.sum(w * disp[nonzero] ** 2) / wsum)) + mask_weight[r, c] = float(np.clip(1.0 - rms_all / rms_rand, 0.0, 1.0)) + + self.u_array = u_array + self.v_array = v_array + self.mask_weight = mask_weight + self.fit_error = fit_error + self.metadata["fit"] = { + "min_num_peaks": int(min_num_peaks), + "max_peak_shift": float(max_peak_shift), + } + + if not (plot or returnfig): + return self + + fig, ax = plot_lattice_fit(mask_weight, fit_error) + if returnfig: + return fig, ax + return self + + def calculate_strain_map( + self, + u_ref: np.ndarray | None = None, + v_ref: np.ndarray | None = None, + mask: np.ndarray | None = None, + ) -> StrainMap: + """Build a :class:`StrainMap` from the fitted per-position lattice vectors. + + Parameters + ---------- + u_ref : np.ndarray, optional + ``(2,)`` reference for the first lattice vector. Defaults to the median + over the scan inside :class:`StrainMap`. + v_ref : np.ndarray, optional + ``(2,)`` reference for the second lattice vector. Defaults to the median + over the scan inside :class:`StrainMap`. + mask : np.ndarray, optional + ``(scan_row, scan_col)`` per-position weighting used when computing the + reference lattice. Defaults to :attr:`mask_weight` from + :meth:`fit_lattice` (the lattice order parameter — how well all detected + intensity snaps to the fitted lattice), so clean single-crystal positions + dominate the reference and positions with off-lattice intensity are + down-weighted. + + Returns + ------- + StrainMap + A strain map initialized from the fitted lattice vectors. + """ + if self.u_array is None or self.v_array is None: + raise ValueError("Run fit_lattice() before calculate_strain_map().") + + if mask is None: + mask = self.mask_weight + + ds_sampling = float(self.dataset.sampling[0]) + ds_units = str(self.dataset.units[0]) + + return StrainMap( + u_array=self.u_array, + v_array=self.v_array, + ds_shape=tuple(self.dataset.shape), + real_space=self.real_space, + u_ref=u_ref, + v_ref=v_ref, + mask=mask, + ds_sampling=ds_sampling, + ds_units=ds_units, + ) + + # ---- visualization ---- + + def show_template( + self, + position: tuple[int, int] = (0, 0), + *, + crop_factor: float | None = None, + returnfig: bool = False, + **kwargs, + ): + """Plot the mean diffraction pattern, the template, and one correlation map. + + Parameters + ---------- + position : tuple of int, default=(0, 0) + ``(row, col)`` scan position whose correlation map is shown. + crop_factor : float, optional + If given, zoom all three panels to a square window of half-width + ``crop_factor * radius`` about the pattern center, where ``radius`` is + the central-beam radius (the synthetic template radius if known, else + estimated from the mean diffraction pattern). For example, + ``crop_factor=2.0`` shows two beam radii either side of the center. The + window is clamped to the detector, so a large factor shows the full + image. ``None`` (default) shows the full panels. + returnfig : bool, default=False + If ``True``, return the ``(fig, ax)`` for further customization. + **kwargs + Extra keyword arguments forwarded to + :func:`~quantem.diffraction.bragg_vectors_visualization.plot_template`. + + Returns + ------- + tuple + ``(fig, ax)`` when ``returnfig=True``; otherwise nothing. + """ + if self._template is None: + raise ValueError("Run a make_template_* method before show_template().") + r, c = int(position[0]), int(position[1]) + dp_mean = np.asarray(self.dataset.dp_mean.array) + + crop = None + if crop_factor is not None: + H, W = int(self.dataset.shape[-2]), int(self.dataset.shape[-1]) + radius = self.metadata.get("template", {}).get("radius") + if radius is None: + _, radius = estimate_central_beam(dp_mean) + crop = (H // 2, W // 2, float(crop_factor) * float(radius)) + + fig, ax = plot_template( + dp_mean, + self.template, + self.correlation_map(r, c), + (r, c), + crop=crop, + **kwargs, + ) + if returnfig: + return fig, ax + + def show_diffraction( + self, + inds: list[tuple[int, int]], + image: np.ndarray | None = None, + *, + ncols: int = 4, + image_kwargs: dict | None = None, + marker_radius: float | None = None, + linewidth: float = 0.5, + sigma_plot: float | None = None, + returnfig: bool = False, + **show_kwargs, + ): + """Preview the diffraction patterns at ``inds``, optionally beside a navigation image. + + No detection is run; this is for choosing scan positions to tune on. The + patterns are tiled ``ncols`` wide and rendered with + :func:`~quantem.core.visualization.show_2d`; the navigation image is styled + independently (real space and reciprocal space rarely want the same + scaling). + + Parameters + ---------- + inds : list of tuple of int + ``(row, col)`` scan positions to preview. + image : np.ndarray or Dataset2d, optional + Real-space navigation image (e.g. a virtual dark-field image) shown on + the left with the positions marked. ``None`` (default) shows only the + diffraction tiles. + ncols : int, default=4 + Number of diffraction tiles per row. + image_kwargs : dict, optional + Keyword arguments styling the navigation image (passed to + :func:`~quantem.core.visualization.show_2d`). + marker_radius : float, optional + Radius in image pixels of the scan-position marker rings. + linewidth : float, default=0.5 + Stroke width of the scan-position markers. + sigma_plot : float, optional + Gaussian blur (sigma) applied to the *displayed* patterns only. + returnfig : bool, default=False + If ``True``, return the ``(fig, ax)`` for further customization. + **show_kwargs + Extra keyword arguments (e.g. ``norm``, ``cmap``, ``cbar``, ``axsize``) + styling the diffraction tiles via + :func:`~quantem.core.visualization.show_2d`. + + Returns + ------- + tuple + ``(fig, ax)`` when ``returnfig=True``; otherwise nothing. + """ + if image is None: + image_arr = None + image_title = "navigation image" + else: + image_title = getattr(image, "name", None) or "navigation image" + image_arr = np.asarray(image.array if hasattr(image, "array") else image) + dps = [np.asarray(self.dataset.array[r, c], dtype=float) for r, c in inds] + fig, ax = plot_diffraction_grid( + image_arr, + dps, + inds, + ncols=ncols, + image_title=image_title, + image_kwargs=image_kwargs, + marker_radius=marker_radius, + linewidth=linewidth, + sigma_plot=sigma_plot, + **show_kwargs, + ) + if returnfig: + return fig, ax + + def show_detection( + self, + positions: list[tuple[int, int]] | None = None, + *, + min_abs_intensity: float = 0.0, + min_spacing: float = 0.0, + edge_boundary: int = 1, + subpixel: str = "upsample", + upsample_factor: int = 16, + max_num_peaks: int = 1000, + image: np.ndarray | None = None, + peak_radius: float = 6.0, + marker_radius: float | None = None, + linewidth: float = 1.0, + sigma_plot: float | None = None, + image_kwargs: dict | None = None, + returnfig: bool = False, + **plot_kwargs, + ): + """Detect on a few patterns and overlay the peaks, for tuning hyperparameters. + + The detection keywords match :meth:`detect_disks`. The workflow state + (:attr:`peaks`/:attr:`bvm`) is left untouched, so this is safe to re-run + while tuning; for the raw peaks, call :meth:`detect_disks` with the same + ``positions``. + + Parameters + ---------- + positions : list of tuple of int, optional + ``(row, col)`` scan positions to detect on. ``None`` (default) + auto-samples four positions spread across the scan. + min_abs_intensity : float, default=0.0 + Drop correlation peaks below this absolute intensity. + min_spacing : float, default=0.0 + Minimum spacing in pixels between kept peaks. + edge_boundary : int, default=1 + Width in pixels of the border in which peaks are ignored. + subpixel : {"none", "parabolic", "upsample"}, default="upsample" + Subpixel refinement mode; see :meth:`detect_disks`. + upsample_factor : int, default=16 + Upsampling factor for the ``"upsample"`` subpixel refinement. + max_num_peaks : int, default=1000 + Maximum number of peaks to keep per pattern. + image : np.ndarray or Dataset2d, optional + Real-space navigation image (e.g. a virtual dark-field image) shown on + the left with the chosen positions marked. ``None`` (default) shows only + the diffraction tiles. + peak_radius : float, default=6.0 + Radius in diffraction pixels of the cyan rings drawn at detected peaks. + marker_radius : float, optional + Radius in image pixels of the scan-position marker rings. + linewidth : float, default=1.0 + Stroke width of the peak and scan-position rings. + sigma_plot : float, optional + Gaussian blur (sigma) applied to the *displayed* patterns only; + detection still uses the raw data. + image_kwargs : dict, optional + Keyword arguments styling the navigation image (passed to + :func:`~quantem.core.visualization.show_2d`). + returnfig : bool, default=False + If ``True``, return the ``(fig, ax)`` for further customization. + **plot_kwargs + Extra keyword arguments (e.g. ``ncols``, ``norm``, ``cmap``, ``cbar``, + ``axsize``) styling the diffraction tiles via + :func:`~quantem.core.visualization.show_2d`. + + Returns + ------- + tuple + ``(fig, ax)`` when ``returnfig=True``; otherwise nothing. + """ + if positions is None: + positions = self._sample_positions() + sub = self.detect_disks( + positions=positions, + min_abs_intensity=min_abs_intensity, + min_spacing=min_spacing, + edge_boundary=edge_boundary, + subpixel=subpixel, + upsample_factor=upsample_factor, + max_num_peaks=max_num_peaks, + progressbar=False, + ) + if image is None: + image_arr = None + image_title = "virtual image" + else: + image_title = getattr(image, "name", None) or "virtual image" + image_arr = np.asarray(image.array if hasattr(image, "array") else image) + dps = [np.asarray(self.dataset.array[r, c], dtype=float) for r, c in positions] + peaks = [sub[i].array for i in range(len(positions))] + fig, ax = plot_detection( + image_arr, + dps, + peaks, + positions, + peak_radius=peak_radius, + marker_radius=marker_radius, + linewidth=linewidth, + sigma_plot=sigma_plot, + image_title=image_title, + image_kwargs=image_kwargs, + **plot_kwargs, + ) + if returnfig: + return fig, ax + + def peak_histogram(self, *, returnfig: bool = False, **kwargs): + """Plot the Bragg vector map beside the per-position peak count. + + The Bragg vector map is the 2-D histogram of all detected peak positions + (intensity-weighted) accumulated over the scan; the second panel shows the + number of peaks detected at each scan position. + + Parameters + ---------- + returnfig : bool, default=False + If ``True``, return the ``(fig, ax)`` for further customization. + **kwargs + Extra keyword arguments forwarded to + :func:`~quantem.diffraction.bragg_vectors_visualization.plot_bvm`. + + Returns + ------- + tuple + ``(fig, ax)`` when ``returnfig=True``; otherwise nothing. + """ + if self.peaks is None or self.bvm is None: + raise ValueError("Run detect_disks() before peak_histogram().") + scan_r, scan_c = int(self.dataset.shape[0]), int(self.dataset.shape[1]) + # row_counts() reads cell_lengths directly; far cheaper than building a + # per-cell view (self.peaks[r, c]) just to read its row count. + counts = np.asarray(self.peaks.row_counts(), dtype=int).reshape(scan_r, scan_c) + fig, ax = plot_bvm(np.asarray(self.bvm.array), counts, **kwargs) + if returnfig: + return fig, ax + + # ---- helpers ---- + + def _set_template( + self, + probe: torch.Tensor, + center: tuple[float, float] | None, + subtract_mean: bool, + ) -> None: + """Validate the probe shape, then store the template and its conjugate FT. + + Parameters + ---------- + probe : torch.Tensor + Probe image; must match the diffraction-pattern shape. + center : tuple of float or None + ``(row, col)`` probe center rolled to the origin, or ``None`` for the + geometric center. + subtract_mean : bool + If ``True``, make the template zero-sum. + """ + dp_shape = tuple(self.dataset.shape[-2:]) + probe_t = torch.as_tensor(probe, dtype=torch.float, device=self.device) + if tuple(probe_t.shape) != dp_shape: + raise ValueError( + f"probe shape {tuple(probe_t.shape)} does not match diffraction pattern " + f"shape {dp_shape}." + ) + self._template = make_template(probe_t, center=center, subtract_mean=subtract_mean) + self._template_ft = template_fourier(self._template) + + def _sample_positions(self) -> list[tuple[int, int]]: + """Four scan positions spread across the field (quadrant centers). + + Returns + ------- + list of tuple of int + Up to four ``(row, col)`` scan positions at the quadrant centers. + """ + R, C = int(self.dataset.shape[0]), int(self.dataset.shape[1]) + rs = sorted({min(max(R // 4, 0), R - 1), min(max(3 * R // 4, 0), R - 1)}) + cs = sorted({min(max(C // 4, 0), C - 1), min(max(3 * C // 4, 0), C - 1)}) + return [(r, c) for r in rs for c in cs] + + def _detect_positions( + self, + coords: list[tuple[int, int]], + detect_kwargs: dict[str, Any], + batch_size: int | None, + *, + progressbar: bool, + ) -> list[NDArray]: + """Batched detection over a list of ``(row, col)`` scan positions. + + Patterns are stacked into chunks of ``batch_size`` and passed to + :func:`~quantem.diffraction.disk_detection.detect_disks_batch`. + + Parameters + ---------- + coords : list of tuple of int + ``(row, col)`` scan positions to detect on. + detect_kwargs : dict + Keyword arguments forwarded to + :func:`~quantem.diffraction.disk_detection.detect_disks_batch`. + batch_size : int or None + Number of patterns per batch. ``None`` picks a size from the detector + dimensions. + progressbar : bool + If ``True``, show a tqdm progress bar over the patterns. + + Returns + ------- + list of np.ndarray + One ``(M, 3)`` array of ``[q_row, q_col, intensity]`` per position, in + ``coords`` order. + """ + H, W = int(self.dataset.shape[-2]), int(self.dataset.shape[-1]) + if batch_size is None: + batch_size = int(min(1024, max(1, 16_000_000 // (H * W)))) + + it = range(0, len(coords), batch_size) + if progressbar: + try: + from tqdm.auto import tqdm + + bar = tqdm(total=len(coords), desc="detect_disks", leave=True) + except Exception: + bar = None + else: + bar = None + + results: list[NDArray] = [] + for start in it: + chunk = coords[start : start + batch_size] + dps = torch.stack( + [ + torch.as_tensor( + np.asarray(self.dataset.array[r, c]), + dtype=torch.float, + device=self.device, + ) + for r, c in chunk + ], + dim=0, + ) + out = detect_disks_batch(dps, self._template_ft, **detect_kwargs) + results.extend( + arr if arr.shape[0] else np.empty((0, len(PEAK_FIELDS)), dtype=float) + for arr in out + ) + if bar is not None: + bar.update(len(chunk)) + + if bar is not None: + bar.close() + return results + + def _bvm_candidates( + self, num_candidates: int, min_spacing: float, min_abs_intensity: float + ) -> tuple[NDArray, NDArray]: + """Find the brightest, well-separated local maxima in the Bragg vector map. + + Parameters + ---------- + num_candidates : int + Maximum number of candidate peaks to return. + min_spacing : float + Minimum spacing in pixels between candidates. + min_abs_intensity : float + Drop candidates below this absolute intensity. + + Returns + ------- + cand_rc : np.ndarray + ``(N, 2)`` ``[row, col]`` candidate positions, brightest first. + cand_int : np.ndarray + ``(N,)`` candidate intensities. + """ + from quantem.diffraction.disk_detection import _filter_maxima, _local_maxima + + bvm = torch.as_tensor(self.bvm.array, dtype=torch.float) + peaks = _local_maxima(bvm, edge_boundary=1) + peaks = _filter_maxima(peaks, min_abs_intensity, min_spacing, num_candidates) + arr = peaks.detach().cpu().numpy() + return arr[:, :2].astype(float), arr[:, 2].astype(float) + + +def _fractional_indices(q: NDArray, origin: NDArray, g1: NDArray, g2: NDArray) -> NDArray: + """Continuous (unrounded) ``(a, b)`` lattice coordinates of peaks ``q``. + + Solves ``B [a, b]^T = q - origin`` (least squares), with + ``B = [[g1_row, g2_row], [g1_col, g2_col]]``. + + Parameters + ---------- + q : np.ndarray + ``(N, 2)`` ``[row, col]`` peak positions. + origin : np.ndarray + ``(2,)`` lattice origin ``[row, col]``. + g1 : np.ndarray + ``(2,)`` first lattice vector ``[row, col]``. + g2 : np.ndarray + ``(2,)`` second lattice vector ``[row, col]``. + + Returns + ------- + np.ndarray + ``(N, 2)`` float array of continuous ``[a, b]`` coordinates. + """ + if q.shape[0] == 0: + return np.empty((0, 2), dtype=float) + beta = np.array([[g1[0], g2[0]], [g1[1], g2[1]]], dtype=float) + alpha = (q - origin[None, :]).T # (2, N) + return np.linalg.lstsq(beta, alpha, rcond=None)[0].T # (N, 2) + + +def _index_directions(q: NDArray, origin: NDArray, g1: NDArray, g2: NDArray) -> NDArray: + """Integer Miller indices for peaks ``q`` against basis ``(g1, g2)`` about ``origin``. + + Rounds the continuous coordinates from :func:`_fractional_indices`. + + Parameters + ---------- + q : np.ndarray + ``(N, 2)`` ``[row, col]`` peak positions. + origin : np.ndarray + ``(2,)`` lattice origin ``[row, col]``. + g1 : np.ndarray + ``(2,)`` first lattice vector ``[row, col]``. + g2 : np.ndarray + ``(2,)`` second lattice vector ``[row, col]``. + + Returns + ------- + np.ndarray + ``(N, 2)`` int array of ``[a, b]`` Miller indices. + """ + if q.shape[0] == 0: + return np.empty((0, 2), dtype=int) + return np.round(_fractional_indices(q, origin, g1, g2)).astype(int) + + +def _fit_lattice_vectors( + q_row: NDArray, + q_col: NDArray, + a: NDArray, + b: NDArray, + intensity: NDArray, +) -> tuple[NDArray | None, NDArray | None]: + """Intensity-weighted lattice fit ``q = x0 + a*g1 + b*g2`` for one pattern. + + Parameters + ---------- + q_row : np.ndarray + ``(N,)`` peak row positions. + q_col : np.ndarray + ``(N,)`` peak column positions. + a : np.ndarray + ``(N,)`` Miller index along ``g1``. + b : np.ndarray + ``(N,)`` Miller index along ``g2``. + intensity : np.ndarray + ``(N,)`` peak intensities, used as fit weights (``sqrt`` of the clamped + intensity). + + Returns + ------- + beta : np.ndarray or None + ``(3, 2)`` fit ``[x0; g1; g2]`` (row/col components per row), or ``None`` if + the fit is rank-deficient (e.g. all peaks share one lattice row). + rms : float + RMS fit residual in pixels (``nan`` when ``beta`` is ``None``). + """ + design = np.stack([np.ones_like(a), a, b], axis=1) # (N, 3) + target = np.stack([q_row, q_col], axis=1) # (N, 2) + w = np.sqrt(np.clip(intensity, 0.0, None))[:, None] + + if np.linalg.matrix_rank(design * w) < 3: + return None, float("nan") + + beta = np.linalg.lstsq(design * w, target * w, rcond=None)[0] # (3, 2): x0, g1, g2 + resid = target - design @ beta + rms = float(np.sqrt(np.mean(np.sum(resid**2, axis=1)))) + return beta, rms diff --git a/src/quantem/diffraction/bragg_vectors_visualization.py b/src/quantem/diffraction/bragg_vectors_visualization.py new file mode 100644 index 000000000..891f2e722 --- /dev/null +++ b/src/quantem/diffraction/bragg_vectors_visualization.py @@ -0,0 +1,768 @@ +from __future__ import annotations + +import matplotlib.pyplot as plt +import numpy as np + + +def plot_template( + dp_mean: np.ndarray, + template: np.ndarray, + corr_map: np.ndarray, + position: tuple[int, int], + *, + crop: tuple[float, float, float] | None = None, + figsize: tuple[float, float] = (13, 4), +): + """Mean diffraction pattern, the (centered) template, and one correlation map. + + Parameters + ---------- + dp_mean : np.ndarray + Mean diffraction pattern. + template : np.ndarray + The correlation template, centered for display. + corr_map : np.ndarray + Correlation map computed at ``position``. + position : tuple of int + ``(row, col)`` scan position the correlation map was computed at. + crop : tuple of float, optional + ``(center_row, center_col, half_width)`` zoom window (in pixels) applied to + all three panels. The view spans ``half_width`` either side of the center, + clamped to each image's bounds, so an over-large ``half_width`` just shows + the full image. ``None`` (default) shows the full panels. + figsize : tuple of float, default=(13, 4) + Figure size in inches. + + Returns + ------- + tuple + ``(fig, ax)`` with ``ax`` a length-3 array of axes. + """ + fig, ax = plt.subplots(1, 3, figsize=figsize) + ax[0].imshow(dp_mean, cmap="gray") + ax[0].set_title("mean diffraction") + ax[1].imshow(template, cmap="gray") + ax[1].set_title("template (centered)") + ax[2].imshow(corr_map, cmap="viridis") + ax[2].set_title(f"correlation @ {tuple(position)}") + for a in ax: + a.set_xticks([]) + a.set_yticks([]) + if crop is not None: + cr, cc, hw = float(crop[0]), float(crop[1]), float(crop[2]) + for a, img in zip(ax, (dp_mean, template, corr_map)): + h, w = img.shape[:2] + a.set_xlim(max(cc - hw, -0.5), min(cc + hw, w - 0.5)) + a.set_ylim(min(cr + hw, h - 0.5), max(cr - hw, -0.5)) + fig.tight_layout() + return fig, ax + + +def _mark_positions(ax, positions, *, radius=None, linewidth=0.5): + """Overlay numbered red markers at each ``(row, col)`` scan position. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Axis to draw the markers on. + positions : sequence of tuple of int + ``(row, col)`` scan positions to mark, numbered in order. + radius : float, optional + Marker radius in image pixels. If given, each marker is a ring of that + radius drawn as a circle patch in data coordinates; if ``None``, a fixed + screen-size scatter marker is used instead. + linewidth : float, default=0.5 + Ring stroke width. + """ + from matplotlib.patches import Circle + + for i, (r, c) in enumerate(positions): + if radius is None: + ax.scatter(c, r, s=70, facecolors="none", edgecolors="red", linewidths=linewidth) + else: + ax.add_patch( + Circle((c, r), radius=radius, fill=False, edgecolor="red", linewidth=linewidth) + ) + ax.annotate( + str(i), + (c, r), + color="red", + fontsize=11, + fontweight="bold", + xytext=(4, 4), + textcoords="offset points", + ) + + +def _blur(dp: np.ndarray, sigma: float | None) -> np.ndarray: + """Gaussian-blur a diffraction pattern for display only (passthrough if ``sigma`` falsy). + + Smoothing the *displayed* pattern (the detection still runs on the raw data) + makes it easier to judge by eye which correlation peaks sit on real disks and + which are noise. + + Parameters + ---------- + dp : np.ndarray + Diffraction pattern to blur. + sigma : float or None + Gaussian blur width in pixels. If falsy (``None``, ``0``, or negative), + ``dp`` is returned unchanged. + + Returns + ------- + np.ndarray + The blurred pattern, or ``dp`` unchanged when ``sigma`` is falsy. + """ + if not sigma or sigma <= 0: + return dp + from scipy.ndimage import gaussian_filter + + return gaussian_filter(np.asarray(dp, dtype=float), float(sigma)) + + +def _grid_axes(n, ncols, *, show_image, axsize=None, figsize=None): + """Figure with an optional left nav-image axis and a right ``nrows x ncols`` tile grid. + + With ``show_image`` a navigation-image axis is placed to the left of the tile + grid; otherwise the figure is just the grid. + + Parameters + ---------- + n : int + Number of tiles to lay out. + ncols : int + Number of tile columns, clamped to ``n``. + show_image : bool + If ``True``, add a navigation-image axis to the left of the tile grid. + axsize : tuple of float, optional + Per-tile size in inches, ``(w, h)``. Sizes the figure when ``figsize`` is + not given, so a larger ``axsize`` zooms every tile. + figsize : tuple of float, optional + Explicit figure size in inches; overrides the ``axsize``-derived size. + + Returns + ------- + tuple + ``(fig, ax_image, dp_axes, ncols)`` where ``ax_image`` is ``None`` when + ``show_image`` is ``False``, ``dp_axes`` is an ``(nrows, ncols)`` object + array of axes (trailing unused tiles already turned off), and ``ncols`` is + clamped to ``n``. + """ + ncols = max(1, min(ncols, n)) + nrows = int(np.ceil(n / ncols)) + + tile_w, tile_h = (float(axsize[0]), float(axsize[1])) if axsize is not None else (3.0, 3.2) + nav_w = (tile_w if axsize is not None else 4.0) if show_image else 0.0 + if figsize is None: + figsize = (nav_w + tile_w * ncols, max(tile_h, tile_h * nrows)) + + fig = plt.figure(figsize=figsize) + if show_image: + outer = fig.add_gridspec(1, 2, width_ratios=[nav_w, tile_w * ncols], wspace=0.12) + ax_image = fig.add_subplot(outer[0, 0]) + grid = outer[0, 1].subgridspec(nrows, ncols, wspace=0.08, hspace=0.2) + else: + ax_image = None + grid = fig.add_gridspec(nrows, ncols, wspace=0.08, hspace=0.2) + + dp_axes = np.empty((nrows, ncols), dtype=object) + for i in range(nrows * ncols): + a = fig.add_subplot(grid[i // ncols, i % ncols]) + dp_axes[i // ncols, i % ncols] = a + if i >= n: + a.axis("off") + return fig, ax_image, dp_axes, ncols + + +def plot_diffraction_grid( + image: np.ndarray | None, + dps: list[np.ndarray], + positions: list[tuple[int, int]], + *, + ncols: int = 4, + image_title: str = "navigation image", + image_kwargs: dict | None = None, + marker_radius: float | None = None, + linewidth: float = 0.5, + sigma_plot: float | None = None, + axsize: tuple[float, float] | None = None, + figsize: tuple[float, float] | None = None, + **show_kwargs, +): + """A tiled grid of the diffraction patterns at ``positions``, optionally beside a nav image. + + When ``image`` is given (e.g. a virtual dark-field image) it is drawn on the + left with each scan position marked as a numbered red marker at ``(x=col, + y=row)``; pass ``image=None`` to omit it and show only the grid. Right: the + diffraction patterns tiled ``ncols`` wide. Both are rendered with + :func:`~quantem.core.visualization.show_2d`, and the navigation image and the + diffraction tiles take separate, independent styling (``image_kwargs`` vs + ``show_kwargs``). + + Parameters + ---------- + image : np.ndarray or None + Navigation image (e.g. a virtual dark-field image) drawn at left, with the + scan positions marked. Pass ``None`` to show only the diffraction grid. + dps : list of np.ndarray + Diffraction patterns to tile, one per entry in ``positions``. + positions : list of tuple of int + ``(row, col)`` scan positions, used for the tile titles and the nav-image + markers. + ncols : int, default=4 + Number of columns in the diffraction-pattern tile grid. + image_title : str, default="navigation image" + Title for the navigation image. + image_kwargs : dict, optional + Extra keyword arguments (e.g. ``norm``, ``cmap``, ``scalebar``) for the + navigation image's :func:`show_2d` call, styling it independently of the + tiles. + marker_radius : float, optional + Scan-position marker radius in image pixels; ``None`` uses a fixed + screen-size marker. + linewidth : float, default=0.5 + Stroke width of the scan-position markers. + sigma_plot : float, optional + Gaussian blur width (pixels) applied to the *displayed* patterns only (the + data is untouched) to ease judging real features. + axsize : tuple of float, optional + Per-tile size in inches; a larger value zooms the tiles. + figsize : tuple of float, optional + Explicit figure size in inches. + **show_kwargs + Forwarded to :func:`show_2d` for the diffraction-pattern tiles (e.g. + ``norm``, ``cmap``, ``cbar``). + + Returns + ------- + tuple + ``(fig, (ax_image, dp_axes))`` with ``ax_image`` ``None`` when no image is + shown. + """ + from quantem.core.visualization import show_2d + + n = len(positions) + show_image = image is not None + fig, ax_image, dp_axes, ncols = _grid_axes( + n, ncols, show_image=show_image, axsize=axsize, figsize=figsize + ) + + if show_image: + image_show_kwargs = {"cmap": "gray", "title": image_title, **(image_kwargs or {})} + show_2d(np.asarray(image), figax=(fig, ax_image), **image_show_kwargs) + _mark_positions(ax_image, positions, radius=marker_radius, linewidth=linewidth) + + user_title = show_kwargs.pop("title", None) + for i in range(n): + r, c = positions[i] + title = user_title if user_title is not None else f"{i}: ({r},{c})" + show_2d( + _blur(dps[i], sigma_plot), + figax=(fig, dp_axes[i // ncols, i % ncols]), + title=title, + **show_kwargs, + ) + return fig, (ax_image, dp_axes) + + +def plot_detection( + image: np.ndarray | None, + dps: list[np.ndarray], + peaks: list[np.ndarray], + positions: list[tuple[int, int]], + *, + ncols: int = 4, + peak_radius: float = 6.0, + marker_radius: float | None = None, + linewidth: float = 0.5, + sigma_plot: float | None = None, + image_title: str = "virtual image", + image_kwargs: dict | None = None, + axsize: tuple[float, float] | None = None, + figsize: tuple[float, float] | None = None, + **show_kwargs, +): + """The diffraction patterns with detected peaks overlaid, optionally beside a nav image. + + When ``image`` is given (e.g. a virtual dark-field image) it is drawn on the + left with each chosen scan position drawn as a numbered red marker at ``(x=col, + y=row)``; pass ``image=None`` to omit it and show only the tiles. Right: the + diffraction patterns tiled ``ncols`` wide, each rendered with + :func:`~quantem.core.visualization.show_2d`, with the detected peaks overlaid as + cyan rings that trace the disks rather than obscuring them. + + Parameters + ---------- + image : np.ndarray or None + Navigation image (e.g. a virtual dark-field image) drawn at left, with the + scan positions marked. Pass ``None`` to show only the diffraction tiles. + dps : list of np.ndarray + Diffraction patterns to tile, one per entry in ``positions``. + peaks : list of np.ndarray + Detected peaks per pattern; ``peaks[i]`` is an ``(M, 3)`` array of + ``[q_row, q_col, intensity]``. Each peak is drawn as a cyan ring at + ``(x=q_col, y=q_row)``. + positions : list of tuple of int + ``(row, col)`` scan positions, used for the tile titles and the nav-image + markers. + ncols : int, default=4 + Number of columns in the diffraction-pattern tile grid. + peak_radius : float, default=6.0 + Radius of the cyan peak rings, in diffraction pixels. + marker_radius : float, optional + Scan-position marker radius in image pixels; ``None`` uses a fixed + screen-size marker. + linewidth : float, default=0.5 + Stroke width of both the scan-position markers and the peak rings. + sigma_plot : float, optional + Gaussian blur width (pixels) applied to the *displayed* patterns only + (detection still uses the raw data) to ease telling real disks from false + positives. + image_title : str, default="virtual image" + Title for the navigation image. + image_kwargs : dict, optional + Extra keyword arguments for the navigation image's :func:`show_2d` call, + styling it independently of the tiles. + axsize : tuple of float, optional + Per-tile size in inches; a larger value zooms the tiles. + figsize : tuple of float, optional + Explicit figure size in inches. + **show_kwargs + Forwarded to :func:`show_2d` for the diffraction-pattern tiles (e.g. + ``norm``, ``cmap``, ``cbar``). + + Returns + ------- + tuple + ``(fig, (ax_image, dp_axes))`` with ``ax_image`` ``None`` when no image is + shown. + """ + from matplotlib.patches import Circle + + from quantem.core.visualization import show_2d + + n = len(positions) + show_image = image is not None + fig, ax_image, dp_axes, ncols = _grid_axes( + n, ncols, show_image=show_image, axsize=axsize, figsize=figsize + ) + + if show_image: + image_show_kwargs = {"cmap": "gray", "title": image_title, **(image_kwargs or {})} + show_2d(np.asarray(image), figax=(fig, ax_image), **image_show_kwargs) + _mark_positions(ax_image, positions, radius=marker_radius, linewidth=linewidth) + + user_title = show_kwargs.pop("title", None) + for i in range(n): + a = dp_axes[i // ncols, i % ncols] + r, c = positions[i] + pk = peaks[i] + title = user_title if user_title is not None else f"{i}: ({r},{c}) n={pk.shape[0]}" + show_2d(_blur(dps[i], sigma_plot), figax=(fig, a), title=title, **show_kwargs) + for q in pk: + a.add_patch( + Circle( + (q[1], q[0]), + radius=peak_radius, + fill=False, + edgecolor="cyan", + linewidth=linewidth, + ) + ) + + return fig, (ax_image, dp_axes) + + +def plot_basis_vectors( + bvm: np.ndarray, + cand_rc: np.ndarray, + cand_int: np.ndarray, + origin: np.ndarray, + g1: np.ndarray, + g2: np.ndarray, + *, + cmap: str = "gray", + norm: str | dict = "log_auto", + zoom: bool = True, + figsize: tuple[float, float] = (6, 6), + **show_kwargs, +): + """The Bragg vector map with the candidate peaks, origin, and basis vectors overlaid. + + Every candidate peak is drawn as a numbered cyan ring; those numbers are the + indices accepted by + :meth:`~quantem.diffraction.bragg_vectors.BraggVectors.choose_basis_vectors` + for overriding ``origin``/``g1``/``g2`` by peak. The chosen origin is a green + marker and ``g1`` (red) / ``g2`` (blue) are arrows drawn from it, labelled at + their midpoints so the labels never sit on top of the candidate numbers. + + Parameters + ---------- + bvm : np.ndarray + Bragg vector map; rendered through + :func:`~quantem.core.visualization.show_2d` so its display scaling is + controlled by ``norm`` and ``show_kwargs``. + cand_rc : np.ndarray + ``(N, 2)`` ``[row, col]`` candidate peak positions, brightest first; the + ring labels are their row indices. + cand_int : np.ndarray + ``(N,)`` candidate intensities (unused for drawing; kept for parity with + the candidate API). + origin : np.ndarray + ``(row, col)`` chosen lattice origin. + g1 : np.ndarray + First lattice vector as a ``(row, col)`` offset from ``origin``. + g2 : np.ndarray + Second lattice vector as a ``(row, col)`` offset from ``origin``. + cmap : str, default="gray" + Colormap for the Bragg vector map; gray keeps the colored overlays legible. + norm : str or dict, default="log_auto" + Intensity scaling forwarded to :func:`show_2d` (e.g. ``"linear_auto"``, + ``"log_auto"``, ``"power_sqrt"``, or ``{"power": 0.5}``). + zoom : bool, default=True + If ``True``, frame the view to the candidate bounding box (plus a margin) + so the numbered peaks are large enough to read. + figsize : tuple of float, default=(6, 6) + Figure size in inches. + **show_kwargs + Extra keyword arguments forwarded to :func:`show_2d` for fine display + control (e.g. ``vmin``, ``vmax``, ``lower_quantile``, ``upper_quantile``). + + Returns + ------- + tuple + ``(fig, ax)``. + """ + import matplotlib.patheffects as path_effects + + from quantem.core.visualization import show_2d + + stroke = [path_effects.withStroke(linewidth=2.5, foreground="black")] + origin_color = (0.0, 0.7, 0.0) + g1_color = (1.0, 0.0, 0.0) + g2_color = (0.0, 0.7, 1.0) + + fig, ax = plt.subplots(figsize=figsize) + show_2d(np.asarray(bvm), figax=(fig, ax), cmap=cmap, norm=norm, **show_kwargs) + + cand_rc = np.asarray(cand_rc, dtype=float).reshape(-1, 2) + for i, (r, c) in enumerate(cand_rc): + ax.scatter(c, r, s=60, facecolors="none", edgecolors="cyan", linewidths=1.0, zorder=3) + ax.annotate( + str(i), + (c, r), + color="cyan", + fontsize=9, + fontweight="bold", + xytext=(4, 4), + textcoords="offset points", + path_effects=stroke, + zorder=4, + ) + + o = np.asarray(origin, dtype=float).reshape(2) + ax.scatter( + o[1], + o[0], + s=160, + marker="P", + facecolors=[origin_color], + edgecolors="white", + linewidths=1.5, + zorder=6, + ) + for g, label, color in ( + (np.asarray(g1, float), "g1", g1_color), + (np.asarray(g2, float), "g2", g2_color), + ): + tip = (o[1] + g[1], o[0] + g[0]) + ax.annotate( + "", + xy=tip, + xytext=(o[1], o[0]), + arrowprops=dict(arrowstyle="-|>", color=color, lw=2.4, shrinkA=0, shrinkB=0), + zorder=5, + ) + # Label at the arrow midpoint, nudged perpendicular to the shaft (in screen + # space) so it clears both the arrow and the candidate numbers on the peaks. + gnorm = float(np.hypot(g[0], g[1])) + 1e-12 + perp = (g[0] / gnorm * 15.0, g[1] / gnorm * 15.0) + mid = (o[1] + g[1] / 2.0, o[0] + g[0] / 2.0) + ax.annotate( + label, + mid, + color=color, + fontsize=14, + fontweight="bold", + xytext=perp, + textcoords="offset points", + ha="center", + va="center", + path_effects=stroke, + zorder=7, + ) + + if zoom and cand_rc.shape[0]: + rmin, cmin = cand_rc.min(axis=0) + rmax, cmax = cand_rc.max(axis=0) + margin = 0.12 * max(rmax - rmin, cmax - cmin, 1.0) + 6.0 + h, w = bvm.shape[:2] + ax.set_xlim(max(cmin - margin, -0.5), min(cmax + margin, w - 0.5)) + ax.set_ylim(min(rmax + margin, h - 0.5), max(rmin - margin, -0.5)) + + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_title("lattice basis (origin +, g1, g2; cyan = candidate index)") + fig.tight_layout() + return fig, ax + + +def plot_bvm( + bvm: np.ndarray, + counts: np.ndarray, + *, + figsize: tuple[float, float] = (10, 4), +): + """The Bragg vector map (log-scaled) beside the per-position peak count. + + Parameters + ---------- + bvm : np.ndarray + Bragg vector map; displayed log-scaled (``log1p``). + counts : np.ndarray + Per-position peak count, shape ``(scan_row, scan_col)``. + figsize : tuple of float, default=(10, 4) + Figure size in inches. + + Returns + ------- + tuple + ``(fig, ax)`` with ``ax`` a length-2 array of axes. + """ + fig, ax = plt.subplots(1, 2, figsize=figsize) + ax[0].imshow(np.log1p(bvm), cmap="inferno") + ax[0].set_title("Bragg vector map (log)") + im = ax[1].imshow(counts, cmap="viridis") + ax[1].set_title("peaks per position") + for a in ax: + a.set_xticks([]) + a.set_yticks([]) + fig.colorbar(im, ax=ax[1], fraction=0.046, pad=0.04) + fig.tight_layout() + return fig, ax + + +def plot_reference_lattice( + bvm: np.ndarray, + ref_qpos: np.ndarray, + ref_ab: np.ndarray, + origin: np.ndarray, + g1: np.ndarray, + g2: np.ndarray, + *, + cmap: str = "gray", + norm: str | dict = "log_auto", + zoom: bool = True, + figsize: tuple[float, float] = (6, 6), + **show_kwargs, +): + """The reference lattice from :meth:`BraggVectors.index_peaks`, drawn over the BVM. + + Each indexed reference site is a ring labelled with its ``(a, b)`` Miller index; + the chosen origin is a green marker and ``g1`` (red) / ``g2`` (blue) are arrows + from it. The ring color encodes how far the picked candidate sits from its *ideal* + lattice site ``origin + a*g1 + b*g2`` (the colorbar reads pixels), scaled to half + the shorter lattice spacing — the default :meth:`fit_lattice` match radius. A + mis-picked or duplicate candidate stands out as a ring far from zero offset (bright + color), sitting off the regular grid, or carrying an index that breaks the pattern. + + Parameters + ---------- + bvm : np.ndarray + Bragg vector map; rendered through + :func:`~quantem.core.visualization.show_2d` so its display scaling is + controlled by ``norm`` and ``show_kwargs``. + ref_qpos : np.ndarray + ``(N, 2)`` ``[row, col]`` reference site positions. + ref_ab : np.ndarray + ``(N, 2)`` integer ``[a, b]`` Miller indices for each site. + origin : np.ndarray + ``(row, col)`` chosen lattice origin. + g1 : np.ndarray + First lattice vector as a ``(row, col)`` offset from ``origin``. + g2 : np.ndarray + Second lattice vector as a ``(row, col)`` offset from ``origin``. + cmap : str, default="gray" + Colormap for the Bragg vector map; gray keeps the colored overlays legible. + norm : str or dict, default="log_auto" + Intensity scaling forwarded to :func:`show_2d` (e.g. ``"linear_auto"``, + ``"log_auto"``, ``"power_sqrt"``, or ``{"power": 0.5}``). + zoom : bool, default=True + If ``True``, frame the view to the reference bounding box (plus a margin) + so the labelled sites are large enough to read. + figsize : tuple of float, default=(6, 6) + Figure size in inches. + **show_kwargs + Extra keyword arguments forwarded to :func:`show_2d` for fine display + control (e.g. ``vmin``, ``vmax``, ``lower_quantile``, ``upper_quantile``). + + Returns + ------- + tuple + ``(fig, ax)``. + """ + import matplotlib.patheffects as path_effects + from matplotlib.cm import ScalarMappable + from matplotlib.colors import Normalize + + from quantem.core.visualization import show_2d + + stroke = [path_effects.withStroke(linewidth=2.5, foreground="black")] + origin_color = (0.0, 0.7, 0.0) + g1_color = (1.0, 0.0, 0.0) + g2_color = (0.0, 0.7, 1.0) + + ref_qpos = np.asarray(ref_qpos, dtype=float).reshape(-1, 2) + ref_ab = np.asarray(ref_ab, dtype=int).reshape(-1, 2) + o = np.asarray(origin, dtype=float).reshape(2) + g1 = np.asarray(g1, dtype=float).reshape(2) + g2 = np.asarray(g2, dtype=float).reshape(2) + + fig, ax = plt.subplots(figsize=figsize) + show_2d(np.asarray(bvm), figax=(fig, ax), cmap=cmap, norm=norm, **show_kwargs) + + # offset of each picked candidate from its ideal lattice site origin + a*g1 + b*g2; + # this is the QC indicator -- a mis-picked or strongly strained candidate rings + # far from zero, while a clean pick rings near it. + ideal_qpos = o[None, :] + ref_ab[:, 0:1] * g1[None, :] + ref_ab[:, 1:2] * g2[None, :] + offset = np.linalg.norm(ref_qpos - ideal_qpos, axis=1) + + # color the rings by that offset, scaled to half the shorter lattice spacing (the + # default fit_lattice match radius) so the colorbar previews which candidates sit + # near the inclusion tolerance. + radius = 0.5 * float(min(np.hypot(*g1), np.hypot(*g2))) + cmap_offset = plt.get_cmap("plasma") + norm_offset = Normalize(vmin=0.0, vmax=radius if radius > 0 else 1.0) + + ax.scatter( + ref_qpos[:, 1], + ref_qpos[:, 0], + s=80, + facecolors="none", + edgecolors=cmap_offset(norm_offset(offset)), + linewidths=1.8, + zorder=3, + ) + for (r, c), (a, b) in zip(ref_qpos, ref_ab): + ax.annotate( + f"{int(a)},{int(b)}", + (c, r), + color="white", + fontsize=9, + fontweight="bold", + xytext=(4, 4), + textcoords="offset points", + path_effects=stroke, + zorder=4, + ) + + ax.scatter( + o[1], + o[0], + s=160, + marker="P", + facecolors=[origin_color], + edgecolors="white", + linewidths=1.5, + zorder=6, + ) + for g, label, color in ((g1, "g1", g1_color), (g2, "g2", g2_color)): + ax.annotate( + "", + xy=(o[1] + g[1], o[0] + g[0]), + xytext=(o[1], o[0]), + arrowprops=dict(arrowstyle="-|>", color=color, lw=2.4, shrinkA=0, shrinkB=0), + zorder=5, + ) + gnorm = float(np.hypot(g[0], g[1])) + 1e-12 + ax.annotate( + label, + (o[1] + g[1] / 2.0, o[0] + g[0] / 2.0), + color=color, + fontsize=13, + fontweight="bold", + xytext=(g[0] / gnorm * 15.0, g[1] / gnorm * 15.0), + textcoords="offset points", + ha="center", + va="center", + path_effects=stroke, + zorder=7, + ) + + if zoom and ref_qpos.shape[0]: + rmin, cmin = ref_qpos.min(axis=0) + rmax, cmax = ref_qpos.max(axis=0) + margin = 0.12 * max(rmax - rmin, cmax - cmin, 1.0) + 6.0 + h, w = bvm.shape[:2] + ax.set_xlim(max(cmin - margin, -0.5), min(cmax + margin, w - 0.5)) + ax.set_ylim(min(rmax + margin, h - 0.5), max(rmin - margin, -0.5)) + + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_title("reference lattice (ring color = offset from ideal index; +origin, g1, g2)") + + sm = ScalarMappable(norm=norm_offset, cmap=cmap_offset) + sm.set_array([]) + cbar = fig.colorbar(sm, ax=ax, fraction=0.046, pad=0.04, extend="max") + cbar.set_label("peak offset from ideal index (px)") + + fig.tight_layout() + return fig, ax + + +def plot_lattice_fit( + mask_weight: np.ndarray, + fit_error: np.ndarray, + *, + figsize: tuple[float, float] = (11, 4.5), +): + """Per-position diagnostics from :meth:`BraggVectors.fit_lattice`. + + Left: the mask weight — a lattice *order parameter* per position (how well all + detected intensity snaps to the fitted lattice, intensity-weighted) — so ``0`` is + a position dominated by off-lattice intensity and ``1`` a clean single crystal. + This is the weighting handed to the strain reference. Right: the RMS lattice-fit + residual over the matched peaks, in pixels (low = a clean fit; high = a poorly + fit, overlapping, or strongly strained position). + + Parameters + ---------- + mask_weight : np.ndarray + ``(scan_row, scan_col)`` lattice-order-parameter weight in ``[0, 1]``. + fit_error : np.ndarray + ``(scan_row, scan_col)`` RMS fit residual in pixels (``nan`` where no fit + was made). + figsize : tuple of float, default=(11, 4.5) + Figure size in inches. + + Returns + ------- + tuple + ``(fig, ax)`` with ``ax`` a length-2 array of axes. + """ + fig, ax = plt.subplots(1, 2, figsize=figsize) + + im0 = ax[0].imshow(np.asarray(mask_weight), cmap="viridis", vmin=0.0, vmax=1.0) + ax[0].set_title("mask weight (lattice order)") + fig.colorbar(im0, ax=ax[0], fraction=0.046, pad=0.04) + + im1 = ax[1].imshow(np.asarray(fit_error), cmap="magma") + ax[1].set_title("fit RMS error (px)") + fig.colorbar(im1, ax=ax[1], fraction=0.046, pad=0.04) + + for a in ax: + a.set_xticks([]) + a.set_yticks([]) + fig.tight_layout() + return fig, ax diff --git a/src/quantem/diffraction/disk_detection.py b/src/quantem/diffraction/disk_detection.py new file mode 100644 index 000000000..05c5600ce --- /dev/null +++ b/src/quantem/diffraction/disk_detection.py @@ -0,0 +1,1093 @@ +from __future__ import annotations + +import numpy as np +import torch + +SUBPIXEL_MODES = ("none", "parabolic", "upsample") + + +def make_template( + probe: torch.Tensor, + center: tuple[float, float] | None = None, + subtract_mean: bool = False, +) -> torch.Tensor: + """Build a cross-correlation template from a (vacuum) probe image. + + The probe is normalized to unit sum and rolled so its center sits at the array + origin ``[0, 0]`` (FFT corner), so correlation peaks land at absolute disk + positions. + + Parameters + ---------- + probe : torch.Tensor + ``(H, W)`` probe / vacuum disk image. + center : tuple of float, optional + ``(row, col)`` probe center rolled to the origin; defaults to the geometric + center ``(H // 2, W // 2)``. + subtract_mean : bool, default=False + If ``True``, make the template zero-sum — a band-pass kernel that suppresses + the uniform background in the correlation. + + Returns + ------- + torch.Tensor + ``(H, W)`` template, corner-centered (and zero-sum when ``subtract_mean``). + """ + probe = torch.as_tensor(probe) + total = probe.sum() + if total != 0: + probe = probe / total + + H, W = probe.shape + if center is None: + cr, cc = H // 2, W // 2 + else: + cr, cc = int(round(float(center[0]))), int(round(float(center[1]))) + + template = torch.roll(probe, shifts=(-cr, -cc), dims=(0, 1)) + if subtract_mean: + template = template - template.mean() + return template + + +def synthetic_probe( + shape: tuple[int, int], + radius: float, + edge: float = 1.0, + center: tuple[float, float] | None = None, +) -> torch.Tensor: + """Soft-edged disk for a synthetic correlation template. + + Returns ``0.5 - 0.5*tanh((r - radius)/edge)``: a disk of the given ``radius`` + (pixels) with a ``tanh`` falloff over ``edge`` pixels. + + Parameters + ---------- + shape : tuple of int + ``(H, W)`` output shape in pixels. + radius : float + Disk radius in pixels. + edge : float, default=1.0 + Width in pixels of the ``tanh`` edge falloff. + center : tuple of float, optional + ``(row, col)`` disk center; defaults to the geometric center + ``((H - 1) / 2, (W - 1) / 2)``. + + Returns + ------- + torch.Tensor + ``(H, W)`` soft-edged disk image. + """ + H, W = int(shape[0]), int(shape[1]) + if center is None: + cr, cc = (H - 1) / 2.0, (W - 1) / 2.0 + else: + cr, cc = float(center[0]), float(center[1]) + rows = torch.arange(H, dtype=torch.float).view(H, 1) + cols = torch.arange(W, dtype=torch.float).view(1, W) + rr = torch.sqrt((rows - cr) ** 2 + (cols - cc) ** 2) + edge = max(float(edge), 1e-6) + return 0.5 - 0.5 * torch.tanh((rr - float(radius)) / edge) + + +def _central_blob(image: torch.Tensor, threshold: float) -> tuple[np.ndarray | None, np.ndarray]: + """Mask of the connected bright region containing the brightest pixel. + + Thresholds ``image`` at ``threshold * max`` and keeps only the connected + component holding the brightest pixel — normally the central (unscattered) + disk of a mean diffraction pattern or vacuum probe — so other diffracted disks + are excluded. + + Parameters + ---------- + image : torch.Tensor + ``(H, W)`` image, e.g. a mean diffraction pattern or vacuum probe. + threshold : float + Fraction of the peak intensity (after min-subtraction) used to threshold + the image before connected-component labeling. + + Returns + ------- + blob_mask : np.ndarray or None + Boolean ``(H, W)`` mask of the central component, or ``None`` for an empty + / flat image. + img : np.ndarray + The min-subtracted image. + """ + from scipy import ndimage + + img = np.asarray(torch.as_tensor(image, dtype=torch.float).detach().cpu()) + img = img - img.min() + peak = float(img.max()) + if peak <= 0: + return None, img + labels, n = ndimage.label(img >= threshold * peak) + if n == 0: + return None, img + peak_label = int(labels[np.unravel_index(int(np.argmax(img)), img.shape)]) + return labels == peak_label, img + + +def estimate_central_beam( + image: torch.Tensor, + threshold: float = 0.5, + plot_result: bool = False, + **kwargs, +) -> tuple[tuple[float, float], float]: + """Center ``(row, col)`` and radius (pixels) of the central (direct) beam. + + Locates the connected bright region containing the brightest pixel (see + :func:`_central_blob`) — the unscattered / direct beam of a mean diffraction + pattern — and returns its intensity-weighted center together with an + area-equivalent radius (``A = pi r^2``). Other diffracted disks are excluded, so + the estimate holds whether the pattern shows one disk or many. Falls back to the + geometric center and unit radius for an empty / flat image. + + Parameters + ---------- + image : torch.Tensor or Dataset2d + ``(H, W)`` image — a raw array / tensor or a :class:`Dataset2d` (e.g. + ``dataset.dp_mean``). + threshold : float, default=0.5 + Fraction of the peak intensity used to threshold the image when isolating + the central beam (see :func:`_central_blob`). + plot_result : bool, default=False + If ``True``, show the image in greyscale with the fitted beam drawn as a red + circle. + **kwargs + Extra keyword arguments (e.g. ``norm``, ``cbar``, ``scalebar``) forwarded to + :func:`~quantem.core.visualization.show_2d` when ``plot_result=True``. + + Returns + ------- + center : tuple of float + ``(row, col)`` intensity-weighted center of the central beam. + radius : float + Area-equivalent radius in pixels (``A = pi r^2``). + """ + arr = image.array if hasattr(image, "array") else image + blob, img = _central_blob(arr, threshold) + if blob is None: + center = (img.shape[0] / 2.0, img.shape[1] / 2.0) + radius = 1.0 + else: + radius = float(np.sqrt(max(float(blob.sum()), 1.0) / np.pi)) + w = img * blob + total = float(w.sum()) + if total <= 0: + rr, cc = np.nonzero(blob) + center = (float(rr.mean()), float(cc.mean())) + else: + rows = np.arange(img.shape[0])[:, None] + cols = np.arange(img.shape[1])[None, :] + center = (float((w * rows).sum() / total), float((w * cols).sum() / total)) + + if plot_result: + from matplotlib.patches import Circle + + from quantem.core.visualization import show_2d + + show_kwargs = {"cmap": "gray", "title": "central beam", **kwargs} + _fig, ax = show_2d(arr, **show_kwargs) + ax.add_patch( + Circle((center[1], center[0]), radius, fill=False, edgecolor="red", linewidth=1.5) + ) + + return center, radius + + +def probe_centroid(probe: torch.Tensor) -> tuple[float, float]: + """Intensity-weighted ``(row, col)`` centroid of a probe image. + + Parameters + ---------- + probe : torch.Tensor + ``(H, W)`` probe image. Negative values are clamped to zero before + weighting. + + Returns + ------- + tuple of float + ``(row, col)`` intensity-weighted centroid; the geometric center for a + non-positive image. + """ + p = torch.clamp(torch.as_tensor(probe, dtype=torch.float), min=0.0) + total = p.sum() + if total <= 0: + return (p.shape[0] / 2.0, p.shape[1] / 2.0) + rows = torch.arange(p.shape[0], dtype=torch.float).view(-1, 1) + cols = torch.arange(p.shape[1], dtype=torch.float).view(1, -1) + return (float((p * rows).sum() / total), float((p * cols).sum() / total)) + + +def template_fourier(template: torch.Tensor) -> torch.Tensor: + """Pre-compute the conjugate FT of a template for repeated correlation. + + Parameters + ---------- + template : torch.Tensor + ``(H, W)`` corner-centered correlation template. + + Returns + ------- + torch.Tensor + ``(H, W)`` complex ``conj(fft2(template))``, ready to multiply against + ``fft2(dp)``. + """ + return torch.conj(torch.fft.fft2(template)) + + +def cross_correlation( + dp: torch.Tensor, + template_ft: torch.Tensor, +) -> tuple[torch.Tensor, torch.Tensor]: + """Cross-correlate a diffraction pattern with a template. + + Parameters + ---------- + dp : torch.Tensor + ``(H, W)`` diffraction pattern. + template_ft : torch.Tensor + ``(H, W)`` pre-computed template FT from :func:`template_fourier`. + + Returns + ------- + corr_map : torch.Tensor + ``(H, W)`` real-space correlation map ``relu(real(ifft2(m)))`` (used for + peak finding). + m : torch.Tensor + ``(H, W)`` Fourier-domain product ``fft2(dp) * template_ft`` (used for DFT + subpixel refinement). + """ + dp = torch.as_tensor(dp) + m = torch.fft.fft2(dp) * template_ft + corr_map = torch.clamp(torch.fft.ifft2(m).real, min=0.0) + return corr_map, m + + +def cross_correlation_batch( + dps: torch.Tensor, + template_ft: torch.Tensor, +) -> tuple[torch.Tensor, torch.Tensor]: + """Cross-correlate a stack of diffraction patterns with one template. + + Batched form of :func:`cross_correlation`. ``fft2`` acts on the trailing two + axes and the ``(H, W)`` ``template_ft`` broadcasts over the batch, so the result + for each pattern is bit-identical to :func:`cross_correlation`. + + Parameters + ---------- + dps : torch.Tensor + ``(B, H, W)`` stack of diffraction patterns. + template_ft : torch.Tensor + ``(H, W)`` pre-computed template FT from :func:`template_fourier`. + + Returns + ------- + corr_map : torch.Tensor + ``(B, H, W)`` real-space correlation maps. + m : torch.Tensor + ``(B, H, W)`` Fourier-domain products. + """ + dps = torch.as_tensor(dps) + m = torch.fft.fft2(dps) * template_ft + corr_map = torch.clamp(torch.fft.ifft2(m).real, min=0.0) + return corr_map, m + + +def _corr_map_rfft(dps: torch.Tensor, template_ft: torch.Tensor) -> torch.Tensor: + """Real-FFT correlation map(s), used when no Fourier product is needed downstream. + + For real ``dps`` and a real template the Fourier product is conjugate-symmetric, + so ``relu(real(ifft2(fft2(dps) * template_ft)))`` is reproduced exactly by an + ``rfft2`` / ``irfft2`` pair at roughly half the FFT cost. Only the correlation map + is returned — the complex product (needed solely for DFT upsampling) is skipped. + + Parameters + ---------- + dps : torch.Tensor + ``(H, W)`` or ``(B, H, W)`` diffraction pattern(s). + template_ft : torch.Tensor + ``(H, W)`` pre-computed template FT from :func:`template_fourier`. + + Returns + ------- + torch.Tensor + ``(H, W)`` or ``(B, H, W)`` real-space correlation map(s), ``relu``-clamped. + """ + dps = torch.as_tensor(dps) + H, W = dps.shape[-2], dps.shape[-1] + prod = torch.fft.rfft2(dps) * template_ft[..., : W // 2 + 1] + corr_map = torch.fft.irfft2(prod, s=(H, W)) + return torch.clamp(corr_map, min=0.0) + + +def detect_disks( + dp: torch.Tensor, + template_ft: torch.Tensor, + *, + min_abs_intensity: float = 0.0, + min_spacing: float = 0.0, + edge_boundary: int = 1, + subpixel: str = "upsample", + upsample_factor: int = 16, + max_num_peaks: int = 1000, +) -> np.ndarray: + """Detect Bragg disks in one diffraction pattern by template matching. + + Parameters + ---------- + dp : torch.Tensor + ``(H, W)`` diffraction pattern. + template_ft : torch.Tensor + ``(H, W)`` pre-computed template FT from :func:`template_fourier`. + min_abs_intensity : float, default=0.0 + Drop correlation peaks below this absolute intensity. + min_spacing : float, default=0.0 + Minimum spacing in pixels between kept peaks; closer / dimmer peaks are + suppressed. + edge_boundary : int, default=1 + Width in pixels of the border in which peaks are ignored. + subpixel : {"none", "parabolic", "upsample"}, default="upsample" + ``"none"`` returns pixel-resolution peaks; ``"parabolic"`` adds a 3-point + quadratic refinement; ``"upsample"`` further refines each peak by + Guizar-Sicairos DFT upsampling. + upsample_factor : int, default=16 + Upsampling factor for the ``"upsample"`` subpixel refinement. + max_num_peaks : int, default=1000 + Maximum number of peaks to keep (after intensity sorting). + + Returns + ------- + np.ndarray + ``(M, 3)`` array of ``[q_row, q_col, intensity]`` rows, sorted by descending + intensity. + """ + if subpixel not in SUBPIXEL_MODES: + raise ValueError(f"subpixel must be in {SUBPIXEL_MODES}, got {subpixel!r}") + + corr_map, m = cross_correlation(dp, template_ft) + + peaks = _local_maxima(corr_map, edge_boundary) + peaks = _filter_maxima(peaks, min_abs_intensity, min_spacing, max_num_peaks) + + if peaks.shape[0] == 0 or subpixel == "none": + return _to_numpy(peaks) + + peaks = _refine_parabolic(corr_map, peaks) + + if subpixel == "parabolic": + return _to_numpy(peaks) + + peaks = _refine_dft(m, peaks, upsample_factor) + return _to_numpy(peaks) + + +def detect_disks_batch( + dps: torch.Tensor, + template_ft: torch.Tensor, + *, + min_abs_intensity: float = 0.0, + min_spacing: float = 0.0, + edge_boundary: int = 1, + subpixel: str = "upsample", + upsample_factor: int = 16, + max_num_peaks: int = 1000, +) -> list[np.ndarray]: + """Detect Bragg disks across a stack of diffraction patterns (batched). + + Batched equivalent of :func:`detect_disks`: cross-correlation, peak extraction, + ``min_spacing`` suppression, and subpixel refinement are all batched across + patterns. The local-maxima search and greedy ``min_spacing`` suppression are + vectorized over the whole stack (no per-pattern Python loop) but reproduce the + per-pattern greedy result bit-for-bit, so each output matches :func:`detect_disks` + for that pattern. When ``subpixel`` is not ``"upsample"`` the correlation maps are + formed with a real FFT (``rfft2``), skipping the complex Fourier product that only + DFT upsampling needs. + + Parameters + ---------- + dps : torch.Tensor + ``(B, H, W)`` stack of diffraction patterns. + template_ft : torch.Tensor + ``(H, W)`` pre-computed template FT from :func:`template_fourier`. + min_abs_intensity : float, default=0.0 + Drop correlation peaks below this absolute intensity. + min_spacing : float, default=0.0 + Minimum spacing in pixels between kept peaks; closer / dimmer peaks are + suppressed. + edge_boundary : int, default=1 + Width in pixels of the border in which peaks are ignored. + subpixel : {"none", "parabolic", "upsample"}, default="upsample" + ``"none"`` returns pixel-resolution peaks; ``"parabolic"`` adds a 3-point + quadratic refinement; ``"upsample"`` further refines each peak by + Guizar-Sicairos DFT upsampling. + upsample_factor : int, default=16 + Upsampling factor for the ``"upsample"`` subpixel refinement. + max_num_peaks : int, default=1000 + Maximum number of peaks to keep per pattern (after intensity sorting). + + Returns + ------- + list of np.ndarray + Length-``B`` list of ``(M, 3)`` arrays of ``[q_row, q_col, intensity]`` + rows, each sorted by descending intensity. + """ + if subpixel not in SUBPIXEL_MODES: + raise ValueError(f"subpixel must be in {SUBPIXEL_MODES}, got {subpixel!r}") + + if subpixel == "upsample": + corr_map, m = cross_correlation_batch(dps, template_ft) + else: + corr_map = _corr_map_rfft(dps, template_ft) + m = None + + peaks_all, bidx, counts = _detect_peaks_batched( + corr_map, edge_boundary, min_abs_intensity, min_spacing, max_num_peaks + ) + + if subpixel == "none" or peaks_all.shape[0] == 0: + return _split_by_counts(peaks_all, counts) + + peaks_all = _refine_parabolic_batched(corr_map, peaks_all, bidx) + if subpixel == "upsample": + peaks_all = _refine_dft_batched(m, peaks_all, bidx, upsample_factor) + + return _split_by_counts(peaks_all, counts) + + +# ---- helpers ---- + + +def _local_maxima(corr_map: torch.Tensor, edge_boundary: int) -> torch.Tensor: + """Find 8-neighbor local maxima, sorted by descending intensity. + + Parameters + ---------- + corr_map : torch.Tensor + ``(H, W)`` correlation map. + edge_boundary : int + Width in pixels of the border in which maxima are ignored. + + Returns + ------- + torch.Tensor + ``(K, 3)`` tensor of ``[row, col, intensity]`` maxima, sorted by descending + intensity. + """ + is_max = _local_maxima_mask(corr_map, edge_boundary) + return _extract_maxima(corr_map, is_max) + + +def _local_maxima_mask(a: torch.Tensor, edge_boundary: int) -> torch.Tensor: + """Boolean 8-neighbor local-maxima mask of ``a``. + + Works on a single ``(H, W)`` map or a batch ``(B, H, W)`` — the neighbor + comparisons and edge masking use the trailing two axes — so the same code drives + the single-pattern and batched detection paths bit-identically. + + Parameters + ---------- + a : torch.Tensor + ``(H, W)`` or ``(B, H, W)`` correlation map(s). + edge_boundary : int + Width in pixels of the border (clamped to at least 1) set to ``False``. + + Returns + ------- + torch.Tensor + Boolean mask the same shape as ``a``, ``True`` at 8-neighbor local maxima. + """ + is_max = ( + (a >= torch.roll(a, (-1, 0), dims=(-2, -1))) + & (a > torch.roll(a, (1, 0), dims=(-2, -1))) + & (a >= torch.roll(a, (0, -1), dims=(-2, -1))) + & (a > torch.roll(a, (0, 1), dims=(-2, -1))) + & (a >= torch.roll(a, (-1, -1), dims=(-2, -1))) + & (a > torch.roll(a, (-1, 1), dims=(-2, -1))) + & (a >= torch.roll(a, (1, -1), dims=(-2, -1))) + & (a > torch.roll(a, (1, 1), dims=(-2, -1))) + ) + + eb = max(1, int(edge_boundary)) + is_max[..., :eb, :] = False + is_max[..., -eb:, :] = False + is_max[..., :, :eb] = False + is_max[..., :, -eb:] = False + return is_max + + +def _extract_maxima(a: torch.Tensor, is_max: torch.Tensor) -> torch.Tensor: + """Gather masked maxima of one ``(H, W)`` map into a descending-sorted ``(K, 3)``. + + Rows are taken in row-major ``nonzero`` order, then stably sorted by descending + intensity — matching the original single-pattern behaviour. + + Parameters + ---------- + a : torch.Tensor + ``(H, W)`` correlation map. + is_max : torch.Tensor + Boolean ``(H, W)`` local-maxima mask from :func:`_local_maxima_mask`. + + Returns + ------- + torch.Tensor + ``(K, 3)`` tensor of ``[row, col, intensity]`` maxima, sorted by descending + intensity. + """ + rows, cols = torch.nonzero(is_max, as_tuple=True) + intensity = a[rows, cols] + order = torch.argsort(intensity, descending=True) + return torch.stack((rows[order].to(a.dtype), cols[order].to(a.dtype), intensity[order]), dim=1) + + +def _filter_maxima( + peaks: torch.Tensor, + min_abs_intensity: float, + min_spacing: float, + max_num_peaks: int, +) -> torch.Tensor: + """Drop dim peaks, suppress peaks closer than ``min_spacing``, cap the count. + + Parameters + ---------- + peaks : torch.Tensor + ``(K, 3)`` ``[row, col, intensity]`` peaks, sorted by descending intensity. + min_abs_intensity : float + Drop peaks below this absolute intensity (ignored when ``<= 0``). + min_spacing : float + Minimum spacing in pixels; for each kept peak, dimmer peaks within this + distance are suppressed (ignored when ``<= 0``). + max_num_peaks : int + Maximum number of peaks to keep; the brightest are retained. + + Returns + ------- + torch.Tensor + ``(M, 3)`` filtered peaks. + """ + if peaks.shape[0] == 0: + return peaks + + if min_abs_intensity > 0: + peaks = peaks[peaks[:, 2] >= min_abs_intensity] + + if min_spacing > 0 and peaks.shape[0] > 1: + keep = torch.ones(peaks.shape[0], dtype=torch.bool, device=peaks.device) + rc = peaks[:, :2] + for i in range(peaks.shape[0]): + if not keep[i]: + continue + d2 = ((rc - rc[i]) ** 2).sum(dim=1) + too_close = d2 < min_spacing**2 + too_close[: i + 1] = False + keep[too_close] = False + peaks = peaks[keep] + + if max_num_peaks is not None and peaks.shape[0] > max_num_peaks: + peaks = peaks[:max_num_peaks] + + return peaks + + +def _detect_peaks_batched( + corr: torch.Tensor, + edge_boundary: int, + min_abs_intensity: float, + min_spacing: float, + max_num_peaks: int, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """Extract, suppress, and cap peaks across a whole ``(B, H, W)`` stack at once. + + Vectorized replacement for the per-pattern ``_extract_maxima`` + ``_filter_maxima`` + loop. Local maxima are found and intensity-thresholded for the whole stack, then + the greedy ``min_spacing`` suppression runs as a single loop over the *rank* axis + (brightest-first) that is batched over every pattern. Suppressing peak ``k``'s + fainter neighbours only when ``k`` is still kept reproduces the per-pattern greedy + result of :func:`_filter_maxima` exactly, with no Python loop over patterns. + + Parameters + ---------- + corr : torch.Tensor + ``(B, H, W)`` correlation maps. + edge_boundary : int + Width in pixels of the border in which maxima are ignored. + min_abs_intensity : float + Drop peaks below this absolute intensity (ignored when ``<= 0``). + min_spacing : float + Minimum spacing in pixels; for each kept peak, fainter peaks within this + distance are suppressed (ignored when ``<= 0``). + max_num_peaks : int + Maximum number of peaks kept per pattern (the brightest are retained). + + Returns + ------- + peaks : torch.Tensor + ``(T, 3)`` ``[row, col, intensity]`` peaks pooled over all patterns, grouped + by ascending pattern index and sorted by descending intensity within a pattern. + bidx : torch.Tensor + ``(T,)`` pattern index of each peak. + counts : torch.Tensor + ``(B,)`` number of peaks kept per pattern. + """ + B = corr.shape[0] + device = corr.device + dtype = corr.dtype + + mask = _local_maxima_mask(corr, edge_boundary) + if min_abs_intensity > 0: + mask = mask & (corr >= min_abs_intensity) + + idx = torch.nonzero(mask) # (T0, 3): [b, row, col] + counts = torch.zeros(B, dtype=torch.long, device=device) + if idx.shape[0] == 0: + return corr.new_zeros((0, 3)), torch.zeros(0, dtype=torch.long, device=device), counts + + bcand, rcand, ccand = idx[:, 0], idx[:, 1], idx[:, 2] + inten = corr[bcand, rcand, ccand] + + # Group candidates by pattern, descending intensity within each pattern. A global + # descending-intensity sort followed by a stable sort on the pattern index keeps the + # within-pattern order identical to the per-pattern argsort in _extract_maxima. + o1 = torch.argsort(inten, descending=True) + order = o1[torch.argsort(bcand[o1], stable=True)] + bcand, rcand, ccand, inten = bcand[order], rcand[order], ccand[order], inten[order] + + counts = torch.bincount(bcand, minlength=B) + kmax = int(counts.max()) + + # Pack candidates into a dense (B, kmax) grid indexed by [pattern, brightness rank]. + starts = torch.zeros(B, dtype=torch.long, device=device) + starts[1:] = torch.cumsum(counts, 0)[:-1] + rank = torch.arange(bcand.shape[0], device=device) - starts[bcand] + flat = bcand * kmax + rank + + rows = torch.zeros(B * kmax, dtype=dtype, device=device) + cols = torch.zeros(B * kmax, dtype=dtype, device=device) + vals = torch.zeros(B * kmax, dtype=dtype, device=device) + valid = torch.zeros(B * kmax, dtype=torch.bool, device=device) + rows[flat] = rcand.to(dtype) + cols[flat] = ccand.to(dtype) + vals[flat] = inten + valid[flat] = True + rows, cols, valid = rows.view(B, kmax), cols.view(B, kmax), valid.view(B, kmax) + + keep = valid.clone() + if min_spacing > 0 and kmax > 1: + s2 = float(min_spacing) ** 2 + for k in range(kmax - 1): + active = keep[:, k] & valid[:, k] # (B,) brightest unsuppressed peak at rank k + dr = rows[:, k + 1 :] - rows[:, k : k + 1] + dc = cols[:, k + 1 :] - cols[:, k : k + 1] + suppress = ((dr * dr + dc * dc) < s2) & active[:, None] & valid[:, k + 1 :] + keep[:, k + 1 :] &= ~suppress + + if max_num_peaks is not None: + kept_rank = torch.cumsum(keep.to(torch.long), dim=1) - 1 + keep &= kept_rank < max_num_peaks + + final = (keep & valid).view(-1) + sel = torch.nonzero(final, as_tuple=True)[0] # row-major: pattern-major, rank-minor + peaks = torch.stack((rows.view(-1)[sel], cols.view(-1)[sel], vals[sel]), dim=1) + bidx = torch.div(sel, kmax, rounding_mode="floor") + counts = torch.bincount(bidx, minlength=B) + return peaks, bidx, counts + + +def _split_by_counts(peaks: torch.Tensor, counts: torch.Tensor) -> list[np.ndarray]: + """Split a pattern-grouped ``(T, 3)`` peak stack into one ``(M, 3)`` array per pattern. + + Parameters + ---------- + peaks : torch.Tensor + ``(T, 3)`` peaks ordered by ascending pattern index (contiguous per pattern). + counts : torch.Tensor + ``(B,)`` number of peaks belonging to each pattern, in order. + + Returns + ------- + list of np.ndarray + Length-``B`` list of ``(M, 3)`` arrays. + """ + out = [] + start = 0 + for cnt in counts.tolist(): + out.append(_to_numpy(peaks[start : start + cnt])) + start += cnt + return out + + +def _refine_parabolic(corr_map: torch.Tensor, peaks: torch.Tensor) -> torch.Tensor: + """3-point quadratic subpixel refinement of every peak (vectorized over peaks). + + Parameters + ---------- + corr_map : torch.Tensor + ``(H, W)`` correlation map the peaks were found in. + peaks : torch.Tensor + ``(M, 3)`` ``[row, col, intensity]`` peaks to refine. + + Returns + ------- + torch.Tensor + ``(M, 3)`` peaks with subpixel ``[row, col]`` and bilinearly interpolated + intensity. + """ + if peaks.shape[0] == 0: + return peaks + a = corr_map + H, W = a.shape + out = peaks.clone() + zero = torch.zeros(out.shape[0], device=a.device, dtype=a.dtype) + + r = out[:, 0].round().long().clamp(0, H - 1) + c = out[:, 1].round().long().clamp(0, W - 1) + + r_in = (r > 0) & (r < H - 1) + ix0, ix1, ix2 = a[(r - 1).clamp(0, H - 1), c], a[r, c], a[(r + 1).clamp(0, H - 1), c] + denom_r = 4.0 * ix1 - 2.0 * ix2 - 2.0 * ix0 + dr = torch.where(r_in & (denom_r != 0), (ix2 - ix0) / denom_r, zero) + + c_in = (c > 0) & (c < W - 1) + iy0, iy1, iy2 = a[r, (c - 1).clamp(0, W - 1)], a[r, c], a[r, (c + 1).clamp(0, W - 1)] + denom_c = 4.0 * iy1 - 2.0 * iy2 - 2.0 * iy0 + dc = torch.where(c_in & (denom_c != 0), (iy2 - iy0) / denom_c, zero) + + r_sub = r.to(a.dtype) + dr + c_sub = c.to(a.dtype) + dc + out[:, 0] = r_sub + out[:, 1] = c_sub + out[:, 2] = _bilinear(a, r_sub, c_sub) + return out + + +def _refine_parabolic_batched( + corr: torch.Tensor, peaks: torch.Tensor, bidx: torch.Tensor +) -> torch.Tensor: + """3-point quadratic refinement of peaks pooled across a batch of correlation maps. + + Batched form of :func:`_refine_parabolic`. Reading ``corr[bidx, r, c]`` gathers + exactly the values the single-pattern path would read, so each peak refines + identically. + + Parameters + ---------- + corr : torch.Tensor + ``(B, H, W)`` correlation maps. + peaks : torch.Tensor + ``(T, 3)`` ``[row, col, intensity]`` peaks pooled over all patterns. + bidx : torch.Tensor + ``(T,)`` batch index selecting each peak's correlation map. + + Returns + ------- + torch.Tensor + ``(T, 3)`` peaks with subpixel ``[row, col]`` and bilinearly interpolated + intensity. + """ + if peaks.shape[0] == 0: + return peaks + H, W = corr.shape[-2], corr.shape[-1] + dtype = corr.dtype + out = peaks.clone() + zero = torch.zeros(out.shape[0], device=corr.device, dtype=dtype) + + r = out[:, 0].round().long().clamp(0, H - 1) + c = out[:, 1].round().long().clamp(0, W - 1) + + r_in = (r > 0) & (r < H - 1) + ix0 = corr[bidx, (r - 1).clamp(0, H - 1), c] + ix1 = corr[bidx, r, c] + ix2 = corr[bidx, (r + 1).clamp(0, H - 1), c] + denom_r = 4.0 * ix1 - 2.0 * ix2 - 2.0 * ix0 + dr = torch.where(r_in & (denom_r != 0), (ix2 - ix0) / denom_r, zero) + + c_in = (c > 0) & (c < W - 1) + iy0 = corr[bidx, r, (c - 1).clamp(0, W - 1)] + iy1 = corr[bidx, r, c] + iy2 = corr[bidx, r, (c + 1).clamp(0, W - 1)] + denom_c = 4.0 * iy1 - 2.0 * iy2 - 2.0 * iy0 + dc = torch.where(c_in & (denom_c != 0), (iy2 - iy0) / denom_c, zero) + + r_sub = r.to(dtype) + dr + c_sub = c.to(dtype) + dc + out[:, 0] = r_sub + out[:, 1] = c_sub + out[:, 2] = _bilinear_batched(corr, bidx, r_sub, c_sub) + return out + + +def _refine_dft(m: torch.Tensor, peaks: torch.Tensor, upsample_factor: int) -> torch.Tensor: + """Guizar-Sicairos DFT upsampling refinement of every peak (vectorized over peaks). + + Each peak is rounded to half-pixel precision (matching py4DSTEM multicorr) before + upsampling, then all peaks are refined together with one batched DFT upsampling. + + Parameters + ---------- + m : torch.Tensor + ``(H, W)`` Fourier-domain correlation product from :func:`cross_correlation`. + peaks : torch.Tensor + ``(M, 3)`` ``[row, col, intensity]`` peaks to refine. + upsample_factor : int + DFT upsampling factor. + + Returns + ------- + torch.Tensor + ``(M, 3)`` peaks with DFT-refined ``[row, col]`` (intensity unchanged). + """ + if peaks.shape[0] == 0: + return peaks + out = peaks.clone() + xy = torch.round(out[:, :2] * 2.0) / 2.0 + refined = _upsampled_correlation_batch(m, int(upsample_factor), xy) + out[:, :2] = refined + return out + + +def _refine_dft_batched( + m: torch.Tensor, peaks: torch.Tensor, bidx: torch.Tensor, upsample_factor: int +) -> torch.Tensor: + """DFT-upsampling refinement of peaks pooled across a batch of correlation products. + + Batched form of :func:`_refine_dft`. Peaks are processed in sub-chunks so the + gathered per-peak product ``m[bidx]`` (``(chunk, H, W)`` complex) stays within a + fixed memory budget regardless of how many peaks were found. + + Parameters + ---------- + m : torch.Tensor + ``(B, H, W)`` Fourier-domain correlation products. + peaks : torch.Tensor + ``(T, 3)`` ``[row, col, intensity]`` peaks pooled over all patterns. + bidx : torch.Tensor + ``(T,)`` batch index selecting each peak's correlation product. + upsample_factor : int + DFT upsampling factor. + + Returns + ------- + torch.Tensor + ``(T, 3)`` peaks with DFT-refined ``[row, col]`` (intensity unchanged). + """ + if peaks.shape[0] == 0: + return peaks + out = peaks.clone() + M, N = m.shape[-2], m.shape[-1] + xy = torch.round(out[:, :2] * 2.0) / 2.0 + cap = max(1, 8_000_000 // (M * N)) + total = peaks.shape[0] + for start in range(0, total, cap): + stop = min(start + cap, total) + b = bidx[start:stop] + out[start:stop, :2] = _upsampled_correlation_batch( + m[b], int(upsample_factor), xy[start:stop] + ) + return out + + +def _upsampled_correlation_batch( + m: torch.Tensor, upsample_factor: int, xy: torch.Tensor +) -> torch.Tensor: + """Batched DFT upsampling of the correlation peak for many shifts at once. + + Vectorizes :func:`~quantem.core.utils.imaging_utils.upsampled_correlation_torch` + over the shifts ``xy`` with batched matmuls. Two exact speedups are applied: (1) ``m`` + is conjugate-symmetric (the diffraction pattern and template are real), so only the + non-negative half of the row frequencies is contracted — halving the dominant matmul — + with a rank-1 correction on the Nyquist column; (2) the per-peak DFT kernels are + factored into shared base kernels times per-peak phases, so ``torch.exp`` runs on far + fewer elements. Both are algebraically identical to the full-spectrum DFT upsample. + + Parameters + ---------- + m : torch.Tensor + ``(M, N)`` or ``(K, M, N)`` Fourier-domain correlation product(s). A single + ``(M, N)`` product broadcasts over all ``K`` shifts. + upsample_factor : int + DFT upsampling factor. + xy : torch.Tensor + ``(K, 2)`` ``[row, col]`` peak shifts at half-pixel precision. + + Returns + ------- + torch.Tensor + ``(K, 2)`` DFT-refined ``[row, col]`` positions. + """ + import math + + device = m.device + dtype = torch.get_default_dtype() + uf = float(upsample_factor) + M, N = m.shape[-2], m.shape[-1] + + xy = torch.round(xy * uf) / uf + global_shift = math.floor(math.ceil(uf * 1.5) / 2.0) + upsample_center = global_shift - uf * xy # (K, 2): [row, col] + + num = int(math.ceil(1.5 * uf)) + half = M // 2 + 1 + col_freq = (torch.fft.ifftshift(torch.arange(N, device=device)) - math.floor(N / 2)).to(dtype) + row_freq = (torch.fft.ifftshift(torch.arange(M, device=device)) - math.floor(M / 2)).to(dtype) + + # ``m = F(dp) * conj(F(template))`` is 2D conjugate-symmetric for real ``dp`` and + # template, so the upper-half row frequencies are redundant. Contract only the + # non-negative half (rows ``u = 0 .. M // 2``) and fold the conjugate upper rows + # back in with a weight of 2 — 1 for the self-paired DC row and, when ``M`` is even, + # the Nyquist row. This halves the dominant matmul over the row axis. + row_freq = row_freq[:half] + row_weight = torch.full((half,), 2.0, device=device, dtype=dtype) + row_weight[0] = 1.0 + if M % 2 == 0: + row_weight[-1] = 1.0 + + base = torch.arange(num, device=device, dtype=dtype) + factor_col = -2j * math.pi / (N * uf) + factor_row = -2j * math.pi / (M * uf) + + # Factor each kernel ``exp(f · freq · (base - center))`` into a peak-independent base + # kernel ``exp(f · freq · base)`` times a per-peak phase ``exp(-f · freq · center)``. + # The base kernels are tiny and built once; the phases fold into ``m`` and the + # intermediate product, so ``torch.exp`` runs on ~20x fewer elements. + base_col = torch.exp(factor_col * (col_freq[:, None] * base[None, :])) # (N, num) + base_row = torch.exp(factor_row * (base[:, None] * row_freq[None, :]))[None] # (1, num, half) + phase_col = torch.exp(-factor_col * (col_freq[None, :] * upsample_center[:, 1:2])) # (K, N) + phase_row = torch.exp(-factor_row * (upsample_center[:, 0:1] * row_freq[None, :])) # (K, half) + + mc_half = m.conj()[..., :half, :] # (K, half, N) — non-negative row frequencies only + # Fold the row phase and conjugate-symmetry weight into ``m`` so the shared base row + # kernel is reused across peaks; then contract rows, apply the column phase, and + # contract columns against the shared base column kernel. + prod = torch.matmul(base_row, mc_half * (row_weight * phase_row)[:, :, None]) # (K, num, N) + up = torch.matmul(prod * phase_col[:, None, :], base_col) # (K, num, num) + + if N % 2 == 0: + # Row-only folding is exact for every column except the Nyquist column ``v = N/2``, + # whose conjugate partner stays in the same column. Correct it with an exact rank-1 + # update ``pr_diff ⊗ col_kern[:, N/2]``, where ``pr_diff = -2i Im(S)`` and ``S`` sums + # only the kept interior rows. + nyq = N // 2 + interior = row_weight - 1.0 # 1 on interior rows, 0 on the DC / Nyquist rows + s = torch.matmul(base_row, (mc_half[..., nyq] * (interior * phase_row)).unsqueeze(-1)) + pr_diff = -2j * s[..., 0].imag # (K, num) + col_nyq = base_col[nyq, :][None, :] * phase_col[:, nyq : nyq + 1] # (K, num) + up = up + pr_diff[:, :, None] * col_nyq[:, None, :] + + image_up = up.real + + K = xy.shape[0] + kidx = torch.arange(K, device=device) + idx = torch.argmax(image_up.reshape(K, -1), dim=1) + sub_r = torch.div(idx, num, rounding_mode="floor") + sub_c = idx % num + + # 3-point parabolic refinement around the upsampled maximum (interior only) + interior = (sub_r > 0) & (sub_r < num - 1) & (sub_c > 0) & (sub_c < num - 1) + rr = sub_r.clamp(1, num - 2) + cc = sub_c.clamp(1, num - 2) + c11 = image_up[kidx, rr, cc] + c21, c01 = image_up[kidx, rr + 1, cc], image_up[kidx, rr - 1, cc] + c12, c10 = image_up[kidx, rr, cc + 1], image_up[kidx, rr, cc - 1] + zero = torch.zeros(K, device=device, dtype=dtype) + denom_x = 4.0 * c11 - 2.0 * c21 - 2.0 * c01 + denom_y = 4.0 * c11 - 2.0 * c12 - 2.0 * c10 + dx = torch.where(interior & (denom_x != 0), (c21 - c01) / denom_x, zero) + dy = torch.where(interior & (denom_y != 0), (c12 - c10) / denom_y, zero) + + sub = torch.stack([sub_r.to(dtype), sub_c.to(dtype)], dim=1) - global_shift + return xy + (sub + torch.stack([dx, dy], dim=1)) / uf + + +def _bilinear(a: torch.Tensor, r: torch.Tensor, c: torch.Tensor) -> torch.Tensor: + """Bilinear interpolation of ``a`` at fractional ``(r, c)`` (vectorized over peaks). + + Parameters + ---------- + a : torch.Tensor + ``(H, W)`` map to sample. + r : torch.Tensor + ``(K,)`` fractional row coordinates. + c : torch.Tensor + ``(K,)`` fractional column coordinates. + + Returns + ------- + torch.Tensor + ``(K,)`` interpolated values. + """ + H, W = a.shape + r0 = torch.floor(r).long() + c0 = torch.floor(c).long() + r1 = (r0 + 1).clamp(max=H - 1) + c1 = (c0 + 1).clamp(max=W - 1) + r0 = r0.clamp(0, H - 1) + c0 = c0.clamp(0, W - 1) + dr = r - r0.to(a.dtype) + dc = c - c0.to(a.dtype) + return ( + (1 - dr) * (1 - dc) * a[r0, c0] + + (1 - dr) * dc * a[r0, c1] + + dr * (1 - dc) * a[r1, c0] + + dr * dc * a[r1, c1] + ) + + +def _bilinear_batched( + corr: torch.Tensor, bidx: torch.Tensor, r: torch.Tensor, c: torch.Tensor +) -> torch.Tensor: + """Bilinear interpolation of a batch ``corr`` ``(B, H, W)`` at per-peak ``(r, c)``. + + Batched form of :func:`_bilinear`. The four corner samples are gathered as + ``corr[bidx, r0, c0]`` etc., matching the single-pattern interpolation + value-for-value. + + Parameters + ---------- + corr : torch.Tensor + ``(B, H, W)`` maps to sample. + bidx : torch.Tensor + ``(T,)`` batch index selecting each peak's map. + r : torch.Tensor + ``(T,)`` fractional row coordinates. + c : torch.Tensor + ``(T,)`` fractional column coordinates. + + Returns + ------- + torch.Tensor + ``(T,)`` interpolated values. + """ + H, W = corr.shape[-2], corr.shape[-1] + dtype = corr.dtype + r0 = torch.floor(r).long() + c0 = torch.floor(c).long() + r1 = (r0 + 1).clamp(max=H - 1) + c1 = (c0 + 1).clamp(max=W - 1) + r0 = r0.clamp(0, H - 1) + c0 = c0.clamp(0, W - 1) + dr = r - r0.to(dtype) + dc = c - c0.to(dtype) + return ( + (1 - dr) * (1 - dc) * corr[bidx, r0, c0] + + (1 - dr) * dc * corr[bidx, r0, c1] + + dr * (1 - dc) * corr[bidx, r1, c0] + + dr * dc * corr[bidx, r1, c1] + ) + + +def _to_numpy(peaks: torch.Tensor) -> np.ndarray: + """Convert a peaks tensor to a contiguous ``(M, 3)`` float64 numpy array. + + Parameters + ---------- + peaks : torch.Tensor + ``(M, 3)`` (or flat) peaks tensor on any device. + + Returns + ------- + np.ndarray + ``(M, 3)`` ``float64`` array of ``[q_row, q_col, intensity]`` rows. + """ + return peaks.detach().cpu().numpy().astype(np.float64).reshape(-1, 3) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 185154ed9..ca49d2941 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -22,7 +22,7 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations -from quantem.diffraction.strain_visualization import StrainFitting +from quantem.diffraction.strain import StrainMap def _parse_init(value: float | int | Sequence[float | int | None], *, name: str) -> float: @@ -1047,7 +1047,7 @@ def initialize_strain_class( self, u_ref: np.ndarray | None = None, v_ref: np.ndarray | None = None, - )->StrainFitting: + )->StrainMap: if self.u_array is None or self.v_array is None: self.get_individual_uv_vectors() if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): @@ -1066,7 +1066,7 @@ def initialize_strain_class( else: default_sampling = float(self.dataset.sampling) - return StrainFitting( + return StrainMap( u_array = self.u_array, v_array = self.v_array, ds_shape = self.dataset.shape, diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py new file mode 100644 index 000000000..a12e8cd56 --- /dev/null +++ b/src/quantem/diffraction/strain.py @@ -0,0 +1,762 @@ +from __future__ import annotations + +import warnings + +import numpy as np +from numpy.lib.stride_tricks import sliding_window_view + +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.io.serialize import AutoSerialize +from quantem.diffraction.strain_visualization import ( + plot_strain_panels, + plot_strain_precision_histogram, +) + + +class StrainMap(AutoSerialize): + """Strain tensor maps fit from per-position lattice vectors. + + Stores the reference-frame strain components ``e_rr`` (row), ``e_cc`` (col), + ``e_rc`` (shear), and ``phi`` (infinitesimal rotation). The reference lattice + is the median of the fitted ``g_u``/``g_v`` over a mask/ROI; the strain tensor + is recomputed by :meth:`update_reference`. + + For nanobeam 4D-STEM the lattice vectors are measured in reciprocal space, so + the shear/rotation signs are flipped relative to real space (``real_space=False``). + + Parameters + ---------- + u_array : np.ndarray + Per-position first lattice vector, shape ``(scan_row, scan_col, 2)``. + v_array : np.ndarray + Per-position second lattice vector, shape ``(scan_row, scan_col, 2)``. + ds_shape : tuple of int + Shape of the parent scan grid, used to size the strain maps. + real_space : bool + ``True`` for real-space lattice vectors; ``False`` for reciprocal-space + (nanobeam) data, which flips the shear/rotation signs. + u_ref : np.ndarray, optional + Fixed reference for ``u``; if omitted the median over the mask/ROI is used. + A value supplied here persists across re-fits. + v_ref : np.ndarray, optional + Fixed reference for ``v``; if omitted the median over the mask/ROI is used. + A value supplied here persists across re-fits. + mask : np.ndarray, optional + ``(scan_row, scan_col)`` weighting/ROI mask; defaults to all ones (the full + scan). Normalized to ``[0, 1]`` on assignment. + ds_sampling : float, optional + Real-space scan sampling (step size); defaults to ``1.0``. + ds_units : str, optional + Units for ``ds_sampling``; defaults to ``"pixels"``. + """ + + mask: np.ndarray | None = None + real_space: bool = False + + e_rr: Dataset2d + e_cc: Dataset2d + e_rc: Dataset2d + phi: Dataset2d + + u_ref: np.ndarray | None = None + v_ref: np.ndarray | None = None + u_array: np.ndarray + v_array: np.ndarray + + ds_sampling: float = 1.0 + ds_units: str = "pixels" + ds_shape: tuple[int, ...] + + def __init__( + self, + u_array: np.ndarray, + v_array: np.ndarray, + ds_shape: tuple[int, ...], + real_space: bool, + u_ref: np.ndarray | None = None, + v_ref: np.ndarray | None = None, + mask: np.ndarray | None = None, + ds_sampling: float | None = None, + ds_units: str | None = None, + ): + super().__init__() + self.u_array = u_array + self.v_array = v_array + + self.ds_shape = ds_shape + self.real_space = real_space + + self.ds_sampling = 1.0 if ds_sampling is None else ds_sampling + self.ds_units = "pixels" if ds_units is None else ds_units + + self.mask = np.ones(ds_shape[:2]) if mask is None else mask + self.mask = (self.mask - np.min(self.mask)) / np.max(self.mask) + + # user-supplied reference vectors persist across re-fits (None = use median) + self._u_ref_fixed = None if u_ref is None else np.asarray(u_ref, dtype=float) + self._v_ref_fixed = None if v_ref is None else np.asarray(v_ref, dtype=float) + self.u_ref = None + self.v_ref = None + + self.update_reference() + + # ---- main methods ---- + + def update_reference( + self, + strain_mask: np.ndarray | None = None, + u_ref: np.ndarray | None = None, + v_ref: np.ndarray | None = None, + plot_strain_roi: bool = False, + **plot_kwargs, + ) -> "StrainMap": + """(Re)compute the reference lattice and strain tensor maps. + + Reference precedence: explicit ``u_ref``/``v_ref`` argument > vectors fixed at + construction > median over ``strain_mask`` (if given) else over ``self.mask`` + else the global median. + + Parameters + ---------- + strain_mask : np.ndarray, optional + ``(scan_row, scan_col)`` ROI selecting the positions used to compute the + median reference lattice. If omitted, ``self.mask`` (else the global + median) is used. + u_ref : np.ndarray, optional + Explicit reference for ``u``; overrides both the construction-time fixed + value and the median. + v_ref : np.ndarray, optional + Explicit reference for ``v``; overrides both the construction-time fixed + value and the median. + plot_strain_roi : bool, default=False + If ``True``, show the recomputed strain via :meth:`plot_strain_roi` + (color-scaled to the ROI) so the chosen reference region can be checked + for flatness. + **plot_kwargs + Forwarded to :meth:`plot_strain_roi` when ``plot_strain_roi=True``. + + Returns + ------- + StrainMap + ``self``, with the reference lattice and strain maps recomputed. + """ + u_med, v_med = _reference_lattice(self.u_array, self.v_array, self.mask, strain_mask) + + if u_ref is not None: + self.u_ref = np.asarray(u_ref, dtype=float) + elif self._u_ref_fixed is not None: + self.u_ref = self._u_ref_fixed + else: + self.u_ref = u_med + + if v_ref is not None: + self.v_ref = np.asarray(v_ref, dtype=float) + elif self._v_ref_fixed is not None: + self.v_ref = self._v_ref_fixed + else: + self.v_ref = v_med + + e_rr, e_cc, e_rc, phi = _strain_tensor( + self.u_array, self.v_array, self.u_ref, self.v_ref, self.real_space + ) + self.e_rr = Dataset2d.from_array(e_rr, name="strain e_rr", signal_units="fractional") + self.e_cc = Dataset2d.from_array(e_cc, name="strain e_cc", signal_units="fractional") + self.e_rc = Dataset2d.from_array(e_rc, name="strain e_rc", signal_units="fractional") + self.phi = Dataset2d.from_array(phi, name="strain rotation", signal_units="radians") + + if plot_strain_roi: + self.plot_strain_roi(strain_mask=strain_mask, **plot_kwargs) + return self + + def rotate_strain( + self, rotation_angle: float = 0.0 + ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Tensor-rotate the strain into a frame rotated by ``rotation_angle`` (degrees). + + The rotation field ``phi`` is invariant under frame rotation and is not + transformed. + + Parameters + ---------- + rotation_angle : float, default=0.0 + Frame rotation angle, in degrees. + + Returns + ------- + tuple of np.ndarray + ``(e_uu, e_vv, e_uv)`` strain components in the rotated frame. + """ + return _rotate_strain_tensor( + self.e_rr.array, self.e_cc.array, self.e_rc.array, rotation_angle + ) + + def plot_strain_roi( + self, + strain_mask: np.ndarray | None = None, + plot_rotation: bool = True, + cmap_strain: str = "RdBu_r", + cmap_rotation: str = "PiYG", + layout: str = "horizontal", + figsize: tuple[float, float] | None = None, + **kwargs, + ): + """Plot the strain in the raw row/col reference frame, color-scaled to the ROI. + + The color range is symmetric about zero and set by the largest absolute + strain (and rotation) *inside the reference ROI* — ``strain_mask`` if given, + else ``self.mask`` — so a well-chosen, strain-free reference region reads as + flat (near mid-color) and any residual gradient or tilt stands out. The ROI + itself is drawn in color while everything outside it is shown in greyscale, + so the chosen reference region is obvious at a glance. Unlike + :meth:`plot_strain`, no display rotation is applied: the panels show the raw + ``e_rr``/``e_cc``/``e_rc`` that :meth:`update_reference` just computed. + + Parameters + ---------- + strain_mask : np.ndarray, optional + ROI defining the color range (and the reference region). If omitted, + ``self.mask`` is used. + plot_rotation : bool, default=True + Whether to include the rotation (``phi``) panel. + cmap_strain : str, default="RdBu_r" + Colormap for the strain panels. + cmap_rotation : str, default="PiYG" + Colormap for the rotation panel. + layout : {"horizontal", "vertical"}, default="horizontal" + Panel arrangement. + figsize : tuple of float, optional + Figure size in inches; if omitted it is derived from the layout. + **kwargs + Forwarded to + :func:`~quantem.diffraction.strain_visualization.plot_strain_panels`. + + Returns + ------- + tuple + ``(fig, ax)`` from :func:`plot_strain_panels`. + """ + roi_src = self.mask if strain_mask is None else strain_mask + e_rr, e_cc, e_rc, phi = ( + self.e_rr.array, + self.e_cc.array, + self.e_rc.array, + self.phi.array, + ) + + inside = np.asarray(roi_src) > 0 if roi_src is not None else np.ones(e_rr.shape, bool) + if not inside.any(): + inside = np.ones(e_rr.shape, bool) + + strain_stack = np.stack([e_rr[inside], e_cc[inside], e_rc[inside]]) + smax = float(np.nanmax(np.abs(strain_stack))) * 100.0 + rmax = float(np.rad2deg(np.nanmax(np.abs(phi[inside])))) + smax = smax if smax > 0 else 1e-6 + rmax = rmax if rmax > 0 else 1e-6 + + return plot_strain_panels( + e_rr, + e_cc, + e_rc, + phi, + self.mask, + self.u_ref, + self.v_ref, + self.ds_shape, + ds_sampling=self.ds_sampling, + ds_units=self.ds_units, + strain_range_percent=(-smax, smax), + rotation_range_degrees=(-rmax, rmax), + roi=inside, + plot_rotation=plot_rotation, + cmap_strain=cmap_strain, + cmap_rotation=cmap_rotation, + layout=layout, + figsize=figsize, + panel_titles=( + r"$\epsilon_{rr}$ $\updownarrow$", + r"$\epsilon_{cc}$ $\leftrightarrow$", + r"$\epsilon_{rc}$ $\nwarrow\!\!\!\!\!\!\!\!\!\:\searrow$", + ), + **kwargs, + ) + + def plot_strain( + self, + rotation_angle: float = 20.0, + strain_range_percent: tuple[float, float] = (-3.0, 3.0), + rotation_range_degrees: tuple[float, float] = (-2.0, 2.0), + mask_range: tuple[float, float] = (0.0, 1.0), + plot_rotation: bool = True, + plot_gvecs: bool = False, + plot_scalebar: bool = False, + cmap_strain: str = "RdBu_r", + cmap_rotation: str = "PiYG", + layout: str = "horizontal", + figsize: tuple[float, float] | None = None, + **kwargs, + ): + """Plot the strain (rotated into the display frame) and rotation panels. + + Parameters + ---------- + rotation_angle : float, default=20.0 + Angle (degrees) by which the strain tensor is rotated into the display + frame before plotting. + strain_range_percent : tuple of float, default=(-3.0, 3.0) + Symmetric color range for the strain panels, in percent. + rotation_range_degrees : tuple of float, default=(-2.0, 2.0) + Symmetric color range for the rotation panel, in degrees. + mask_range : tuple of float, default=(0.0, 1.0) + ``(low, high)`` window remapping the mask brightness: positions with + mask ``>= high`` are shown at full color, ``<= low`` are black, and + values between ramp linearly from black to full. The default leaves the + normalized mask unchanged. + plot_rotation : bool, default=True + Whether to include the rotation (``phi``) panel. + plot_gvecs : bool, default=False + Whether to overlay the reference lattice vectors. + plot_scalebar : bool, default=False + Whether to draw a real-space scale bar. + cmap_strain : str, default="RdBu_r" + Colormap for the strain panels. + cmap_rotation : str, default="PiYG" + Colormap for the rotation panel. + layout : {"horizontal", "vertical"}, default="horizontal" + Panel arrangement. + figsize : tuple of float, optional + Figure size in inches; if omitted it is derived from the layout. + **kwargs + Forwarded to + :func:`~quantem.diffraction.strain_visualization.plot_strain_panels`. + + Returns + ------- + tuple + ``(fig, ax)`` from :func:`plot_strain_panels`. + """ + e_uu, e_vv, e_uv = self.rotate_strain(rotation_angle) + return plot_strain_panels( + e_uu, + e_vv, + e_uv, + self.phi.array, + self.mask, + self.u_ref, + self.v_ref, + self.ds_shape, + ds_sampling=self.ds_sampling, + ds_units=self.ds_units, + strain_range_percent=strain_range_percent, + rotation_range_degrees=rotation_range_degrees, + mask_range=mask_range, + plot_rotation=plot_rotation, + plot_gvecs=plot_gvecs, + plot_scalebar=plot_scalebar, + cmap_strain=cmap_strain, + cmap_rotation=cmap_rotation, + layout=layout, + figsize=figsize, + **kwargs, + ) + + def estimate_strain_precision( + self, + mask_range: tuple[float, float] = (0.0, 1.0), + rotation_angle: float = 0.0, + window: int = 5, + mask_threshold: float = 0.5, + min_neighbors: int = 3, + component: str = "combined", + bins: int = 50, + bounds: tuple[float, float] | None = None, + plot: bool = True, + returnfig: bool = False, + ): + """Estimate strain *precision* (random scatter) from local median deviations. + + This measures repeatability, not accuracy. Without a ground truth (e.g. a + simulation) it cannot detect systematic error — only how far each position + scatters from its local neighborhood. For every position the deviation from + the median of its surrounding well-indexed neighbors is + + ``error(r, c) = | strain(r, c) - median( strain over neighbors with + scaled mask > mask_threshold ) |`` + + computed for each tensor component (the center position is excluded from its + own median). The three strain components are reduced to one rotation-invariant + number via the Frobenius norm of the symmetric strain-tensor deviation, + + ``combined = sqrt(d_uu**2 + d_vv**2 + 2*d_uv**2)``, + + (equivalently the root-sum-square of the principal-strain deviations) so a + single strain precision can be quoted and compared between datasets. Rotation + precision is reported separately, not folded into ``combined``. + + Each component's precision is summarized as the mask-weighted **RMS** of its + per-position deviations — a sigma-like scatter — and a weighted histogram of + those deviations is shown. RMS combining is self-consistent under the + Frobenius sum: ``rms(combined) == sqrt(rms_uu**2 + rms_vv**2 + 2*rms_uv**2)``. + + Parameters + ---------- + mask_range : tuple of float, default=(0.0, 1.0) + ``(low, high)`` window remapping :attr:`mask` to ``[0, 1]`` (same + convention as :meth:`plot_strain`); the remapped mask both selects + neighbors (``> mask_threshold``) and weights the histogram and mean. + rotation_angle : float, default=0.0 + Frame rotation (degrees) applied before measuring per-component precision, + matching :meth:`plot_strain`. ``0`` reports the raw row/col frame + (``e_uu == e_rr`` ...). The combined number is rotation-invariant. + window : int, default=5 + Odd edge length (px) of the neighborhood bounding box; the footprint is + the inscribed disk of radius ``window / 2`` (3 -> 8 neighbors, 5 -> 20, + 7 -> 36). A pure linear strain ramp cancels in the (symmetric) median, so + larger windows mostly just steady the median — at the cost of blurring + *curved* strain and biasing the masked edges. ``5`` roughly halves the + noise-floor over-estimate of ``3`` (~9% -> ~4%) while staying local. + mask_threshold : float, default=0.5 + A neighbor contributes to the local median only if its scaled mask + exceeds this value. + min_neighbors : int, default=3 + Minimum number of valid neighbors required; positions with fewer get no + precision estimate (``nan``, dropped from the statistics). + component : {"combined","e_uu","e_vv","e_uv","rotation"}, default="combined" + Which error distribution to histogram. + bins : int, default=50 + Number of histogram bins (or a sequence of bin edges). + bounds : tuple of float, optional + ``(low, high)`` histogram range in display units (percent for strain, + degrees for rotation). Fix it to compare datasets on the same axis. Mass + outside the range (or outside an explicit ``bins`` edge array) is piled + into the edge bins as overflow rather than dropped, so the histogram + always integrates to the same weight as the reported RMS. + plot : bool, default=True + If ``True``, draw the weighted precision histogram. + returnfig : bool, default=False + If ``True``, return ``(fig, ax)`` instead of the results dict. + + Returns + ------- + dict or tuple + A results dict with the mask-weighted ``rms`` precision per component and + the ``combined`` value (strain in percent, rotation in degrees), the + normalized ``counts`` and ``edges`` of the histogrammed ``component`` (and + ``counts_raw``, the weighted bin sums), ``out_of_range_fraction`` (weighted + mass piled into the edge bins as overflow), and the chosen settings; or + ``(fig, ax)`` when ``returnfig=True``. + """ + if window < 3 or window % 2 == 0: + raise ValueError("window must be an odd integer >= 3.") + valid_components = ("combined", "e_uu", "e_vv", "e_uv", "rotation") + if component not in valid_components: + raise ValueError(f"component must be one of {valid_components}.") + + # number of neighbors in the circular footprint (matches _local_masked_median) + p = window // 2 + oy, ox = np.ogrid[-p : p + 1, -p : p + 1] + n_neighbors = int(np.sum((oy ** 2 + ox ** 2) <= (window / 2.0) ** 2) - 1) + + # per-component fields in the (optionally rotated) display frame; phi is + # rotation-invariant and is carried through unchanged + e_uu, e_vv, e_uv = self.rotate_strain(rotation_angle) + fields = {"e_uu": e_uu, "e_vv": e_vv, "e_uv": e_uv, "rotation": self.phi.array} + + # remap the mask exactly as plot_strain does, then use it both to select + # neighbors (> mask_threshold) and to weight the histogram / mean + low, high = float(mask_range[0]), float(mask_range[1]) + m = np.asarray(self.mask, dtype=float) + if high > low: + scaled = np.clip((m - low) / (high - low), 0.0, 1.0) + else: + scaled = (m >= high).astype(float) + valid = scaled > float(mask_threshold) + + # per-component local-median deviation, native units (fractional / radians) + dev = { + name: np.abs(field - _local_masked_median(field, valid, window, min_neighbors)) + for name, field in fields.items() + } + # single rotation-invariant number: Frobenius norm of the symmetric + # strain-tensor deviation (== root-sum-square of the principal-strain + # deviations). Rotation is reported separately, not folded in: in nanobeam + # data it is partly a systematic (tilt/descan) and would mix radians into a + # percent figure. + dev["combined"] = np.sqrt( + dev["e_uu"] ** 2 + dev["e_vv"] ** 2 + 2.0 * dev["e_uv"] ** 2 + ) + + # display-unit scaling: strain -> percent, rotation -> degrees + scale = { + "e_uu": 100.0, + "e_vv": 100.0, + "e_uv": 100.0, + "rotation": float(np.rad2deg(1.0)), + "combined": 100.0, + } + + def _weighted_rms(err_native: np.ndarray, factor: float) -> float: + e = err_native * factor + finite = np.isfinite(e) + w = scaled[finite] + wsum = float(w.sum()) + return float(np.sqrt(np.sum(e[finite] ** 2 * w) / wsum)) if wsum > 0 else float("nan") + + # mask-weighted RMS deviation (a sigma-like scatter). Under the Frobenius + # sum this is self-consistent: rms(combined) == sqrt(rms_uu**2 + rms_vv**2 + # + 2*rms_uv**2), so the combined number agrees with the per-component ones. + rms = {name: _weighted_rms(dev[name], scale[name]) for name in scale} + + # weighted histogram of the chosen component, in display units. The headline + # RMS above is computed over *all* finite positions, so the histogram must be + # too: with an explicit bin-edge array (e.g. bins=np.arange(0, 1, 0.02)) + # np.histogram ignores `range` and silently DROPS everything outside the + # edges, then renormalizing makes the plot look far tighter than the reported + # RMS. Instead pile any out-of-range mass into the edge bins (overflow) so the + # histogram integrates to the same weight the RMS sees, and report the + # fraction that landed there. + e = dev[component] * scale[component] + finite = np.isfinite(e) + e_f = e[finite] + w_f = scaled[finite] + edges = np.histogram_bin_edges(e_f, bins=bins, range=bounds) + lo, hi = float(edges[0]), float(edges[-1]) + wtot = float(w_f.sum()) + frac_below = float(w_f[e_f < lo].sum()) / wtot if wtot > 0 else 0.0 + frac_above = float(w_f[e_f > hi].sum()) / wtot if wtot > 0 else 0.0 + out_of_range_fraction = frac_below + frac_above + counts_raw, edges = np.histogram(np.clip(e_f, lo, hi), bins=edges, weights=w_f) + total = float(counts_raw.sum()) + counts = counts_raw / total if total > 0 else counts_raw + + unit = "°" if component == "rotation" else "%" + result = { + "rms": rms, + "component": component, + "unit": unit, + "counts": counts, + "counts_raw": counts_raw, + "edges": edges, + "out_of_range_fraction": out_of_range_fraction, + "window": int(window), + "n_neighbors": n_neighbors, + "mask_threshold": float(mask_threshold), + "mask_range": (low, high), + "rotation_angle": float(rotation_angle), + } + + print("Strain precision (RMS of local median deviation, weighted by scaled mask)") + print( + f" reference={n_neighbors} neighbors (disk, window={window}) " + f"mask>{mask_threshold:g} min_neighbors={min_neighbors} " + f"rotation_angle={rotation_angle:g} deg" + ) + for name in ("e_uu", "e_vv", "e_uv"): + print(f" {name:<9}: {rms[name]:7.4f} %") + print(f" {'rotation':<9}: {rms['rotation']:7.4f} deg") + print( + f" {'combined':<9}: {rms['combined']:7.4f} % " + "(strain-only Frobenius norm; rotation excluded)" + ) + if out_of_range_fraction > 0: + print( + f" note: {100 * out_of_range_fraction:.1f}% of weighted mass fell " + f"outside the histogram range [{lo:g}, {hi:g}] {unit} and was piled " + "into the edge bins (overflow); widen `bins`/`bounds` to resolve the " + "tail. The RMS above includes it." + ) + + if not (plot or returnfig): + return result + + fig, ax = plot_strain_precision_histogram(edges, counts, rms, component, unit) + if returnfig: + return fig, ax + return result + + +# ---- module-level fitting functions ---- + + +def _local_masked_median( + field: np.ndarray, + valid: np.ndarray, + window: int, + min_neighbors: int, +) -> np.ndarray: + """Median of each position's surrounding neighbors over valid (masked) pixels. + + The center position is excluded ("surrounding" only); a neighbor contributes + only where ``valid`` is True and the field is finite. Neighbors are taken over a + circular (isotropic) footprint of radius ``window / 2`` inscribed in the + ``window`` x ``window`` box — a disk avoids the square's far corners, which + over-weight the diagonals and sample the most strain-different points. Positions + left with fewer than ``min_neighbors`` contributing neighbors return ``nan``. + + Parameters + ---------- + field : np.ndarray + ``(scan_row, scan_col)`` field to take local medians of. + valid : np.ndarray + ``(scan_row, scan_col)`` boolean mask of usable neighbor positions. + window : int + Odd edge length of the bounding box; the footprint is the disk of radius + ``window / 2`` within it (3 -> 8 neighbors, 5 -> 20, 7 -> 36). + min_neighbors : int + Minimum contributing neighbors required, else ``nan``. + + Returns + ------- + np.ndarray + ``(scan_row, scan_col)`` local masked median (``nan`` where undefined). + """ + p = window // 2 + fpad = np.pad(np.asarray(field, dtype=float), p, mode="constant", constant_values=np.nan) + vpad = np.pad(np.asarray(valid, dtype=bool), p, mode="constant", constant_values=False) + + # writable per-position (window, window) neighborhoods + fw = sliding_window_view(fpad, (window, window)).copy() + vw = sliding_window_view(vpad, (window, window)) + fw[~vw] = np.nan + fw[:, :, p, p] = np.nan # exclude the center position from its own median + # restrict the square box to a circular footprint of radius window/2 + oy, ox = np.ogrid[-p : p + 1, -p : p + 1] + outside = (oy ** 2 + ox ** 2) > (window / 2.0) ** 2 + fw[:, :, outside] = np.nan + + flat = fw.reshape(fw.shape[0], fw.shape[1], -1) + count = np.sum(np.isfinite(flat), axis=-1) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=RuntimeWarning) + med = np.nanmedian(flat, axis=-1) + med[count < min_neighbors] = np.nan + return med + + +def _reference_lattice( + u_array: np.ndarray, + v_array: np.ndarray, + mask: np.ndarray | None = None, + strain_mask: np.ndarray | None = None, +) -> tuple[np.ndarray, np.ndarray]: + """Median reference lattice vectors over an ROI / mask, else the global median. + + Parameters + ---------- + u_array : np.ndarray + Per-position first lattice vector, shape ``(scan_row, scan_col, 2)``. + v_array : np.ndarray + Per-position second lattice vector, shape ``(scan_row, scan_col, 2)``. + mask : np.ndarray, optional + ``(scan_row, scan_col)`` ROI; positions equal to 1 are included. Used when + ``strain_mask`` is not given. + strain_mask : np.ndarray, optional + ``(scan_row, scan_col)`` ROI taking precedence over ``mask``. + + Returns + ------- + tuple of np.ndarray + ``(u_ref, v_ref)``, each a length-2 reference vector. + """ + if strain_mask is not None: + m = np.asarray(strain_mask == 1, dtype=bool) + elif mask is not None: + m = np.asarray(mask == 1, dtype=bool) + else: + m = None + + # nan-median: positions fit_lattice could not fit are NaN and must be ignored, + # otherwise the reference (and hence the whole strain map) collapses to NaN. + if m is None or not m.any(): + u_ref = np.nanmedian(u_array.reshape(-1, 2), axis=0) + v_ref = np.nanmedian(v_array.reshape(-1, 2), axis=0) + else: + u_ref = np.array((np.nanmedian(u_array[m, 0]), np.nanmedian(u_array[m, 1])), dtype=float) + v_ref = np.array((np.nanmedian(v_array[m, 0]), np.nanmedian(v_array[m, 1])), dtype=float) + return u_ref, v_ref + + +def _strain_tensor( + u_array: np.ndarray, + v_array: np.ndarray, + u_ref: np.ndarray, + v_ref: np.ndarray, + real_space: bool, +) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """Per-position strain tensor from lattice vectors relative to a reference. + + For reciprocal-space (nanobeam) data the shear and rotation are sign-flipped via + ``const = -1``. + + Parameters + ---------- + u_array : np.ndarray + Per-position first lattice vector, shape ``(scan_row, scan_col, 2)``. + v_array : np.ndarray + Per-position second lattice vector, shape ``(scan_row, scan_col, 2)``. + u_ref : np.ndarray + Reference first lattice vector (length 2). + v_ref : np.ndarray + Reference second lattice vector (length 2). + real_space : bool + ``True`` for real-space data; ``False`` (nanobeam, reciprocal space) flips + the shear and rotation signs. + + Returns + ------- + tuple of np.ndarray + ``(e_rr, e_cc, e_rc, phi)``, each of shape ``(scan_row, scan_col)``. + """ + scan_r, scan_c = u_array.shape[0], u_array.shape[1] + Uref = np.stack((u_ref, v_ref), axis=1).astype(float) + strain_trans = np.zeros((scan_r, scan_c, 2, 2)) + for r in range(scan_r): + for c in range(scan_c): + U = np.stack((u_array[r, c, :], v_array[r, c, :]), axis=1) + # Positions fit_lattice could not fit are NaN; a degenerate (collinear) + # fit is singular. Either way there is no meaningful inverse -- leave the + # strain NaN (masked out downstream) rather than feeding NaN into pinv, + # whose SVD does not converge and raises LinAlgError. + if not np.all(np.isfinite(U)) or abs(np.linalg.det(U)) < 1e-12: + strain_trans[r, c, :, :] = np.nan + continue + strain_trans[r, c, :, :] = Uref @ np.linalg.inv(U) + + const = 1 if real_space else -1 + e_rr = strain_trans[:, :, 0, 0] - 1 + e_cc = strain_trans[:, :, 1, 1] - 1 + e_rc = strain_trans[:, :, 1, 0] * 0.5 * const + strain_trans[:, :, 0, 1] * 0.5 * const + phi = strain_trans[:, :, 1, 0] * -0.5 * const + strain_trans[:, :, 0, 1] * 0.5 * const + return e_rr, e_cc, e_rc, phi + + +def _rotate_strain_tensor( + e_rr: np.ndarray, + e_cc: np.ndarray, + e_rc: np.ndarray, + rotation_angle: float, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Rotate a 2D strain tensor by ``rotation_angle`` (degrees). + + Parameters + ---------- + e_rr : np.ndarray + Row-row (normal) strain component. + e_cc : np.ndarray + Column-column (normal) strain component. + e_rc : np.ndarray + Row-column (shear) strain component. + rotation_angle : float + Frame rotation angle, in degrees. + + Returns + ------- + tuple of np.ndarray + ``(e_uu, e_vv, e_uv)`` in the rotated frame. + """ + angle = np.deg2rad(rotation_angle) + c = np.cos(angle) + s = np.sin(angle) + e_uu = e_rr * (c * c) + 2.0 * e_rc * (c * s) + e_cc * (s * s) + e_vv = e_rr * (s * s) - 2.0 * e_rc * (c * s) + e_cc * (c * c) + e_uv = (e_cc - e_rr) * (c * s) + e_rc * (c * c - s * s) + return e_uu, e_vv, e_uv diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 79fa4de0e..18df58f30 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -17,7 +17,7 @@ from quantem.core.utils.utils import electron_wavelength_angstrom from quantem.core.utils.validators import ensure_valid_array from quantem.core.visualization import ScalebarConfig, show_2d -from quantem.diffraction.strain_visualization import StrainFitting +from quantem.diffraction.strain import StrainMap class StrainMapAutocorrelation(AutoSerialize): @@ -589,16 +589,16 @@ def create_mask( return self - def initilize_strain_class( + def initialize_strain_class( self, u_ref: np.ndarray | None = None, v_ref: np.ndarray | None = None, - )->StrainFitting: + )->StrainMap: if self.u_array is None or self.v_array is None: - raise RuntimeWarning("Need to run fit_lattice_vectors before initilizing strain class") + raise RuntimeWarning("Need to run fit_lattice_vectors before initializing strain class") if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - + default_units = None default_sampling = None if hasattr(self.dataset, 'units'): @@ -611,30 +611,18 @@ def initilize_strain_class( default_sampling = float(self.dataset.sampling[0]) else: default_sampling = float(self.dataset.sampling) - - if self.mask is not None: - return StrainFitting( - u_array = self.u_array, - v_array = self.v_array, - ds_shape = self.dataset.shape, - real_space = self.real_space, - u_ref = u_ref, - v_ref = v_ref, - mask = self.mask, - ds_sampling=default_sampling, - ds_units = default_units, - ) - else: - return StrainFitting( - u_array = self.u_array, - v_array = self.v_array, - ds_shape = self.dataset.shape, - real_space = self.real_space, - u_ref = u_ref, - v_ref = v_ref, - ds_sampling=default_sampling, - ds_units = default_units, - ) + + return StrainMap( + u_array = self.u_array, + v_array = self.v_array, + ds_shape = self.dataset.shape, + real_space = self.real_space, + u_ref = u_ref, + v_ref = v_ref, + mask = self.mask, + ds_sampling=default_sampling, + ds_units = default_units, + ) def plot_lattice_vectors( self, diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index e5b02d500..0326d5131 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -5,381 +5,323 @@ from matplotlib.cm import ScalarMappable from matplotlib.colors import Normalize from matplotlib.patches import FancyArrowPatch +from matplotlib.ticker import FuncFormatter -from quantem.core.datastructures.dataset2d import Dataset2d -from quantem.core.io.serialize import AutoSerialize from quantem.core.visualization.visualization_utils import ScalebarConfig, add_scalebar_to_ax -class StrainFitting(AutoSerialize): - mask: np.ndarray | None = None - real_space: bool = False - - strain_raw_err: Dataset2d - strain_raw_ecc: Dataset2d - strain_raw_erc: Dataset2d - strain_rotation: Dataset2d - - u_ref: np.ndarray | None = None - v_ref: np.ndarray | None = None - u_array: np.ndarray - v_array: np.ndarray - - ds_sampling = 1.0 - ds_units = 'pixels' - ds_shape: tuple[int, ...] - - - def __init__( - self, - u_array: np.ndarray, - v_array: np.ndarray, - ds_shape: tuple[int, ...], - real_space: bool, - - u_ref: np.ndarray | None = None, - v_ref: np.ndarray | None = None, - mask: np.ndarray | None = None, - - ds_sampling: float | None = None, - ds_units: str | None = None, - ): - super().__init__() - self.u_array = u_array - self.v_array = v_array - - self.ds_shape = ds_shape - self.real_space = real_space - - self.ds_sampling = 1.0 if ds_sampling is None else ds_sampling - self.ds_units = 'pixels' if ds_units is None else ds_units - - self.mask = np.ones(ds_shape[:2]) if mask is None else mask - self.mask = (self.mask - np.min(self.mask))/np.max(self.mask) - - self.u_ref = u_ref if u_ref is not None else None - self.v_ref = v_ref if v_ref is not None else None - - - - def fit_strain( - self, - plot_strain = False, - strain_mask: np.ndarray | None = None, - ) -> "StrainFitting": - - if self.u_array is None: - raise RuntimeError("u_array not available. Set u_array before calling fit_strain().") - if self.v_array is None: - raise RuntimeError("v_array not available. Set v_array before calling fit_strain().") - - u_fit = self.u_array - v_fit = self.v_array - - if strain_mask is not None: - m = np.asarray((strain_mask==1), dtype=bool) - u_ref = np.array( - (np.median(u_fit[m, 0]), np.median(u_fit[m, 1])), - dtype=float, - ) - v_ref = np.array( - (np.median(v_fit[m, 0]), np.median(v_fit[m, 1])), - dtype=float, - ) - elif self.mask is None: - u_ref = np.median(u_fit.reshape(-1, 2), axis=0) - v_ref = np.median(v_fit.reshape(-1, 2), axis=0) - else: - m = np.asarray((self.mask==1), dtype=bool) - u_ref = np.array( - (np.median(u_fit[m, 0]), np.median(u_fit[m, 1])), - dtype=float, - ) - v_ref = np.array( - (np.median(v_fit[m, 0]), np.median(v_fit[m, 1])), - dtype=float, - ) - if self.u_ref is not None: - u_ref = self.u_ref - if self.v_ref is not None: - v_ref = self.v_ref - self.u_ref = u_ref - self.v_ref = v_ref - scan_r = self.ds_shape[0] - scan_c = self.ds_shape[1] - - Uref = np.stack((u_ref, v_ref), axis=1).astype(float) - strain_trans = np.zeros((scan_r, scan_c, 2, 2)) - for r in range(scan_r): - for c in range(scan_c): - U = np.stack((u_fit[r, c, :], v_fit[r, c, :]), axis=1) - det = np.linalg.det(U) - if not np.isfinite(det) or abs(det) < 1e-12: - U_inv = np.linalg.pinv(U) - else: - U_inv = np.linalg.inv(U) - strain_trans[r, c, :, :] = Uref @ U_inv - const = 1 - if self.real_space is False: - const = -1 - - self.strain_raw_err = Dataset2d.from_array( - strain_trans[:, :, 0, 0] - 1, - name="strain err", - signal_units="fractional", - ) - self.strain_raw_ecc = Dataset2d.from_array( - strain_trans[:, :, 1, 1] - 1, - name="strain ecc", - signal_units="fractional", +def plot_strain_panels( + e_uu: np.ndarray, + e_vv: np.ndarray, + e_uv: np.ndarray, + rotation: np.ndarray, + mask: np.ndarray | None, + u_ref: np.ndarray | None, + v_ref: np.ndarray | None, + ds_shape: tuple[int, ...], + ds_sampling: float = 1.0, + ds_units: str = "pixels", + strain_range_percent: tuple[float, float] = (-3.0, 3.0), + rotation_range_degrees: tuple[float, float] = (-2.0, 2.0), + mask_range: tuple[float, float] = (0.0, 1.0), + roi: np.ndarray | None = None, + plot_rotation: bool = True, + plot_gvecs: bool = False, + plot_scalebar: bool = False, + cmap_strain: str = "RdBu_r", + cmap_rotation: str = "PiYG", + layout: str = "horizontal", + figsize: tuple[float, float] | None = None, + panel_titles: tuple[str, str, str] | None = None, + **kwargs, +): + """Render strain (e_uu, e_vv, e_uv) and rotation panels. + + Strain arrays are fractional (multiplied by 100 for display); ``rotation`` is + in radians (converted to degrees for display). ``panel_titles`` overrides the + three strain-panel titles (e.g. to label the raw row/col reference frame). + + The mask modulates panel brightness (black where masked out). ``mask_range`` + ``(low, high)`` remaps it linearly before display: mask values ``>= high`` show + full color, ``<= low`` go black, and values between ramp from black to full. + The default ``(0.0, 1.0)`` leaves the already-normalized mask unchanged. + + When ``roi`` (a boolean ``(scan_row, scan_col)`` array) is given, positions + inside it are drawn in color and positions outside it in greyscale (the same + field, desaturated), so a chosen reference region stands out from its context. + """ + if mask is None: + mask = np.ones(ds_shape[:2]) + + # remap the mask brightness onto the [low, high] window: <= low -> black, + # >= high -> full color, linear between. default (0, 1) is a no-op. + low, high = float(mask_range[0]), float(mask_range[1]) + if high > low: + mask = np.clip((np.asarray(mask, dtype=float) - low) / (high - low), 0.0, 1.0) + else: + mask = (np.asarray(mask, dtype=float) >= high).astype(float) + + if cmap_rotation is None: + cmap_rotation = cmap_strain + + if layout not in ["horizontal", "vertical"]: + raise ValueError("layout must be 'horizontal' or 'vertical'") + + ncols = 4 if plot_rotation else 3 + is_horizontal = layout == "horizontal" + + if figsize is None: + figsize = (8, 3) if is_horizontal else (6, 6) + + if is_horizontal: + fig, ax = plt.subplots(1, ncols, figsize=figsize) + else: + fig, ax = plt.subplots(ncols, 1, figsize=figsize) + + cm_strain = plt.get_cmap(cmap_strain).copy() + cm_strain.set_bad(color="black") + cm_rot = plt.get_cmap(cmap_rotation).copy() + cm_rot.set_bad(color="black") + + euu_pct = e_uu * 100 + evv_pct = e_vv * 100 + euv_pct = e_uv * 100 + rot_deg = np.rad2deg(rotation) + + roi_bool = None if roi is None else np.asarray(roi).astype(bool) + gray_cm = plt.get_cmap("gray").copy() + gray_cm.set_bad(color="black") + + def _roi_compose(norm_vals, color_cm): + """Color the field inside the ROI; show it in greyscale outside the ROI.""" + rgb = color_cm(norm_vals)[:, :, :3] + if roi_bool is None: + return rgb + rgb_gray = gray_cm(norm_vals)[:, :, :3] + return np.where(roi_bool[:, :, np.newaxis], rgb, rgb_gray) + + norm_strain = Normalize(vmin=strain_range_percent[0], vmax=strain_range_percent[1]) + euu_disp = _roi_compose(norm_strain(euu_pct), cm_strain) + evv_disp = _roi_compose(norm_strain(evv_pct), cm_strain) + euv_disp = _roi_compose(norm_strain(euv_pct), cm_strain) + + title_fs = 16 + ax[0].imshow(euu_disp * mask[:, :, np.newaxis]) + ax[1].imshow(evv_disp * mask[:, :, np.newaxis]) + ax[2].imshow(euv_disp * mask[:, :, np.newaxis]) + + if panel_titles is None: + panel_titles = ( + r"$\epsilon_{uu}$ $\updownarrow$", + r"$\epsilon_{vv}$ $\leftrightarrow$", + r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\!\:\searrow$", ) - self.strain_raw_erc = Dataset2d.from_array( - strain_trans[:, :, 1, 0] * 0.5 * const + strain_trans[:, :, 0, 1] * 0.5 * const, - name="strain erc", - signal_units="fractional", + ax[0].set_title(panel_titles[0], fontsize=title_fs) + ax[1].set_title(panel_titles[1], fontsize=title_fs) + ax[2].set_title(panel_titles[2], fontsize=title_fs) + + if plot_rotation: + norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) + rot_disp = _roi_compose(norm_rot(rot_deg), cm_rot) + ax[3].imshow(rot_disp * mask[:, :, np.newaxis]) + ax[3].set_title(r"Rotation $\circlearrowleft$", fontsize=title_fs) + + for a in ax: + a.set_xticks([]) + a.set_yticks([]) + a.set_facecolor("black") + a.set_aspect("equal") + + if plot_scalebar: + scalebar_kwargs = {} + for key, value in kwargs.items(): + if key.startswith("scalebar_"): + scalebar_key = key[len("scalebar_"):] + scalebar_kwargs[scalebar_key] = value + + scalebar_defaults = { + "sampling": ds_sampling, + "units": ds_units, + "length": None, + "width_px": 1, + "pad_px": 0.5, + "color": "black", + "loc": "lower left", + "fontsize": 12, + "bold": True, + } + scalebar_defaults.update(scalebar_kwargs) + scalebar_config = ScalebarConfig(**scalebar_defaults) + add_scalebar_to_ax( + ax[0], + array_size=int(ds_shape[0]), + sampling=scalebar_config.sampling, + length_units=scalebar_config.length, + units=scalebar_config.units, + width_px=scalebar_config.width_px, + pad_px=scalebar_config.pad_px, + color=scalebar_config.color, + loc=scalebar_config.loc, + fontsize=scalebar_config.fontsize, + bold=scalebar_config.bold, ) - self.strain_rotation = Dataset2d.from_array( - strain_trans[:, :, 1, 0] * -0.5 * const + strain_trans[:, :, 0, 1] * 0.5 * const, - name="strain rotation", - signal_units="fractional", - ) - if plot_strain: - self.plot_strain() - return self - - - def plot_strain( - self, - rotation_angle=20, - strain_range_percent=(-3.0, 3.0), - rotation_range_degrees=(-2.0, 2.0), - plot_rotation=True, - plot_gvecs=True, - plot_scalebar=False, - cmap_strain="RdBu_r", - cmap_rotation="PiYG", - layout="horizontal", - figsize=None, - **kwargs, - ): - if not hasattr(self, 'strain_raw_err') or self.strain_raw_err is None: - raise RuntimeError("Call fit_strain() first.") - if not hasattr(self, 'strain_raw_ecc') or self.strain_raw_ecc is None: - raise RuntimeError("Call fit_strain() first.") - if not hasattr(self, 'strain_raw_erc') or self.strain_raw_erc is None: - raise RuntimeError("Call fit_strain() first.") - if not hasattr(self, 'strain_rotation') or self.strain_rotation is None: - raise RuntimeError("Call fit_strain() first.") - - if self.mask is None: - self.mask = np.zeros(self.ds_shape[:2]) - - if cmap_rotation is None: - cmap_rotation = cmap_strain - - if layout not in ["horizontal", "vertical"]: - raise ValueError("layout must be 'horizontal' or 'vertical'") - - angle = np.deg2rad(rotation_angle) - c = np.cos(angle) - s = np.sin(angle) - - err = self.strain_raw_err.array - ecc = self.strain_raw_ecc.array - erc = self.strain_raw_erc.array - - euu = err * (c * c) + 2.0 * erc * (c * s) + ecc * (s * s) - evv = err * (s * s) - 2.0 * erc * (c * s) + ecc * (c * c) - euv = (ecc - err) * (c * s) + erc * (c * c - s * s) - - strain_euu = self.strain_raw_err.copy() - strain_evv = self.strain_raw_ecc.copy() - strain_euv = self.strain_raw_erc.copy() - strain_euu.array[...] = euu - strain_evv.array[...] = evv - strain_euv.array[...] = euv - - ncols = 4 if plot_rotation else 3 - is_horizontal = layout == "horizontal" - - if figsize is None: - figsize = (6, 6) if is_horizontal else (6, 6) - - if is_horizontal: - fig, ax = plt.subplots(1, ncols, figsize=figsize) - else: - fig, ax = plt.subplots(ncols, 1, figsize=figsize) - cm_strain = plt.get_cmap(cmap_strain).copy() - cm_strain.set_bad(color="black") - cm_rot = plt.get_cmap(cmap_rotation).copy() - cm_rot.set_bad(color="black") + if is_horizontal: + fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) + if plot_rotation: + pos3 = ax[3].get_position() + ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) - euu_pct = strain_euu.array * 100 - evv_pct = strain_evv.array * 100 - euv_pct = strain_euv.array * 100 - rot_deg = np.rad2deg(self.strain_rotation.array) + cb_orientation = "horizontal" + cb_size = 0.02 + cb_pad = 0.02 - norm_strain = Normalize(vmin=strain_range_percent[0], vmax=strain_range_percent[1]) - euu_rgb = cm_strain(norm_strain(euu_pct))[:, :, :3] - evv_rgb = cm_strain(norm_strain(evv_pct))[:, :, :3] - euv_rgb = cm_strain(norm_strain(euv_pct))[:, :, :3] + b0 = ax[0].get_position() + b2 = ax[2].get_position() + strain_cb_pos = [b0.x0, b0.y0 - cb_pad - cb_size, b2.x1 - b0.x0, cb_size] - title_fs = 16 - ax[0].imshow(euu_rgb * self.mask[:, :, np.newaxis],) - ax[1].imshow(evv_rgb * self.mask[:, :, np.newaxis],) - ax[2].imshow(euv_rgb * self.mask[:, :, np.newaxis],) + if plot_rotation: + b3 = ax[3].get_position() + rot_cb_pos = [b3.x0, b0.y0 - cb_pad - cb_size, b3.x1 - b3.x0, cb_size] + last_pos = b3 + else: + rot_cb_pos = None + last_pos = b2 + + else: + fig.subplots_adjust(left=0.04, right=0.80, top=0.98, bottom=0.04, hspace=0.15) + + cb_orientation = "vertical" + cb_size = 0.02 + cb_pad = 0.02 - ax[0].set_title(r"$\epsilon_{uu}$ $\updownarrow$", fontsize=title_fs) - ax[1].set_title(r"$\epsilon_{vv}$ $\leftrightarrow$", fontsize=title_fs) - ax[2].set_title(r"$\epsilon_{uv}$ $\nearrow\!\!\!\!\!\!\!\!\!\:\swarrow$", fontsize=title_fs) + b0 = ax[0].get_position() + b2 = ax[2].get_position() + strain_cb_pos = [b0.x1 + cb_pad, b2.y0, cb_size, b0.y1 - b2.y0] if plot_rotation: - norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) - rot_rgb = cm_rot(norm_rot(rot_deg))[:, :, :3] - ax[3].imshow(rot_rgb * self.mask[:, :, np.newaxis],) - ax[3].set_title(r"Rotation $\circlearrowleft$", fontsize=title_fs) - - for a in ax: - a.set_xticks([]) - a.set_yticks([]) - a.set_facecolor("black") - a.set_aspect("equal") - - if plot_scalebar: - scalebar_kwargs = {} - for key, value in kwargs.items(): - if key.startswith('scalebar_'): - scalebar_key = key[len('scalebar_'):] - scalebar_kwargs[scalebar_key] = value - - scalebar_defaults = { - 'sampling': self.ds_sampling, - 'units': self.ds_units, - 'length': None, - 'width_px': 1, - 'pad_px': 0.5, - 'color': 'black', - 'loc': 'lower left', - 'fontsize': 12, - 'bold': True, - } - scalebar_defaults.update(scalebar_kwargs) - scalebar_config = ScalebarConfig(**scalebar_defaults) - add_scalebar_to_ax( - ax[0], - array_size=int(self.ds_shape[0]), - sampling=scalebar_config.sampling, - length_units=scalebar_config.length, - units=scalebar_config.units, - width_px=scalebar_config.width_px, - pad_px=scalebar_config.pad_px, - color=scalebar_config.color, - loc=scalebar_config.loc, - fontsize=scalebar_config.fontsize, - bold=scalebar_config.bold, - ) + b3 = ax[3].get_position() + rot_cb_pos = [b0.x1 + cb_pad, b3.y0, cb_size, b3.y1 - b3.y0] + last_pos = b3 + else: + rot_cb_pos = None + last_pos = b2 + + cax1 = fig.add_axes(strain_cb_pos) + sm_strain = ScalarMappable(norm=norm_strain, cmap=cm_strain) + cbar1 = fig.colorbar(sm_strain, cax=cax1, orientation=cb_orientation) + cbar1.set_label("Strain", fontsize=title_fs) + cbar1.formatter = FuncFormatter(lambda v, _pos: f"{v:g}%") + cbar1.update_ticks() + cbar1.ax.tick_params(labelsize=12) + + if plot_rotation and rot_cb_pos is not None: + cax2 = fig.add_axes(rot_cb_pos) + sm_rot = ScalarMappable(norm=norm_rot, cmap=cm_rot) + cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation=cb_orientation) + cbar2.set_label("Rotation", fontsize=title_fs) + cbar2.formatter = FuncFormatter(lambda v, _pos: f"{v:g}°") + cbar2.update_ticks() + cbar2.ax.tick_params(labelsize=12) + + if plot_gvecs: + if u_ref is None or v_ref is None: + print("Warning: u_ref and v_ref not found. Call fit_strain() first.") + return fig, ax if is_horizontal: - fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) - if plot_rotation: - pos3 = ax[3].get_position() - ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) - - cb_orientation = "horizontal" - cb_size = 0.02 - cb_pad = 0.02 - - b0 = ax[0].get_position() - b2 = ax[2].get_position() - strain_cb_pos = [b0.x0, b0.y0 - cb_pad - cb_size, b2.x1 - b0.x0, cb_size] - - if plot_rotation: - b3 = ax[3].get_position() - rot_cb_pos = [b3.x0, b0.y0 - cb_pad - cb_size, b3.x1 - b3.x0, cb_size] - last_pos = b3 - else: - rot_cb_pos = None - last_pos = b2 - + ref_width = last_pos.width * 0.8 + ref_left = last_pos.x1 - 0.035 + ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) else: - fig.subplots_adjust(left=0.04, right=0.80, top=0.98, bottom=0.04, hspace=0.15) - - cb_orientation = "vertical" - cb_size = 0.02 - cb_pad = 0.02 - - b0 = ax[0].get_position() - b2 = ax[2].get_position() - strain_cb_pos = [b0.x1 + cb_pad, b2.y0, cb_size, b0.y1 - b2.y0] - - if plot_rotation: - b3 = ax[3].get_position() - rot_cb_pos = [b0.x1 + cb_pad, b3.y0, cb_size, b3.y1 - b3.y0] - last_pos = b3 - else: - rot_cb_pos = None - last_pos = b2 - - cax1 = fig.add_axes(strain_cb_pos) - sm_strain = ScalarMappable(norm=norm_strain, cmap=cm_strain) - cbar1 = fig.colorbar(sm_strain, cax=cax1, orientation=cb_orientation) - cbar1.set_label("Strain (%)", fontsize=title_fs) - cbar1.ax.tick_params(labelsize=12) - - if plot_rotation and rot_cb_pos is not None: - cax2 = fig.add_axes(rot_cb_pos) - sm_rot = ScalarMappable(norm=norm_rot, cmap=cm_rot) - cbar2 = fig.colorbar(sm_rot, cax=cax2, orientation=cb_orientation) - cbar2.set_label("Rotation (deg)", fontsize=title_fs) - cbar2.ax.tick_params(labelsize=12) - - if plot_gvecs: - if self.u_ref is None or self.v_ref is None: - print("Warning: u_ref and v_ref not found. Call fit_strain() first.") - return fig, ax - - if is_horizontal: - ref_width = last_pos.width * 0.8 - ref_left = last_pos.x1 - 0.035 - ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) - else: - ref_height = last_pos.height * 0.5 - ref_bottom = last_pos.y0 - ref_height - 0.05 - ref_width = last_pos.width * 0.8 - ref_left = last_pos.x0 + (last_pos.width - ref_width) / 2 # Center it - ref_ax = fig.add_axes([ref_left, ref_bottom, ref_width, ref_height]) - - ref_ax.set_xlim(-1.5, 1.5) - ref_ax.set_ylim(-1.5, 1.5) - ref_ax.set_aspect('equal') - ref_ax.axis('off') - u_norm = self.u_ref / np.linalg.norm(self.u_ref) - v_norm = self.v_ref / np.linalg.norm(self.v_ref) - - u_row, u_col = u_norm - v_row, v_col = v_norm - arrow_props_ref = dict(arrowstyle='->', lw=3, mutation_scale=25) - - u_arrow = FancyArrowPatch( - (0, 0), (u_col, -u_row), - color='darkred', **arrow_props_ref - ) - ref_ax.add_patch(u_arrow) - - v_arrow = FancyArrowPatch( - (0, 0), (v_col, -v_row), - color='darkblue', **arrow_props_ref - ) - ref_ax.add_patch(v_arrow) - ref_ax.text(u_col * 1.3, -u_row * 1.3, r'$\mathbf{g}_{1}$', - fontsize=14, fontweight='bold', color='darkred', - ha='center', va='center') - - ref_ax.text(v_col * 1.3, -v_row * 1.3, r'$\mathbf{g}_{2}$', - fontsize=14, fontweight='bold', color='darkblue', - ha='center', va='center') - - - return fig, ax + ref_height = last_pos.height * 0.5 + ref_bottom = last_pos.y0 - ref_height - 0.05 + ref_width = last_pos.width * 0.8 + ref_left = last_pos.x0 + (last_pos.width - ref_width) / 2 + ref_ax = fig.add_axes([ref_left, ref_bottom, ref_width, ref_height]) + + ref_ax.set_xlim(-1.5, 1.5) + ref_ax.set_ylim(-1.5, 1.5) + ref_ax.set_aspect("equal") + ref_ax.axis("off") + u_norm = u_ref / np.linalg.norm(u_ref) + v_norm = v_ref / np.linalg.norm(v_ref) + + u_row, u_col = u_norm + v_row, v_col = v_norm + arrow_props_ref = dict(arrowstyle="->", lw=3, mutation_scale=25) + + u_arrow = FancyArrowPatch( + (0, 0), (u_col, -u_row), + color="darkred", **arrow_props_ref + ) + ref_ax.add_patch(u_arrow) + v_arrow = FancyArrowPatch( + (0, 0), (v_col, -v_row), + color="darkblue", **arrow_props_ref + ) + ref_ax.add_patch(v_arrow) + ref_ax.text(u_col * 1.3, -u_row * 1.3, r"$\mathbf{g}_{1}$", + fontsize=14, fontweight="bold", color="darkred", + ha="center", va="center") + + ref_ax.text(v_col * 1.3, -v_row * 1.3, r"$\mathbf{g}_{2}$", + fontsize=14, fontweight="bold", color="darkblue", + ha="center", va="center") + + return fig, ax + + +def plot_strain_precision_histogram( + edges: np.ndarray, + counts: np.ndarray, + precision: dict[str, float], + component: str, + unit: str, + *, + figsize: tuple[float, float] = (6.0, 4.0), +): + """Weighted histogram of the local-deviation strain precision. + + ``edges``/``counts`` describe the (mask-weighted, normalized) distribution of + the chosen ``component`` deviation in display units (``unit``). ``precision`` + carries the mask-weighted RMS precision of every component for the annotation + box; the RMS of the plotted component is marked with a dashed line. + """ + fig, ax = plt.subplots(figsize=figsize) + edges = np.asarray(edges, dtype=float) + counts = np.asarray(counts, dtype=float) + centers = 0.5 * (edges[:-1] + edges[1:]) + widths = np.diff(edges) + + ax.bar(centers, counts, width=widths, align="center", + color="#4C72B0", edgecolor="white", linewidth=0.3) + + rms_value = precision[component] + if np.isfinite(rms_value): + ax.axvline(rms_value, color="crimson", ls="--", lw=2, + label=f"RMS = {rms_value:.3g} {unit}") + ax.legend(loc="upper left", fontsize=9) + + label = "combined" if component == "combined" else component + ax.set_xlabel(f"{label} precision ({unit})", fontsize=12) + ax.set_ylabel("weighted fraction", fontsize=12) + ax.set_title("Strain precision (RMS of local median deviation)", fontsize=13) + ax.tick_params(labelsize=10) + + annotation = "\n".join( + [ + rf"$\epsilon_{{uu}}$: {precision['e_uu']:.3g} %", + rf"$\epsilon_{{vv}}$: {precision['e_vv']:.3g} %", + rf"$\epsilon_{{uv}}$: {precision['e_uv']:.3g} %", + f"rotation: {precision['rotation']:.3g} °", + f"combined: {precision['combined']:.3g} %", + ] + ) + ax.text(0.97, 0.97, annotation, transform=ax.transAxes, ha="right", va="top", + fontsize=9, family="monospace", + bbox=dict(boxstyle="round", fc="white", ec="0.7", alpha=0.9)) + + fig.tight_layout() + return fig, ax From c9faf13cf32720a5a24e351ffcb848c4ec144a78 Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 1 Jun 2026 10:09:13 +0200 Subject: [PATCH 081/113] minor fixes --- src/quantem/diffraction/strain.py | 133 ++++++++++-------- .../diffraction/strain_visualization.py | 42 +++--- 2 files changed, 103 insertions(+), 72 deletions(-) diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index a12e8cd56..5f5046ee2 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -392,17 +392,22 @@ def estimate_strain_precision( single strain precision can be quoted and compared between datasets. Rotation precision is reported separately, not folded into ``combined``. - Each component's precision is summarized as the mask-weighted **RMS** of its - per-position deviations — a sigma-like scatter — and a weighted histogram of - those deviations is shown. RMS combining is self-consistent under the - Frobenius sum: ``rms(combined) == sqrt(rms_uu**2 + rms_vv**2 + 2*rms_uv**2)``. + Each component's precision is summarized by the mask-weighted **median** of its + per-position deviations — the center of the histogram bulk. The median is used + (not the mean or RMS) because a handful of bad-fit pixels form a heavy tail that + would drag a second moment far to the right of where the distribution actually + sits, leaving the reported number disconnected from the histogram; the median + ignores that tail. A weighted histogram of the chosen component is shown, marked + with its median. Parameters ---------- mask_range : tuple of float, default=(0.0, 1.0) ``(low, high)`` window remapping :attr:`mask` to ``[0, 1]`` (same - convention as :meth:`plot_strain`); the remapped mask both selects - neighbors (``> mask_threshold``) and weights the histogram and mean. + convention as :meth:`plot_strain`); the remapped mask both selects which + positions are trusted (``> mask_threshold`` -- used as neighbors *and* as + the set the precision is computed over) and weights the histogram and the + median. rotation_angle : float, default=0.0 Frame rotation (degrees) applied before measuring per-component precision, matching :meth:`plot_strain`. ``0`` reports the raw row/col frame @@ -415,8 +420,11 @@ def estimate_strain_precision( *curved* strain and biasing the masked edges. ``5`` roughly halves the noise-floor over-estimate of ``3`` (~9% -> ~4%) while staying local. mask_threshold : float, default=0.5 - A neighbor contributes to the local median only if its scaled mask - exceeds this value. + A position is trusted only if its scaled mask exceeds this value. Trusted + positions are the ones used as local-median neighbors *and* the ones whose + deviations enter the reported median and histogram; sub-threshold positions + are excluded from both (not merely down-weighted), so a poorly-indexed + pixel cannot leak its scatter into the precision. min_neighbors : int, default=3 Minimum number of valid neighbors required; positions with fewer get no precision estimate (``nan``, dropped from the statistics). @@ -426,10 +434,10 @@ def estimate_strain_precision( Number of histogram bins (or a sequence of bin edges). bounds : tuple of float, optional ``(low, high)`` histogram range in display units (percent for strain, - degrees for rotation). Fix it to compare datasets on the same axis. Mass - outside the range (or outside an explicit ``bins`` edge array) is piled - into the edge bins as overflow rather than dropped, so the histogram - always integrates to the same weight as the reported RMS. + degrees for rotation). Fix it to compare datasets on the same axis. Values + outside the range are left out of the bars (no overflow spike); the median + is computed from all trusted positions regardless, and + ``out_of_range_fraction`` records how much was off-range. plot : bool, default=True If ``True``, draw the weighted precision histogram. returnfig : bool, default=False @@ -438,12 +446,13 @@ def estimate_strain_precision( Returns ------- dict or tuple - A results dict with the mask-weighted ``rms`` precision per component and - the ``combined`` value (strain in percent, rotation in degrees), the - normalized ``counts`` and ``edges`` of the histogrammed ``component`` (and - ``counts_raw``, the weighted bin sums), ``out_of_range_fraction`` (weighted - mass piled into the edge bins as overflow), and the chosen settings; or - ``(fig, ax)`` when ``returnfig=True``. + A results dict with the ``precision`` (mask-weighted median local + deviation) per component and ``combined`` (strain in percent, rotation in + degrees), the normalized ``counts`` and ``edges`` of the histogrammed + ``component`` (and ``counts_raw``, the weighted bin sums), + ``out_of_range_fraction`` (weighted mass outside the histogram range, + excluded from the bars), and the chosen settings; or ``(fig, ax)`` when + ``returnfig=True``. """ if window < 3 or window % 2 == 0: raise ValueError("window must be an odd integer >= 3.") @@ -494,43 +503,43 @@ def estimate_strain_precision( "combined": 100.0, } - def _weighted_rms(err_native: np.ndarray, factor: float) -> float: + # Precision = the weighted MEDIAN of each per-position deviation distribution, + # in display units. Restricted to trusted positions (valid == scaled > + # mask_threshold, the SAME set used to pick neighbors) and mask-weighted within + # it -- otherwise sub-threshold junk pixels, already excluded as neighbors, + # would leak in. The median sits at the center of the histogram bulk and is + # immune to the heavy outlier tail that a mean / RMS would chase out to the + # right (a few bad-fit pixels dominate a second moment but not the median). + def _weighted_median(err_native: np.ndarray, factor: float) -> float: e = err_native * factor - finite = np.isfinite(e) - w = scaled[finite] - wsum = float(w.sum()) - return float(np.sqrt(np.sum(e[finite] ** 2 * w) / wsum)) if wsum > 0 else float("nan") - - # mask-weighted RMS deviation (a sigma-like scatter). Under the Frobenius - # sum this is self-consistent: rms(combined) == sqrt(rms_uu**2 + rms_vv**2 - # + 2*rms_uv**2), so the combined number agrees with the per-component ones. - rms = {name: _weighted_rms(dev[name], scale[name]) for name in scale} - - # weighted histogram of the chosen component, in display units. The headline - # RMS above is computed over *all* finite positions, so the histogram must be - # too: with an explicit bin-edge array (e.g. bins=np.arange(0, 1, 0.02)) - # np.histogram ignores `range` and silently DROPS everything outside the - # edges, then renormalizing makes the plot look far tighter than the reported - # RMS. Instead pile any out-of-range mass into the edge bins (overflow) so the - # histogram integrates to the same weight the RMS sees, and report the - # fraction that landed there. + use = np.isfinite(e) & valid + return _weighted_quantile(e[use], scaled[use], 0.5) + + precision = {name: _weighted_median(dev[name], scale[name]) for name in scale} + + # weighted histogram of the chosen component, over the same trusted positions + # as the median above. This is purely a picture of the common error values, so + # anything beyond the bin range is left OUT of the bars -- no overflow spike at + # the edge to crush the bulk. Nothing is lost: the median is computed from all + # trusted positions regardless. Bars are normalized by the total trusted + # weight, so each bar is the true fraction of all trusted positions and the + # off-range mass simply isn't drawn (the bars sum to 1 - out_of_range_fraction). e = dev[component] * scale[component] - finite = np.isfinite(e) - e_f = e[finite] - w_f = scaled[finite] + use = np.isfinite(e) & valid # trusted positions only, consistent with median + e_f = e[use] + w_f = scaled[use] edges = np.histogram_bin_edges(e_f, bins=bins, range=bounds) lo, hi = float(edges[0]), float(edges[-1]) wtot = float(w_f.sum()) frac_below = float(w_f[e_f < lo].sum()) / wtot if wtot > 0 else 0.0 frac_above = float(w_f[e_f > hi].sum()) / wtot if wtot > 0 else 0.0 out_of_range_fraction = frac_below + frac_above - counts_raw, edges = np.histogram(np.clip(e_f, lo, hi), bins=edges, weights=w_f) - total = float(counts_raw.sum()) - counts = counts_raw / total if total > 0 else counts_raw + counts_raw, edges = np.histogram(e_f, bins=edges, weights=w_f) + counts = counts_raw / wtot if wtot > 0 else counts_raw unit = "°" if component == "rotation" else "%" result = { - "rms": rms, + "precision": precision, "component": component, "unit": unit, "counts": counts, @@ -544,31 +553,24 @@ def _weighted_rms(err_native: np.ndarray, factor: float) -> float: "rotation_angle": float(rotation_angle), } - print("Strain precision (RMS of local median deviation, weighted by scaled mask)") + print("Strain precision (median local deviation, mask-weighted)") print( f" reference={n_neighbors} neighbors (disk, window={window}) " f"mask>{mask_threshold:g} min_neighbors={min_neighbors} " f"rotation_angle={rotation_angle:g} deg" ) for name in ("e_uu", "e_vv", "e_uv"): - print(f" {name:<9}: {rms[name]:7.4f} %") - print(f" {'rotation':<9}: {rms['rotation']:7.4f} deg") + print(f" {name:<9}: {precision[name]:7.4f} %") + print(f" {'rotation':<9}: {precision['rotation']:7.4f} deg") print( - f" {'combined':<9}: {rms['combined']:7.4f} % " + f" {'combined':<9}: {precision['combined']:7.4f} % " "(strain-only Frobenius norm; rotation excluded)" ) - if out_of_range_fraction > 0: - print( - f" note: {100 * out_of_range_fraction:.1f}% of weighted mass fell " - f"outside the histogram range [{lo:g}, {hi:g}] {unit} and was piled " - "into the edge bins (overflow); widen `bins`/`bounds` to resolve the " - "tail. The RMS above includes it." - ) if not (plot or returnfig): return result - fig, ax = plot_strain_precision_histogram(edges, counts, rms, component, unit) + fig, ax = plot_strain_precision_histogram(edges, counts, precision, component, unit) if returnfig: return fig, ax return result @@ -577,6 +579,25 @@ def _weighted_rms(err_native: np.ndarray, factor: float) -> float: # ---- module-level fitting functions ---- +def _weighted_quantile(values: np.ndarray, weights: np.ndarray, q: float) -> float: + """Weighted ``q``-quantile of ``values`` (``q`` in ``[0, 1]``); ``nan`` if no weight. + + Uses cumulative-weight interpolation with weights centered on each sorted sample, + so with uniform weights it tracks ``np.quantile``'s linear interpolation and is + robust to a heavy upper tail (the median ignores how far the outliers reach). + """ + values = np.asarray(values, dtype=float) + weights = np.asarray(weights, dtype=float) + total = float(weights.sum()) + if values.size == 0 or total <= 0: + return float("nan") + order = np.argsort(values) + v = values[order] + w = weights[order] + cw = np.cumsum(w) - 0.5 * w + return float(np.interp(q * total, cw, v)) + + def _local_masked_median( field: np.ndarray, valid: np.ndarray, diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index 0326d5131..609ef6d49 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -284,10 +284,10 @@ def plot_strain_precision_histogram( ): """Weighted histogram of the local-deviation strain precision. - ``edges``/``counts`` describe the (mask-weighted, normalized) distribution of - the chosen ``component`` deviation in display units (``unit``). ``precision`` - carries the mask-weighted RMS precision of every component for the annotation - box; the RMS of the plotted component is marked with a dashed line. + ``edges``/``counts`` describe the (mask-weighted, normalized) distribution of the + chosen ``component`` deviation in display units (``unit``). ``precision`` is the + weighted-median local deviation per component (used for the annotation box); the + plotted component's median is marked with a solid line. """ fig, ax = plt.subplots(figsize=figsize) edges = np.asarray(edges, dtype=float) @@ -298,25 +298,35 @@ def plot_strain_precision_histogram( ax.bar(centers, counts, width=widths, align="center", color="#4C72B0", edgecolor="white", linewidth=0.3) - rms_value = precision[component] - if np.isfinite(rms_value): - ax.axvline(rms_value, color="crimson", ls="--", lw=2, - label=f"RMS = {rms_value:.3g} {unit}") - ax.legend(loc="upper left", fontsize=9) + median_value = precision[component] + if np.isfinite(median_value): + ax.axvline(median_value, color="crimson", ls="-", lw=2) + # label the line inline -- a legend box here would sit on top of the info box. + # Put the text on whichever side of the line keeps it clear of the right box. + span = float(edges[-1] - edges[0]) + on_right = span > 0 and (median_value - edges[0]) / span > 0.5 + ax.annotate( + f"median = {median_value:.3g} {unit}", + xy=(median_value, 0.96), xycoords=("data", "axes fraction"), + xytext=(-6 if on_right else 6, 0), textcoords="offset points", + ha="right" if on_right else "left", va="top", + color="crimson", fontsize=9, + ) label = "combined" if component == "combined" else component - ax.set_xlabel(f"{label} precision ({unit})", fontsize=12) + ax.set_xlabel(f"{label} deviation ({unit})", fontsize=12) ax.set_ylabel("weighted fraction", fontsize=12) - ax.set_title("Strain precision (RMS of local median deviation)", fontsize=13) + ax.set_title("Strain precision (median local deviation)", fontsize=13) ax.tick_params(labelsize=10) annotation = "\n".join( [ - rf"$\epsilon_{{uu}}$: {precision['e_uu']:.3g} %", - rf"$\epsilon_{{vv}}$: {precision['e_vv']:.3g} %", - rf"$\epsilon_{{uv}}$: {precision['e_uv']:.3g} %", - f"rotation: {precision['rotation']:.3g} °", - f"combined: {precision['combined']:.3g} %", + r"median:", + rf" $\epsilon_{{uu}}$: {precision['e_uu']:.3g} %", + rf" $\epsilon_{{vv}}$: {precision['e_vv']:.3g} %", + rf" $\epsilon_{{uv}}$: {precision['e_uv']:.3g} %", + rf" rotation: {precision['rotation']:.3g} °", + rf" combined: {precision['combined']:.3g} %", ] ) ax.text(0.97, 0.97, annotation, transform=ax.transAxes, ha="right", va="top", From 2b81d107018cacdb1fa7b25bf2d350ebf97d4dcf Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 1 Jun 2026 11:28:18 +0200 Subject: [PATCH 082/113] cepstral updates --- src/quantem/diffraction/bragg_vectors.py | 67 +- src/quantem/diffraction/strain.py | 56 +- .../diffraction/strain_autocorrelation.py | 603 ++++++++++++++++-- .../diffraction/strain_visualization.py | 52 +- 4 files changed, 707 insertions(+), 71 deletions(-) diff --git a/src/quantem/diffraction/bragg_vectors.py b/src/quantem/diffraction/bragg_vectors.py index ed36ede40..aceb19108 100644 --- a/src/quantem/diffraction/bragg_vectors.py +++ b/src/quantem/diffraction/bragg_vectors.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Any +from pathlib import Path +from typing import Any, Literal, Sequence, Union import numpy as np import torch @@ -86,7 +87,7 @@ def __init__( ): if _token is not self._token: raise RuntimeError("Use BraggVectors.from_dataset() to instantiate this class.") - super().__init__() + super(BraggVectors, self).__init__() self.dataset = dataset self.device = device @@ -141,6 +142,68 @@ def from_dataset( dataset.name = name return cls(dataset=dataset, device=device, _token=cls._token) + def save( + self, + path: str | Path, + mode: Literal["w", "o"] = "w", + store: Literal["auto", "zip", "dir"] = "auto", + skip: Union[str, type, Sequence[Union[str, type]]] = (), + compression_level: int | None = 4, + *, + include_dataset: bool = False, + ) -> None: + """Save the workflow to disk, excluding the raw 4D-STEM dataset by default. + + Overrides :meth:`~quantem.core.io.serialize.AutoSerialize.save` to drop + :attr:`dataset` — the raw 4D-STEM cube, which dominates the file size — from + serialization by default. The detected :attr:`peaks`, lattice fit + (:attr:`u_array`/:attr:`v_array`), Bragg vector map and all diagnostics are + kept, so the file holds the *results* of the workflow (orders of magnitude + smaller than the data) rather than the data itself. + + ``"dataset"`` is recorded in the file's skip metadata, so a reloaded workflow + simply has no ``dataset`` attribute. Re-attach one (``bv.dataset = ds``) before + calling methods that read the raw cube — :meth:`detect_disks`, + :meth:`correlation_map`, :meth:`make_template_from_data`, + :meth:`calculate_strain_map`, etc. Pass ``include_dataset=True`` to keep the + dataset in the file instead. + + Parameters + ---------- + path : str or Path + Target file path. Use a ``.zip`` extension for zip format, otherwise a + directory is written. + mode : {'w', 'o'}, default='w' + ``'w'`` writes only if the path does not exist; ``'o'`` overwrites. + store : {'auto', 'zip', 'dir'}, default='auto' + Storage format; ``'auto'`` infers from the file extension. + skip : str, type, or sequence of (str or type), default=() + Additional attribute names/types to skip during serialization, merged with + the default ``dataset`` exclusion. + compression_level : int or None, default=4 + Zstandard/Blosc compression level (0–9); ``0`` disables compression. + include_dataset : bool, default=False + If ``True``, keep the raw 4D-STEM :attr:`dataset` in the file (large). The + default ``False`` excludes it. + """ + if isinstance(skip, (str, type)): + skip = [skip] + else: + skip = list(skip) + if not include_dataset and "dataset" not in skip: + skip.append("dataset") + # Explicit (two-arg) super() rather than the bare super(): the zero-arg form + # needs a compiler-created __class__ closure cell that is absent when this + # method's source is re-exec'd from a string (Jupyter autoreload), which + # raises "super(): __class__ cell not found". The explicit form is immune. + super(BraggVectors, self).save( + path, + mode=mode, + store=store, + skip=skip, + compression_level=compression_level, + ) + # ---- main methods ---- def make_template_synthetic( diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index 5f5046ee2..649746c1d 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -21,8 +21,10 @@ class StrainMap(AutoSerialize): is the median of the fitted ``g_u``/``g_v`` over a mask/ROI; the strain tensor is recomputed by :meth:`update_reference`. - For nanobeam 4D-STEM the lattice vectors are measured in reciprocal space, so - the shear/rotation signs are flipped relative to real space (``real_space=False``). + Two measurement modalities are supported and give identical strain for the same + deformation, so correlation and cepstral maps can be compared directly: + reciprocal-space Bragg vectors (``real_space=False``, nanobeam correlation) and + real-space cepstral/autocorrelation vectors (``real_space=True``). Parameters ---------- @@ -33,8 +35,9 @@ class StrainMap(AutoSerialize): ds_shape : tuple of int Shape of the parent scan grid, used to size the strain maps. real_space : bool - ``True`` for real-space lattice vectors; ``False`` for reciprocal-space - (nanobeam) data, which flips the shear/rotation signs. + ``False`` for reciprocal-space (Bragg/correlation) lattice vectors; ``True`` + for real-space (cepstral autocorrelation / DPC) vectors. Both modalities are + arranged to yield matching strain (see :func:`_strain_tensor`). u_ref : np.ndarray, optional Fixed reference for ``u``; if omitted the median over the mask/ROI is used. A value supplied here persists across re-fits. @@ -705,8 +708,21 @@ def _strain_tensor( ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """Per-position strain tensor from lattice vectors relative to a reference. - For reciprocal-space (nanobeam) data the shear and rotation are sign-flipped via - ``const = -1``. + Two measurement modalities are supported and are arranged to give *identical* + strain for the same physical deformation, so correlation (Bragg) and cepstral + (autocorrelation) maps can be compared directly: + + * ``real_space=False`` -- reciprocal-space lattice vectors (nanobeam Bragg + disks), which contract under tension. The per-position transform is + ``strain_trans = U_ref @ inv(U)``. + * ``real_space=True`` -- real-space lattice vectors (cepstral / Patterson + autocorrelation peaks, or DPC), which expand under tension. The transform is + ``strain_trans = (U @ inv(U_ref)).T``. + + Both expressions evaluate to ``F.T`` (the transpose of the real-space deformation + gradient), so the normal strains, shear, and rotation come out the same + regardless of modality, and the reciprocal-space sign convention (``const = -1``, + which sets the shear/rotation handedness) is shared. Parameters ---------- @@ -719,8 +735,9 @@ def _strain_tensor( v_ref : np.ndarray Reference second lattice vector (length 2). real_space : bool - ``True`` for real-space data; ``False`` (nanobeam, reciprocal space) flips - the shear and rotation signs. + ``False`` for reciprocal-space (Bragg/correlation) vectors; ``True`` for + real-space (cepstral autocorrelation / DPC) vectors. Selects the per-position + transform above; both yield matching strain. Returns ------- @@ -730,6 +747,13 @@ def _strain_tensor( scan_r, scan_c = u_array.shape[0], u_array.shape[1] Uref = np.stack((u_ref, v_ref), axis=1).astype(float) strain_trans = np.zeros((scan_r, scan_c, 2, 2)) + + # For real-space vectors the reference is inverted once (it is shared by every + # position); a non-finite or singular reference leaves the whole map undefined. + Uref_inv = None + if real_space and np.all(np.isfinite(Uref)) and abs(np.linalg.det(Uref)) >= 1e-12: + Uref_inv = np.linalg.inv(Uref) + for r in range(scan_r): for c in range(scan_c): U = np.stack((u_array[r, c, :], v_array[r, c, :]), axis=1) @@ -740,9 +764,19 @@ def _strain_tensor( if not np.all(np.isfinite(U)) or abs(np.linalg.det(U)) < 1e-12: strain_trans[r, c, :, :] = np.nan continue - strain_trans[r, c, :, :] = Uref @ np.linalg.inv(U) - - const = 1 if real_space else -1 + if real_space: + # real-space vectors expand under tension: (U @ U_ref^-1).T == F.T + if Uref_inv is None: + strain_trans[r, c, :, :] = np.nan + else: + strain_trans[r, c, :, :] = (U @ Uref_inv).T + else: + # reciprocal-space vectors contract under tension: U_ref @ U^-1 == F.T + strain_trans[r, c, :, :] = Uref @ np.linalg.inv(U) + + # const = -1 is the reciprocal-space (nanobeam) shear/rotation convention. Both + # modalities reduce strain_trans to F.T above, so the convention is shared. + const = -1 e_rr = strain_trans[:, :, 0, 0] - 1 e_cc = strain_trans[:, :, 1, 1] - 1 e_rc = strain_trans[:, :, 1, 0] * 0.5 * const + strain_trans[:, :, 0, 1] * 0.5 * const diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 18df58f30..61c0c1daf 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -21,19 +21,88 @@ class StrainMapAutocorrelation(AutoSerialize): + """Cepstral / autocorrelation lattice fitting and strain mapping for 4D-STEM. + + An alternative to correlation-based :class:`~quantem.diffraction.bragg_vectors.BraggVectors` + that needs no disk template: every diffraction pattern is transformed into the + autocorrelation (real-space) domain, where the crystal periodicity shows up as a + lattice of sharp peaks, and those peaks are fit per scan position to recover the + local lattice vectors. Because the transform lives in real space, the peaks track + real-space lattice spacings (they *expand* under tension, opposite to Bragg disks), + so ``real_space=True`` -- :func:`~quantem.diffraction.strain._strain_tensor` then + reduces both this and the reciprocal-space Bragg path to the same deformation + gradient, and the two strain maps can be compared directly. + + Workflow (each step writes state consumed by the next): + + 1. :meth:`diffraction_mask` -- build a soft mask over the detector that suppresses + the bright central beam / vacuum so the transform is dominated by the lattice. + 2. :meth:`preprocess` -- transform every pattern and average the magnitudes into a + mean transform image. Three ``mode`` choices set the intensity scaling applied + before the FFT: ``"linear"`` (Patterson / autocorrelation), ``"log"`` + (cepstrum), or ``"gamma"`` (power law). The detector->scan rotation + (``q_to_r_rotation_ccw_deg`` + ``q_transpose``) is read from the parent dataset + metadata, the single source of truth shared with the DPC/CoM and Bragg + workflows. + 3. :meth:`choose_lattice_vector` -- refine a hand-picked initial ``(u, v)`` basis + against the mean transform, optionally auto-detecting and re-fitting all peaks. + 4. :meth:`fit_lattice_vectors` -- the heavy step: transform and fit the lattice + vectors at every scan position into ``u_array``/``v_array`` of shape + ``(scan_row, scan_col, 2)``. + 5. :meth:`create_mask` -- compute the per-position weight :attr:`mask_weight` + (lattice signal strength) used to weight the reference lattice. + 6. :meth:`calculate_strain_map` -- hand the lattice vectors (and + :attr:`mask_weight`) to a :class:`~quantem.diffraction.strain.StrainMap`. + + Use :meth:`from_dataset` or :meth:`from_array` to construct an instance. + + Parameters + ---------- + dataset : Dataset4dstem + The 4D-STEM dataset to analyze. + input_data : Any, optional + The original object passed to the constructor (a dataset or array), retained + for provenance. + """ + _token = object() + # Cepstral / Patterson lattice vectors are measured in the autocorrelation + # (real-space) domain -- the peaks track real-space lattice spacings and expand + # under tension -- so real_space=True. _strain_tensor reduces both this and the + # reciprocal-space Bragg path to F.T, so the two strain maps agree. + real_space: bool = True + def __init__( self, dataset: Dataset4dstem, input_data: Any | None = None, _token: object | None = None, ): + """Private constructor; use :meth:`from_dataset` or :meth:`from_array`. + + Direct instantiation is blocked by the ``_token`` guard. The factory + classmethods are the supported entry points and document their arguments. + + Parameters + ---------- + dataset : Dataset4dstem + The 4D-STEM dataset to analyze. + input_data : Any, optional + The original object passed to a factory (kept for provenance). + _token : object, optional + Internal sentinel; must match the class token or a ``RuntimeError`` is + raised. + """ if _token is not self._token: raise RuntimeError( "Use StrainMapAutocorrelation.from_dataset() or StrainMapAutocorrelation.from_array() to instantiate this class." ) - super().__init__() + # Explicit (two-arg) super() rather than the bare super(): the zero-arg form + # needs a compiler-created __class__ closure cell that is absent when this + # method's source is re-exec'd from a string (Jupyter autoreload), which would + # raise "super(): __class__ cell not found". + super(StrainMapAutocorrelation, self).__init__() self.dataset = dataset self.input_data = input_data self.strain = None @@ -47,16 +116,34 @@ def __init__( self.mask_diffraction = np.ones(self.dataset.array.shape[2:]) self.mask_diffraction_inv = np.zeros(self.dataset.array.shape[2:]) + # initial basis from choose_lattice_vector(); per-position fits from + # fit_lattice_vectors(); per-position weight from create_mask(). + self.u: np.ndarray | None = None + self.v: np.ndarray | None = None + self.u_peak_fit: Dataset3d | None = None + self.v_peak_fit: Dataset3d | None = None self.u_ref: np.ndarray | None = None self.v_ref: np.ndarray | None = None self.u_array: np.ndarray | None = None self.v_array: np.ndarray | None = None - self.mask: np.ndarray | None = None - - self.real_space = True + self.mask_weight: np.ndarray | None = None @classmethod def from_dataset(cls, dataset: Dataset4dstem, *, name: str | None = None) -> "StrainMapAutocorrelation": + """Create a cepstral strain workflow bound to a 4D-STEM dataset. + + Parameters + ---------- + dataset : Dataset4dstem + The 4D-STEM dataset to analyze. + name : str, optional + If given, sets ``dataset.name``. + + Returns + ------- + StrainMapAutocorrelation + A new workflow instance bound to ``dataset``. + """ if not isinstance(dataset, Dataset4dstem): raise TypeError("StrainMapAutocorrelation.from_dataset expects a Dataset4dstem instance.") if name is not None: @@ -65,6 +152,20 @@ def from_dataset(cls, dataset: Dataset4dstem, *, name: str | None = None) -> "St @classmethod def from_array(cls, array: NDArray, *, name: str = "strain_map_autocorrelation") -> "StrainMapAutocorrelation": + """Create a cepstral strain workflow from a raw 4D array. + + Parameters + ---------- + array : np.ndarray + 4D-STEM data with shape ``(scan_row, scan_col, dp_row, dp_col)``. + name : str, default="strain_map_autocorrelation" + Name for the wrapped :class:`Dataset4dstem`. + + Returns + ------- + StrainMapAutocorrelation + A new workflow instance wrapping the data in a :class:`Dataset4dstem`. + """ arr = ensure_valid_array(array) if arr.ndim != 4: raise ValueError( @@ -80,6 +181,37 @@ def diffraction_mask( plot_mask=True, figsize=(8, 4), ): + """Build a soft detector mask suppressing the central beam and vacuum. + + Pixels of the mean diffraction pattern below ``threshold`` (plus the detector + border) are treated as "outside" the useful signal. The kept region is feathered + with a raised-cosine taper of width ``edge_blend`` (via a Euclidean distance + transform) into :attr:`mask_diffraction` (multiplicative, in ``[0, 1]``), and a + complementary fill :attr:`mask_diffraction_inv` replaces the masked region with a + flat edge intensity. Both are applied to every pattern in :meth:`preprocess` and + :meth:`fit_lattice_vectors` so the transform is dominated by the crystalline + signal rather than the bright unscattered beam. + + Parameters + ---------- + threshold : float, optional + Mean-intensity level below which detector pixels are masked out. Required; + choose it from the mean diffraction pattern (e.g. just above the vacuum + level). + edge_blend : float, default=64.0 + Feather width in pixels of the raised-cosine taper between kept and masked + regions; larger values give a softer transition. + plot_mask : bool, default=True + If ``True``, show the raw mean pattern beside the masked/filled pattern (log + scaled) so the mask can be checked. + figsize : tuple of float, default=(8, 4) + Figure size in inches for the diagnostic plot. + + Returns + ------- + StrainMapAutocorrelation + ``self``, with :attr:`mask_diffraction` and :attr:`mask_diffraction_inv` set. + """ dp_mean = np.mean(self.dataset.array, axis=(0, 1)) mask_init = dp_mean < threshold mask_init[:, 0] = True @@ -125,6 +257,54 @@ def preprocess( gamma: float = 0.5, **plot_kwargs: Any, ) -> "StrainMapAutocorrelation": + """Transform every pattern into the autocorrelation domain and average it. + + For each diffraction pattern the masked/filled pattern (from + :meth:`diffraction_mask`) is intensity-scaled per ``mode``, Fourier transformed, + and its magnitude accumulated; the mean over all scan positions is stored + (fft-shifted, origin centered) as :attr:`transform`. A display copy rotated by + the detector->scan rotation is stored as :attr:`transform_rotated`. The crystal + periodicity appears as a lattice of peaks about the center, which later steps fit. + + The detector->scan rotation (``q_to_r_rotation_ccw_deg``) and transpose + (``q_transpose``) default to the parent dataset metadata -- the same source used + by the DPC/CoM and :class:`BraggVectors` workflows -- so the strain frame is + consistent across methods; pass them explicitly to override. + + Parameters + ---------- + mode : {"linear", "log", "gamma"}, default="linear" + Intensity scaling applied before the FFT. ``"linear"`` is the Patterson / + autocorrelation (aliases ``"patterson"``, ``"acf"``, ``"autocorrelation"``); + ``"log"`` is the cepstrum, ``log1p(I)`` (aliases ``"cepstrum"``, + ``"cepstral"``); ``"gamma"`` raises intensity to the power ``gamma`` + (aliases ``"power"``, ``"sqrt"``). + q_to_r_rotation_ccw_deg : float, optional + Counter-clockwise detector->scan rotation in degrees for the rotated display + transform. Defaults to ``dataset.metadata["q_to_r_rotation_ccw_deg"]`` if + present, else ``0`` (with a warning). + q_transpose : bool, optional + Whether to transpose the detector axes before rotating the display transform. + Defaults to ``dataset.metadata["q_transpose"]`` if present, else ``False``. + skip : int, optional + If given, subsample the scan by this stride (``array[::skip, ::skip]``) when + building the mean transform -- a fast preview over fewer patterns. + plot_transform : bool, default=True + If ``True``, show the original and rotated mean transforms via + :meth:`plot_transform`. + cropping_factor : float, default=0.25 + Fraction of the transform width/height shown when ``plot_transform`` is + ``True`` (the lattice peaks sit near the center). + gamma : float, default=0.5 + Exponent for ``mode="gamma"`` (ignored otherwise). + **plot_kwargs + Forwarded to :meth:`plot_transform`. + + Returns + ------- + StrainMapAutocorrelation + ``self``, with :attr:`transform` and :attr:`transform_rotated` set. + """ mode_in = mode.strip().lower() if mode_in in {"linear", "patterson", "paterson", "acf", "autocorrelation"}: mode_norm = "linear" @@ -202,7 +382,7 @@ def preprocess( q_to_r_rotation_ccw_deg = 0.0 if q_to_r_rotation_ccw_deg is None else q_to_r_rotation_ccw_deg q_transpose = False if q_transpose is None else q_transpose warnings.warn( - "StrainMapPatterson.preprocess: setting q_to_r_rotation_ccw_deg=0.0 and q_transpose=False.", + "StrainMapAutocorrelation.preprocess: setting q_to_r_rotation_ccw_deg=0.0 and q_transpose=False.", UserWarning, ) @@ -259,6 +439,29 @@ def plot_transform( scalebar_fraction: float = 0.25, **plot_kwargs: Any, ): + """Show the original and rotated mean transform images side by side. + + The color range is set from the brightest lattice peak (the global max of the + radially weighted transform) so the central DC peak does not wash out the panel, + and both panels are cropped to the central ``cropping_factor`` window where the + lattice peaks lie. A scale bar is drawn in real-space units. + + Parameters + ---------- + cropping_factor : float, default=0.25 + Fraction of the full transform width/height shown about the center. + scalebar_fraction : float, default=0.25 + Target scale-bar length as a fraction of the cropped view width (snapped to + a "nice" round value). + **plot_kwargs + Forwarded to :func:`~quantem.core.visualization.show_2d` (overriding the + defaults computed here). + + Returns + ------- + tuple + ``(fig, ax)`` from :func:`~quantem.core.visualization.show_2d`. + """ if self.transform is None or self.transform_rotated is None: raise ValueError("Run preprocess() first to compute transform images.") @@ -306,6 +509,31 @@ def plot_single_transform( scalebar_fraction: float = 0.25, **plot_kwargs: Any, ): + """Show the transform of a single scan position with its fitted lattice vectors. + + Recomputes the transform for the pattern at ``(row, col)`` using the current + ``mode``, and overlays the per-position fitted ``u``/``v`` vectors (from + :meth:`fit_lattice_vectors`) plus any detected peaks. Useful for inspecting the + fit quality at a specific position. + + Parameters + ---------- + row : int, default=0 + Scan row index of the pattern to transform. + col : int, default=0 + Scan column index of the pattern to transform. + cropping_factor : float, default=0.25 + Fraction of the full transform width/height shown about the center. + scalebar_fraction : float, default=0.25 + Target scale-bar length as a fraction of the cropped view width. + **plot_kwargs + Forwarded to :func:`~quantem.core.visualization.show_2d`. + + Returns + ------- + None + Draws the figure; nothing is returned. + """ if self.transform is None or self.transform_rotated is None: raise ValueError("Run preprocess() first to compute transform images.") if self.u_peak_fit is None or self.v_peak_fit is None: @@ -393,6 +621,57 @@ def choose_lattice_vector( cropping_factor: float = 0.25, **plot_kwargs: Any, ) -> "StrainMapAutocorrelation": + """Refine a hand-picked initial lattice basis against the mean transform. + + Takes an approximate basis ``(u, v)`` -- read off the transform plot by eye -- + and refines each vector to the nearest transform peak, storing the result in + :attr:`u` and :attr:`v`. These seed the per-position fit in + :meth:`fit_lattice_vectors`. Vectors are ``(row, col)`` offsets from the + transform center, in transform pixels. + + Parameters + ---------- + u : tuple of float or np.ndarray + Initial first lattice vector ``(d_row, d_col)`` relative to the center. + v : tuple of float or np.ndarray + Initial second lattice vector ``(d_row, d_col)`` relative to the center. + define_in_rotated : bool, default=False + If ``True``, ``u``/``v`` are given in the rotated (display) frame and are + converted back to the raw detector frame before fitting. + refine_gaussian : bool, default=True + If ``True``, refine each peak by a 2D isotropic Gaussian fit; otherwise use + the parabolic sub-pixel estimate only. + refine_dft : bool, default=False + If ``True``, additionally refine by DFT upsampling (uses ``upsample``). + refine_all_peaks : bool, default=False + If ``True``, auto-detect all peaks above ``threshold_percentile`` and fit + the basis to the full set by weighted least squares (storing the detected + peaks/weights for reuse), rather than refining only ``u`` and ``v``. + refine_radius_px : float, default=2.0 + Half-width in pixels of the window used for sub-pixel/Gaussian refinement. + upsample : int, default=16 + DFT upsampling factor used when ``refine_dft=True``. + gaussian_maxfev : int, default=100 + Maximum function evaluations for the Gaussian fit. + threshold_percentile : float, default=0.9975 + Intensity percentile (0--1) above which local maxima are kept as peaks when + ``refine_all_peaks=True``. + min_peak_spacing : float, default=0 + Minimum spacing in pixels between accepted peaks when + ``refine_all_peaks=True`` (0 disables the spacing filter). + plot : bool, default=True + If ``True``, show the transform with the refined ``u``/``v`` overlaid. + cropping_factor : float, default=0.25 + Fraction of the transform shown about the center when ``plot=True``. + **plot_kwargs + Forwarded to :meth:`plot_transform`. + + Returns + ------- + StrainMapAutocorrelation + ``self``, with :attr:`u` and :attr:`v` set (and the detected peaks/weights + when ``refine_all_peaks=True``). + """ if self.transform is None or self.transform_rotated is None: raise ValueError("Run preprocess() first to compute transform images.") @@ -462,6 +741,42 @@ def fit_lattice_vectors( gaussian_maxfev: int = 100, progressbar: bool = True, ) -> "StrainMapAutocorrelation": + """Fit the lattice vectors at every scan position (the heavy step). + + For each scan position the masked/filled pattern is transformed (same ``mode`` + as :meth:`preprocess`) and the lattice basis is refined from the + :meth:`choose_lattice_vector` seed ``(self.u, self.v)``. The fitted vectors are + written to :attr:`u_array`/:attr:`v_array` (shape ``(scan_row, scan_col, 2)``, + row/col components) for :meth:`calculate_strain_map`; the full fit records + (position, amplitude, width, background) are kept in :attr:`u_peak_fit`/ + :attr:`v_peak_fit` (shape ``(scan_row, scan_col, 5)``). + + Parameters + ---------- + refine_gaussian : bool, default=True + If ``True``, refine each peak with a 2D isotropic Gaussian fit; otherwise + use the parabolic sub-pixel estimate only. + refine_dft : bool, default=False + If ``True``, additionally refine by DFT upsampling (uses ``upsample``). + refine_all_peaks : bool, default=False + If ``True``, fit the basis to all peaks detected in + :meth:`choose_lattice_vector` (which must have been called with + ``refine_all_peaks=True``) rather than just ``u`` and ``v``. + refine_radius_px : float, default=2.0 + Half-width in pixels of the window used for sub-pixel/Gaussian refinement. + upsample : int, default=16 + DFT upsampling factor used when ``refine_dft=True``. + gaussian_maxfev : int, default=100 + Maximum function evaluations for each Gaussian fit. + progressbar : bool, default=True + If ``True``, show a tqdm progress bar over the scan positions. + + Returns + ------- + StrainMapAutocorrelation + ``self``, with :attr:`u_array`, :attr:`v_array`, :attr:`u_peak_fit`, and + :attr:`v_peak_fit` set. + """ if self.u is None or self.v is None: raise ValueError("Run choose_lattice_vector() first to set initial lattice vectors (self.u, self.v).") if refine_all_peaks: @@ -552,25 +867,62 @@ def create_mask( max_threshold: float = 0.6, exclusion_radius_fraction: float = 0.1, smooth: bool = True, - ): + ): + """Compute the per-position weight :attr:`mask_weight` from lattice signal. + + Builds a ``(scan_row, scan_col)`` weight in ``[0, 1]`` measuring how much + crystalline signal each position carries, the analogue of + :attr:`BraggVectors.mask_weight`. It is the default reference weighting handed to + :meth:`calculate_strain_map`, so strong, well-fit positions dominate the + reference lattice and weak/vacuum positions are down-weighted. Two estimators are + available, both rescaled by the ``(min_threshold, max_threshold)`` window (values + below ``min`` -> 0, above ``max`` -> 1) and optionally smoothed. + + Parameters + ---------- + use_radial_method : bool, default=False + If ``True``, weight by the total diffracted intensity *outside* a central + disk (radius ``exclusion_radius_fraction`` of the detector width), excluding + the bright unscattered beam. If ``False`` (default), weight by the mean + fitted peak amplitude from :meth:`fit_lattice_vectors` (requires it to have + been run). + min_threshold : float, default=0.4 + Lower edge of the rescaling window (as a fraction of the max signal); weights + at or below this map to 0. + max_threshold : float, default=0.6 + Upper edge of the rescaling window; weights at or above this map to 1. + exclusion_radius_fraction : float, default=0.1 + Central-disk radius (fraction of detector width) excluded by the radial + method. + smooth : bool, default=True + If ``True``, apply a ``sin^2`` easing to the rescaled weight for a smoother + ramp. + + Returns + ------- + StrainMapAutocorrelation + ``self``, with :attr:`mask_weight` set. + """ if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - + scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] - self.mask = np.ones(self.dataset.shape[:2]) - + self.mask_weight = np.ones(self.dataset.shape[:2]) + i0_sum_array = np.empty(shape=(scan_r, scan_c)) if use_radial_method: - center_y, center_x = np.array(self.dataset.shape[:-2]) / 2 + # center / radius are in DETECTOR coordinates (the last two axes) + center_y = self.dataset.shape[-2] / 2.0 + center_x = self.dataset.shape[-1] / 2.0 y, x = np.ogrid[:self.dataset.shape[-2], :self.dataset.shape[-1]] radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] + outside_mask = radius_map > exclusion_radius for r in range(scan_r): for c in range(scan_c): - dp = self.dataset.array[r, c] - outside_mask = radius_map > exclusion_radius + dp = self.dataset.array[r, c] i0_sum_array[r, c] = np.sum(dp[outside_mask]) else: if self.u_peak_fit is None or self.v_peak_fit is None: @@ -578,50 +930,75 @@ def create_mask( u_amplitudes = self.u_peak_fit.array[:, :, 2] v_amplitudes = self.v_peak_fit.array[:, :, 2] i0_sum_array = (u_amplitudes + v_amplitudes) / 2.0 - + max_intensity = np.max(i0_sum_array) if max_intensity == 0: - return np.ones_like(i0_sum_array) - self.mask = i0_sum_array / max_intensity - self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) + self.mask_weight = np.ones_like(i0_sum_array) + return self + self.mask_weight = i0_sum_array / max_intensity + self.mask_weight = np.clip( + (self.mask_weight - min_threshold) / (max_threshold - min_threshold), 0, 1 + ) if smooth: - self.mask = np.sin(np.pi / 2 * self.mask) ** 2 + self.mask_weight = np.sin(np.pi / 2 * self.mask_weight) ** 2 return self - def initialize_strain_class( + def calculate_strain_map( self, u_ref: np.ndarray | None = None, v_ref: np.ndarray | None = None, - )->StrainMap: + mask: np.ndarray | None = None, + ) -> StrainMap: + """Build a :class:`StrainMap` from the fitted per-position lattice vectors. + + Mirrors :meth:`BraggVectors.calculate_strain_map` so the downstream strain cells + (``plot_strain``, ``update_reference``, ``estimate_strain_precision``) are + identical for the correlation and cepstral workflows. Because the cepstral + vectors are real-space, ``real_space=True`` is passed and + :func:`~quantem.diffraction.strain._strain_tensor` yields strain matching the + correlation result on the same data. + + Parameters + ---------- + u_ref : np.ndarray, optional + ``(2,)`` reference for the first lattice vector. Defaults to the median over + the scan inside :class:`StrainMap`. + v_ref : np.ndarray, optional + ``(2,)`` reference for the second lattice vector. Defaults to the median over + the scan inside :class:`StrainMap`. + mask : np.ndarray, optional + ``(scan_row, scan_col)`` per-position weighting used when computing the + reference lattice. Defaults to :attr:`mask_weight` from :meth:`create_mask` + (the lattice signal strength), so strong, well-fit positions dominate the + reference. + + Returns + ------- + StrainMap + A strain map initialized from the fitted lattice vectors. + """ if self.u_array is None or self.v_array is None: - raise RuntimeWarning("Need to run fit_lattice_vectors before initializing strain class") + raise ValueError("Run fit_lattice_vectors() before calculate_strain_map().") if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - default_units = None - default_sampling = None - if hasattr(self.dataset, 'units'): - if isinstance(self.dataset.units, (tuple, list)): - default_units = str(self.dataset.units[0]) - else: - default_units = str(self.dataset.units) - if hasattr(self.dataset, 'sampling'): - if isinstance(self.dataset.sampling, (tuple, list, np.ndarray)): - default_sampling = float(self.dataset.sampling[0]) - else: - default_sampling = float(self.dataset.sampling) + if mask is None: + mask = self.mask_weight + + ds_sampling = float(self.dataset.sampling[0]) + ds_units = str(self.dataset.units[0]) return StrainMap( - u_array = self.u_array, - v_array = self.v_array, - ds_shape = self.dataset.shape, - real_space = self.real_space, - u_ref = u_ref, - v_ref = v_ref, - mask = self.mask, - ds_sampling=default_sampling, - ds_units = default_units, + u_array=self.u_array, + v_array=self.v_array, + ds_shape=tuple(self.dataset.shape), + real_space=self.real_space, + u_ref=u_ref, + v_ref=v_ref, + mask=mask, + ds_sampling=ds_sampling, + ds_units=ds_units, ) def plot_lattice_vectors( @@ -633,6 +1010,37 @@ def plot_lattice_vectors( figsize: tuple[float, float] | None = None, **imshow_kwargs: Any, ): + """Plot the four per-position lattice-vector component maps. + + Shows ``u_row``, ``u_col``, ``v_row``, ``v_col`` from :attr:`u_array`/ + :attr:`v_array` as four panels (optionally with the mean subtracted), on a + shared symmetric color range. Positions whose vectors deviate from the + :meth:`choose_lattice_vector` seed by more than ``max_shift`` are masked out, so + bad fits do not blow up the color scale. A quick diagnostic of fit smoothness + before computing strain. + + Parameters + ---------- + subtract_mean : bool, default=True + If ``True``, subtract the (in-range) mean of each component so deviations + from the average lattice are shown. + max_shift : float, default=1.0 + Maximum allowed deviation (pixels) of a vector from the seed before its + position is masked out of the plot and the color-range statistics. + cmap : str, default="PiYG_r" + Diverging colormap; masked positions are drawn black. + axsize : tuple of float, optional + Per-panel size in inches; defaults to ``(4, 4)`` when ``figsize`` is unset. + figsize : tuple of float, optional + Overall figure size in inches; defaults to ``(4 * axsize[0], axsize[1])``. + **imshow_kwargs + Forwarded to :meth:`matplotlib.axes.Axes.imshow`. + + Returns + ------- + tuple + ``(fig, ax)`` with the four-panel figure. + """ if self.u_array is None or self.v_array is None: raise ValueError("Run fit_lattice_vectors() first to compute u_array and v_array.") if self.u is None or self.v is None: @@ -716,6 +1124,7 @@ def plot_lattice_vectors( def _nice_length_units(target: float) -> float: + """Round ``target`` to the nearest "nice" scale-bar length (1/2/5 x 10^n).""" if not np.isfinite(target) or target <= 0: return 0.0 exp = np.floor(np.log10(target)) @@ -732,6 +1141,10 @@ def _nice_length_units(target: float) -> float: def _apply_center_crop_limits(ax: Any, shape: tuple[int, int], cropping_factor: float) -> None: + """Zoom ``ax`` to the central ``cropping_factor`` fraction of a ``shape`` image. + + Preserves the existing y-axis direction (inverted for image coordinates). + """ if cropping_factor >= 1.0: return if not (0.0 < cropping_factor <= 1.0): @@ -753,6 +1166,7 @@ def _apply_center_crop_limits(ax: Any, shape: tuple[int, int], cropping_factor: def _flatten_axes(ax: Any) -> list[Any]: + """Flatten a matplotlib axes container (array/list/tuple) to a flat list of axes.""" if isinstance(ax, np.ndarray): return list(ax.ravel()) if isinstance(ax, (list, tuple)): @@ -764,6 +1178,11 @@ def _flatten_axes(ax: Any) -> list[Any]: def _raw_vec_to_display(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: bool) -> NDArray: + """Map a raw-detector ``(row, col)`` vector into the rotated display frame. + + Applies the optional axis transpose, then a counter-clockwise rotation of + ``rotation_ccw_deg``. Inverse of :func:`_display_vec_to_raw`. + """ v = np.asarray(vec_rc, dtype=float).reshape(2) dr, dc = v[0], v[1] @@ -780,6 +1199,10 @@ def _raw_vec_to_display(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: def _display_vec_to_raw(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: bool) -> NDArray: + """Map a rotated-display ``(row, col)`` vector back to the raw detector frame. + + Inverse of :func:`_raw_vec_to_display`: undo the rotation, then the transpose. + """ v = np.asarray(vec_rc, dtype=float).reshape(2) dr, dc = v[0], v[1] @@ -797,6 +1220,7 @@ def _display_vec_to_raw(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: def _plot_lattice_vectors(ax: Any, center_rc: tuple[float, float], u_rc: NDArray, v_rc: NDArray) -> None: + """Draw the ``u`` (red) and ``v`` (cyan) lattice vectors from ``center_rc`` on ``ax``.""" r0, c0 = center_rc def _draw(vec: NDArray, label: str, color: tuple[float, float, float]) -> None: @@ -809,6 +1233,7 @@ def _draw(vec: NDArray, label: str, color: tuple[float, float, float]) -> None: _draw(np.asarray(v_rc, dtype=float).reshape(2), "v", (0.0, 0.7, 1.0)) def _plot_peaks(ax: Any, center_rc: tuple[float, float], peaks_plot: NDArray) -> None: + """Mark each detected peak (green dot), offset from ``center_rc``, on ``ax``.""" r0, c0 = center_rc def _draw(vec: NDArray, color: tuple[float, float, float]) -> None: @@ -828,6 +1253,11 @@ def _overlay_lattice_vectors( q_transpose: bool, peaks_plot: NDArray | None = None, ) -> None: + """Overlay lattice vectors on the original (and, if present, rotated) transform axes. + + Draws ``u``/``v`` (and any ``peaks_plot``) on the first axis in raw coordinates, and + on the second axis (if any) in the rotated display frame. + """ axs = _flatten_axes(ax) if not axs: return @@ -846,6 +1276,11 @@ def _overlay_lattice_vectors( def _parabolic_vertex_delta(v_m1: float, v_0: float, v_p1: float) -> float: + """Sub-pixel vertex offset (in ``[-1, 1]``) of a parabola through three samples. + + Given values at offsets ``-1, 0, +1``, returns the offset of the parabola's extremum + from the center sample; ``0`` for a degenerate (flat/non-finite) fit. + """ denom = v_m1 - 2.0 * v_0 + v_p1 if denom == 0 or not np.isfinite(denom): return 0.0 @@ -862,6 +1297,11 @@ def _refine_peak_subpixel( c_guess: float, radius_px: float = 2.0, ) -> tuple[float, float]: + """Refine a peak near ``(r_guess, c_guess)`` to sub-pixel ``(row, col)``. + + Finds the brightest pixel in a ``radius_px`` window, then applies independent + parabolic vertex offsets along each axis. + """ im = np.asarray(im, dtype=float) H, W = im.shape @@ -904,6 +1344,31 @@ def _refine_peak_subpixel_dft( c0: float, upsample: int, ) -> tuple[float, float]: + """Refine a peak location to subpixel precision via local DFT upsampling. + + Uses a matrix-multiply DFT (the Guizar-Sicairos upsampled cross-correlation + trick) to evaluate the image's Fourier interpolant on a fine grid in a small + neighborhood around the initial estimate, then locates the maximum of that + upsampled patch with a 3-point parabolic vertex refinement. This avoids + interpolating the whole image and is accurate to roughly ``1 / upsample`` of a + pixel. + + Parameters + ---------- + im : NDArray + 2D real image (a transform panel) whose peak is being refined. + r0, c0 : float + Initial peak estimate in pixel coordinates (row, column). If ``r0`` or + ``c0`` is a torch tensor it is converted to a Python float. + upsample : int + DFT upsampling factor. Values ``<= 1`` skip refinement and return the + input estimate unchanged; larger values give finer subpixel resolution. + + Returns + ------- + tuple[float, float] + The refined ``(row, column)`` peak location in pixel coordinates. + """ if upsample <= 1: return r0, c0 @@ -955,6 +1420,62 @@ def _refine_lattice_vectors( threshold_percentile: float = 0.9975, min_peak_spacing: float = 0, ) -> tuple[NDArray, NDArray, NDArray, NDArray]: + """Refine the two lattice vectors of a transform panel to subpixel precision. + + Starting from integer-pixel guesses for the ``u`` and ``v`` lattice vectors + (expressed as row/column offsets from the panel center), this locates the + corresponding autocorrelation/cepstral peaks and refines them with up to + three successively finer stages: a 3-point parabolic vertex estimate, an + isotropic 2D Gaussian least-squares fit, and DFT upsampling. When + ``refine_all_peaks`` is set, all bright peaks are detected and the lattice + basis is recovered by an intensity-weighted least-squares fit to the full + peak lattice instead of refining only the two seed vectors. + + Parameters + ---------- + im : NDArray + 2D real transform panel (Patterson/cepstral image) to fit. + u_rc, v_rc : NDArray + Length-2 initial lattice vectors as ``(row, column)`` offsets relative to + the panel center. + radius_px : float, optional + Half-width (in pixels) of the fitting window used for the Gaussian fit. + Default 2.0. + refine_gaussian : bool, optional + If True (default), refine each peak with an isotropic 2D Gaussian fit. + refine_dft : bool, optional + If True, follow the Gaussian fit with DFT-upsampled refinement + (requires ``upsample > 1``). Default False. + refine_all_peaks : bool, optional + If True, detect every bright peak and solve a weighted least-squares fit + for the lattice basis rather than refining only ``u_rc`` and ``v_rc``. + Default False. + peaks : NDArray or None, optional + Precomputed peak positions (row/col offsets from center) to use when + ``refine_all_peaks`` is set; if None they are detected automatically. + weights : NDArray or None, optional + Precomputed peak weights paired with ``peaks``; if None they are derived + from peak amplitudes. + upsample : int, optional + DFT upsampling factor for ``refine_dft``. Default 16. + maxfev : int, optional + Maximum function evaluations for the Gaussian ``curve_fit``. Default 100. + threshold_percentile : float, optional + Fractional intensity percentile (0-1) used as the peak-detection + threshold when auto-detecting peaks. Default 0.9975. + min_peak_spacing : float, optional + Minimum allowed spacing (in pixels) between detected peaks; ``0`` (default) + disables the spacing filter. + + Returns + ------- + tuple[NDArray, NDArray, NDArray, NDArray] + ``(u_result, v_result, pts, weights)``. ``u_result`` and ``v_result`` are + length-5 arrays ``(row_offset, col_offset, amplitude, sigma, background)`` + giving the refined lattice vectors relative to the panel center. ``pts`` + and ``weights`` are the detected peak positions and their normalized + weights when ``refine_all_peaks`` is True, otherwise both are ``None``. + """ from scipy.optimize import curve_fit im = np.asarray(im, dtype=float) diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index 609ef6d49..310084af4 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -166,35 +166,53 @@ def _roi_compose(norm_vals, color_cm): bold=scalebar_config.bold, ) + cb_size = 0.02 + cb_pad = 0.02 + + def _finalize_layout(): + # set_aspect("equal") only resizes/recenters each panel at draw time, so + # get_position() before a draw returns stale boxes -- placing the colorbars + # and g-vector compass off those boxes then spills them off the figure. + # Settle the layout cheaply first so every box read below is the real one. + try: + fig.draw_without_rendering() + except AttributeError: # matplotlib < 3.5 + fig.canvas.draw() + if is_horizontal: - fig.subplots_adjust(left=0.04, right=0.98, top=0.90, bottom=0.16, wspace=0.05) + # Reserve a bottom band wide enough for the colorbar + its tick labels and + # title (fontsize 16) and a right band for the rotation-panel gap; widen the + # right band when the g-vector compass is drawn in it. These keep the figure + # usable when saved "as is" (no bbox_inches='tight'). + right = 0.78 if plot_gvecs else 0.93 + fig.subplots_adjust(left=0.04, right=right, top=0.88, bottom=0.24, wspace=0.05) if plot_rotation: + # nudge the rotation panel right for a visual gap from the strain panels; + # 0.03 stays inside the reserved right band so nothing is clipped. pos3 = ax[3].get_position() ax[3].set_position([pos3.x0 + 0.03, pos3.y0, pos3.width, pos3.height]) + _finalize_layout() cb_orientation = "horizontal" - cb_size = 0.02 - cb_pad = 0.02 - b0 = ax[0].get_position() b2 = ax[2].get_position() - strain_cb_pos = [b0.x0, b0.y0 - cb_pad - cb_size, b2.x1 - b0.x0, cb_size] + cb_y = b2.y0 - cb_pad - cb_size + strain_cb_pos = [b0.x0, cb_y, b2.x1 - b0.x0, cb_size] if plot_rotation: b3 = ax[3].get_position() - rot_cb_pos = [b3.x0, b0.y0 - cb_pad - cb_size, b3.x1 - b3.x0, cb_size] + rot_cb_pos = [b3.x0, cb_y, b3.x1 - b3.x0, cb_size] last_pos = b3 else: rot_cb_pos = None last_pos = b2 else: - fig.subplots_adjust(left=0.04, right=0.80, top=0.98, bottom=0.04, hspace=0.15) + # Top band for the panel titles, right band for the vertical colorbars + labels. + fig.subplots_adjust(left=0.04, right=0.80, top=0.92, bottom=0.06, hspace=0.15) + _finalize_layout() cb_orientation = "vertical" - cb_size = 0.02 - cb_pad = 0.02 - b0 = ax[0].get_position() b2 = ax[2].get_position() strain_cb_pos = [b0.x1 + cb_pad, b2.y0, cb_size, b0.y1 - b2.y0] @@ -229,16 +247,16 @@ def _roi_compose(norm_vals, color_cm): print("Warning: u_ref and v_ref not found. Call fit_strain() first.") return fig, ax + # The compass goes in the reserved margin beside the last panel; clamp its + # right edge to 0.99 so it never spills off the figure when saved "as is". if is_horizontal: - ref_width = last_pos.width * 0.8 - ref_left = last_pos.x1 - 0.035 + ref_left = last_pos.x1 + 0.005 + ref_width = min(last_pos.width, 0.99 - ref_left) ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) else: - ref_height = last_pos.height * 0.5 - ref_bottom = last_pos.y0 - ref_height - 0.05 - ref_width = last_pos.width * 0.8 - ref_left = last_pos.x0 + (last_pos.width - ref_width) / 2 - ref_ax = fig.add_axes([ref_left, ref_bottom, ref_width, ref_height]) + ref_left = min(last_pos.x1 + 0.18, 0.74) + ref_width = min(last_pos.width, 0.99 - ref_left) + ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) ref_ax.set_xlim(-1.5, 1.5) ref_ax.set_ylim(-1.5, 1.5) From d21b50ad69d7039a6239fb3669d014d68aed607c Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 1 Jun 2026 15:01:54 +0200 Subject: [PATCH 083/113] updates to cepstral pipeline --- src/quantem/diffraction/bragg_vectors.py | 25 +- .../bragg_vectors_visualization.py | 22 +- src/quantem/diffraction/strain.py | 94 ++- .../diffraction/strain_autocorrelation.py | 624 ++++++++++++++---- 4 files changed, 602 insertions(+), 163 deletions(-) diff --git a/src/quantem/diffraction/bragg_vectors.py b/src/quantem/diffraction/bragg_vectors.py index aceb19108..fccbdaf8c 100644 --- a/src/quantem/diffraction/bragg_vectors.py +++ b/src/quantem/diffraction/bragg_vectors.py @@ -959,13 +959,14 @@ def show_template( position : tuple of int, default=(0, 0) ``(row, col)`` scan position whose correlation map is shown. crop_factor : float, optional - If given, zoom all three panels to a square window of half-width - ``crop_factor * radius`` about the pattern center, where ``radius`` is - the central-beam radius (the synthetic template radius if known, else - estimated from the mean diffraction pattern). For example, - ``crop_factor=2.0`` shows two beam radii either side of the center. The - window is clamped to the detector, so a large factor shows the full - image. ``None`` (default) shows the full panels. + If given, zoom to a square window of half-width ``crop_factor * radius`` + about the central-beam center, where ``radius`` is the central-beam + radius (the synthetic template radius if known, else estimated from the + mean diffraction pattern). The mean-diffraction and correlation panels are + centered on the beam; the template panel on its own (fftshifted) center. + For example, ``crop_factor=2.0`` shows two beam radii either side of the + center. The window is clamped to the detector, so a large factor shows the + full image. ``None`` (default) shows the full panels. returnfig : bool, default=False If ``True``, return the ``(fig, ax)`` for further customization. **kwargs @@ -984,11 +985,15 @@ def show_template( crop = None if crop_factor is not None: - H, W = int(self.dataset.shape[-2]), int(self.dataset.shape[-1]) + # Center the mean-diffraction and correlation panels on the actual + # central-beam position rather than the geometric center (H//2, W//2): + # the unscattered beam -- and the correlation peak that matches it -- are + # generally offset by a few pixels from the detector center. + center, radius_est = estimate_central_beam(dp_mean) radius = self.metadata.get("template", {}).get("radius") if radius is None: - _, radius = estimate_central_beam(dp_mean) - crop = (H // 2, W // 2, float(crop_factor) * float(radius)) + radius = radius_est + crop = (float(center[0]), float(center[1]), float(crop_factor) * float(radius)) fig, ax = plot_template( dp_mean, diff --git a/src/quantem/diffraction/bragg_vectors_visualization.py b/src/quantem/diffraction/bragg_vectors_visualization.py index 891f2e722..047927bcc 100644 --- a/src/quantem/diffraction/bragg_vectors_visualization.py +++ b/src/quantem/diffraction/bragg_vectors_visualization.py @@ -26,10 +26,13 @@ def plot_template( position : tuple of int ``(row, col)`` scan position the correlation map was computed at. crop : tuple of float, optional - ``(center_row, center_col, half_width)`` zoom window (in pixels) applied to - all three panels. The view spans ``half_width`` either side of the center, - clamped to each image's bounds, so an over-large ``half_width`` just shows - the full image. ``None`` (default) shows the full panels. + ``(center_row, center_col, half_width)`` zoom window (in pixels). The mean + diffraction and correlation panels are centered on ``(center_row, + center_col)`` -- the central-beam position -- while the template panel is + centered on its own array center (it is displayed fftshifted to there). The + view spans ``half_width`` either side of the center, clamped to each image's + bounds, so an over-large ``half_width`` just shows the full image. ``None`` + (default) shows the full panels. figsize : tuple of float, default=(13, 4) Figure size in inches. @@ -50,10 +53,15 @@ def plot_template( a.set_yticks([]) if crop is not None: cr, cc, hw = float(crop[0]), float(crop[1]), float(crop[2]) - for a, img in zip(ax, (dp_mean, template, corr_map)): + th, tw = template.shape[:2] + # The mean-diffraction beam and the correlation peak sit at the beam center + # (cr, cc); the displayed template is fftshifted to its own array center, so + # zoom that panel about its center instead. + panel_centers = ((cr, cc), (th / 2.0, tw / 2.0), (cr, cc)) + for a, img, (ecr, ecc) in zip(ax, (dp_mean, template, corr_map), panel_centers): h, w = img.shape[:2] - a.set_xlim(max(cc - hw, -0.5), min(cc + hw, w - 0.5)) - a.set_ylim(min(cr + hw, h - 0.5), max(cr - hw, -0.5)) + a.set_xlim(max(ecc - hw, -0.5), min(ecc + hw, w - 0.5)) + a.set_ylim(min(ecr + hw, h - 0.5), max(ecr - hw, -0.5)) fig.tight_layout() return fig, ax diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index 649746c1d..637fb4d87 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -92,8 +92,21 @@ def __init__( self.ds_sampling = 1.0 if ds_sampling is None else ds_sampling self.ds_units = "pixels" if ds_units is None else ds_units - self.mask = np.ones(ds_shape[:2]) if mask is None else mask - self.mask = (self.mask - np.min(self.mask)) / np.max(self.mask) + # Per-position weighting / ROI in [0, 1]. The mask producers + # (BraggVectors.fit_lattice, StrainMapAutocorrelation.create_mask) already emit a + # [0, 1] weight, so a well-formed mask is taken as-is: re-normalizing it here + # would collide with that scaling -- a near-constant mask (e.g. the radial + # cepstral weight) would be squashed to ~0 and blank the strain display. Only a + # mask that falls outside [0, 1] (e.g. a raw-intensity ROI) is rescaled, and a + # constant / empty / all-NaN mask falls back to uniform full weight. + m = np.ones(ds_shape[:2], dtype=float) if mask is None else np.asarray(mask, dtype=float) + m_lo = np.nanmin(m) + m_hi = np.nanmax(m) + if not (np.isfinite(m_lo) and np.isfinite(m_hi)) or m_hi <= m_lo: + m = np.ones_like(m) + elif m_lo < 0.0 or m_hi > 1.0: + m = (m - m_lo) / (m_hi - m_lo) + self.mask = m # user-supplied reference vectors persist across re-fits (None = use median) self._u_ref_fixed = None if u_ref is None else np.asarray(u_ref, dtype=float) @@ -434,13 +447,17 @@ def estimate_strain_precision( component : {"combined","e_uu","e_vv","e_uv","rotation"}, default="combined" Which error distribution to histogram. bins : int, default=50 - Number of histogram bins (or a sequence of bin edges). + Number of histogram bins, or a sequence of explicit bin edges. With a + bin *count* and no ``bounds``, the range defaults to ``[0, weighted 99th + percentile]`` of the trusted deviations -- robust to the heavy outlier + tail, which otherwise sets the range to its max and crushes the bulk into + the first bin. Passing explicit edges (or ``bounds``) overrides this. bounds : tuple of float, optional ``(low, high)`` histogram range in display units (percent for strain, - degrees for rotation). Fix it to compare datasets on the same axis. Values - outside the range are left out of the bars (no overflow spike); the median - is computed from all trusted positions regardless, and - ``out_of_range_fraction`` records how much was off-range. + degrees for rotation). Fix it to compare datasets on the same axis, or to + see the full tail. Values outside the range are left out of the bars (no + overflow spike); the median is computed from all trusted positions + regardless, and ``out_of_range_fraction`` records how much was off-range. plot : bool, default=True If ``True``, draw the weighted precision histogram. returnfig : bool, default=False @@ -531,6 +548,17 @@ def _weighted_median(err_native: np.ndarray, factor: float) -> float: use = np.isfinite(e) & valid # trusted positions only, consistent with median e_f = e[use] w_f = scaled[use] + # Default histogram range: a robust weighted upper percentile, NOT the raw + # max. A handful of bad-fit positions can reach tens of percent; used as the + # range they crush the entire bulk into the first bin and leave the rest of + # the axis empty (a spurious "spike at 0" plus a far outlier spike). Capping + # at the weighted 99th percentile keeps the common error values readable; the + # few positions past it spill into out_of_range_fraction (reported, not + # drawn). An explicit `bounds`, or passing bin EDGES as `bins`, overrides it. + if bounds is None and np.ndim(bins) == 0 and e_f.size and float(w_f.sum()) > 0: + hi_default = _weighted_quantile(e_f, w_f, 0.99) + if np.isfinite(hi_default) and hi_default > 0: + bounds = (0.0, hi_default) edges = np.histogram_bin_edges(e_f, bins=bins, range=bounds) lo, hi = float(edges[0]), float(edges[-1]) wtot = float(w_f.sum()) @@ -662,7 +690,18 @@ def _reference_lattice( mask: np.ndarray | None = None, strain_mask: np.ndarray | None = None, ) -> tuple[np.ndarray, np.ndarray]: - """Median reference lattice vectors over an ROI / mask, else the global median. + """Weighted-median reference lattice vectors, else the global median. + + The reference is the per-component **weighted median** of the lattice vectors. + Weights come from ``strain_mask`` if given, else the continuous ``mask`` + (the ``[0, 1]`` per-position weight from :meth:`create_mask` / ``fit_lattice``): + strong, well-indexed positions dominate the reference and weak / vacuum / bad-fit + positions are down-weighted. A boolean ROI (weights in ``{0, 1}``) reduces to the + plain median over the selected positions, so an explicit ``strain_mask`` behaves + as before. The weighted median (not ``mask == 1``) is used because a continuous + weight rarely hits *exactly* 1 -- the old exact-equality test collapsed a min-max + normalized mask to its single global-max position and made the reference one + arbitrary pixel. Parameters ---------- @@ -671,10 +710,10 @@ def _reference_lattice( v_array : np.ndarray Per-position second lattice vector, shape ``(scan_row, scan_col, 2)``. mask : np.ndarray, optional - ``(scan_row, scan_col)`` ROI; positions equal to 1 are included. Used when - ``strain_mask`` is not given. + ``(scan_row, scan_col)`` per-position weight in ``[0, 1]``. Used as the median + weights when ``strain_mask`` is not given. strain_mask : np.ndarray, optional - ``(scan_row, scan_col)`` ROI taking precedence over ``mask``. + ``(scan_row, scan_col)`` ROI / weight taking precedence over ``mask``. Returns ------- @@ -682,20 +721,29 @@ def _reference_lattice( ``(u_ref, v_ref)``, each a length-2 reference vector. """ if strain_mask is not None: - m = np.asarray(strain_mask == 1, dtype=bool) + w = np.asarray(strain_mask, dtype=float).reshape(-1) elif mask is not None: - m = np.asarray(mask == 1, dtype=bool) + w = np.asarray(mask, dtype=float).reshape(-1) else: - m = None - - # nan-median: positions fit_lattice could not fit are NaN and must be ignored, - # otherwise the reference (and hence the whole strain map) collapses to NaN. - if m is None or not m.any(): - u_ref = np.nanmedian(u_array.reshape(-1, 2), axis=0) - v_ref = np.nanmedian(v_array.reshape(-1, 2), axis=0) - else: - u_ref = np.array((np.nanmedian(u_array[m, 0]), np.nanmedian(u_array[m, 1])), dtype=float) - v_ref = np.array((np.nanmedian(v_array[m, 0]), np.nanmedian(v_array[m, 1])), dtype=float) + w = None + + u_flat = u_array.reshape(-1, 2) + v_flat = v_array.reshape(-1, 2) + + def _wmed(vals: np.ndarray) -> float: + # weighted median over finite, positively-weighted positions; positions + # fit_lattice could not fit are NaN and must be dropped, else the reference + # (and the whole strain map) collapses to NaN. Falls back to the unweighted + # nan-median when no weight is given or none survives. + finite = np.isfinite(vals) + ww = np.ones_like(vals) if w is None else w + use = finite & (ww > 0) + if not use.any(): + return float(np.nanmedian(vals)) if finite.any() else float("nan") + return _weighted_quantile(vals[use], ww[use], 0.5) + + u_ref = np.array((_wmed(u_flat[:, 0]), _wmed(u_flat[:, 1])), dtype=float) + v_ref = np.array((_wmed(v_flat[:, 0]), _wmed(v_flat[:, 1])), dtype=float) return u_ref, v_ref diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 61c0c1daf..c65e08c4b 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -177,6 +177,7 @@ def from_array(cls, array: NDArray, *, name: str = "strain_map_autocorrelation") def diffraction_mask( self, threshold=None, + threshold_percentile=50.0, edge_blend=64.0, plot_mask=True, figsize=(8, 4), @@ -195,9 +196,14 @@ def diffraction_mask( Parameters ---------- threshold : float, optional - Mean-intensity level below which detector pixels are masked out. Required; - choose it from the mean diffraction pattern (e.g. just above the vacuum - level). + Absolute mean-intensity level below which detector pixels are masked out. If + ``None`` (default), the level is taken from ``threshold_percentile`` of the + mean diffraction pattern, so no manual intensity value is needed. + threshold_percentile : float, default=50.0 + Percentile (``0``-``100``) of the mean diffraction pattern used to set the + masking level when ``threshold`` is ``None``. The default keeps the brighter + half of the detector (disks and central beam) and masks the dimmer vacuum; + raise it to mask more aggressively. Ignored when ``threshold`` is given. edge_blend : float, default=64.0 Feather width in pixels of the raised-cosine taper between kept and masked regions; larger values give a softer transition. @@ -213,6 +219,8 @@ def diffraction_mask( ``self``, with :attr:`mask_diffraction` and :attr:`mask_diffraction_inv` set. """ dp_mean = np.mean(self.dataset.array, axis=(0, 1)) + if threshold is None: + threshold = np.percentile(dp_mean, threshold_percentile) mask_init = dp_mean < threshold mask_init[:, 0] = True mask_init[0, :] = True @@ -740,6 +748,7 @@ def fit_lattice_vectors( upsample: int = 16, gaussian_maxfev: int = 100, progressbar: bool = True, + device: str = "cpu", ) -> "StrainMapAutocorrelation": """Fit the lattice vectors at every scan position (the heavy step). @@ -751,6 +760,19 @@ def fit_lattice_vectors( (position, amplitude, width, background) are kept in :attr:`u_peak_fit`/ :attr:`v_peak_fit` (shape ``(scan_row, scan_col, 5)``). + Whenever ``refine_dft=False`` (including ``refine_all_peaks=True``) the transforms + and the isotropic-Gaussian peak fits are batched across scan positions on + ``device`` (the analogue of the correlation pipeline's + :func:`~quantem.diffraction.disk_detection.detect_disks_batch`): the Gaussian fit + is a vectorized Levenberg-Marquardt solve rather than a per-position + ``scipy.optimize.curve_fit``. For ``refine_all_peaks=True`` every detected peak is + batch-refined across the stack and the basis is solved per position by weighted + least squares -- avoiding the ``n_positions x n_peaks`` ``curve_fit`` calls of the + old loop. This removes the dominant per-call overhead and reproduces the + per-position result to ~1e-6 px, so the fitted vectors are unchanged while the + step runs far faster (and faster still on a GPU). Only ``refine_dft=True`` falls + back to the per-position path. + Parameters ---------- refine_gaussian : bool, default=True @@ -800,56 +822,73 @@ def fit_lattice_vectors( self.u_array = np.zeros((scan_r, scan_c, 2)) self.v_array = np.zeros((scan_r, scan_c, 2)) - mode = self.metadata.get("mode", "linear").lower() - if mode == "gamma": - g = self.metadata["gamma"] - - it = np.ndindex(scan_r, scan_c) - if progressbar: - try: - from tqdm.auto import tqdm # type: ignore - - it = tqdm(it, total=scan_r * scan_c, desc="fit_lattice_vectors", leave=True) - except Exception: - pass - u0 = np.asarray(self.u, dtype=float).reshape(2) v0 = np.asarray(self.v, dtype=float).reshape(2) - - for r, c in it: - dp = self.dataset.array[r, c] * self.mask_diffraction + self.mask_diffraction_inv - - if mode == "linear": - im = np.fft.fftshift(np.abs(np.fft.fft2(dp))) - elif mode == "log": - im = np.fft.fftshift(np.abs(np.fft.fft2(np.log1p(dp)))) - elif mode == "gamma": - im = np.fft.fftshift(np.abs(np.fft.fft2(np.power(np.clip(dp, 0.0, None), g)))) - else: - raise ValueError("metadata['mode'] must be 'linear', 'log', or 'gamma'") - - u_fit_abs, v_fit_abs, _, _ = _refine_lattice_vectors( - im, - u_rc=u0, - v_rc=v0, - radius_px=refine_radius_px, + if not refine_dft: + # Fast path: batch the transforms and isotropic-Gaussian fits across scan + # positions on `device` (per-position scipy.optimize.curve_fit -> vectorized + # LM). Covers both the 2-vector fit and the all-peaks basis fit; reproduces + # the per-position result to ~1e-6 px. Only DFT upsampling still falls back. + self._fit_lattice_vectors_batched( + u0=u0, + v0=v0, refine_gaussian=refine_gaussian, - refine_dft=refine_dft, + refine_radius_px=refine_radius_px, + device=device, + progressbar=progressbar, refine_all_peaks=refine_all_peaks, - peaks=self.mean_img_peaks, - weights=self.mean_img_weights, - upsample=upsample, - maxfev=gaussian_maxfev, + peaks=self.mean_img_peaks if refine_all_peaks else None, + weights=self.mean_img_weights if refine_all_peaks else None, ) - - self.u_peak_fit.array[r, c, :] = u_fit_abs - self.v_peak_fit.array[r, c, :] = v_fit_abs - - self.u_array[r, c, 0] = u_fit_abs[0] - self.u_array[r, c, 1] = u_fit_abs[1] - self.v_array[r, c, 0] = v_fit_abs[0] - self.v_array[r, c, 1] = v_fit_abs[1] + else: + # Per-position fallback for DFT upsampling (refine_dft=True). + mode = self.metadata.get("mode", "linear").lower() + if mode == "gamma": + g = self.metadata["gamma"] + + it = np.ndindex(scan_r, scan_c) + if progressbar: + try: + from tqdm.auto import tqdm # type: ignore + + it = tqdm(it, total=scan_r * scan_c, desc="fit_lattice_vectors", leave=True) + except Exception: + pass + + for r, c in it: + dp = self.dataset.array[r, c] * self.mask_diffraction + self.mask_diffraction_inv + + if mode == "linear": + im = np.fft.fftshift(np.abs(np.fft.fft2(dp))) + elif mode == "log": + im = np.fft.fftshift(np.abs(np.fft.fft2(np.log1p(dp)))) + elif mode == "gamma": + im = np.fft.fftshift(np.abs(np.fft.fft2(np.power(np.clip(dp, 0.0, None), g)))) + else: + raise ValueError("metadata['mode'] must be 'linear', 'log', or 'gamma'") + + u_fit_abs, v_fit_abs, _, _ = _refine_lattice_vectors( + im, + u_rc=u0, + v_rc=v0, + radius_px=refine_radius_px, + refine_gaussian=refine_gaussian, + refine_dft=refine_dft, + refine_all_peaks=refine_all_peaks, + peaks=self.mean_img_peaks, + weights=self.mean_img_weights, + upsample=upsample, + maxfev=gaussian_maxfev, + ) + + self.u_peak_fit.array[r, c, :] = u_fit_abs + self.v_peak_fit.array[r, c, :] = v_fit_abs + + self.u_array[r, c, 0] = u_fit_abs[0] + self.u_array[r, c, 1] = u_fit_abs[1] + self.v_array[r, c, 0] = v_fit_abs[0] + self.v_array[r, c, 1] = v_fit_abs[1] self.metadata["fit_refine_gaussian"] = refine_gaussian self.metadata["fit_refine_dft"] = refine_dft @@ -858,45 +897,225 @@ def fit_lattice_vectors( self.metadata["fit_upsample"] = upsample self.metadata["fit_gaussian_maxfev"] = gaussian_maxfev + # Populate the per-position weight directly from the fitted peak amplitudes, + # mirroring the correlation pipeline where BraggVectors.fit_lattice emits + # mask_weight. create_mask() is therefore optional -- call it only to plot the + # weight map, recompute, or switch to the radial estimator. + self.mask_weight = self._amplitude_mask_weight() + return self + def _fit_lattice_vectors_batched( + self, + *, + u0: NDArray, + v0: NDArray, + refine_gaussian: bool, + refine_radius_px: float, + device: str, + progressbar: bool, + refine_all_peaks: bool = False, + peaks: NDArray | None = None, + weights: NDArray | None = None, + ) -> None: + """Batched torch implementation of the non-DFT :meth:`fit_lattice_vectors` path. + + The transform (mask/fill -> ``mode`` -> ``|FFT|`` -> fftshift) and the + parabolic/isotropic-Gaussian peak refinement are evaluated for a stack of scan + positions at once on ``device``, the analogue of the correlation pipeline's + :func:`~quantem.diffraction.disk_detection.detect_disks_batch`. This reproduces + :func:`_refine_lattice_vectors` (with ``refine_dft=False``) to ~1e-6 px while + removing the per-position ``scipy.optimize.curve_fit`` overhead. Results are + written into :attr:`u_array`/:attr:`v_array` and :attr:`u_peak_fit`/ + :attr:`v_peak_fit`. + + With ``refine_all_peaks=False`` only the two seed vectors ``u0``/``v0`` are + refined per position. With ``refine_all_peaks=True`` every peak in ``peaks`` (the + basis detected by :meth:`choose_lattice_vector`, weighted by ``weights``) is + batch-refined across the stack and the basis is recovered per position by the + same intensity-weighted least-squares fit as the per-position all-peaks branch -- + so that path is batched too rather than looping ``scipy.optimize.curve_fit`` over + ``n_positions x n_peaks``. + """ + scan_r, scan_c, H, W = self.dataset.array.shape + n_pos = scan_r * scan_c + rcent, ccent = H // 2, W // 2 + + mode = self.metadata.get("mode", "linear").lower() + if mode not in ("linear", "log", "gamma"): + raise ValueError("metadata['mode'] must be 'linear', 'log', or 'gamma'") + gamma = float(self.metadata["gamma"]) if mode == "gamma" else None + + dev = torch.device(device) + mask = torch.as_tensor(self.mask_diffraction, dtype=torch.float64, device=dev) + mask_inv = torch.as_tensor(self.mask_diffraction_inv, dtype=torch.float64, device=dev) + + if refine_all_peaks: + # Precompute the fixed pieces of the per-position all-peaks fit (these do not + # change across scan positions): the detected peak seeds, their normalized + # weights, and the seed basis used to assign integer (h, k) indices. + peaks_arr = np.asarray(peaks, dtype=float).reshape(-1, 2) + w = np.asarray(weights, dtype=float).reshape(-1) + wsum = float(w.sum()) + w_norm = w / wsum if wsum != 0 else np.full(w.shape, 1.0 / max(1, w.size)) + sqrt_w = np.sqrt(w_norm)[:, None] + A_seed = np.column_stack((u0, v0)) # (2, 2) + n_pk = peaks_arr.shape[0] + + # Match the correlation chunking heuristic (see BraggVectors._detect_positions). + batch_size = int(min(1024, max(1, 16_000_000 // (H * W)))) + + starts = range(0, n_pos, batch_size) + if progressbar: + try: + from tqdm.auto import tqdm # type: ignore + + bar = tqdm(total=n_pos, desc="fit_lattice_vectors", leave=True) + except Exception: + bar = None + else: + bar = None + + for start in starts: + stop = min(start + batch_size, n_pos) + idxs = [(idx // scan_c, idx % scan_c) for idx in range(start, stop)] + + dps = torch.stack( + [ + torch.as_tensor( + np.asarray(self.dataset.array[r, c]), dtype=torch.float64, device=dev + ) + for r, c in idxs + ], + dim=0, + ) + dpm = dps * mask + mask_inv + if mode == "linear": + tr = dpm + elif mode == "log": + tr = torch.log1p(dpm) + else: # gamma + tr = dpm.clamp(min=0.0).pow(gamma) + ims = torch.fft.fftshift(torch.fft.fft2(tr).abs(), dim=(-2, -1)) + + if refine_all_peaks: + # Batch-refine each detected peak across the stack (one vectorized LM + # solve per peak), then recover the basis per position with the same + # weighted least squares as _refine_lattice_vectors' all-peaks branch. + pts_all = np.empty((len(idxs), n_pk, 2), dtype=float) + for j in range(n_pk): + rj = _refine_peaks_batched( + ims, peaks_arr[j], radius_px=refine_radius_px, + refine_gaussian=refine_gaussian, + ).cpu().numpy() + pts_all[:, j, :] = rj[:, :2] + ims_np = ims.cpu().numpy() + for k, (r, c) in enumerate(idxs): + pts = pts_all[k] # (n_pk, 2) row/col offsets from center + ab = np.round(np.linalg.lstsq(A_seed, pts.T, rcond=None)[0]).T + M = np.ones((n_pk, 3)) + M[:, :2] = ab # integer (h, k) indices + constant (center) column + uvc = np.linalg.lstsq(M * sqrt_w, pts * sqrt_w, rcond=None)[0] # (3, 2) + u_ref, v_ref = uvc[0], uvc[1] + amp_u = _parabolic_peak_rc_amp(ims_np[k], rcent + u_ref[0], ccent + u_ref[1])[2] + amp_v = _parabolic_peak_rc_amp(ims_np[k], rcent + v_ref[0], ccent + v_ref[1])[2] + self.u_peak_fit.array[r, c, :] = (u_ref[0], u_ref[1], amp_u, 0.0, 0.0) + self.v_peak_fit.array[r, c, :] = (v_ref[0], v_ref[1], amp_v, 0.0, 0.0) + self.u_array[r, c, 0] = u_ref[0] + self.u_array[r, c, 1] = u_ref[1] + self.v_array[r, c, 0] = v_ref[0] + self.v_array[r, c, 1] = v_ref[1] + else: + u_np = _refine_peaks_batched( + ims, u0, radius_px=refine_radius_px, refine_gaussian=refine_gaussian + ).cpu().numpy() + v_np = _refine_peaks_batched( + ims, v0, radius_px=refine_radius_px, refine_gaussian=refine_gaussian + ).cpu().numpy() + for k, (r, c) in enumerate(idxs): + self.u_peak_fit.array[r, c, :] = u_np[k] + self.v_peak_fit.array[r, c, :] = v_np[k] + self.u_array[r, c, 0] = u_np[k, 0] + self.u_array[r, c, 1] = u_np[k, 1] + self.v_array[r, c, 0] = v_np[k, 0] + self.v_array[r, c, 1] = v_np[k, 1] + + if bar is not None: + bar.update(len(idxs)) + + if bar is not None: + bar.close() + + def _amplitude_mask_weight(self) -> np.ndarray: + """Per-position weight from the fitted u/v peak amplitudes, min-max to ``[0, 1]``. + + The mean of the two fitted lattice-peak amplitudes is the cepstral order + parameter (how much crystalline signal a position carries). It is min-max + normalized to ``[0, 1]`` with no contrast windowing -- the honest weight; set + display contrast later via :meth:`StrainMap.plot_strain`'s ``mask_range``. + Degenerate input (constant / non-finite / amplitudes unavailable) falls back to + uniform full weight. + """ + scan_r = self.dataset.shape[0] + scan_c = self.dataset.shape[1] + if self.u_peak_fit is None or self.v_peak_fit is None: + return np.ones((scan_r, scan_c)) + signal = (self.u_peak_fit.array[:, :, 2] + self.v_peak_fit.array[:, :, 2]) / 2.0 + lo = np.nanmin(signal) + hi = np.nanmax(signal) + if np.isfinite(lo) and np.isfinite(hi) and hi > lo: + return (signal - lo) / (hi - lo) + return np.ones((scan_r, scan_c)) + def create_mask( self, use_radial_method: bool = False, - min_threshold: float = 0.4, - max_threshold: float = 0.6, exclusion_radius_fraction: float = 0.1, - smooth: bool = True, + plot: bool = True, + figsize: tuple[float, float] = (5, 4), ): - """Compute the per-position weight :attr:`mask_weight` from lattice signal. + """(Re)compute the per-position weight :attr:`mask_weight` from lattice signal. + + Usually **optional**: :meth:`fit_lattice_vectors` already populates + :attr:`mask_weight` from the fitted peak amplitudes (mirroring the correlation + pipeline, where ``BraggVectors.fit_lattice`` emits ``mask_weight``). Call this + only to plot the weight map, to recompute it, or to switch to the radial + estimator. Builds a ``(scan_row, scan_col)`` weight in ``[0, 1]`` measuring how much crystalline signal each position carries, the analogue of :attr:`BraggVectors.mask_weight`. It is the default reference weighting handed to :meth:`calculate_strain_map`, so strong, well-fit positions dominate the - reference lattice and weak/vacuum positions are down-weighted. Two estimators are - available, both rescaled by the ``(min_threshold, max_threshold)`` window (values - below ``min`` -> 0, above ``max`` -> 1) and optionally smoothed. + reference lattice and weak/vacuum positions are down-weighted. + + The raw signal is min-max normalized to ``[0, 1]`` with **no** contrast windowing + or smoothing: this is the honest per-position order parameter. Set the display + contrast later, in one place, via :meth:`StrainMap.plot_strain`'s ``mask_range`` + argument (e.g. ``mask_range=(0.6, 0.8)``) -- weights at/below ``low`` render + black, at/above ``high`` render full color. (Min-max is sensitive to a few very + bright positions, which compress the bulk toward 0; pick ``mask_range`` to match + where the bulk actually sits -- the weight-map plot here shows it.) Parameters ---------- use_radial_method : bool, default=False If ``True``, weight by the total diffracted intensity *outside* a central disk (radius ``exclusion_radius_fraction`` of the detector width), excluding - the bright unscattered beam. If ``False`` (default), weight by the mean - fitted peak amplitude from :meth:`fit_lattice_vectors` (requires it to have - been run). - min_threshold : float, default=0.4 - Lower edge of the rescaling window (as a fraction of the max signal); weights - at or below this map to 0. - max_threshold : float, default=0.6 - Upper edge of the rescaling window; weights at or above this map to 1. + the bright unscattered beam. Use a **small** exclusion (~0.1) so the Bragg + disks are kept: a large exclusion keeps only the far-corner diffuse scatter, + which is *anti*-correlated with crystallinity and inverts the contrast. If + ``False`` (default, recommended), weight by the mean fitted peak amplitude + from :meth:`fit_lattice_vectors` (requires it to have been run) -- the direct + cepstral order parameter (identical to the weight ``fit_lattice_vectors`` + stores automatically). exclusion_radius_fraction : float, default=0.1 Central-disk radius (fraction of detector width) excluded by the radial method. - smooth : bool, default=True - If ``True``, apply a ``sin^2`` easing to the rescaled weight for a smoother - ramp. + plot : bool, default=True + If ``True``, show the resulting per-position weight map (the analogue of the + correlation pipeline's :meth:`BraggVectors.fit_lattice` plot). + figsize : tuple of float, default=(5, 4) + Figure size in inches for the weight-map plot. Returns ------- @@ -908,9 +1127,6 @@ def create_mask( scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] - self.mask_weight = np.ones(self.dataset.shape[:2]) - - i0_sum_array = np.empty(shape=(scan_r, scan_c)) if use_radial_method: # center / radius are in DETECTOR coordinates (the last two axes) @@ -920,27 +1136,32 @@ def create_mask( radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] outside_mask = radius_map > exclusion_radius + signal = np.empty(shape=(scan_r, scan_c)) for r in range(scan_r): for c in range(scan_c): dp = self.dataset.array[r, c] - i0_sum_array[r, c] = np.sum(dp[outside_mask]) + signal[r, c] = np.sum(dp[outside_mask]) + # honest min-max normalization to [0, 1]; constant/degenerate -> ones + lo = np.nanmin(signal) + hi = np.nanmax(signal) + if np.isfinite(lo) and np.isfinite(hi) and hi > lo: + self.mask_weight = (signal - lo) / (hi - lo) + else: + self.mask_weight = np.ones((scan_r, scan_c)) else: if self.u_peak_fit is None or self.v_peak_fit is None: raise RuntimeError("For intensity-based masking, run fit_lattice_vectors() first.") - u_amplitudes = self.u_peak_fit.array[:, :, 2] - v_amplitudes = self.v_peak_fit.array[:, :, 2] - i0_sum_array = (u_amplitudes + v_amplitudes) / 2.0 + self.mask_weight = self._amplitude_mask_weight() + + if plot: + fig, ax = plt.subplots(1, 1, figsize=figsize) + handle = ax.imshow(self.mask_weight, cmap="gray", vmin=0.0, vmax=1.0) + ax.set_title("Per-position lattice weight (mask_weight)") + ax.set_xlabel("scan column") + ax.set_ylabel("scan row") + fig.colorbar(handle, ax=ax, fraction=0.046, pad=0.04) + fig.tight_layout() - max_intensity = np.max(i0_sum_array) - if max_intensity == 0: - self.mask_weight = np.ones_like(i0_sum_array) - return self - self.mask_weight = i0_sum_array / max_intensity - self.mask_weight = np.clip( - (self.mask_weight - min_threshold) / (max_threshold - min_threshold), 0, 1 - ) - if smooth: - self.mask_weight = np.sin(np.pi / 2 * self.mask_weight) ** 2 return self @@ -1290,6 +1511,46 @@ def _parabolic_vertex_delta(v_m1: float, v_0: float, v_p1: float) -> float: return np.clip(delta, -1.0, 1.0) +def _parabolic_peak_rc_amp(im: NDArray, r_guess: float, c_guess: float) -> tuple[float, float, float]: + """3-point parabolic sub-pixel peak near ``(r_guess, c_guess)`` and its amplitude. + + Snaps to the brightest pixel in the 3x3 window around the (absolute-coordinate) + guess, refines row and column independently by a parabolic vertex, and returns + ``(r_sub, c_sub, amp)`` -- the sub-pixel peak position and the image value at the + rounded vertex. Shared by the per-position :func:`_refine_lattice_vectors` and the + batched :meth:`StrainMapAutocorrelation._fit_lattice_vectors_batched` so they agree + exactly. + """ + H, W = im.shape + r0 = int(np.clip(int(np.round(r_guess)), 0, H - 1)) + c0 = int(np.clip(int(np.round(c_guess)), 0, W - 1)) + win = im[max(0, r0 - 1) : min(H, r0 + 2), max(0, c0 - 1) : min(W, c0 + 2)] + if win.size == 0: + return r_guess, c_guess, 0.0 + + ir, ic = np.unravel_index(np.argmax(win), win.shape) + r_peak = max(0, r0 - 1) + ir + c_peak = max(0, c0 - 1) + ic + + if 0 < r_peak < H - 1: + col = im[r_peak - 1 : r_peak + 2, c_peak] + dr = _parabolic_vertex_delta(col[0], col[1], col[2]) + else: + dr = 0.0 + + if 0 < c_peak < W - 1: + row = im[r_peak, c_peak - 1 : c_peak + 2] + dc = _parabolic_vertex_delta(row[0], row[1], row[2]) + else: + dc = 0.0 + + r_sub = r_peak + dr + c_sub = c_peak + dc + r_int = int(np.clip(int(np.round(r_sub)), 0, H - 1)) + c_int = int(np.clip(int(np.round(c_sub)), 0, W - 1)) + return r_sub, c_sub, float(im[r_int, c_int]) + + def _refine_peak_subpixel( im: NDArray, *, @@ -1381,7 +1642,7 @@ def _refine_peak_subpixel_dft( up = upsample du = int(np.fix(np.ceil(1.5 * up))) - patch = np.abs(dft_upsample(F, up=up, shift=(r0, c0), device="cpu")) + patch = np.abs(dft_upsample(F, up=up, shift=(r0, c0))) patch = np.asarray(patch, dtype=float) i0, j0 = np.unravel_index(np.argmax(patch), patch.shape) @@ -1486,43 +1747,6 @@ def _refine_lattice_vectors( r_center = H // 2 c_center = W // 2 - def _parabolic_peak_rc_amp(*, r_guess: float, c_guess: float) -> tuple[float, float, float]: - r0 = int(np.clip(int(np.round(r_guess)), 0, H - 1)) - c0 = int(np.clip(int(np.round(c_guess)), 0, W - 1)) - win = im[ - max(0, r0 - 1) : min(H, r0 + 2), - max(0, c0 - 1) : min(W, c0 + 2), - ] - if win.size == 0: - return r_guess, c_guess, 0.0 - - ir, ic = np.unravel_index(np.argmax(win), win.shape) - r_peak = max(0, r0 - 1) + ir - c_peak = max(0, c0 - 1) + ic - - r_ref = r_peak - c_ref = c_peak - - if 0 < r_peak < H - 1: - col = im[r_peak - 1 : r_peak + 2, c_peak] - dr = _parabolic_vertex_delta(col[0], col[1], col[2]) - else: - dr = 0.0 - - if 0 < c_peak < W - 1: - row = im[r_peak, c_peak - 1 : c_peak + 2] - dc = _parabolic_vertex_delta(row[0], row[1], row[2]) - else: - dc = 0.0 - - r_sub = r_ref + dr - c_sub = c_ref + dc - r_int = int(np.clip(int(np.round(r_sub)), 0, H - 1)) - c_int = int(np.clip(int(np.round(c_sub)), 0, W - 1)) - amp = im[r_int, c_int] - - return r_sub, c_sub, amp - def _fit_gaussian_isotropic( *, r0: float, @@ -1599,7 +1823,7 @@ def _refine_one(vec: NDArray) -> NDArray: r_guess = r_center + vec[0] c_guess = c_center + vec[1] - r_par, c_par, amp_par = _parabolic_peak_rc_amp(r_guess=r_guess, c_guess=c_guess) + r_par, c_par, amp_par = _parabolic_peak_rc_amp(im, r_guess, c_guess) if refine_gaussian: r_fit, c_fit, amp, sig, bg = _fit_gaussian_isotropic( @@ -1684,10 +1908,164 @@ def _find_initial_peaks_weights(initial_peaks: NDArray) -> tuple[NDArray, NDArra uvr0 = np.linalg.lstsq(A_weighted, pts_weighted, rcond=None)[0] u_refined = uvr0[0,:] v_refined = uvr0[1,:] - _,_, amp_par_u = _parabolic_peak_rc_amp(r_guess=u_refined[0], c_guess=u_refined[1]) - _,_, amp_par_v = _parabolic_peak_rc_amp(r_guess=v_refined[0], c_guess=v_refined[1]) + _, _, amp_par_u = _parabolic_peak_rc_amp(im, r_center + u_refined[0], c_center + u_refined[1]) + _, _, amp_par_v = _parabolic_peak_rc_amp(im, r_center + v_refined[0], c_center + v_refined[1]) return np.array((u_refined[0], u_refined[1], amp_par_u, 0, 0), dtype=float), np.array((v_refined[0], v_refined[1], amp_par_v, 0, 0), dtype=float), pts, weights return _refine_one(u_rc), _refine_one(v_rc), None, None + +def _refine_peaks_batched( + ims: torch.Tensor, + vec: NDArray, + *, + radius_px: float, + refine_gaussian: bool, + lm_iters: int = 50, +) -> torch.Tensor: + """Batched, vectorized version of the single-image ``_refine_one`` peak refinement. + + Refines one lattice peak (located near ``center + vec``) for every transform in the + stack ``ims`` of shape ``(B, H, W)`` at once. The parabolic sub-pixel step matches + :func:`_refine_lattice_vectors`' ``_parabolic_peak_rc_amp`` exactly; when + ``refine_gaussian`` is set, the per-position ``scipy.optimize.curve_fit`` of an + isotropic 2D Gaussian is replaced by a batched Levenberg-Marquardt solve of the + identical objective and bounds. The two agree to ~1e-6 px (machine precision) but the + batched solve removes the dominant per-call overhead, so it runs far faster (and + scales onto a GPU via the tensor ``device``). + + Parameters + ---------- + ims : torch.Tensor + Stack of transformed (fftshifted ``|FFT|``) images, shape ``(B, H, W)``, float. + vec : NDArray + Lattice vector ``(row, col)`` relative to the panel center; the peak is sought + near ``(H // 2 + row, W // 2 + col)``. + radius_px : float + Half-width in pixels of the Gaussian-fit window. + refine_gaussian : bool + If ``True``, refine with the batched isotropic-Gaussian LM fit; otherwise return + the parabolic estimate only (``sigma`` and ``background`` set to 0). + lm_iters : int, default=50 + Number of Levenberg-Marquardt iterations. + + Returns + ------- + torch.Tensor + Shape ``(B, 5)``: ``(row_offset, col_offset, amplitude, sigma, background)`` with + ``row_offset``/``col_offset`` measured relative to the panel center (matching + :func:`_refine_lattice_vectors`). + """ + Bn, Hn, Wn = ims.shape + dev, dt = ims.device, ims.dtype + rcent, ccent = Hn // 2, Wn // 2 + bidx = torch.arange(Bn, device=dev) + + # Clamp the seed into the interior so the 3x3 window below is always in-bounds + # (mirrors the clamping in _parabolic_peak_rc_amp; matters only for edge peaks, + # which are interior in normal use but must not crash the batched solve). + r0 = int(np.clip(round(rcent + float(vec[0])), 1, Hn - 2)) + c0 = int(np.clip(round(ccent + float(vec[1])), 1, Wn - 2)) + + # --- 3x3 argmax around the seed (matches _parabolic_peak_rc_amp) --- + win3 = ims[:, r0 - 1 : r0 + 2, c0 - 1 : c0 + 2].reshape(Bn, -1) + am = win3.argmax(1) + r_peak = r0 - 1 + torch.div(am, 3, rounding_mode="floor") + c_peak = c0 - 1 + (am % 3) + + def _parab(vm1: torch.Tensor, v0_: torch.Tensor, vp1: torch.Tensor) -> torch.Tensor: + denom = vm1 - 2.0 * v0_ + vp1 + delta = 0.5 * (vm1 - vp1) / denom + delta = torch.where(torch.isfinite(delta), delta, torch.zeros_like(delta)) + delta = torch.where(denom == 0, torch.zeros_like(delta), delta) + return delta.clamp(-1.0, 1.0) + + v0_ = ims[bidx, r_peak, c_peak] + dr = _parab(ims[bidx, (r_peak - 1).clamp(0, Hn - 1), c_peak], v0_, ims[bidx, (r_peak + 1).clamp(0, Hn - 1), c_peak]) + dr = torch.where((r_peak > 0) & (r_peak < Hn - 1), dr, torch.zeros_like(dr)) + dc = _parab(ims[bidx, r_peak, (c_peak - 1).clamp(0, Wn - 1)], v0_, ims[bidx, r_peak, (c_peak + 1).clamp(0, Wn - 1)]) + dc = torch.where((c_peak > 0) & (c_peak < Wn - 1), dc, torch.zeros_like(dc)) + r_sub = r_peak.to(dt) + dr + c_sub = c_peak.to(dt) + dc + + if not refine_gaussian: + ri = r_sub.round().long().clamp(0, Hn - 1) + ci = c_sub.round().long().clamp(0, Wn - 1) + amp = ims[bidx, ri, ci] + zeros = torch.zeros(Bn, device=dev, dtype=dt) + return torch.stack([r_sub - rcent, c_sub - ccent, amp, zeros, zeros], 1) + + # --- isotropic Gaussian fit over a (2*rad+1)^2 window, batched LM --- + rad = int(max(1, np.ceil(radius_px))) + P = 2 * rad + 1 + r0i = r_sub.round().long() + c0i = c_sub.round().long() + off = torch.arange(-rad, rad + 1, device=dev) + rows_idx = (r0i[:, None, None] + off[None, :, None]).expand(Bn, P, P) + cols_idx = (c0i[:, None, None] + off[None, None, :]).expand(Bn, P, P) + bexp = bidx[:, None, None].expand(Bn, P, P) + win = ims[bexp, rows_idx.clamp(0, Hn - 1), cols_idx.clamp(0, Wn - 1)] + RR = rows_idx.to(dt).reshape(Bn, -1) + CC = cols_idx.to(dt).reshape(Bn, -1) + y = win.reshape(Bn, -1) + + am2 = y.argmax(1) + r_pk2 = (r0i - rad + torch.div(am2, P, rounding_mode="floor")).to(dt) + c_pk2 = (c0i - rad + (am2 % P)).to(dt) + bg0 = y.median(1).values + amp0 = (y.max(1).values - bg0).clamp(min=0.0) + sig0 = torch.full((Bn,), max(0.75, radius_px / 2.0), device=dev, dtype=dt) + p = torch.stack([r_pk2, c_pk2, amp0, sig0, bg0], 1) + + rlo = (r0i - rad).to(dt) - 0.5 + rhi = (r0i + rad).to(dt) + 0.5 + clo = (c0i - rad).to(dt) - 0.5 + chi = (c0i + rad).to(dt) + 0.5 + sig_hi = radius_px * 4.0 + + lam = torch.full((Bn,), 1e-2, device=dev, dtype=dt) + eye5 = torch.eye(5, device=dev, dtype=dt)[None] + + def _sse(p_: torch.Tensor) -> torch.Tensor: + row, col, amp, sig, bg = [p_[:, i : i + 1] for i in range(5)] + sig = sig.clamp(min=1e-9) + E = torch.exp(-((RR - row) ** 2 + (CC - col) ** 2) / (2.0 * sig * sig)) + return ((bg + amp * E - y) ** 2).sum(1) + + for _ in range(lm_iters): + row, col, amp, sig, bg = [p[:, i : i + 1] for i in range(5)] + sig = sig.clamp(min=1e-9) + d_row = RR - row + d_col = CC - col + E = torch.exp(-(d_row * d_row + d_col * d_col) / (2.0 * sig * sig)) + res = (bg + amp * E) - y + g_row = amp * E * d_row / (sig * sig) + g_col = amp * E * d_col / (sig * sig) + g_amp = E + g_sig = amp * E * ((d_row * d_row + d_col * d_col) / (sig**3)) + g_bg = torch.ones_like(E) + J = torch.stack([g_row, g_col, g_amp, g_sig, g_bg], 2) # (B, N, 5) + JT = J.transpose(1, 2) + JTJ = JT @ J + JTr = JT @ res[..., None] + diag = torch.diagonal(JTJ, dim1=1, dim2=2).clamp(min=1e-12) + A = JTJ + lam[:, None, None] * eye5 * diag[:, None, :] + delta = torch.linalg.solve(A, -JTr)[..., 0] + pn = p + delta + pn[:, 0] = pn[:, 0].clamp(rlo, rhi) + pn[:, 1] = pn[:, 1].clamp(clo, chi) + pn[:, 2] = pn[:, 2].clamp(min=0.0) + pn[:, 3] = pn[:, 3].clamp(0.25, sig_hi) + s_new = _sse(pn) + s_old = res.pow(2).sum(1) + better = s_new < s_old + p = torch.where(better[:, None], pn, p) + lam = torch.where(better, (lam * 0.5).clamp(min=1e-9), (lam * 3.0).clamp(max=1e6)) + + row, col, amp, sig, bg = [p[:, i] for i in range(5)] + ok = torch.isfinite(p).all(1) + row = torch.where(ok, row, r_sub) + col = torch.where(ok, col, c_sub) + return torch.stack([row - rcent, col - ccent, amp, sig, bg], 1) + From 836887b34dce0d14156b6b0723d442d7904f0097 Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 1 Jun 2026 15:21:40 +0200 Subject: [PATCH 084/113] fixes and speed up --- .../diffraction/strain_autocorrelation.py | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index c65e08c4b..34d23d040 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -768,10 +768,13 @@ def fit_lattice_vectors( ``scipy.optimize.curve_fit``. For ``refine_all_peaks=True`` every detected peak is batch-refined across the stack and the basis is solved per position by weighted least squares -- avoiding the ``n_positions x n_peaks`` ``curve_fit`` calls of the - old loop. This removes the dominant per-call overhead and reproduces the - per-position result to ~1e-6 px, so the fitted vectors are unchanged while the - step runs far faster (and faster still on a GPU). Only ``refine_dft=True`` falls - back to the per-position path. + old loop. This removes the dominant per-call overhead and runs far faster (and + faster still on a GPU). The batched Levenberg-Marquardt fit reproduces the + per-position ``scipy.optimize.curve_fit`` to ~1e-6 px on clean, well-isolated + peaks; on noisy, non-Gaussian cepstral peaks the bounded-trf and LM optima can + land a few hundredths of a pixel apart (amplitude-preserving and well below + strain-relevant precision). Only ``refine_dft=True`` falls back to the + per-position path. Parameters ---------- @@ -828,8 +831,9 @@ def fit_lattice_vectors( if not refine_dft: # Fast path: batch the transforms and isotropic-Gaussian fits across scan # positions on `device` (per-position scipy.optimize.curve_fit -> vectorized - # LM). Covers both the 2-vector fit and the all-peaks basis fit; reproduces - # the per-position result to ~1e-6 px. Only DFT upsampling still falls back. + # LM). Covers both the 2-vector fit and the all-peaks basis fit; matches the + # per-position result to ~1e-6 px on clean peaks (a few 0.01 px on noisy, + # non-Gaussian peaks -- optimizer variance). Only DFT upsampling still falls back. self._fit_lattice_vectors_batched( u0=u0, v0=v0, @@ -924,8 +928,10 @@ def _fit_lattice_vectors_batched( parabolic/isotropic-Gaussian peak refinement are evaluated for a stack of scan positions at once on ``device``, the analogue of the correlation pipeline's :func:`~quantem.diffraction.disk_detection.detect_disks_batch`. This reproduces - :func:`_refine_lattice_vectors` (with ``refine_dft=False``) to ~1e-6 px while - removing the per-position ``scipy.optimize.curve_fit`` overhead. Results are + :func:`_refine_lattice_vectors` (with ``refine_dft=False``) -- to ~1e-6 px on + clean peaks, within a few 0.01 px on noisy non-Gaussian peaks (bounded-trf vs LM + optimizer variance) -- while removing the per-position + ``scipy.optimize.curve_fit`` overhead. Results are written into :attr:`u_array`/:attr:`v_array` and :attr:`u_peak_fit`/ :attr:`v_peak_fit`. @@ -1931,9 +1937,12 @@ def _refine_peaks_batched( :func:`_refine_lattice_vectors`' ``_parabolic_peak_rc_amp`` exactly; when ``refine_gaussian`` is set, the per-position ``scipy.optimize.curve_fit`` of an isotropic 2D Gaussian is replaced by a batched Levenberg-Marquardt solve of the - identical objective and bounds. The two agree to ~1e-6 px (machine precision) but the - batched solve removes the dominant per-call overhead, so it runs far faster (and - scales onto a GPU via the tensor ``device``). + identical objective and bounds. On clean, well-isolated peaks the two agree to + ~1e-6 px; on noisy, non-Gaussian peaks (e.g. a sinc-like cepstral peak on the sloped + tail of a bright neighbor) the bounded-trf and LM optima can differ by a few 0.01 px + -- amplitude-preserving and well below strain-relevant precision. The batched solve + removes the dominant per-call overhead, so it runs far faster (and scales onto a GPU + via the tensor ``device``). Parameters ---------- From 00edf7c6bb72688e1a88a1e507c6ded7bc003cc7 Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 1 Jun 2026 17:04:20 +0200 Subject: [PATCH 085/113] strain model fitting updates --- src/quantem/diffraction/model_fitting.py | 101 +++++++++++++----- .../diffraction/strain_autocorrelation.py | 49 +++++++-- 2 files changed, 111 insertions(+), 39 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index ca49d2941..f4b5390e9 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -986,61 +986,104 @@ def render_individual_pattern(self, row, col): def create_mask( self, use_radial_method: bool = False, - min_threshold: float = 0.4, - max_threshold: float = 0.6, exclusion_radius_fraction: float = 0.1, - smooth: bool = True, + plot: bool = True, + figsize: tuple[float, float] = (5, 4), ): + """Compute the per-position weight :attr:`mask` from the fitted lattice signal. + + Builds a ``(scan_row, scan_col)`` weight in ``[0, 1]`` measuring how much + crystalline signal each position carries -- the model-fitting analogue of + :attr:`BraggVectors.mask_weight` and the cepstral ``mask_weight``. It is handed to + :meth:`initialize_strain_class`, so strong, well-fit positions dominate the + reference lattice and weak/vacuum positions are down-weighted. + + The raw signal (summed intensity of the non-central fitted disks, or the + diffracted intensity outside a central disk for the radial method) is normalized + to ``[0, 1]`` with **no** contrast windowing or smoothing: this is the honest + per-position order parameter. Set the display contrast later, in one place, via + the ``mask_range`` argument of :meth:`StrainMap.plot_strain`, + :meth:`StrainMap.update_reference`, and :meth:`StrainMap.estimate_strain_precision` + (e.g. ``mask_range=(0.37, 0.5)``) -- weights at/below ``low`` render black, at/above + ``high`` render full color. The weight-map plot here shows where the bulk sits so + an appropriate ``mask_range`` can be chosen. + + Parameters + ---------- + use_radial_method : bool, default=False + If ``True``, weight by the total diffracted intensity *outside* a central + disk (radius ``exclusion_radius_fraction`` of the detector width). If + ``False`` (default, recommended), weight by the summed intensity of the + fitted non-central lattice disks -- requires + :meth:`fit_individual_diffraction_pattern` to have been run. + exclusion_radius_fraction : float, default=0.1 + Central-disk radius (fraction of detector width) excluded by the radial + method. + plot : bool, default=True + If ``True``, show the resulting per-position weight map (so a separate + ``plt.imshow(mask)`` cell is unnecessary). + figsize : tuple of float, default=(5, 4) + Figure size in inches for the weight-map plot. + + Returns + ------- + ModelDiffraction + ``self``, with :attr:`mask` set. + """ + if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): + raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] - self.mask = np.zeros(self.dataset.shape[:2]) - - self.i0_sum_array = np.empty(shape=(scan_r, scan_c)) - - if self.state_individual_refined is None: - raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") - if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): - raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") + self.i0_sum_array = np.zeros(shape=(scan_r, scan_c)) if use_radial_method: center_y, center_x = np.array(self.dataset.shape[:-2]) / 2 - y, x = np.ogrid[:self.dataset.shape[-2], :self.dataset.shape[-1]] - radius_map = np.sqrt((x - center_x)**2 + (y - center_y)**2) + y, x = np.ogrid[: self.dataset.shape[-2], : self.dataset.shape[-1]] + radius_map = np.sqrt((x - center_x) ** 2 + (y - center_y) ** 2) exclusion_radius = exclusion_radius_fraction * self.dataset.shape[-1] + outside_mask = radius_map > exclusion_radius for r in range(scan_r): for c in range(scan_c): - dp = self.dataset.array[r, c] - outside_mask = radius_map > exclusion_radius - self.i0_sum_array[r, c] = np.sum(dp[outside_mask]) + self.i0_sum_array[r, c] = np.sum(self.dataset.array[r, c][outside_mask]) else: + if self.state_individual_refined is None: + raise RuntimeError("Call .fit_individual_diffraction_pattern(...) first.") for r in range(scan_r): for c in range(scan_c): pos_state = self.state_individual_refined[r, c] if pos_state is None: - self.i0_sum_array[r, c] = 0.0 continue - i0_raw = None uv_indices = None for key in pos_state.keys(): - if key.endswith('i0_raw'): + if key.endswith("i0_raw"): i0_raw = pos_state[key].cpu().numpy() - if key.endswith('uv_indices'): + if key.endswith("uv_indices"): uv_indices = pos_state[key].cpu().numpy() - if i0_raw is None or uv_indices is None: - self.i0_sum_array[r, c] = 0.0 continue - is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) + + # honest [0, 1] normalization, no contrast windowing (set mask_range at display) max_intensity = np.max(self.i0_sum_array) - if max_intensity == 0: - return np.zeros_like(self.i0_sum_array) - self.mask = self.i0_sum_array / max_intensity - self.mask = np.clip((self.mask - min_threshold) / (max_threshold - min_threshold), 0, 1) - if smooth: - self.mask = np.sin(np.pi / 2 * self.mask) ** 2 + if max_intensity > 0: + self.mask = self.i0_sum_array / max_intensity + else: + self.mask = np.zeros_like(self.i0_sum_array) + + if plot: + import matplotlib.pyplot as plt + + fig, ax = plt.subplots(1, 1, figsize=figsize) + handle = ax.imshow(self.mask, cmap="gray", vmin=0.0, vmax=1.0) + ax.set_title("Per-position lattice weight (mask)") + ax.set_xlabel("scan column") + ax.set_ylabel("scan row") + fig.colorbar(handle, ax=ax, fraction=0.046, pad=0.04) + fig.tight_layout() + return self def initialize_strain_class( diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 34d23d040..502c89bc4 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -941,7 +941,13 @@ def _fit_lattice_vectors_batched( batch-refined across the stack and the basis is recovered per position by the same intensity-weighted least-squares fit as the per-position all-peaks branch -- so that path is batched too rather than looping ``scipy.optimize.curve_fit`` over - ``n_positions x n_peaks``. + ``n_positions x n_peaks``. In both cases the amplitude/width/background stored in + :attr:`u_peak_fit`/:attr:`v_peak_fit` (columns 2-4, the source of the mask weight) + come from the single-peak refinement at ``u0``/``v0`` -- the background-subtracted + Gaussian height, the crystalline order parameter. The all-peaks least squares + only improves the u/v *positions* (columns 0-1); the raw cepstral value at those + positions rides the central-autocorrelation pedestal and would invert the mask + (bright in vacuum), so it is deliberately not used. """ scan_r, scan_c, H, W = self.dataset.array.shape n_pos = scan_r * scan_c @@ -1015,7 +1021,18 @@ def _fit_lattice_vectors_batched( refine_gaussian=refine_gaussian, ).cpu().numpy() pts_all[:, j, :] = rj[:, :2] - ims_np = ims.cpu().numpy() + # Amplitude/width/background for the mask weight come from the SAME + # single-peak refinement at the u/v seeds as the single-peak branch + # below -- i.e. the background-subtracted Gaussian height, the true + # crystalline order parameter. (The all-peaks lstsq only improves the + # u/v *positions*; the raw cepstral value at those positions rides the + # central-autocorrelation pedestal and would invert the mask in vacuum.) + u_fit = _refine_peaks_batched( + ims, u0, radius_px=refine_radius_px, refine_gaussian=refine_gaussian + ).cpu().numpy() + v_fit = _refine_peaks_batched( + ims, v0, radius_px=refine_radius_px, refine_gaussian=refine_gaussian + ).cpu().numpy() for k, (r, c) in enumerate(idxs): pts = pts_all[k] # (n_pk, 2) row/col offsets from center ab = np.round(np.linalg.lstsq(A_seed, pts.T, rcond=None)[0]).T @@ -1023,10 +1040,12 @@ def _fit_lattice_vectors_batched( M[:, :2] = ab # integer (h, k) indices + constant (center) column uvc = np.linalg.lstsq(M * sqrt_w, pts * sqrt_w, rcond=None)[0] # (3, 2) u_ref, v_ref = uvc[0], uvc[1] - amp_u = _parabolic_peak_rc_amp(ims_np[k], rcent + u_ref[0], ccent + u_ref[1])[2] - amp_v = _parabolic_peak_rc_amp(ims_np[k], rcent + v_ref[0], ccent + v_ref[1])[2] - self.u_peak_fit.array[r, c, :] = (u_ref[0], u_ref[1], amp_u, 0.0, 0.0) - self.v_peak_fit.array[r, c, :] = (v_ref[0], v_ref[1], amp_v, 0.0, 0.0) + self.u_peak_fit.array[r, c, :] = ( + u_ref[0], u_ref[1], u_fit[k, 2], u_fit[k, 3], u_fit[k, 4] + ) + self.v_peak_fit.array[r, c, :] = ( + v_ref[0], v_ref[1], v_fit[k, 2], v_fit[k, 3], v_fit[k, 4] + ) self.u_array[r, c, 0] = u_ref[0] self.u_array[r, c, 1] = u_ref[1] self.v_array[r, c, 0] = v_ref[0] @@ -1742,6 +1761,11 @@ def _refine_lattice_vectors( giving the refined lattice vectors relative to the panel center. ``pts`` and ``weights`` are the detected peak positions and their normalized weights when ``refine_all_peaks`` is True, otherwise both are ``None``. + When ``refine_all_peaks`` is True the position comes from the + intensity-weighted all-peaks least squares, but the amplitude/sigma/background + come from the single-peak refinement at the u/v seed -- the + background-subtracted Gaussian height (the mask order parameter), not the raw + cepstral value, which rides the central pedestal and inverts the mask in vacuum. """ from scipy.optimize import curve_fit @@ -1914,10 +1938,15 @@ def _find_initial_peaks_weights(initial_peaks: NDArray) -> tuple[NDArray, NDArra uvr0 = np.linalg.lstsq(A_weighted, pts_weighted, rcond=None)[0] u_refined = uvr0[0,:] v_refined = uvr0[1,:] - _, _, amp_par_u = _parabolic_peak_rc_amp(im, r_center + u_refined[0], c_center + u_refined[1]) - _, _, amp_par_v = _parabolic_peak_rc_amp(im, r_center + v_refined[0], c_center + v_refined[1]) - - return np.array((u_refined[0], u_refined[1], amp_par_u, 0, 0), dtype=float), np.array((v_refined[0], v_refined[1], amp_par_v, 0, 0), dtype=float), pts, weights + # Position from the weighted all-peaks lstsq; amplitude/width/background from + # the SAME single-peak refinement at the u/v seeds as the single-peak return + # below -- the background-subtracted Gaussian height (the crystalline order + # parameter), not the raw cepstral value, which rides the central pedestal and + # would invert the mask in vacuum. + u_amp_fit = _refine_one(u_rc) + v_amp_fit = _refine_one(v_rc) + + return np.array((u_refined[0], u_refined[1], u_amp_fit[2], u_amp_fit[3], u_amp_fit[4]), dtype=float), np.array((v_refined[0], v_refined[1], v_amp_fit[2], v_amp_fit[3], v_amp_fit[4]), dtype=float), pts, weights return _refine_one(u_rc), _refine_one(v_rc), None, None From 071355a352459194a65a605ccf84f09db1854a77 Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 1 Jun 2026 21:17:32 +0200 Subject: [PATCH 086/113] few more fixes --- src/quantem/diffraction/bragg_vectors.py | 2 +- src/quantem/diffraction/model_fitting.py | 79 +++++++++++++------ .../diffraction/strain_autocorrelation.py | 60 +++++++------- 3 files changed, 87 insertions(+), 54 deletions(-) diff --git a/src/quantem/diffraction/bragg_vectors.py b/src/quantem/diffraction/bragg_vectors.py index fccbdaf8c..a2da2eba2 100644 --- a/src/quantem/diffraction/bragg_vectors.py +++ b/src/quantem/diffraction/bragg_vectors.py @@ -659,7 +659,7 @@ def index_peaks( brightest is kept. The result is a compact reference lattice stored on :attr:`reference_ab` / :attr:`reference_qpos` / :attr:`reference_intensity` which :meth:`fit_lattice` matches against at every scan position. This step - is deliberately quick and lightweight. + is quick and lightweight. With ``plot=True`` (default) the reference lattice is drawn over the Bragg vector map, each site ringed and labelled with its ``(a, b)`` index. The ring diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index f4b5390e9..b148fa915 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -992,21 +992,21 @@ def create_mask( ): """Compute the per-position weight :attr:`mask` from the fitted lattice signal. - Builds a ``(scan_row, scan_col)`` weight in ``[0, 1]`` measuring how much - crystalline signal each position carries -- the model-fitting analogue of - :attr:`BraggVectors.mask_weight` and the cepstral ``mask_weight``. It is handed to - :meth:`initialize_strain_class`, so strong, well-fit positions dominate the + Builds a ``(scan_row, scan_col)`` weight in ``[0, 1]`` measuring the lattice + signal at each position -- the model-fitting analogue of + :attr:`BraggVectors.mask_weight` and the cepstral ``mask_weight``. It is passed to + :meth:`calculate_strain_map`, where higher-weight positions contribute more to the reference lattice and weak/vacuum positions are down-weighted. The raw signal (summed intensity of the non-central fitted disks, or the - diffracted intensity outside a central disk for the radial method) is normalized - to ``[0, 1]`` with **no** contrast windowing or smoothing: this is the honest - per-position order parameter. Set the display contrast later, in one place, via - the ``mask_range`` argument of :meth:`StrainMap.plot_strain`, - :meth:`StrainMap.update_reference`, and :meth:`StrainMap.estimate_strain_precision` - (e.g. ``mask_range=(0.37, 0.5)``) -- weights at/below ``low`` render black, at/above - ``high`` render full color. The weight-map plot here shows where the bulk sits so - an appropriate ``mask_range`` can be chosen. + diffracted intensity outside a central disk for the radial method) is min-max + normalized to ``[0, 1]``; no contrast windowing or smoothing is applied. Display + contrast is applied via the ``mask_range`` argument of + :meth:`StrainMap.plot_strain`, :meth:`StrainMap.update_reference`, and + :meth:`StrainMap.estimate_strain_precision` (e.g. ``mask_range=(0.37, 0.5)``): + weights at/below ``low`` render black, at/above ``high`` render full color. The + weight-map plot shows the distribution so an appropriate ``mask_range`` can be + chosen. Parameters ---------- @@ -1066,12 +1066,17 @@ def create_mask( is_not_center = ~((uv_indices[:, 0] == 0) & (uv_indices[:, 1] == 0)) self.i0_sum_array[r, c] = np.sum(i0_raw[is_not_center]) - # honest [0, 1] normalization, no contrast windowing (set mask_range at display) - max_intensity = np.max(self.i0_sum_array) - if max_intensity > 0: - self.mask = self.i0_sum_array / max_intensity + # Min-max normalization to [0, 1], no contrast windowing (set mask_range at + # display). Subtracting the floor -- rather than only dividing by the max -- keeps + # the weight from saturating near 1.0 when every position carries a baseline + # lattice intensity, and matches the cepstral _amplitude_mask_weight. Degenerate + # (constant / non-finite) input falls back to uniform full weight. + lo = np.nanmin(self.i0_sum_array) + hi = np.nanmax(self.i0_sum_array) + if np.isfinite(lo) and np.isfinite(hi) and hi > lo: + self.mask = (self.i0_sum_array - lo) / (hi - lo) else: - self.mask = np.zeros_like(self.i0_sum_array) + self.mask = np.ones_like(self.i0_sum_array) if plot: import matplotlib.pyplot as plt @@ -1086,16 +1091,46 @@ def create_mask( return self - def initialize_strain_class( + def calculate_strain_map( self, u_ref: np.ndarray | None = None, v_ref: np.ndarray | None = None, - )->StrainMap: + mask: np.ndarray | None = None, + ) -> StrainMap: + """Build a :class:`StrainMap` from the fitted per-position lattice vectors. + + Mirrors :meth:`BraggVectors.calculate_strain_map` and + :meth:`StrainMapAutocorrelation.calculate_strain_map` so the downstream strain + cells (``plot_strain``, ``update_reference``, ``estimate_strain_precision``) are + identical across the correlation, cepstral, and model-fitting workflows. + + Parameters + ---------- + u_ref : np.ndarray, optional + ``(2,)`` reference for the first lattice vector. Defaults to the median over + the scan inside :class:`StrainMap`. + v_ref : np.ndarray, optional + ``(2,)`` reference for the second lattice vector. Defaults to the median over + the scan inside :class:`StrainMap`. + mask : np.ndarray, optional + ``(scan_row, scan_col)`` per-position weighting used when computing the + reference lattice. Defaults to :attr:`mask` from :meth:`create_mask` (the + lattice signal strength), so strong, well-fit positions dominate the + reference. + + Returns + ------- + StrainMap + A strain map initialized from the fitted lattice vectors. + """ if self.u_array is None or self.v_array is None: self.get_individual_uv_vectors() if not isinstance(self.dataset, (Dataset4d, Dataset4dstem)): raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - + + if mask is None: + mask = self.mask + default_units = None default_sampling = None if hasattr(self.dataset, 'units'): @@ -1108,7 +1143,7 @@ def initialize_strain_class( default_sampling = float(self.dataset.sampling[0]) else: default_sampling = float(self.dataset.sampling) - + return StrainMap( u_array = self.u_array, v_array = self.v_array, @@ -1116,7 +1151,7 @@ def initialize_strain_class( real_space = self.real_space, u_ref = u_ref, v_ref = v_ref, - mask = self.mask, + mask = mask, ds_sampling=default_sampling, ds_units = default_units, ) diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 502c89bc4..c9d4282af 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -944,10 +944,10 @@ def _fit_lattice_vectors_batched( ``n_positions x n_peaks``. In both cases the amplitude/width/background stored in :attr:`u_peak_fit`/:attr:`v_peak_fit` (columns 2-4, the source of the mask weight) come from the single-peak refinement at ``u0``/``v0`` -- the background-subtracted - Gaussian height, the crystalline order parameter. The all-peaks least squares - only improves the u/v *positions* (columns 0-1); the raw cepstral value at those - positions rides the central-autocorrelation pedestal and would invert the mask - (bright in vacuum), so it is deliberately not used. + Gaussian height. The all-peaks least squares only improves the u/v *positions* + (columns 0-1); the raw cepstral value at those positions rides the + central-autocorrelation pedestal and would invert the mask (bright in vacuum), so + it is not used for the weight. """ scan_r, scan_c, H, W = self.dataset.array.shape n_pos = scan_r * scan_c @@ -1023,10 +1023,10 @@ def _fit_lattice_vectors_batched( pts_all[:, j, :] = rj[:, :2] # Amplitude/width/background for the mask weight come from the SAME # single-peak refinement at the u/v seeds as the single-peak branch - # below -- i.e. the background-subtracted Gaussian height, the true - # crystalline order parameter. (The all-peaks lstsq only improves the - # u/v *positions*; the raw cepstral value at those positions rides the - # central-autocorrelation pedestal and would invert the mask in vacuum.) + # below -- i.e. the background-subtracted Gaussian height. (The all-peaks + # lstsq only improves the u/v *positions*; the raw cepstral value at those + # positions rides the central-autocorrelation pedestal and would invert + # the mask in vacuum.) u_fit = _refine_peaks_batched( ims, u0, radius_px=refine_radius_px, refine_gaussian=refine_gaussian ).cpu().numpy() @@ -1074,12 +1074,11 @@ def _fit_lattice_vectors_batched( def _amplitude_mask_weight(self) -> np.ndarray: """Per-position weight from the fitted u/v peak amplitudes, min-max to ``[0, 1]``. - The mean of the two fitted lattice-peak amplitudes is the cepstral order - parameter (how much crystalline signal a position carries). It is min-max - normalized to ``[0, 1]`` with no contrast windowing -- the honest weight; set - display contrast later via :meth:`StrainMap.plot_strain`'s ``mask_range``. - Degenerate input (constant / non-finite / amplitudes unavailable) falls back to - uniform full weight. + The mean of the two fitted lattice-peak amplitudes measures the lattice signal at + each position. It is min-max normalized to ``[0, 1]`` with no contrast windowing; + display contrast is applied later via :meth:`StrainMap.plot_strain`'s + ``mask_range``. Degenerate input (constant / non-finite / amplitudes unavailable) + falls back to uniform full weight. """ scan_r = self.dataset.shape[0] scan_c = self.dataset.shape[1] @@ -1107,19 +1106,19 @@ def create_mask( only to plot the weight map, to recompute it, or to switch to the radial estimator. - Builds a ``(scan_row, scan_col)`` weight in ``[0, 1]`` measuring how much - crystalline signal each position carries, the analogue of - :attr:`BraggVectors.mask_weight`. It is the default reference weighting handed to - :meth:`calculate_strain_map`, so strong, well-fit positions dominate the - reference lattice and weak/vacuum positions are down-weighted. + Builds a ``(scan_row, scan_col)`` weight in ``[0, 1]`` measuring the lattice + signal at each position, the analogue of :attr:`BraggVectors.mask_weight`. It is + the default reference weighting handed to :meth:`calculate_strain_map`, so strong, + well-fit positions dominate the reference lattice and weak/vacuum positions are + down-weighted. The raw signal is min-max normalized to ``[0, 1]`` with **no** contrast windowing - or smoothing: this is the honest per-position order parameter. Set the display - contrast later, in one place, via :meth:`StrainMap.plot_strain`'s ``mask_range`` - argument (e.g. ``mask_range=(0.6, 0.8)``) -- weights at/below ``low`` render - black, at/above ``high`` render full color. (Min-max is sensitive to a few very - bright positions, which compress the bulk toward 0; pick ``mask_range`` to match - where the bulk actually sits -- the weight-map plot here shows it.) + or smoothing. Display contrast is applied via :meth:`StrainMap.plot_strain`'s + ``mask_range`` argument (e.g. ``mask_range=(0.6, 0.8)``) -- weights at/below + ``low`` render black, at/above ``high`` render full color. (Min-max is sensitive + to a few very bright positions, which compress the bulk toward 0; pick + ``mask_range`` to match where the bulk actually sits -- the weight-map plot here + shows it.) Parameters ---------- @@ -1130,9 +1129,8 @@ def create_mask( disks are kept: a large exclusion keeps only the far-corner diffuse scatter, which is *anti*-correlated with crystallinity and inverts the contrast. If ``False`` (default, recommended), weight by the mean fitted peak amplitude - from :meth:`fit_lattice_vectors` (requires it to have been run) -- the direct - cepstral order parameter (identical to the weight ``fit_lattice_vectors`` - stores automatically). + from :meth:`fit_lattice_vectors` (requires it to have been run), identical to + the weight ``fit_lattice_vectors`` stores automatically. exclusion_radius_fraction : float, default=0.1 Central-disk radius (fraction of detector width) excluded by the radial method. @@ -1166,7 +1164,7 @@ def create_mask( for c in range(scan_c): dp = self.dataset.array[r, c] signal[r, c] = np.sum(dp[outside_mask]) - # honest min-max normalization to [0, 1]; constant/degenerate -> ones + # min-max normalization to [0, 1]; constant/degenerate -> ones lo = np.nanmin(signal) hi = np.nanmax(signal) if np.isfinite(lo) and np.isfinite(hi) and hi > lo: @@ -1764,8 +1762,8 @@ def _refine_lattice_vectors( When ``refine_all_peaks`` is True the position comes from the intensity-weighted all-peaks least squares, but the amplitude/sigma/background come from the single-peak refinement at the u/v seed -- the - background-subtracted Gaussian height (the mask order parameter), not the raw - cepstral value, which rides the central pedestal and inverts the mask in vacuum. + background-subtracted Gaussian height, not the raw cepstral value, which rides + the central pedestal and inverts the mask in vacuum. """ from scipy.optimize import curve_fit From 990e25dae4b38d4dbd4d5ab5763c0f46d8824c83 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Mon, 1 Jun 2026 22:42:49 -0700 Subject: [PATCH 087/113] updating imports --- src/quantem/core/fitting/base.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 8a64cb3bb..9b075c6e3 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -11,9 +11,6 @@ from quantem.core.ml.optimizer_mixin import ( OptimizerMixin, - OptimizerParams, - OptimizerType, - SchedulerType, ) From 947d3af56bc83409238b5c615fd5bf9d52d16548 Mon Sep 17 00:00:00 2001 From: Miti Shah Date: Tue, 2 Jun 2026 13:52:08 -0700 Subject: [PATCH 088/113] fixing code to match new dev updates --- src/quantem/core/fitting/base.py | 11 +++++++---- src/quantem/core/fitting/diffraction.py | 12 ++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index 9b075c6e3..21f7a7f6c 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -295,15 +295,18 @@ def constraint_loss( ) -> torch.Tensor: return torch.zeros((), device=ctx.device, dtype=ctx.dtype) - def get_optimization_parameters(self) -> Any: - return [p for p in self.parameters() if p.requires_grad] + def get_optimization_parameters(self) -> dict[str, list]: + params = [p for p in self.parameters() if p.requires_grad] + if not params: + return {} + return {'default': params} def initialize_optimizer(self, optimizer_params: dict[str, Any] | None = None, scheduler_params: dict[str, Any] | None = None, num_iter: int | None = None, ) -> None: - trainable_params = list(self.get_optimization_parameters()) + trainable_params = (self.get_optimization_parameters()) if not trainable_params: self._optimizer = None self._scheduler = None @@ -359,7 +362,7 @@ def _infer_scheduler_rebuild_params(self) -> Any: } def _rebuild_optimizer_after_trainability_change(self) -> None: - trainable_params = list(self.get_optimization_parameters()) + trainable_params = (self.get_optimization_parameters()) if not trainable_params: self._optimizer = None self._scheduler = None diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index 2da095d76..a62c01f34 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -489,12 +489,14 @@ def constraint_loss_batched( return tv_loss + cutoff_loss + circular_loss - def get_optimization_parameters(self) -> Any: + def get_optimization_parameters(self) -> dict[str, list[torch.nn.Parameter]]: params = [] for name, param in self.named_parameters(recurse=True): if not name.startswith('origin.') and param.requires_grad: params.append(param) - return params + if not params: + return {} + return {'default': params} @@ -916,9 +918,11 @@ def forward_batched( device=ctx.device, dtype=ctx.dtype, ) - def get_optimization_parameters(self) -> Any: + def get_optimization_parameters(self) -> dict[str, list[torch.nn.Parameter]]: params = [] for name, param in self.named_parameters(recurse=True): if not name.startswith('disk.') and param.requires_grad: params.append(param) - return params + if not params: + return {} + return {'default': params} From aac7239153132fb8c58eabfa36e60fe823345048 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 8 Jun 2026 13:11:18 +0000 Subject: [PATCH 089/113] chore: update lock file --- uv.lock | 163 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/uv.lock b/uv.lock index 0e8424aa9..34073ea1f 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 090/113] chore: update lock file --- uv.lock | 356 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 178 insertions(+), 178 deletions(-) diff --git a/uv.lock b/uv.lock index 34073ea1f..3bec33e5f 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 091/113] 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 2255636c0..f9d87d067 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 092/113] 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 f9d87d067..aa11f0a1d 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 093/113] 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 aa11f0a1d..6ede23beb 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 094/113] 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 000000000..2e510aff5 --- /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 000000000..e660e68a2 --- /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 04abd3b58..f0b6aa2d8 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 095/113] 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 2e510aff5..000000000 --- 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 e660e68a2..000000000 --- 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 096/113] chore: update lock file --- uv.lock | 861 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 492 insertions(+), 369 deletions(-) diff --git a/uv.lock b/uv.lock index 3bec33e5f..0a6651062 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 097/113] chore: update lock file --- uv.lock | 830 +++++++++++++++++++++++++++----------------------------- 1 file changed, 407 insertions(+), 423 deletions(-) diff --git a/uv.lock b/uv.lock index 0a6651062..888375c27 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 098/113] 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 6ede23beb..a1a51bad8 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 099/113] 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 19882bf90..4df9fc229 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 888375c27..f1c50c06a 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" }, From 3a214a3ae00977b22d481da06c22a9182382fa61 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 13 Jul 2026 12:11:33 +0000 Subject: [PATCH 100/113] chore: update lock file --- uv.lock | 791 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 406 insertions(+), 385 deletions(-) diff --git a/uv.lock b/uv.lock index 888375c27..71d96affd 100644 --- a/uv.lock +++ b/uv.lock @@ -38,15 +38,15 @@ wheels = [ [[package]] name = "anyio" -version = "4.14.1" +version = "4.14.2" 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/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] @@ -130,11 +130,11 @@ wheels = [ [[package]] name = "asttokens" -version = "3.0.1" +version = "3.0.2" 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/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } 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/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, ] [[package]] @@ -205,72 +205,100 @@ wheels = [ [[package]] name = "cffi" -version = "2.0.0" +version = "2.1.0" 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/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, ] [[package]] @@ -284,91 +312,76 @@ wheels = [ [[package]] name = "charset-normalizer" -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" }, +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, ] [[package]] @@ -535,86 +548,86 @@ wheels = [ [[package]] name = "coverage" -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" }, +version = "7.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/83/6051c2a2feab48ae5bd27c84ef047191d2d4a3172f689e38eaa48ed17db1/coverage-7.15.1.tar.gz", hash = "sha256:165e9949eaf222ef1f018635d0d7f368a23bfe0212af558534c40d8c04686d67", size = 927640, upload-time = "2026-07-12T20:58:19.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/ee/5095e07a0986930174fc153fd0bb115f7f2724f5344cc672e016d4f23a4e/coverage-7.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6506330b4a8dcf53b95bd84d8d0e817107cdb3fc1438e835029cdf0bc6612eb0", size = 221320, upload-time = "2026-07-12T20:56:16.036Z" }, + { url = "https://files.pythonhosted.org/packages/43/7a/7e2598ce98433a214020a61c0755d5961f9f26df0c630dc73e06b86d3416/coverage-7.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8a51a8ec382c39d939cba0ab07ae949077ae4e842343bd4eed22d432358cea9", size = 221823, upload-time = "2026-07-12T20:56:17.54Z" }, + { url = "https://files.pythonhosted.org/packages/8b/4f/67dac6a69139b490a239d91b6d5ef127982ffb89159769241656ab879ee5/coverage-7.15.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:18ea20e3922d7f8ca9e0ef1084408d08c4ad62d5e531cb9c1f6896a99297ebea", size = 252242, upload-time = "2026-07-12T20:56:18.868Z" }, + { url = "https://files.pythonhosted.org/packages/05/37/5e439725a96de592309725550c8df639c359c209dcb4b152ed46032d4c0d/coverage-7.15.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aab9902a64b8390e3b56e539fddae1d79a267807fe5cb0c18d7d2f544ce867e2", size = 254153, upload-time = "2026-07-12T20:56:20.118Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1c/671ba9e15f9651a99962fb588a1fe4fb267cae4bb85f9bb4ac4413c6f7ef/coverage-7.15.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cc316264317b07a9e90d7f2b4188a15e36e9b54e651081b791b0515fa612a29", size = 256261, upload-time = "2026-07-12T20:56:21.682Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3c/88305322b4d015b4536b7174b8f9b87f850f01af9d89008cdbf984b65ff2/coverage-7.15.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d4e47e7eea81a8ccf060a07627654151d929da62c7b715738387c200905cec89", size = 258222, upload-time = "2026-07-12T20:56:22.962Z" }, + { url = "https://files.pythonhosted.org/packages/8d/20/d02e42333a57f266ded61ed4fb3821da1f2dc143734aaa3dcc9c117204c5/coverage-7.15.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c6829d9a3b55ad2b73ef5fda8302e5be03683789e88b1a079dcf4a773229c21d", size = 252368, upload-time = "2026-07-12T20:56:24.475Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d0/94ebc010926a191d83e83b1f1236f8bd0d14d0eb98b5c324aa4be2589a8e/coverage-7.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:764e045811f9c8cda436641f3f088283351d331a519b5807f19041cd0a68da1c", size = 253953, upload-time = "2026-07-12T20:56:26.036Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/2c8553191da0c2da2c43ff294fb9bab57a5b0fae03560304a82a8b5addc3/coverage-7.15.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:09b3b088aa24489c4082bcc35fcc8224281ab94a653dfb6d3f0c8165b0d628ab", size = 252016, upload-time = "2026-07-12T20:56:27.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/20f47986d8360fa1a31d9858dd2ba0be009d44ecfa8d02120b1eb93c028b/coverage-7.15.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7911b02f57053adf8164ae63edb1c26574d24dfccabadc5268cf69310a69a358", size = 255783, upload-time = "2026-07-12T20:56:28.921Z" }, + { url = "https://files.pythonhosted.org/packages/17/36/fb61983b5259f78fa5e62ee74e91fe4de4c556fcbc9a3b9c9021c2f8b57b/coverage-7.15.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:664e279ed40599b8ed16f4db18d92a7e212c73129672bec8f5d96d4da48d2404", size = 251735, upload-time = "2026-07-12T20:56:30.465Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2e/d109d8d2d6c726ba2e78125297073918a2a91464f0ea777e5398fa3cf75c/coverage-7.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34fe7cf79d5f1f87f2e8ce7dc1c32950841f50e10d0120f263856acfad66de34", size = 252645, upload-time = "2026-07-12T20:56:32.076Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/5b3219cbb46135e14673427f2bf5890c4da4e6f655243692f3448aa3f42c/coverage-7.15.1-cp311-cp311-win32.whl", hash = "sha256:5e2d2536d2f57a354aa382ed303ac0e2e5c9522a508c05b998d26181b94163a7", size = 223414, upload-time = "2026-07-12T20:56:33.432Z" }, + { url = "https://files.pythonhosted.org/packages/5b/80/24e13ade0b6df8090e2492f9c55044bf1423f7ef28c76fc1af8c827a61cb/coverage-7.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:c337da8fca7ea93ab43f3868cfcde6cf6dad32c3906b273cfbad5d7390bc423b", size = 223888, upload-time = "2026-07-12T20:56:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9e/34659930ae380491af48e2f5f5f9a2e99cd9fb7de3d30f27be5ac5425137/coverage-7.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:db3403fdb7a94d5eb73e099befad8104d2a7d110a0f0d99df0de61c5d1fa756c", size = 223435, upload-time = "2026-07-12T20:56:36.302Z" }, + { url = "https://files.pythonhosted.org/packages/d9/76/32c1826309beaf4604c54accef108fdd611e5e5e93f2f5192f050cd5f6bd/coverage-7.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9476292594309db922cc841dd13b303b3c388f4c25d279884f7e2341c681f80", size = 221497, upload-time = "2026-07-12T20:56:37.628Z" }, + { url = "https://files.pythonhosted.org/packages/db/5c/b88ce0d68fa550c7f3b58617fbf363bce64df5bf8295a01b627e4696e022/coverage-7.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c579056b0de461b3a62318b63d0b6ce90aed7f8158d3f00da094df82f29d189", size = 221854, upload-time = "2026-07-12T20:56:39.033Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fe/8509fd2a66fc4e0a829f76a0f0b1dc3cc163368352435b5f243168658077/coverage-7.15.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:23214bdbe226f2b0e9c66a7d6a1d59d4a88045dcf86e702cf0fe0d0935e3d615", size = 253359, upload-time = "2026-07-12T20:56:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/e5/81/c7009ed7ea9765adb2b9d095054d748266fae5f07ac6c5f925f33715fcde/coverage-7.15.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df164be93b46b4825cc39339440a05edc54c4d1d865ba4a60fd43d151a2a1cd3", size = 256096, upload-time = "2026-07-12T20:56:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/21/52/dc8ee03968a5ba86e2da5aa48ddc9e3747bd65d63825fdce2d96acb9c5ff/coverage-7.15.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a524fca1a6f08927d9dc2d4c873cfb7bd7202c247f08b14bdc02424071b8b304", size = 257211, upload-time = "2026-07-12T20:56:43.513Z" }, + { url = "https://files.pythonhosted.org/packages/b8/27/95d7623908da8937deb53d48efcdbf423907a47540e63c62fa21372c652b/coverage-7.15.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d70f3542cd38de85a9e257dcb1ac4c1ab4b6d7d2c2a645809207556628755d1c", size = 259473, upload-time = "2026-07-12T20:56:44.974Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3b/730d761928de97d585465680b568ae69622fb40716babadeabffe75cb51b/coverage-7.15.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d78aa537237212c4313aabe5e964b66acc86350ed19ebc56a3e202df33b6077b", size = 253759, upload-time = "2026-07-12T20:56:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fc/6b9277acff1f9484b6c12857af5774689d1a6a95e13265f7405329d2f5da/coverage-7.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a318112bb4f79d9d04766196d5a3388caa825908a6a9b052aa87de3d9aea7c61", size = 255131, upload-time = "2026-07-12T20:56:48.073Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f2/c704f86129594ba34e25a64695d2068c71d51c2b98907184d716c94f4aec/coverage-7.15.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e55d24cada901963eed5bc89fa562aa033f0d84b9d3de4ecf363737c13aed11e", size = 253275, upload-time = "2026-07-12T20:56:49.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/29/80fee8af47de4a6dce71ccf2938491f444687a756af258a56d8469b8f1b0/coverage-7.15.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3c78f0cea7275342cf2adc2ad5fdd0aafa106ad91e66d573568f2fcf62c41df5", size = 257345, upload-time = "2026-07-12T20:56:51.038Z" }, + { url = "https://files.pythonhosted.org/packages/20/21/a1e7d7ed1b48a8adf8fd5154d9e83fcc5ad8e6ff20ae00e44865057dce8d/coverage-7.15.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:86bd37eabe39977216f630a7fc1b698e7f5e81a191c7186013245c6c3d313f9d", size = 252844, upload-time = "2026-07-12T20:56:52.535Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8c/a4bc26e6ee207d412f3678f04d74be1550e83140563ca0e4997510579712/coverage-7.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6db15c217693bdc3ca0b84de1ba9afafe1c14c26a8a29d77f4ed0de2b6132e2", size = 254716, upload-time = "2026-07-12T20:56:53.968Z" }, + { url = "https://files.pythonhosted.org/packages/11/9d/8ad0266ecfada6353cf6627a1a02294cf55a907521b6ee0bd7b770cfd659/coverage-7.15.1-cp312-cp312-win32.whl", hash = "sha256:359f3fbe09a51500c51966596ee4ee4070b356552c70b3b2420eb200d68e0f76", size = 223554, upload-time = "2026-07-12T20:56:55.583Z" }, + { url = "https://files.pythonhosted.org/packages/81/6d/24224929e06c6e05a93f738bc5f9e8e6ab658f8f1d9b823e7b85430e28b8/coverage-7.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:fa75dc099c126e941a9c0baa8ebd2cbc78bd778687534fe410baf754f6d9e374", size = 224087, upload-time = "2026-07-12T20:56:57.041Z" }, + { url = "https://files.pythonhosted.org/packages/35/23/f81441dd01de88e53c97842e706907b307d9078918c3f4998b11e9ac7250/coverage-7.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:26f89cf6d0634375f454fa71057945ad18edb0f1607a90fecf22c57dc3dc289a", size = 223472, upload-time = "2026-07-12T20:56:58.594Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1e/6fa289d7993a2a39f1b283ddb58c4bfec80f7800be654b8ba8a9f6a07c63/coverage-7.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:71ac4ca1658ca99160fd58cc6967110e989c34b04627f24ed6ec9f70fb24571a", size = 221519, upload-time = "2026-07-12T20:57:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e1/0db4902a0588234a70ab0218073c0b20fbc5c740aa35f91d360160a2ebc9/coverage-7.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26a40cbf2b13bd94af53ee02a424cb3bb96a9edfac0d00834bd068512a62714b", size = 221895, upload-time = "2026-07-12T20:57:01.867Z" }, + { url = "https://files.pythonhosted.org/packages/b4/cb/3719783865092dac5e08df842730305ee9ab1973ae7ddb6fbdf27d401f30/coverage-7.15.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4c5a5eff4ad4f9f7088fd3fc7a66d98d06566ee294b3b053309fb0a3b45be1e", size = 252882, upload-time = "2026-07-12T20:57:03.459Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5e/caf3abbdbb22629626160ffc9c017eb995b7cb11c0be46b974834cef1792/coverage-7.15.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:962aa56c1c9b016d681265880eb6acc9966029d2c4c559319cc43a1abbb9b59a", size = 255479, upload-time = "2026-07-12T20:57:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f1/d60f375bfe095fef944f0f19427aefdbf9bdd5a9571c41a4bf6e2f5fdb81/coverage-7.15.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1678eb2dc57a8ce67601b029582ef6d41e9e6ca22692aaeccd4107e40f27386c", size = 256715, upload-time = "2026-07-12T20:57:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/d7/17/8b0cbc90d02dc5adad4d9034c1824ec3fa567771b4c39d9c1e3f9b1431b8/coverage-7.15.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1174900a43f6f8c425fee10d7dbddc308adefcdc78aaced32357f5ab750a0e90", size = 258845, upload-time = "2026-07-12T20:57:08.092Z" }, + { url = "https://files.pythonhosted.org/packages/92/29/c5e69f5fb75c322e9a3e4ef64d02eebfc3d66efceccc8514ff80a3c13a56/coverage-7.15.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98847557a6859cadf693792ce89f440cb89692993f60dc6d3a7e35f3d340216f", size = 253098, upload-time = "2026-07-12T20:57:09.636Z" }, + { url = "https://files.pythonhosted.org/packages/64/57/21144252fdd0c01d707d48fbcea13a80b0b7c42ced3f299f885ab8978c3a/coverage-7.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8697b2edb57143546a24389efc11e1b000cd5800fc20d84f04edb601e4a7cfb8", size = 254844, upload-time = "2026-07-12T20:57:11.141Z" }, + { url = "https://files.pythonhosted.org/packages/59/2a/499a28a322b0ce6768328e6c5bb2e2ad00ac068a7c7adb2ecd8533c8c5d9/coverage-7.15.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6827ac0519be3fe91bf96b4060eb00d1d24f82649b29862cd75a3cfca248b02a", size = 252807, upload-time = "2026-07-12T20:57:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/928a95da5da8b60f2b00e1482c7787b3316188e6d2d227fb8e124ada43a1/coverage-7.15.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2de8ecbbc77c7e4d22572779920ed8979c69168675e96be3a548c996568c6c31", size = 256965, upload-time = "2026-07-12T20:57:14.326Z" }, + { url = "https://files.pythonhosted.org/packages/16/10/889adbc1b8c9f866ed51e18a98bcafc0259fb9d29b81f50a719407c64ea8/coverage-7.15.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b25f0f0fa5260df9d7bb55d47c8bdc23fa3382c1a18f7c9cae122e6c320b1ad", size = 252628, upload-time = "2026-07-12T20:57:15.892Z" }, + { url = "https://files.pythonhosted.org/packages/1a/30/a5e1871e5d93416511f8e359d1ccebfe0cbb050a1bbf7dd20228533ec0cf/coverage-7.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a2effcbd93ae340a58db718fe4181d967f84d352c4cefeaab4ff82ce813901a", size = 254399, upload-time = "2026-07-12T20:57:17.703Z" }, + { url = "https://files.pythonhosted.org/packages/2d/26/c36fbffd549dadbdd1a75827528fb00a4c46aa3187b007b750b1e2cebbf2/coverage-7.15.1-cp313-cp313-win32.whl", hash = "sha256:895e65c96aef0cecea250f6e35e9a32f11375514e1a0cb5210e0fda128c04e8e", size = 223564, upload-time = "2026-07-12T20:57:19.253Z" }, + { url = "https://files.pythonhosted.org/packages/16/fc/becbb9d2c4206d242b9b1e1e8e24a42f7926c0200dd3c788b9fab4bb96d5/coverage-7.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6d0a28b63a0d75f9ed5118105d1154fc3aa40a8605a30d5d87e3d043ad90fe7", size = 224106, upload-time = "2026-07-12T20:57:21.108Z" }, + { url = "https://files.pythonhosted.org/packages/d3/30/1cfc641461369b6858799fca61c0a8b5edc490c519bf7c636ffa6bbf556f/coverage-7.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:b4ee9818e8bae3544379ad2c09b851c4fb886aaa8860d57a1c1316ddcb16db49", size = 223497, upload-time = "2026-07-12T20:57:22.734Z" }, + { url = "https://files.pythonhosted.org/packages/f0/46/81961952e7aebfb38ad0ae4264e8954cc607a7af9e7ac111f9fa986595cc/coverage-7.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a886af95f59edf67d5770fd3564d53f4a8af93f25f8c1d60d27e00d7f5674ee8", size = 221560, upload-time = "2026-07-12T20:57:24.282Z" }, + { url = "https://files.pythonhosted.org/packages/13/d2/ee14d715889f216baf47301d9f469e08fff6995552aaf67e897b282865f6/coverage-7.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:985657ebd707941de90d488d1cbb5efac20bdf81f7b91eba771624ccda4d36f4", size = 221894, upload-time = "2026-07-12T20:57:25.87Z" }, + { url = "https://files.pythonhosted.org/packages/f3/38/f830bc6e6c2c5f23f43847125e6c650d378872f7eeba8d49f1d42193e8a9/coverage-7.15.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5bbe2a06e0a5e1404d9ffbdb49b819bbd6a3bb198ebea4c8dfe7ad9f1e1c2e81", size = 252938, upload-time = "2026-07-12T20:57:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/e1/53/0d3dd963631259d794c898735d5436e68d6a8d40749c419a07ff7c171469/coverage-7.15.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bde0fe24083d0b7b3dbafa7a09f0796410af1afa2523f28f5f208d8340a4aaca", size = 255445, upload-time = "2026-07-12T20:57:29.234Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fd/aabed228557565c958259251b89bab8c5669b31291fa63b3e2154ebb017a/coverage-7.15.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f89f7453d6d46db14cf233e2cd8edcd78de2b9c49d4f1dc109590b4e5dbfbb74", size = 256790, upload-time = "2026-07-12T20:57:30.826Z" }, + { url = "https://files.pythonhosted.org/packages/bc/aa/1cc888e5d3623e603c4e5399653cb25728bb2b40d7519188a3e293d24620/coverage-7.15.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc3656c9ecc27b36bd0907455b77f83c0069ca9ad4a66dec892b76c696eb6047", size = 259104, upload-time = "2026-07-12T20:57:32.63Z" }, + { url = "https://files.pythonhosted.org/packages/5f/61/fc16d5f5e53098dae41efa21e8ccc611a9b4fe922750dd03dc56db552182/coverage-7.15.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:24d8e85a2a45e44883b488c2659f51fa761dad5353fdb319b672a93facbd2ca9", size = 252956, upload-time = "2026-07-12T20:57:34.316Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f3/52384668c3de4519ca770bf1975a89e4d6eb5aa2faf0da0577a14008cba4/coverage-7.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68931b5fe746ed4fdaa8892989cab9e6c35781eeb3b0ab2ded893d561e1b3652", size = 254797, upload-time = "2026-07-12T20:57:35.947Z" }, + { url = "https://files.pythonhosted.org/packages/ce/68/54b807e7c1868178e902fd8360b5d4e559394462f97285c50edf1c4608db/coverage-7.15.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1ce6947e2a95534ecaa5a15e73c21e550514c980d80eda204d064d789a95f6a4", size = 252762, upload-time = "2026-07-12T20:57:37.856Z" }, + { url = "https://files.pythonhosted.org/packages/7b/48/dde8adf0338e3ace738757dccf1ce817e5fdcadfae77e1b48a77e5a3b265/coverage-7.15.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:841befdbc89b9c82435fc25b0f4f41858b6238693e45af758bec4cfc1968171c", size = 257037, upload-time = "2026-07-12T20:57:39.488Z" }, + { url = "https://files.pythonhosted.org/packages/07/f2/179dd88cf60a0aeeee16a970ffe250dccea8b80ed4beab4c5d3f6c41ad4b/coverage-7.15.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5d3de58b837375e7f4c0e1a088ccab5f655efb2fd7427b729df02c862a559633", size = 252577, upload-time = "2026-07-12T20:57:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/2c/37/8a593d69ab521beb6a105a2017cac4ba94425ee0a8349e29c3c0b522d24f/coverage-7.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b1801963f9f44ae0c0f6d737bc7aeb2bbcde7d1fe7e3b43cddc1961af42d3b41", size = 254235, upload-time = "2026-07-12T20:57:43.025Z" }, + { url = "https://files.pythonhosted.org/packages/6d/34/bc9b3bced66f2cdad4bf5e57ae51c54ea226e8aaaebfc9370a9a11877bf3/coverage-7.15.1-cp314-cp314-win32.whl", hash = "sha256:8c7953c4128ef53b6ffb5f90d87c87d4ce26731df294760bb2314eb0e069e44b", size = 223771, upload-time = "2026-07-12T20:57:44.662Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/4d20337bed61915d14349e62b88d5e4144d5a9872b64adbe90e9906db6db/coverage-7.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:6f0bab60a582d415f0fb535ccff13ba334a47a1538f98913330a525d23bd535a", size = 224257, upload-time = "2026-07-12T20:57:46.412Z" }, + { url = "https://files.pythonhosted.org/packages/7b/df/bbfeae4948f3ded516f92b32f2d57952427fc5ecfc0924487bb6ee6a5f38/coverage-7.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:0f410ee8f0ac4ec7db71bc0b7632a8b9994e1cad2755bd1566c17e6a162caa74", size = 223683, upload-time = "2026-07-12T20:57:48.106Z" }, + { url = "https://files.pythonhosted.org/packages/35/65/0b431856064e387d1f5cf474625e4a0465e907024d42f35de6af19ced0be/coverage-7.15.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fc868bab88e049d41fcd41766810d790a8b960053be2a45e060f5ce0d31d258b", size = 222298, upload-time = "2026-07-12T20:57:49.882Z" }, + { url = "https://files.pythonhosted.org/packages/a6/96/50eac9bd49df8a3df5f3d38746d1bf332299dffb554486c94ebd55c9dc49/coverage-7.15.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:206d4ec6028f2773b40932d09f074539d6bcdd8f6b318d40cb04bdbd68ed0b49", size = 222561, upload-time = "2026-07-12T20:57:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5b/6ba1c4a27e10b8816fd2622b98162c83d3bdf1185097360373611bf96364/coverage-7.15.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:620482ef1c9f4e61f962e159325fe77dea59d16e39d9c9470d069053b244d864", size = 263923, upload-time = "2026-07-12T20:57:53.392Z" }, + { url = "https://files.pythonhosted.org/packages/e4/59/fe03ade97a3ca2d890e98c572cf48a99fda9adba85757c34b823f41efe1e/coverage-7.15.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d385fc9b054e309ad3cecdc77b586d2af0c98aeec2fdb3773544586f366e817c", size = 266043, upload-time = "2026-07-12T20:57:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/16/e0/55c4b1217a572a43e13b39e1eb78d0da29fb23679003bd0cdf22c50b1978/coverage-7.15.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1198bca9c0dd7c188aae1f185b0c0b5fc4f0a2b6909000858c29550320bdb07", size = 268465, upload-time = "2026-07-12T20:57:57.017Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/ee47944f76afc03909119b036fe9e0da8cbd274a5141287de79791a0fb6d/coverage-7.15.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d0297e6a070eadb49df7cddd0ab6f420b8b689dd8904c7dd815a323168fa57e", size = 269584, upload-time = "2026-07-12T20:57:58.958Z" }, + { url = "https://files.pythonhosted.org/packages/cb/8a/6b4d9779c7b2e21c3d12c3425e3261aa7411399319e27aa402dfec4db5d0/coverage-7.15.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916fcf2214f56960e409561b37fc32a160a42b6e85483d0652d7b70fa55d707e", size = 263019, upload-time = "2026-07-12T20:58:00.979Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1e/db5c7fa0c8ba5ece390a1e1a3f30db71d440240a80589df28e66a7503c40/coverage-7.15.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f837bae572c7869ffaa502e604c87e182543012831cf87aae4586ad090ac6dcf", size = 265916, upload-time = "2026-07-12T20:58:03.005Z" }, + { url = "https://files.pythonhosted.org/packages/83/53/fe5176682b00709b13fab36addd26883139d0dea430816fea412e69255e2/coverage-7.15.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3ea65e3ee6c7c32349fd00559927a9e577bdd72386087eeed1c42b62dfce9b82", size = 263520, upload-time = "2026-07-12T20:58:04.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e7/16f15127be93fbc70c667df5ec5dce934fc76c9b0888d84969a5d5341e2c/coverage-7.15.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:345034976f46a1c54bd17f4e43eb30bb92cb7082fcddff03250cff136cc4eb82", size = 267254, upload-time = "2026-07-12T20:58:06.824Z" }, + { url = "https://files.pythonhosted.org/packages/cd/73/e5119111f6f065376395a525f7ce6e9174d83f3db6d217ea0211a61cca4d/coverage-7.15.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4f051a64eb8f8addb4661c2b41d6eea5b7ebc68ad4b2baea8d9bc54e1956e5f7", size = 262366, upload-time = "2026-07-12T20:58:08.555Z" }, + { url = "https://files.pythonhosted.org/packages/d7/9c/6d0a81182df18a73b081e7a8630f0e2a52b12dfd7898c6ab839551a454d2/coverage-7.15.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a7625770f7720b49bb30d194ad2f8d50fab3c5177874af3d2399676f95f9c594", size = 264680, upload-time = "2026-07-12T20:58:10.359Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a2/bac0cbd4450638f1be2041a464b1766c8cc94abf705a2df6f1c8d4be870d/coverage-7.15.1-cp314-cp314t-win32.whl", hash = "sha256:81e503d130a472ad1bd38199ecd35116b40d92bcd31e27a2cacde035381f2070", size = 224077, upload-time = "2026-07-12T20:58:12.065Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b2/d83c5403155172a43ba47c08641bad3f89822d8405102423a41339d2c857/coverage-7.15.1-cp314-cp314t-win_amd64.whl", hash = "sha256:724e878b213b302ad46e9f2fc872d386613f20ebfc492a211482d917ea76c14f", size = 224908, upload-time = "2026-07-12T20:58:13.956Z" }, + { url = "https://files.pythonhosted.org/packages/cc/41/442b74cad832cc77712080585455482e7cc4f4a9a13192f65731dcd18231/coverage-7.15.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ce2f05c14d077f406fefc4fa5e4f093ad0e0787549f6582535d6e28766f0361b", size = 224219, upload-time = "2026-07-12T20:58:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/34/98/07a67cf1a26e795d617ed5c540c042b0ac87b72f810c30c07f076cf334f3/coverage-7.15.1-py3-none-any.whl", hash = "sha256:717d01e6e00bed56ad13306f19e0dd2f4f645ee8159d2c72c72301d6cfc7090c", size = 213284, upload-time = "2026-07-12T20:58:18.079Z" }, ] [package.optional-dependencies] @@ -652,42 +665,51 @@ wheels = [ [[package]] name = "cuda-toolkit" -version = "13.0.2" +version = "13.0.3.0" 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, upload-time = "2025-12-19T23:24:07.328Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" }, ] [package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] cudart = [ - { name = "nvidia-cuda-runtime" }, + { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cufft = [ - { name = "nvidia-cufft" }, + { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cufile = [ - { name = "nvidia-cufile" }, + { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cupti = [ - { name = "nvidia-cuda-cupti" }, + { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] curand = [ - { name = "nvidia-curand" }, + { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cusolver = [ - { name = "nvidia-cusolver" }, + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] cusparse = [ - { name = "nvidia-cusparse" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] nvtx = [ - { name = "nvidia-nvtx" }, + { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, ] [[package]] @@ -701,7 +723,7 @@ wheels = [ [[package]] name = "dask" -version = "2026.6.0" +version = "2026.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -713,9 +735,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/b5/aa50877159f1efd65a744107e0662a15d2b80fa0c427d980313367f49493/dask-2026.7.0.tar.gz", hash = "sha256:b039970642ce97a063070bae2763dada8aae27e3cbd8ff6d894072f920a1e252", size = 11548914, upload-time = "2026-07-06T16:58:36.496Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/90/aa/ceeb81d9dc886d445416310f0eff5c3978f20224b4143e96e36b7e85b011/dask-2026.7.0-py3-none-any.whl", hash = "sha256:68def8b467529ab8425ef9fcc6735dcaefaaf715d5e19ae219485f99a394dbef", size = 1496878, upload-time = "2026-07-06T16:58:34.597Z" }, ] [package.optional-dependencies] @@ -817,11 +839,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.5" +version = "3.29.7" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/35/94/00f2059e4835eace3ae8fde680b932c496f8ec7bdc99168dfa53fb2e6b79/filelock-3.29.7.tar.gz", hash = "sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d", size = 71521, upload-time = "2026-07-08T05:46:58.716Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/60/02/be4a57b60c7149b55b9e3b3c13f609cd8eb5307c751f22bd8fb8d262e75b/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51", size = 46036, upload-time = "2026-07-08T05:46:57.53Z" }, ] [[package]] @@ -1010,53 +1032,53 @@ wheels = [ [[package]] name = "grpcio" -version = "1.82.0" +version = "1.82.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -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" }, +sdist = { url = "https://files.pythonhosted.org/packages/90/bc/656b89387d6f4ed7e0686c7b64c2ae7e554a759aa58122c8e5fb99392c32/grpcio-1.82.1.tar.gz", hash = "sha256:707b24abd90fcb1e45bcc080577da1dbf9971d107490589b9539af8e1e77b4b5", size = 13187300, upload-time = "2026-07-08T12:36:16.588Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/5b/e5092af97fa671ca279b3e373251af4bf87d5fbda7dc85f6a616899562a7/grpcio-1.82.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:0ddb18a9a9e1f46692b3567ae4abb3f8d117ce6afea48650f8eca06d8ab5d06f", size = 6181472, upload-time = "2026-07-08T12:34:31.009Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/18053a3a2ca03d0c2a1b8cc7271e705007a16aa5dae84bac00935c5b1a7f/grpcio-1.82.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cf855b1af246720f567b0ce5d0724d45dfa4188eecc3296a2a69257b11b9e94b", size = 11970995, upload-time = "2026-07-08T12:34:33.603Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/21b1acb052876ad00959ec4d1b05fe08607d650bcfa282073bb164c2703c/grpcio-1.82.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb30cb13e25bc13cea70ffc69d6d90c49d36ea6c1d4549e6912f70177834cac", size = 6760127, upload-time = "2026-07-08T12:34:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/3e/12/25eef9c245c54f0061317d13a302357fe8ea03bac240b2b02ececcf54da4/grpcio-1.82.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1e822b2774f719c017cbe700b6e47173b6ae290fb84906f52a5a3c2c60b62e1e", size = 7484377, upload-time = "2026-07-08T12:34:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/1a348767eb9d9bd7765dc4fa8a01723d3bb386d67f981ee5c6f9c02b8b1c/grpcio-1.82.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5dafb1ece8ed45dee7c738f166ec82e19673221ed5ab8967f72858a4685345b2", size = 6924269, upload-time = "2026-07-08T12:34:40.583Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b9/3aae7a03d34c86ea27988db859a6087c186f6c3f53f9b551e07afd989bfa/grpcio-1.82.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06503106e7271e0a49fd5a1ac04747f1e47e87d900476db6fe45bc87ee411f4", size = 7531848, upload-time = "2026-07-08T12:34:43.277Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/3c4afa625d0dac9090707966916284c035fc5b2fb3e2c51e156accee6735/grpcio-1.82.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ff99bc8cafb6a952201c37b995f425e641c93ffa6e072258525feab57290141d", size = 8568217, upload-time = "2026-07-08T12:34:45.502Z" }, + { url = "https://files.pythonhosted.org/packages/20/9c/d8489c628e73e20a3d034e7f66912de7b1acb405f01d388f056a88e47924/grpcio-1.82.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:644ae1b94266ac785330f4590a69e52b6a7eb73029043a02209db81c81397d69", size = 7938771, upload-time = "2026-07-08T12:34:48.323Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b7/0a92cfd1658f3a896d4aa12d4efeb7dd4ddfc723725ae22741a5241ea710/grpcio-1.82.1-cp311-cp311-win32.whl", hash = "sha256:e203d2e19d471630084a16c815616f8211dff21c268ab3c5f5bf38417832e074", size = 4256432, upload-time = "2026-07-08T12:34:50.432Z" }, + { url = "https://files.pythonhosted.org/packages/c7/6a/2872c761b025d9ec74386f22a4a7d59c5a5b00ebf718761b33739ffc45de/grpcio-1.82.1-cp311-cp311-win_amd64.whl", hash = "sha256:0d8299c285fe6cc6a1f56badf8d3bc5078c8d20273ee64bafa3783b4bc29a769", size = 5009633, upload-time = "2026-07-08T12:34:52.67Z" }, + { url = "https://files.pythonhosted.org/packages/dc/88/d1350bf3343a2ed87d801584e40609f6c6bd3087926eeca03de50348cf4a/grpcio-1.82.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:c09bd5fa0d5b1fbd773ec349fe61441c3e4ebf168c229aa7538a820bdfad6a58", size = 6144689, upload-time = "2026-07-08T12:34:55.567Z" }, + { url = "https://files.pythonhosted.org/packages/e6/33/71875cdecd27c24ac1385d4783a09853f01b84a825a36aec2a2bc7d0d080/grpcio-1.82.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1eae24810720734598e3e6a1a528d5de0f265fe3fc86575e9ecce424b9ec7379", size = 11952034, upload-time = "2026-07-08T12:34:58.128Z" }, + { url = "https://files.pythonhosted.org/packages/82/b2/d9125df3d8a140dec12cc82c05b7deafedeababcff6496f28b2fd5634d10/grpcio-1.82.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a6bd5daf5bde7b24d7ad2cbaf8bf9eac620d96222016bb5e7ddde930dec0673f", size = 6710772, upload-time = "2026-07-08T12:35:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/88/9b/69e2d1627398b964f34437dc476a5aff5a2cc8e7f247d26272b5674b5faf/grpcio-1.82.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1ecfde669cb687ac020d31ff76debe5dc7a62213335f02262eb6625628da1c03", size = 7450677, upload-time = "2026-07-08T12:35:03.926Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e7/8f855ca29c294956122a2a73023655b9b02602d5111dad2b9b00e7631c68/grpcio-1.82.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:011c8badee95734dee8bf05ce3464756a0ac3ebb8d443afd20c0e2b5e4640ad9", size = 6886855, upload-time = "2026-07-08T12:35:06.174Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f0/fa87e85f49925f44c479d07e58b051e69bcfef6b6d5fbc6749d140f6730a/grpcio-1.82.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b85f4564926fb23114d239392bdcae200db1e6179629edd7d7ab0ab89c96a197", size = 7501323, upload-time = "2026-07-08T12:35:08.49Z" }, + { url = "https://files.pythonhosted.org/packages/67/55/2e0b10ae1d3ef9dcc480b91dc2158f4931fc4675d3af0a2836e39b2a744f/grpcio-1.82.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2c0c8270833395644c3fe6b6a806397955a2bc0538000a19a78b90c05a6c16e0", size = 8536899, upload-time = "2026-07-08T12:35:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/a3d8b0431fa221efc51ee39d73595ede74ba82a43b7c4313192e580face2/grpcio-1.82.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2ba199205ff46c7778290fe1673c91ac8e7e45678dd5c86e9e56fa33ec8788f6", size = 7913892, upload-time = "2026-07-08T12:35:13.944Z" }, + { url = "https://files.pythonhosted.org/packages/b8/92/f2651ec704d9852a56faef394775038afba435b50ce82ab2404d119c3355/grpcio-1.82.1-cp312-cp312-win32.whl", hash = "sha256:06127691866e295c14e84a1fb86356dd962254f6abd0da4ca4b001eea9e89438", size = 4240985, upload-time = "2026-07-08T12:35:16.048Z" }, + { url = "https://files.pythonhosted.org/packages/96/4f/a5fe8bf0d0a1b24855f370293075c931f27de4eb55f0f158786095bf3c11/grpcio-1.82.1-cp312-cp312-win_amd64.whl", hash = "sha256:1fa3223a3a2e1db74f4c2b255189eb7ea875dfba56e221d252ee3fc7b204778e", size = 5001580, upload-time = "2026-07-08T12:35:18.689Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3e/496992d08c0aaa11272eb6228dc8ab947da01fe835de243cd00521bce4c4/grpcio-1.82.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b454a2d97bfab7565683a02345f86bd182ab69fd7c2bdb7414171e7538f266b1", size = 6146068, upload-time = "2026-07-08T12:35:21.365Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8f/f263d6f14fdba6b56cfadd91fd3e158a52682b72c6016d1f8723d435659f/grpcio-1.82.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3dde70abfc80b3be11de53ba0d601c439e7fb2afd3583ad1788d1146bec92fdc", size = 11948600, upload-time = "2026-07-08T12:35:24.312Z" }, + { url = "https://files.pythonhosted.org/packages/8c/14/3a02e6ee49c2d85bc15eaae321e0e11ab3542cad3c5b2de121ecce0c4296/grpcio-1.82.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5523099c98c292ea1ae08e617249db760c56a78f8deae879027fe7d1ffbcbf6", size = 6714591, upload-time = "2026-07-08T12:35:27.027Z" }, + { url = "https://files.pythonhosted.org/packages/69/80/58e3738696f48ab7645347b98d8a7f93d10e00e6218388fbfcd6c9310e3d/grpcio-1.82.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5e5c4dc0a59b0f8490a6bdfd6fc8395b9d8ad8a8407c7d67ca7b5bba15c0877f", size = 7454995, upload-time = "2026-07-08T12:35:29.599Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6c/2557c1a889363072fbf2285ecd0e8c44860d4dbd60f017a32537c5b863e2/grpcio-1.82.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c40d94ba820329cc191981bc22fa6f6eed0799c6d921f3c6709521d59d4a2fd7", size = 6888621, upload-time = "2026-07-08T12:35:32.38Z" }, + { url = "https://files.pythonhosted.org/packages/d2/66/907706ccaff1223f1e10fd5b37fc16faead43392fccb4e786e7e390ac141/grpcio-1.82.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c816180e31e273caaec6f8bd86a8392499d5bbb26f41da44e3dce48bde69095", size = 7505069, upload-time = "2026-07-08T12:35:35.072Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7c/ff97b0d0f635987ee5ec80dfedafa1aad629303745d48e8637d10eec5b80/grpcio-1.82.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e31fd780b261830720cb70b0fd8f0aa51d49e75a66d7464ad2e31d4b765f2580", size = 8535384, upload-time = "2026-07-08T12:35:37.954Z" }, + { url = "https://files.pythonhosted.org/packages/62/9e/a97fddd970a8d1588cade06eca20443761c1858b0ad6590a5c835aa18062/grpcio-1.82.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d76152d7c31d7210d4a106e5d8b64da5bba5d6abf11be30e2f7b0a0c59bbcbf", size = 7910707, upload-time = "2026-07-08T12:35:40.797Z" }, + { url = "https://files.pythonhosted.org/packages/20/e4/eaba1517888af483a88d449eb7566f0f7f63446d46f339c5891798435875/grpcio-1.82.1-cp313-cp313-win32.whl", hash = "sha256:38e9dcb5258226fb3282630b31b16a968df52c8c6ad514af540646e0a4578f8a", size = 4240363, upload-time = "2026-07-08T12:35:43.298Z" }, + { url = "https://files.pythonhosted.org/packages/b0/42/66a98d47732e35290bef722f6149fed3709cd4cf61166f6f53a12f417302/grpcio-1.82.1-cp313-cp313-win_amd64.whl", hash = "sha256:3dbfb52c36d9511ac2b8e6c94fdde837b393ae520cc321f52a333a2deedf5a90", size = 5000980, upload-time = "2026-07-08T12:35:46.262Z" }, + { url = "https://files.pythonhosted.org/packages/b4/cb/cf9ae9e164c6e6dc8a494faa9771763df9da150eefe19671009624d1559f/grpcio-1.82.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:35f990f7784c8fd2872644f07f96ebb4d9e48e145a190ab80d0280af91a1bfb2", size = 6146901, upload-time = "2026-07-08T12:35:49.261Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/eccf26dbcfb7f7cab8027c5490a16c8937c5aa7a2ec20a3eab2cf7a43165/grpcio-1.82.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:46536a4a1f4434df3c851b9254ff6fc7df5705b273681a15ca277d5921c178a0", size = 11954756, upload-time = "2026-07-08T12:35:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/ad/75/3b3b4a3cc9f084b026af96e1d3e539b1af29ec7f41ed0dfff3cb99cc8626/grpcio-1.82.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d6650a7c1ebb7921c70e12a385439a8118efb99e669fa9ed31cf25db1843937c", size = 6723087, upload-time = "2026-07-08T12:35:54.973Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/b0f0c9b1400a99a4da4c09b114f101b192f8f11192e76f620b8962f5d90b/grpcio-1.82.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b8e110c66df5204c0506d6c8787b35d48b8b699ef5aa366d6c4d67325c67fe9a", size = 7454542, upload-time = "2026-07-08T12:35:57.586Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bd/428e38868382aa193697a5aa53973f29c58e58ba4268aa0c86a2715ee58b/grpcio-1.82.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f853eae07235a51a27bb5d6a9a175a59ca55dc9b99edc6ce2f76f07332d333ae", size = 6889588, upload-time = "2026-07-08T12:36:00.012Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/03e01d5e10259bf5c08ee50570cc94724e79c956f61fd2f09b341af0956c/grpcio-1.82.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:60b0f2c95337694fc094b77d9f60f50566c84b5677393e342eb98daeee242d98", size = 7514166, upload-time = "2026-07-08T12:36:02.693Z" }, + { url = "https://files.pythonhosted.org/packages/ff/59/278b4b600329e2ba3849f3c1ea3c820b3a01b38a7ad184ba09595e8d2733/grpcio-1.82.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b064fc444812bdaa9825d33c26f8d732d63ee6a5d78557c1faf92c98687fed27", size = 8536166, upload-time = "2026-07-08T12:36:05.349Z" }, + { url = "https://files.pythonhosted.org/packages/44/27/7ccf2ef00f27a8e47a79d641c8ceaf7d3028c7a03d9a97b4c8a9a783c086/grpcio-1.82.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d7ede11d747b4e1bd05e3bc0260e155b65a88735a895a10f6521f19b889511e", size = 7912572, upload-time = "2026-07-08T12:36:08.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/be/33742482d2753f2d3a1b7641664b6622262d44f2f3b609f13425dd86d36f/grpcio-1.82.1-cp314-cp314-win32.whl", hash = "sha256:3d21f19838dc255ecbb79321b15ae9b98fbddff4c3d4aedb0a81bdd7f4ab572a", size = 4321856, upload-time = "2026-07-08T12:36:10.899Z" }, + { url = "https://files.pythonhosted.org/packages/cc/67/03329c847172c78ddeb1eb9be6b444fdbc12775a84c958b27e427e7b926d/grpcio-1.82.1-cp314-cp314-win_amd64.whl", hash = "sha256:e20f1edbb15f99e3128ec86433f9785fd5a451d8f115e74fe0056134f092a9d5", size = 5141114, upload-time = "2026-07-08T12:36:13.595Z" }, ] [[package]] @@ -1389,15 +1411,15 @@ wheels = [ [[package]] name = "jupyter-builder" -version = "1.0.2" +version = "1.1.0" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/df/db5efc4e28803a1421350445e53d85ec571a4e6928e3524ccc39f4e16584/jupyter_builder-1.1.0.tar.gz", hash = "sha256:b996e8af616900f18724fa34883169d869be2205497940bec78a3a1031eb897d", size = 971142, upload-time = "2026-07-11T07:24:02.4Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/7c/fb/53adbc72931b4429275b160044c3e1bcbc8fbb5ea6b8f318a357834842fb/jupyter_builder-1.1.0-py3-none-any.whl", hash = "sha256:d9d5280e8845f726663545c3a6db55bd205127616eeb8958481091d6cba71b6a", size = 912964, upload-time = "2026-07-11T07:24:00.279Z" }, ] [[package]] @@ -1887,11 +1909,11 @@ wheels = [ [[package]] name = "mistune" -version = "3.3.2" +version = "3.3.3" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/a5/2dab368d6950e6808904dec98f54c7e726ee7be4a0c6afe00e6e011bd52d/mistune-3.3.3.tar.gz", hash = "sha256:c4c6c0c840b8637a2e9b8b6d607eb7c8f00888bf14c754409bcd339e848c2477", size = 115363, upload-time = "2026-07-09T06:18:05.268Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/89/70/b1e4737b84163db5bb1dfde6f216dbfbf32783330a9989c965e121172830/mistune-3.3.3-py3-none-any.whl", hash = "sha256:99de1585e42dcbd826faa9e11a202727a5e202e4e4722a4c69ac1ff615793dd7", size = 63569, upload-time = "2026-07-09T06:18:03.839Z" }, ] [[package]] @@ -2294,11 +2316,11 @@ wheels = [ [[package]] name = "nvidia-nvjitlink" -version = "13.0.88" +version = "13.3.33" 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, 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" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/580ca6f29dcab0221db8706badca1bbbb084f1975c4d4e83329c3a7e31f0/nvidia_nvjitlink-13.3.33-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:26a6de7fb4c8fdaa7703d3dad720d6d427ddfea5c48a528fd97c11733ad830e5", size = 40742423, upload-time = "2026-05-26T16:54:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/69/30/45414e35ff2eee7db3da037e5707037ccf9d2b5218ffbdb055ea4d5aa98a/nvidia_nvjitlink-13.3.33-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce48b37dfeb3cb1eae4cf85adacb47d7a6539ea2272870c9a3628ce275c2037e", size = 39168635, upload-time = "2026-05-26T16:54:13.906Z" }, ] [[package]] @@ -2735,15 +2757,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.3" +version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/81/58c70036dffeccb7fe7d79d6260c69f7a28272bbd3909c29a01ea9422744/python_discovery-1.4.4.tar.gz", hash = "sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3", size = 72212, upload-time = "2026-07-08T23:06:50.691Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/9d/ae/84bc0d2440c95772272bb6f4b3d09ccf08b2898fce89b3d4f969a9fc74e9/python_discovery-1.4.4-py3-none-any.whl", hash = "sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe", size = 34181, upload-time = "2026-07-08T23:06:49.402Z" }, ] [[package]] @@ -3240,27 +3262,27 @@ wheels = [ [[package]] name = "ruff" -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" }, +version = "0.15.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/36/6f65aa9989acdec45d417192d8f4e7921931d8a6cf87ac74bce3eed98a8e/ruff-0.15.21.tar.gz", hash = "sha256:d0cfc841c572283c36548f82664a54ce6565567f1b0d5b4cf2caac693d8b7500", size = 4769401, upload-time = "2026-07-09T20:01:34.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/c6/ede15cac6839f3dbce52565c8f5164a8210e669c7bc4decb03e5bdf47d0d/ruff-0.15.21-py3-none-linux_armv6l.whl", hash = "sha256:63ea0e965e5d73c90e95b2434beeafc70820536717f561b32ab6e777cb9bdf5d", size = 10854342, upload-time = "2026-07-09T20:00:53.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/9d/d825b07ee7ea9e2d61df92a860033c94e06e7300d50a1c2653aac27d24fe/ruff-0.15.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0f212c5d7d54c01bbfe6dcab02b724a39300f3e34ed7acbe995ccb320a2c58bd", size = 11139539, upload-time = "2026-07-09T20:00:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/3b107712e642f063c7a9e0887c427b22cb44097de5aab36c05f2e280670c/ruff-0.15.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e6312e41bc96791299614995ea3a977c5857c3b5662b1ecef6755b02b87cb646", size = 10595437, upload-time = "2026-07-09T20:01:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6f/b4523cc90ba239ede441447a19d0c968846a3012e5a0b0c5b62831a3d5e3/ruff-0.15.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d65b4831c6b2a4ba8ee6faa84049d44d982b7a706e622c4094c509e51673be", size = 10990053, upload-time = "2026-07-09T20:01:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/92/cc/c6a9872a5375f0628875481cf2f66b13d7d865bf3ca2e57f91c7e762d976/ruff-0.15.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c5a913a589120ce67933d5d05fd6ddbcc2481c6a054980ee767f7414c72b4fd", size = 10666096, upload-time = "2026-07-09T20:01:04.299Z" }, + { url = "https://files.pythonhosted.org/packages/ab/97/c621f7a17e097f1790fa3af6374138823b330b2d03fc38337945daca212c/ruff-0.15.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef04b681d02ad4dc9620f00f83ac5c22f652d0e9a9cfe431d219b16ad5ccc41", size = 11537011, upload-time = "2026-07-09T20:01:06.771Z" }, + { url = "https://files.pythonhosted.org/packages/ea/51/d928727e476e25ccc57c6f449ffd80241a651a973ad949d39cfb2a771d28/ruff-0.15.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16d090c0740916594157e75b80d666eab8e78083b39b3b0e1d698f4670a17b86", size = 12347101, upload-time = "2026-07-09T20:01:08.859Z" }, + { url = "https://files.pythonhosted.org/packages/1e/88/8cd62026802b16018ad06931d87997cf795ba2a6239ab659606c87d96bf0/ruff-0.15.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a10e74757dd65004d779b73e2f3c5210156d9980b41224d50d2ebcf1db51e67", size = 11572001, upload-time = "2026-07-09T20:01:11.092Z" }, + { url = "https://files.pythonhosted.org/packages/b2/97/f63084cf55444fc110e8cb985ebfcc592af47f597d44453d778cb81bc156/ruff-0.15.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab0905d2f29e0d9fbc3c373ed23db0095edaa3f71f1f4f519ec15134d9e85c8", size = 11549239, upload-time = "2026-07-09T20:01:13.27Z" }, + { url = "https://files.pythonhosted.org/packages/9d/77/f107da4a2874b7715914b03f09ba9c54424de3ff8a1cc5d015d3ee2ce0ac/ruff-0.15.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:00eca240af5789fec6fe7df74c088cc1f9644ed83027113468efba7c92b94075", size = 11535340, upload-time = "2026-07-09T20:01:15.206Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e9/601deb322d3303a7bf212b0100ead6f2ee3f6a044d89c30f2f92bf83c731/ruff-0.15.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262ab31557a75141325e32d3357f3597645a7f084e732b6b054dde428ecd9341", size = 10964048, upload-time = "2026-07-09T20:01:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2e/0f2176d1e99c15192caea19c8c3a0a955246b4cb4de795042eeb616345cd/ruff-0.15.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:659c4e7a4212f83306045ec7c5e5a356d16d9a6ef4ae0c7a4d872914fc655d9d", size = 10667055, upload-time = "2026-07-09T20:01:19.73Z" }, + { url = "https://files.pythonhosted.org/packages/48/60/abd74a02e0c4214f12a68becfd30af7165cfdcb0e661ecdc60bbb949c09a/ruff-0.15.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9e866eab611a5f959d36df2d10e446973a3610bc42b0c15b31dc27977d59c233", size = 11242043, upload-time = "2026-07-09T20:01:21.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c6/583075d8ccabb4b229345edcaf1545eb3d8d6be90f686a479d7e94088bbf/ruff-0.15.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e89bc93c0d3803ba870b55c29671bad9dc6d94bb1eb181b056b52eb05b52854f", size = 11648064, upload-time = "2026-07-09T20:01:24.023Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3c/37d0ecb729a7cc2d393ea7dce316fc585680f35d93b8d62139d7d0a3700c/ruff-0.15.21-py3-none-win32.whl", hash = "sha256:01f8d5be84823c172b389e123174f781f9daf86d6c58719d603f941932195cdd", size = 10896555, upload-time = "2026-07-09T20:01:26.941Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b8/e43466b2a6067ce91e669068f6e28d6c719a920f014b070d5c8731725de3/ruff-0.15.21-py3-none-win_amd64.whl", hash = "sha256:d4b8d9a2f0f12b816b50447f6eccb9f4bb01a6b82c86b50fb3b5354b458dc6d3", size = 12038772, upload-time = "2026-07-09T20:01:29.497Z" }, + { url = "https://files.pythonhosted.org/packages/dd/75/e90ab9aeece218a9fc5a5bc3ec97d0ee6bb3c4ff95869463c1de58e29a1c/ruff-0.15.21-py3-none-win_arm64.whl", hash = "sha256:6e83115d4b9377c1cbc13abf0e051f069fab0ef815ea0504a8a008cee24dd0a8", size = 11375265, upload-time = "2026-07-09T20:01:31.772Z" }, ] [[package]] @@ -3472,11 +3494,11 @@ wheels = [ [[package]] name = "setuptools" -version = "81.0.0" +version = "83.0.0" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, ] [[package]] @@ -3724,46 +3746,45 @@ wheels = [ [[package]] name = "torch" -version = "2.12.1" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, - { name = "cuda-toolkit", extra = ["cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "cuda-bindings", marker = "python_full_version < '3.15' and 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", 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'" }, { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, { name = "setuptools" }, { name = "sympy" }, - { name = "triton", marker = "sys_platform == 'linux'" }, + { name = "triton", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/1e3160e18e12aa3038390efab3ce02b36a9d4d6a527ecdd8520dca2e68d8/torch-2.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:092790c696a760c729fd5722835f50b9d81fd7c8f141571f3f3cf4081a8f664c", size = 427199369, upload-time = "2026-07-08T16:04:51.054Z" }, + { url = "https://files.pythonhosted.org/packages/01/79/1f2d34ad7034ee1c7ffc1cf8bf0f8213af2a81df6ecdb3997ecec107c09d/torch-2.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60fcdcb2f3876e21146cb4524ef06397d727ca9ad5f020818547e25075fe3cb7", size = 526574961, upload-time = "2026-07-08T16:04:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fd/0f2ce40f58aefbdb3392f9acce3c8171940943ae2d661f70558bfa73befb/torch-2.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a0d8b11f16a48d60e2015d8213aa0390744cbebb98e58b62b3514dddc656e330", size = 122015870, upload-time = "2026-07-08T16:05:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/ed0f4d4d1dcde03bced7aac9a28e800abcdc0cbd06b6775044c9fbd877b7/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", size = 111213045, upload-time = "2026-07-08T16:05:22.997Z" }, + { url = "https://files.pythonhosted.org/packages/df/a9/f6a2a4d763ff1df02e9a64c477029db614295bc9367f4131223791ccc243/torch-2.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:572df8be8ffb4599c88cbd6a0726f1f854f4da65d2e3c09f0e2c2283333cd6d4", size = 427210998, upload-time = "2026-07-08T16:04:37.708Z" }, + { url = "https://files.pythonhosted.org/packages/f3/82/fea946351658e6534db52d2cc12bc53087cbf87f9440c5f180f367c1950b/torch-2.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:796633c4cdf0fe2cdced72d8f88f22e73dbcfce83132763162f6d4bff13b820b", size = 526605292, upload-time = "2026-07-08T16:04:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/21/d6/e8f3c6f7e01f626f77259de9860d2a78bc84c40539e28e79b7e98b0bb659/torch-2.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:024c6cc0c1b085f2f91f20a3dc27b0471d021c31ce84b81be3afdc39f791fd9d", size = 122057313, upload-time = "2026-07-08T16:03:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fa/c1c10b7aff4a9a3e8956d4f0a5f468fa6db7abc3208805719076772b4833/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", size = 111213743, upload-time = "2026-07-08T16:03:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/11/18/9ecb37b56293a0be8d80f810bf672a72fe7e02f8b475d5ef1b9bf8a0d748/torch-2.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1e09d6a722504957c694faceca843acde562786df1144ebcc5a74075ec7f6005", size = 427213008, upload-time = "2026-07-08T16:03:44.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5a/7c50ba1b7b713d71d34669c6d13dab0a11531a3eceb0307a5162dbfec0f7/torch-2.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a3a9a21312872af8a26950b2c15680335a386a1f56ed03e780653d78b9607e9e", size = 526602329, upload-time = "2026-07-08T16:03:12.649Z" }, + { url = "https://files.pythonhosted.org/packages/91/3d/e7adcc6aaf36961cd18f56cf8ad0f3058c3a5c84ccf391762176c94581b8/torch-2.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:49b58f1e2c52440abb6f17c28f0335fe6c6d01ad1a7f55b0183b81e4b34d64e6", size = 122057920, upload-time = "2026-07-08T16:03:01.808Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/6dcc7f0c07052102dd36f83cbc5800842a909c8c3fbf1a7f8a5844954de9/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", size = 111227066, upload-time = "2026-07-08T16:03:33.6Z" }, + { url = "https://files.pythonhosted.org/packages/e9/09/2c10e8cd0e00fa5d23c052df6ce467eaa7182399f5e0f824f1e4ff42ccae/torch-2.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a3893dc2da0a972a8ca5d698c85a9f967559ac5f8ee1797b77408aa8734d073c", size = 427226309, upload-time = "2026-07-08T16:02:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/22c2102bbef14ca6a6cb4c20e42f088e49c5f812be4e160ae57502e325f9/torch-2.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:49f1ea385c754e54919408a9bb3b5a72b0b755bbe2c916c1d6f70afbec4908a2", size = 526614507, upload-time = "2026-07-08T16:02:16.441Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0c/7d1deb6bce5bc3e6042caf39100ac768eba3b9a098e1dddd16f75bd6489b/torch-2.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f8573e3ce9ebcd53fe922f01077a6085ccdfbe5f12fd215883a9d87d7a744fd", size = 122051871, upload-time = "2026-07-08T16:03:23.521Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ce/aa8b7f9949d32e0f2f624f342bc3b48112c1b8a130288465938bc83bcbf9/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", size = 111537025, upload-time = "2026-07-08T16:02:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/69/d1/491e3a0389430946145888b0203f2b6a759ce2a61481b96a85c2da4f2ced/torch-2.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:31061ff56ed8fbf26c749806905aeb749ebeb819810fd5d52508aa5afd90dddc", size = 427219769, upload-time = "2026-07-08T16:02:31.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1d/38006e045bf0a1fc28ef01e757c554e59e59a8770c284bc4f47b14e60441/torch-2.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:cc26eead4cf51d0b544e31e364dcf000846549c273bd148936fe9d24d29acb92", size = 526571320, upload-time = "2026-07-08T16:01:59.348Z" }, + { url = "https://files.pythonhosted.org/packages/56/94/655c91992a882bd5071aa0b5d22a07dbb130d801e872be97c0b627a7c693/torch-2.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a7de8a313090dc5c7d7ba4bfe5c3be222528f9a4dba1acc83bddb1157360c4b8", size = 122306773, upload-time = "2026-07-08T16:02:39.832Z" }, ] [[package]] @@ -3793,7 +3814,7 @@ wheels = [ [[package]] name = "torchvision" -version = "0.27.1" +version = "0.28.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'" }, @@ -3802,26 +3823,26 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/7a/b2/1e010052079e4c577007b789db336ea7075f1a426e84d17121fbc3745516/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", size = 1856017, upload-time = "2026-07-08T16:07:55.533Z" }, + { url = "https://files.pythonhosted.org/packages/27/be/1b9c5de9c655ca2df4a74100fa671a7b848532ff787e077ccde14a7dea2a/torchvision-0.28.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5a38bc6da3d72621be003400b66f66a2b4c6d644fde05f680c2cb7ca8cf8dd6c", size = 7841822, upload-time = "2026-07-08T16:07:49.207Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9b/f1e68e861d4462e3e195a642c2b448e7b7d3fad5f209487162b9a2133d9b/torchvision-0.28.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7e80f543b22503d9415e126db5f0ff3917036925e38560ee6b9ae38c571a4002", size = 7670718, upload-time = "2026-07-08T16:07:46.525Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/1494610ff54cbb154beb55033cc2cd50f3de04dac132fa2dd00e4f2b2556/torchvision-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:9a45ea67235d965ef52187130d20002a4de20c54ea3d927a24286961d268dc37", size = 3814319, upload-time = "2026-07-08T16:07:37.153Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/c1cab1ecbb3ff1a380a3f99283db1dee61b8afe354f6352c643b65937130/torchvision-0.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e9f54c30cd52e3ef7fd034cc69b7bb7e0964e1c8f8743e018ab92e95b40f9eee", size = 1856020, upload-time = "2026-07-08T16:07:52.182Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4c/95233776e2def960e5abb7a07931230a545f43717a56a1e1140162033598/torchvision-0.28.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5cf78ebc401ce64ae19b8c55de866bb836797d559a4de9c25ccbe74cfa642d3a", size = 7842127, upload-time = "2026-07-08T16:07:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/93/e4/e9b2495d0d57b9f60d63c57d0a910410a81b4b073bf70917bef815291119/torchvision-0.28.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:028a3d481b37d785605620d7cdad897064c5a55bae2aa1f2658766333e291940", size = 7675040, upload-time = "2026-07-08T16:07:58.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9c/55ed9cb6dfe3ee9c837df5cd0e758372e5829aa38b8dd71343aa632cc4e2/torchvision-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:87dc16b2df427c1318ad335f1e2be2b3b15b2cf20f7934c83b0505a48425ee5d", size = 4085785, upload-time = "2026-07-08T16:07:50.928Z" }, + { url = "https://files.pythonhosted.org/packages/20/55/08a726c14c67b37c8aca04b077766909f1c7ed23f76116884fe63b9bd033/torchvision-0.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d483b4aa3f5237569053f749cd1a2b5bb548ca456e40461a5dd087f21149d123", size = 1856021, upload-time = "2026-07-08T16:07:45.386Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/40beacd53809194f5259e590d1afaeaa8ad57da15f77c646e6560bcc4616/torchvision-0.28.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:bb6dd6918460ed89cc7644adcc2402991474d6933cf1ce92b390641cb233fddf", size = 7797014, upload-time = "2026-07-08T16:07:43.04Z" }, + { url = "https://files.pythonhosted.org/packages/32/db/062cdb5a84380a60439775311fff34d89229760d2a50680393dc18699956/torchvision-0.28.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad7b3a439265cc3739a4ab5b4c998c0e38ea99c0ee7ca4dea35c5d0b099ec237", size = 7674669, upload-time = "2026-07-08T16:07:38.91Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a6/b4081e2d04e1541abf82785ac9e5178a494c19330391f551356c8c18b7b3/torchvision-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:7e9dd6f60d6e15f8dc27d4f877fdb6002fc70d70272412135f1c2ff9cfa08d3b", size = 4157380, upload-time = "2026-07-08T16:07:40.22Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b9/da40eca5bbe9596c12ae9899ab7abaf887f5e20f29d08b924b4633714821/torchvision-0.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3bd9dba55224a9db4a2d77f6feaa5651770d8c8e86d3d0ddb0fa6bec54c8712b", size = 1856014, upload-time = "2026-07-08T16:07:44.282Z" }, + { url = "https://files.pythonhosted.org/packages/06/d6/313aafd3df4eaf5f330211bd4e75b7598bddbfee4f55580d3b58536e1b20/torchvision-0.28.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:89f90e29b0966352811b12589f3a3c61943bf2bb9487b9d7bbec10efb1096bb5", size = 7796873, upload-time = "2026-07-08T16:07:30.907Z" }, + { url = "https://files.pythonhosted.org/packages/b3/41/31f8e959ab8f942600b6357f8999c21d779d5fd3304b0fd204ff4b518239/torchvision-0.28.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:36beb0782976906069ca03d4c9aacaf4b6b838b06ed6c20960ea9c51cce7acdd", size = 7674634, upload-time = "2026-07-08T16:07:29.657Z" }, + { url = "https://files.pythonhosted.org/packages/15/15/4c5115253fd470672cdac0a1cf139e06b4f3e29d041238a2b255937f63be/torchvision-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:3557cc7b539f46dabcda2b6f2b14017ccbeef024de466d4fc5835fc3f287f769", size = 4184005, upload-time = "2026-07-08T16:07:35.805Z" }, + { url = "https://files.pythonhosted.org/packages/6a/80/822a6163da716f8a78141cf6678d74e26a572285d4ea866ef8aa657bb307/torchvision-0.28.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:09ce8f56e81f19b9c378ae7bb109f83f6659fd8bc3cd14241a48e4af46e9ed49", size = 1856011, upload-time = "2026-07-08T16:07:33.404Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d1/cd3f9463b39a790ec8c0c2f6e6c8061edb1562114d04fcdfa786ed889345/torchvision-0.28.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:62c7d110f86a039245b587e4fae60278c649f3bd42ff79cfbc1178eca4e72542", size = 7796742, upload-time = "2026-07-08T16:07:28.339Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/3e0a7ad18e99831e2d7f4713d3be717b7159ff5a920862dd5c23c454aa71/torchvision-0.28.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:904cf89af220f8c6b2ed0296bb5065b474ce43b77558e48b2bf9de8b0ba17204", size = 7675526, upload-time = "2026-07-08T16:07:34.572Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/23aea03b28297bc66a4461f55ae4296368a9d85fa9a454bafcb2a5348bd7/torchvision-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:46f581979c010ad6da6bd85ee602aa707e1ff44312670223b7a0ee517ad06d47", size = 4291452, upload-time = "2026-07-08T16:07:32.236Z" }, ] [[package]] @@ -3843,14 +3864,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.68.3" +version = "4.68.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/57ff8b434839e70dab45601284ea413e947a63799891b7553e5960a793a8/tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520", size = 792418, upload-time = "2026-07-07T09:58:18.369Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/22/2a/5e5e750890ada51017d18d0d4c30da696e5b5bd3180947729927628fc3cb/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2", size = 676612, upload-time = "2026-07-07T09:58:16.256Z" }, ] [[package]] @@ -3890,11 +3911,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2026.2" +version = "2026.3" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] [[package]] @@ -3917,7 +3938,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.5.1" +version = "21.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3925,9 +3946,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/34/d9/b477fddb68840b570af8b22afe9b035cbc277b5fb7b33dea390617a8b10f/virtualenv-21.6.1.tar.gz", hash = "sha256:15f978b7cd329f24855ff4a0c4b4899cc7678589f49adbdcbbb4d3232e641128", size = 5526620, upload-time = "2026-07-10T19:33:53.312Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/c1/7c/4e7225d46d634a0d8d534dd8a6ce0c319d09b4d0cf0337eb314ca4789d8c/virtualenv-21.6.1-py3-none-any.whl", hash = "sha256:afe991df855715a2b2f60edfcc0107ef95a79fdfd8cb4cdaa71603d1c12e463b", size = 5506392, upload-time = "2026-07-10T19:33:51.629Z" }, ] [[package]] From 2d0d7205d2960ec4a0c7ff63b363d651772d43fb Mon Sep 17 00:00:00 2001 From: cophus Date: Tue, 14 Jul 2026 17:41:11 -0400 Subject: [PATCH 101/113] some possible updates --- src/quantem/core/fitting/diffraction.py | 297 +++++++++++++++++++++-- src/quantem/diffraction/model_fitting.py | 242 +++++++++++++++--- 2 files changed, 486 insertions(+), 53 deletions(-) diff --git a/src/quantem/core/fitting/diffraction.py b/src/quantem/core/fitting/diffraction.py index a62c01f34..adddcf5e2 100644 --- a/src/quantem/core/fitting/diffraction.py +++ b/src/quantem/core/fitting/diffraction.py @@ -48,6 +48,143 @@ def put(rr: torch.Tensor, cc: torch.Tensor, ww: torch.Tensor) -> None: put(r0i + 1, c0i + 1, w11) +def _gaussian_tap_weights( + frac: torch.Tensor, taps: torch.Tensor, sigma: float +) -> torch.Tensor: + """ + Normalized 1D Gaussian weights from fractional positions to integer taps. + + Parameters + ---------- + frac : torch.Tensor + Fractional parts in ``[0, 1)``, any shape ``(...,)``. + taps : torch.Tensor + Integer tap offsets, shape ``(T,)``. + sigma : float + Gaussian width in pixels. + + Returns + ------- + torch.Tensor + Weights of shape ``(..., T)`` summing to 1 along the last axis, so the + splatted flux is conserved and (unlike bilinear) the effective blur is + independent of the subpixel phase — no integer-pixel "locking" minima. + """ + d = taps.reshape((1,) * frac.ndim + (-1,)) - frac.unsqueeze(-1) + w = torch.exp(-(d * d) / (2.0 * sigma * sigma)) + return w / w.sum(dim=-1, keepdim=True) + + +def _smooth_tap_weights( + frac: torch.Tensor, taps: torch.Tensor, width: float +) -> torch.Tensor: + """ + Normalized finite-support smoothstep splat weights. + + A compact alternative to the Gaussian tap weights with NO tails: the weight + is ``smoothstep(1 - |d|/width)`` (``smoothstep(x) = 3x^2 - 2x^3``), which is + exactly zero for ``|d| >= width`` and, for ``width > 1``, still nonzero at + the neighbouring integer taps when centred -- so the effective blur stays + phase-independent (no integer-pixel locking) while the kernel has finite + bandwidth. The disk edge itself is carried by the template, so this kernel + only interpolates sub-pixel position and provides anti-lock. + """ + d = (taps.reshape((1,) * frac.ndim + (-1,)) - frac.unsqueeze(-1)).abs() + x = torch.clamp(1.0 - d / float(width), min=0.0) + w = x * x * (3.0 - 2.0 * x) + return w / w.sum(dim=-1, keepdim=True) + + +def _kernel_taps_weights(frac, width, kernel, device, dtype): + """Return (taps, weights) for the requested finite render kernel.""" + if kernel == "smoothstep": + K = max(2, int(np.ceil(float(width)))) + taps = torch.arange(-K + 1, K + 1, device=device, dtype=dtype) + return taps, _smooth_tap_weights(frac, taps, width) + K = max(2, int(np.ceil(3.0 * float(width)))) + taps = torch.arange(-K + 1, K + 1, device=device, dtype=dtype) + return taps, _gaussian_tap_weights(frac, taps, width) + + +def _splat_patch_gaussian( + out: torch.Tensor, + *, + r0: torch.Tensor, + c0: torch.Tensor, + patch_vals: torch.Tensor, + dr: torch.Tensor, + dc: torch.Tensor, + scale: torch.Tensor, + sigma: float, + kernel: str = "gaussian", +) -> None: + """Finite-kernel analogue of ``_splat_patch`` (phase-independent blur).""" + h, w = out.shape + r = r0 + dr + c = c0 + dc + r_base = torch.floor(r) + c_base = torch.floor(c) + fr = r - r_base + fc = c - c_base + r0i = r_base.to(torch.long) + c0i = c_base.to(torch.long) + v = patch_vals * scale + + taps, wr = _kernel_taps_weights(fr, sigma, kernel, out.device, out.dtype) + _, wc = _kernel_taps_weights(fc, sigma, kernel, out.device, out.dtype) + taps_i = taps.to(torch.long) + + for i in range(taps_i.numel()): + rr = r0i + taps_i[i] + r_ok = (rr >= 0) & (rr < h) + for j in range(taps_i.numel()): + cc = c0i + taps_i[j] + keep = r_ok & (cc >= 0) & (cc < w) + if torch.any(keep): + ww = wr[:, i] * wc[:, j] + out.index_put_( + (rr[keep], cc[keep]), v[keep] * ww[keep], accumulate=True + ) + + +def _splat_patch_batched_gaussian( + shape: tuple[int, int], + *, + r0: torch.Tensor, + c0: torch.Tensor, + vals: torch.Tensor, + device: torch.device, + dtype: torch.dtype, + sigma: float, + kernel: str = "gaussian", +) -> torch.Tensor: + """Finite-kernel analogue of ``_splat_patch_batched``.""" + h, w = int(shape[0]), int(shape[1]) + B, N = r0.shape + r_base = torch.floor(r0) + c_base = torch.floor(c0) + fr = r0 - r_base + fc = c0 - c_base + r0i = r_base.to(torch.long) + c0i = c_base.to(torch.long) + + taps, wr = _kernel_taps_weights(fr, sigma, kernel, device, dtype) + _, wc = _kernel_taps_weights(fc, sigma, kernel, device, dtype) + taps_i = taps.to(torch.long) + + out_flat = torch.zeros(B, h * w, device=device, dtype=dtype) + for i in range(taps_i.numel()): + rr = r0i + taps_i[i] + r_ok = (rr >= 0) & (rr < h) + rr_c = rr.clamp(0, h - 1) + for j in range(taps_i.numel()): + cc = c0i + taps_i[j] + keep = r_ok & (cc >= 0) & (cc < w) + weighted = wr[:, :, i] * wc[:, :, j] * vals * keep.to(dtype) + out_flat.scatter_add_(1, rr_c * w + cc.clamp(0, w - 1), weighted) + return out_flat.reshape(B, h, w) + + def _splat_patch_batched( shape: tuple[int, int], *, @@ -125,6 +262,8 @@ def __init__( origin: OriginND | None = None, origin_key: str = "origin", intensity: float | Sequence[float] = 1.0, + render_sigma: float | None = None, + render_kernel: str = "gaussian", constraint_params: dict[str, Any] | None = None, constraint_config: dict[str, Any] | None = None, ): @@ -136,6 +275,12 @@ def __init__( intensity : float | Sequence[float], optional Trainable scalar amplitude applied to the rendered template. Accepts ``x``, ``(x0, delta)``, or ``(x0, lo, hi)``. + render_sigma : float | None, optional + If set, splat template pixels with a normalized Gaussian kernel of + this width (px) instead of bilinear interpolation. The Gaussian + blur is identical at every subpixel phase, which removes the + integer-pixel "locking" minima of bilinear splatting. Applies to + this template and to any lattice that renders it. Returns ------- @@ -154,6 +299,11 @@ def __init__( super().__init__() self.name = str(name) self.refine_all_pixels = bool(refine_all_pixels) + self.render_sigma = None if render_sigma is None else float(render_sigma) + # "gaussian" (tails) or "smoothstep" (finite support, no tails); with + # smoothstep, render_sigma is the half-support in px (needs > 1 to + # anti-lock). The disk edge is carried by the template either way. + self.render_kernel = str(render_kernel) self.origin = origin self.origin_key = str(origin_key) intensity_init, intensity_lo, intensity_hi = self.parse_bounded_init( @@ -209,6 +359,8 @@ def from_array( origin: OriginND | None = None, origin_key: str = "origin", intensity: float | Sequence[float] = 1.0, + render_sigma: float | None = None, + render_kernel: str = "gaussian", constraint_params: dict[str, Any] | None = None, constraint_config: dict[str, Any] | None = None, @@ -221,6 +373,8 @@ def from_array( origin=origin, origin_key=origin_key, intensity=intensity, + render_sigma=render_sigma, + render_kernel=render_kernel, constraint_params=constraint_params, constraint_config=constraint_config, @@ -246,7 +400,14 @@ def add_patch( vals = self.patch_values().to(device=out.device, dtype=out.dtype) dr = cast(torch.Tensor, self.dr).to(device=out.device, dtype=out.dtype) dc = cast(torch.Tensor, self.dc).to(device=out.device, dtype=out.dtype) - _splat_patch(out, r0=r0, c0=c0, patch_vals=vals, dr=dr, dc=dc, scale=scale) + if self.render_sigma is not None: + _splat_patch_gaussian( + out, r0=r0, c0=c0, patch_vals=vals, dr=dr, dc=dc, scale=scale, + sigma=self.render_sigma, + kernel=self.render_kernel, + ) + else: + _splat_patch(out, r0=r0, c0=c0, patch_vals=vals, dr=dr, dc=dc, scale=scale) def forward(self, ctx: RenderContext) -> torch.Tensor: """ @@ -285,6 +446,11 @@ def forward_batched( r0 = origin_coords_b[:, 0:1] + dr.unsqueeze(0) c0 = origin_coords_b[:, 1:2] + dc.unsqueeze(0) vals = template_raw_b.reshape(B, N) * intensity_raw_b.view(B, 1) + if self.render_sigma is not None: + return _splat_patch_batched_gaussian( + ctx.shape, r0=r0, c0=c0, vals=vals, device=ctx.device, + dtype=ctx.dtype, sigma=self.render_sigma, kernel=self.render_kernel, + ) return _splat_patch_batched( ctx.shape, r0=r0, c0=c0, vals=vals, device=ctx.device, dtype=ctx.dtype ) @@ -530,6 +696,10 @@ def __init__( center_intensity_0: float | Sequence[float] | None = None, exclude_indices: Iterable[tuple[int, int]] | None = None, boundary_px: float = 0.0, + max_slope_ratio: float | None = None, + own_template: bool = False, + intensity_softplus_beta: float | None = None, + slope_l2_weight: float = 0.0, origin: OriginND | None = None, origin_key: str = "origin", constraint_params: dict[str, Any] | None = None, @@ -552,6 +722,13 @@ def __init__( center_intensity_0 : float | Sequence[float] | None, optional Optional center-disk baseline. Accepts ``x``, ``(x0, delta)``, or ``(x0, lo, hi)`` and routes by center ownership rules. + max_slope_ratio : float | None, optional + If set, cap the per-disk linear slope magnitude at + ``ratio * i0 / support_radius`` (projected after each step). + ``1.0`` keeps the intensity ramp nonnegative everywhere (no + saturation); larger values allow the rendered disk to saturate at + zero over part of its support while bounding how far the lit + region's centroid can shift. ``None`` leaves slopes unconstrained. exclude_indices : Iterable[tuple[int, int]] | None, optional Lattice indices excluded from rendering. By default, ``(0, 0)`` is excluded. To include center explicitly, pass ``exclude_indices`` that @@ -586,6 +763,23 @@ def __init__( self.u_max = int(u_max) self.v_max = int(v_max) self.boundary_px = float(boundary_px) + self.max_slope_ratio = None if max_slope_ratio is None else float(max_slope_ratio) + # When True, ``disk`` is the lattice's OWN template (a distinct + # DiskTemplate from the center-beam component), so its ``template_raw`` + # is trained via this lattice and shaped by the disk's constraints. When + # False (default) the lattice shares the center disk's template. + self.own_template = bool(own_template) + # None -> hard clamp max(f, 0); float -> softplus(beta*f)/beta (smooth, + # gradient-alive positivity that approaches zero without crossing it). + self.intensity_softplus_beta = ( + None if intensity_softplus_beta is None else float(intensity_softplus_beta) + ) + # Soft L2 prior on the per-disk linear slopes (ir, ic). Unlike the hard + # max_slope_ratio cap this shrinks slopes toward zero proportionally to + # the data misfit, so it auto-adapts: slopes relax to ~0 where there is + # no real intensity asymmetry (e.g. precession-averaged data) and grow + # only where the data supports them. + self.slope_l2_weight = float(slope_l2_weight) if max_intensity_order is None: max_intensity_order = 1 if bool(per_disk_slopes) else 0 @@ -721,18 +915,44 @@ def __init__( def set_origin(self, origin: OriginND) -> None: self.origin = origin + def _slope_support_radius(self) -> float: + """Maximum pixel radius of the disk-template support (for slope caps).""" + template = self.disk.template_raw.detach() + dr = cast(torch.Tensor, self.disk.dr) + dc = cast(torch.Tensor, self.disk.dc) + radius = torch.sqrt(dr * dr + dc * dc) + support = template.reshape(-1) > 1e-3 * template.max().clamp(min=1e-12) + if bool(support.any()): + return float(radius[support].max()) + return float(radius.max()) + def _enforce_positive_intensity_params(self) -> None: """ Project base intensity parameter(s) to nonnegative values. Notes ----- - Positivity is enforced as a hard projection after optimizer steps. - The forward path intentionally avoids clamp-based dead gradients. - Only ``i0_raw`` is projected; slope terms remain unconstrained. + ``i0_raw`` is projected to ``>= 0`` after optimizer steps. The forward + path saturates the per-pixel intensity polynomial at zero, so a steep + tilt clips part of the disk dark instead of rendering negative counts. + If ``max_slope_ratio`` is set, the linear slope pair ``(ir, ic)`` is + additionally capped at ``ratio * i0 / support_radius`` per disk, which + bounds how far the clipped disk's lit centroid can wander. """ with torch.no_grad(): self.i0_raw.clamp_(min=0.0) + if ( + self.max_slope_ratio is not None + and self.ir is not None + and self.ic is not None + ): + radius = self._slope_support_radius() + if radius > 0.0: + slope = torch.sqrt(self.ir * self.ir + self.ic * self.ic) + limit = self.max_slope_ratio * self.i0_raw / radius + scale = torch.clamp(limit / slope.clamp(min=1e-12), max=1.0) + self.ir.mul_(scale) + self.ic.mul_(scale) def enforce_hard_constraints(self, ctx: RenderContext) -> None: if bool(self.hard_constraints.get("force_positive_intensity", False)): @@ -745,9 +965,35 @@ def enforce_hard_constraints(self, ctx: RenderContext) -> None: self.i0_raw[idx].clamp_(min=float(lo)) if hi is not None: self.i0_raw[idx].clamp_(max=float(hi)) + # Shape the lattice's own template with the disk's hard constraints + # (center/norm/circular mask), the same way the center disk is shaped. + if self.own_template: + self.disk.enforce_hard_constraints(ctx) super().enforce_hard_constraints(ctx) + def _apply_positivity(self, inten: torch.Tensor) -> torch.Tensor: + """Map the per-pixel intensity polynomial to nonnegative values. + + Hard ``max(f, 0)`` clips part of a tilted disk dark but leaves a dead + gradient in the clipped region; softplus (``intensity_softplus_beta`` + set) is smooth and keeps the slope gradient alive everywhere while still + approaching zero without crossing it. + """ + if self.intensity_softplus_beta is not None: + beta = self.intensity_softplus_beta + return F.softplus(beta * inten) / beta + return torch.clamp(inten, min=0.0) + + def constraint_loss( + self, ctx: RenderContext, params: dict[str, object] | None = None + ) -> torch.Tensor: + if self.slope_l2_weight <= 0.0 or self.ir is None or self.ic is None: + return torch.zeros((), device=ctx.device, dtype=ctx.dtype) + ir = self.ir.to(device=ctx.device, dtype=ctx.dtype) + ic = self.ic.to(device=ctx.device, dtype=ctx.dtype) + return self.slope_l2_weight * torch.mean(ir * ir + ic * ic) + def forward(self, ctx: RenderContext) -> torch.Tensor: if self.origin is None: raise RuntimeError("SyntheticDiskLattice requires an OriginND instance.") @@ -808,6 +1054,8 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: if active_order >= 2: inten = inten + self.irr * centers_r**2 + self.icc * centers_c**2 + self.irc * centers_r * centers_c + # Positivity: hard clip, or smooth softplus if a beta is configured. + inten = self._apply_positivity(inten) inten = inten[:, None].expand(-1, num_pixels) if inten.ndim == 1 else inten.expand(num_disks, num_pixels) total_pixels = num_disks * num_pixels r0_all = centers_r[:, None].expand(-1, num_pixels).reshape(total_pixels) @@ -815,15 +1063,23 @@ def forward(self, ctx: RenderContext) -> torch.Tensor: dr_all = dr[None, :].expand(num_disks, -1).reshape(total_pixels) dc_all = dc[None, :].expand(num_disks, -1).reshape(total_pixels) vals_all = (patch_vals[None, :] * inten).reshape(total_pixels) - _splat_patch( - out, - r0=r0_all, - c0=c0_all, - patch_vals=vals_all, - dr=dr_all, - dc=dc_all, - scale=torch.ones_like(vals_all) - ) + if self.disk.render_sigma is not None: + _splat_patch_gaussian( + out, r0=r0_all, c0=c0_all, patch_vals=vals_all, + dr=dr_all, dc=dc_all, scale=torch.ones_like(vals_all), + sigma=self.disk.render_sigma, + kernel=self.disk.render_kernel, + ) + else: + _splat_patch( + out, + r0=r0_all, + c0=c0_all, + patch_vals=vals_all, + dr=dr_all, + dc=dc_all, + scale=torch.ones_like(vals_all) + ) return out def forward_batched( @@ -906,6 +1162,8 @@ def forward_batched( + irc_b.view(B, 1, 1) * (r0_kb * c0_kb).unsqueeze(2) ) + # Positivity: hard clip, or smooth softplus if a beta is configured. + inten = self._apply_positivity(inten) inten = inten * keep_f.unsqueeze(2) vals = patch_vals.unsqueeze(1) * inten @@ -913,6 +1171,11 @@ def forward_batched( c0_full = (c0_kb.unsqueeze(2) + dc.view(1, 1, N_pix)).reshape(B, K * N_pix) vals_full = vals.reshape(B, K * N_pix) + if self.disk.render_sigma is not None: + return _splat_patch_batched_gaussian( + ctx.shape, r0=r0_full, c0=c0_full, vals=vals_full, + device=ctx.device, dtype=ctx.dtype, sigma=self.disk.render_sigma, kernel=self.disk.render_kernel, + ) return _splat_patch_batched( ctx.shape, r0=r0_full, c0=c0_full, vals=vals_full, device=ctx.device, dtype=ctx.dtype, @@ -921,7 +1184,13 @@ def forward_batched( def get_optimization_parameters(self) -> dict[str, list[torch.nn.Parameter]]: params = [] for name, param in self.named_parameters(recurse=True): - if not name.startswith('disk.') and param.requires_grad: + if name.startswith('disk.'): + # The lattice's own template is trained here; other disk params + # (intensity, its origin) are not owned by the lattice. + if self.own_template and name == 'disk.template_raw' and param.requires_grad: + params.append(param) + continue + if param.requires_grad: params.append(param) if not params: return {} diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index b148fa915..5855d2278 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -1,5 +1,6 @@ from __future__ import annotations +import math from typing import Any, Literal, Sequence, cast import numpy as np @@ -14,9 +15,11 @@ from quantem.core.fitting.base import ( AdditiveRenderModel, FitBase, + LogMSELoss, OriginND, RenderComponent, RenderContext, + SqrtMSELoss, ) from quantem.core.fitting.diffraction import DiskTemplate, SyntheticDiskLattice from quantem.core.io.serialize import AutoSerialize @@ -622,6 +625,7 @@ def fit_individual_diffraction_pattern( constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, constraint_config_params: dict[str, Any] | None = None, + edge_weight: float = 0.0, progress: bool = True, batch_size: int | None = None, frozen_components: list[str] | str | None = None, @@ -640,6 +644,7 @@ def fit_individual_diffraction_pattern( constraint_weight=float(constraint_weight), constraint_params=constraint_params, constraint_config_params=constraint_config_params, + edge_weight=float(edge_weight), frozen_components=frozen_components, sample_trainability=sample_trainability, progress=progress, @@ -721,6 +726,7 @@ def fit_individual_diffraction_pattern_batched( constraint_weight: float = 1.0, constraint_params: dict[str, Any] | None = None, constraint_config_params: dict[str, Any] | None = None, + edge_weight: float = 0.0, frozen_components: list[str] | str | None = None, sample_trainability: dict[str, Any] | None = None, progress: bool = True, @@ -763,12 +769,17 @@ def fit_individual_diffraction_pattern_batched( raise RuntimeError("Call .define_model(...) first.") if not isinstance(self.dataset, Dataset4d): raise ValueError("Dataset must be Dataset4d or Dataset4dstem.") - if reset not in ("initialized", "mean_refined"): - raise ValueError("reset must be 'initialized' or 'mean_refined'.") + if reset not in ("initialized", "mean_refined", "individual"): + raise ValueError("reset must be 'initialized', 'mean_refined', or 'individual'.") # Choose initialization state and load into the live model so we can # read current parameter values + constraint settings off the modules. - if reset == "mean_refined": + # For reset="individual" each position warm-starts from its own + # previously refined state (state_individual_refined); the live model + # is loaded from mean_refined (or initialized) only to resolve the + # plan layout and constraint settings. + warm_start = reset == "individual" + if reset == "mean_refined" or (warm_start and self.state_mean_refined is not None): if self.state_mean_refined is None: raise RuntimeError("mean_refined state is unavailable. Run fit_mean_diffraction_pattern first.") init_state = self._clone_state_dict(self.state_mean_refined) @@ -777,6 +788,11 @@ def fit_individual_diffraction_pattern_batched( raise RuntimeError("initialized state is unavailable. Call define_model first.") init_state = self._clone_state_dict(self.state_initialized) self._load_model_state_dict_copy(init_state) + if warm_start and self.state_individual_refined is None: + raise RuntimeError( + "reset='individual' requires a previous per-position fit " + "(run fit_individual_diffraction_pattern first)." + ) if constraint_params is not None: self.model.apply_constraint_params(constraint_params, strict=True) @@ -839,7 +855,22 @@ def fit_individual_diffraction_pattern_batched( dim=0, ) - stacked = plan.build_stacked_params(B) + if warm_start: + prev_states = [] + for (r, c) in chunk: + st = self.state_individual_refined[r, c] + if st is None: + raise RuntimeError( + f"reset='individual': position ({r}, {c}) has no previous " + "per-position state to warm-start from." + ) + prev_states.append(st) + stacked, fixed_b = plan.build_stacked_params_from_states( + prev_states, device=ctx.device, dtype=ctx.dtype + ) + else: + stacked = plan.build_stacked_params(B) + fixed_b = None adam_state: dict[str, dict[str, torch.Tensor]] = { name: { @@ -866,30 +897,51 @@ def fit_individual_diffraction_pattern_batched( mixed_trainable_keys.append(key) init_snapshots[key] = stacked[key].detach().clone() + # Hoist target-only loss terms out of the step loop: these depend on + # `targets` (constant for the chunk), not on `pred`, so recomputing + # them every step wastes a full-frame amin+pow (SqrtMSE) or log1p. + is_sqrt = isinstance(loss_fn, SqrtMSELoss) + is_log = isinstance(loss_fn, LogMSELoss) + cw = float(constraint_weight) + ew = float(edge_weight) + if is_sqrt: + sqrt_gamma = float(loss_fn.gamma) + tgt_mod = (targets - targets.amin(dim=(1, 2), keepdim=True) + 1.0) ** sqrt_gamma + elif is_log: + tgt_log = torch.log1p(targets) + tgt_mod = tgt_log + else: + tgt_mod = targets + if ew > 0.0: + # Edge term compares spatial gradients of the (compressed) images. + # Disk-position information is concentrated at the aperture edges, + # while per-disk intensity tilts act on disk interiors, so this + # term anchors the lattice geometry against tilt-shift mimicry. + tgt_dr = tgt_mod[:, 1:, :] - tgt_mod[:, :-1, :] + tgt_dc = tgt_mod[:, :, 1:] - tgt_mod[:, :, :-1] + for step in range(int(n_steps)): - pred = plan.batched_forward(ctx, stacked) - # Per-sample fidelity loss summed → scalar with per-sample grads - diff2 = (pred.float() - targets.float()) - # Match SqrtMSELoss behavior approximately when loss_fn is SqrtMSELoss: - # gamma-power transform of (x - min(x) + 1), per-sample independently. - from quantem.core.fitting.base import SqrtMSELoss, LogMSELoss - if isinstance(loss_fn, SqrtMSELoss): - gamma = float(loss_fn.gamma) - eps = 1.0 - pred_min = pred.amin(dim=(1, 2), keepdim=True) - tgt_min = targets.amin(dim=(1, 2), keepdim=True) - pred_mod = (pred - pred_min + eps) ** gamma - tgt_mod = (targets - tgt_min + eps) ** gamma - per_sample_loss = ((pred_mod - tgt_mod) ** 2).mean(dim=(1, 2)) - elif isinstance(loss_fn, LogMSELoss): - per_sample_loss = ((torch.log1p(pred) - torch.log1p(targets)) ** 2).mean(dim=(1, 2)) + pred = plan.batched_forward(ctx, stacked, fixed=fixed_b) + # Per-sample fidelity loss summed → scalar with per-sample grads. + if is_sqrt: + pred_mod = (pred - pred.amin(dim=(1, 2), keepdim=True) + 1.0) ** sqrt_gamma + elif is_log: + pred_mod = torch.log1p(pred) else: - per_sample_loss = (diff2 * diff2).mean(dim=(1, 2)) + pred_mod = pred + per_sample_loss = ((pred_mod - tgt_mod) ** 2).mean(dim=(1, 2)) + if ew > 0.0: + pr_dr = pred_mod[:, 1:, :] - pred_mod[:, :-1, :] + pr_dc = pred_mod[:, :, 1:] - pred_mod[:, :, :-1] + per_sample_loss = per_sample_loss + ew * ( + ((pr_dr - tgt_dr) ** 2).mean(dim=(1, 2)) + + ((pr_dc - tgt_dc) ** 2).mean(dim=(1, 2)) + ) # Add per-sample soft constraint loss. - if float(constraint_weight) != 0.0: + if cw != 0.0: constraint_per_sample = plan.batched_constraint_loss(ctx, stacked) - per_sample_loss = per_sample_loss + float(constraint_weight) * constraint_per_sample + per_sample_loss = per_sample_loss + cw * constraint_per_sample total_loss = per_sample_loss.sum() @@ -931,9 +983,12 @@ def fit_individual_diffraction_pattern_batched( pbar.update(B) - # Unstack each sample back into a state_dict and store + # Unstack each sample back into a state_dict and store. Warm starts + # keep each sample's own previous state as the base so frozen + # parameters retain their per-position values. for b, (r, c) in enumerate(chunk): - sample_state = plan.build_sample_state_dict(init_state, stacked, b) + base_state = prev_states[b] if warm_start else init_state + sample_state = plan.build_sample_state_dict(base_state, stacked, b) self.state_individual_refined[r, c] = sample_state pbar.close() @@ -1409,6 +1464,70 @@ def build_stacked_params(self, B: int) -> dict[str, torch.Tensor]: return out + def _stacked_key_to_state_key(self, key: str) -> str | None: + """Map a stacked-param key to its canonical model state-dict key.""" + if key == "origin.coords": + return "origin.coords" + prefix, pname = key.split(".", 1) + idx = { + "disk": self.disk_idx, + "dcbg": self.dcbg_idx, + "gaussbg": self.gaussbg_idx, + "lat": self.lat_idx, + }.get(prefix) + if idx is None: + return None + return f"components.{idx}.{pname}" + + def _all_plan_keys(self) -> list[str]: + keys = ["origin.coords"] + if self.disk is not None: + keys += ["disk.template_raw", "disk.intensity_raw"] + if self.dcbg is not None: + keys += ["dcbg.intensity_raw"] + if self.gaussbg is not None: + keys += ["gaussbg.sigma_raw", "gaussbg.intensity_raw"] + if self.lat is not None: + for a in ("u_row", "u_col", "v_row", "v_col", "i0_raw", "ir", "ic", "irr", "icc", "irc"): + if getattr(self.lat, a, None) is not None: + keys.append(f"lat.{a}") + return keys + + def build_stacked_params_from_states( + self, + states: list[dict[str, torch.Tensor]], + *, + device: torch.device, + dtype: torch.dtype, + ) -> tuple[dict[str, torch.Tensor], dict[str, torch.Tensor]]: + """ + Build per-sample parameter stacks from previously refined states. + + Returns + ------- + (stacked, fixed) : tuple of dict + ``stacked`` holds the trainable keys (requires_grad), initialized + from each sample's own state; ``fixed`` holds every other + plan-known key as a per-sample constant stack, so frozen + parameters keep their per-position values instead of collapsing + to the shared live-model value. + """ + stacked: dict[str, torch.Tensor] = {} + fixed: dict[str, torch.Tensor] = {} + for key in self._all_plan_keys(): + skey = self._stacked_key_to_state_key(key) + if skey is None or skey not in states[0]: + continue + vals = torch.stack( + [st[skey].to(device=device, dtype=dtype) for st in states], dim=0 + ).contiguous() + if self._trainable_flags.get(key, False): + vals.requires_grad_(True) + stacked[key] = vals + else: + fixed[key] = vals + return stacked, fixed + @staticmethod def _stack(p: torch.Tensor, B: int) -> torch.Tensor: x = p.detach().clone().unsqueeze(0).expand(B, *p.shape).contiguous() @@ -1416,9 +1535,18 @@ def _stack(p: torch.Tensor, B: int) -> torch.Tensor: return x def batched_forward( - self, ctx: RenderContext, stacked: dict[str, torch.Tensor] + self, + ctx: RenderContext, + stacked: dict[str, torch.Tensor], + fixed: dict[str, torch.Tensor] | None = None, ) -> torch.Tensor: - """Batched forward with frozen parameter support.""" + """Batched forward with frozen parameter support. + + ``fixed`` optionally supplies per-sample constant stacks for frozen + keys (warm starts); lookups fall back stacked -> fixed -> live module. + """ + if fixed: + stacked = {**fixed, **stacked} B = next(iter(stacked.values())).shape[0] if stacked else 1 # Get origin (might be frozen) @@ -1511,11 +1639,17 @@ def batched_forward( if irc_b is None and self.lat.irc is not None: irc_b = self.lat.irc.unsqueeze(0).expand(B, *self.lat.irc.shape).clone() - # Template (from disk) - template_b = stacked.get("disk.template_raw") - if template_b is None and self.disk is not None: - template_b = self.disk.template_raw.unsqueeze(0).expand(B, *self.disk.template_raw.shape).clone() - + # Template for the lattice. With own_template the lattice renders + # its own (per-position frozen) template, distinct from the center + # disk; otherwise it shares the center-disk template. + if getattr(self.lat, "own_template", False): + lt = self.lat.disk.template_raw + template_b = lt.unsqueeze(0).expand(B, *lt.shape).clone() + else: + template_b = stacked.get("disk.template_raw") + if template_b is None and self.disk is not None: + template_b = self.disk.template_raw.unsqueeze(0).expand(B, *self.disk.template_raw.shape).clone() + pred = pred + self.lat.forward_batched( ctx, u_row_b=u_row_b, @@ -1591,14 +1725,31 @@ def apply_hard_constraints( if bool(self.disk.hard_constraints.get("force_norm", False)): self._batched_enforce_norm(template) - if ( - self.lat is not None - and "lat.i0_raw" not in skip_keys - and bool(self.lat.hard_constraints.get("force_positive_intensity", False)) + if self.lat is not None and bool( + self.lat.hard_constraints.get("force_positive_intensity", False) ): i0 = stacked.get("lat.i0_raw") - if i0 is not None: + if i0 is not None and "lat.i0_raw" not in skip_keys: i0.clamp_(min=0.0) + # Optional slope-magnitude cap (see SyntheticDiskLattice.max_slope_ratio). + ir = stacked.get("lat.ir") + ic = stacked.get("lat.ic") + if ( + self.lat.max_slope_ratio is not None + and ir is not None + and ic is not None + and "lat.ir" not in skip_keys + and "lat.ic" not in skip_keys + ): + if i0 is None: + i0 = self.lat.i0_raw.detach().to(ir.device, ir.dtype).expand_as(ir) + radius = self.lat._slope_support_radius() + if radius > 0.0: + slope = torch.sqrt(ir * ir + ic * ic) + limit = float(self.lat.max_slope_ratio) * i0 / radius + scale = torch.clamp(limit / slope.clamp(min=1e-12), max=1.0) + ir.mul_(scale) + ic.mul_(scale) @staticmethod def _clamp_bounds_inplace(t: torch.Tensor, lo: float | None, hi: float | None) -> None: @@ -1721,8 +1872,14 @@ def build_sample_state_dict( i_val = i_val.detach().clone() if t_val is not None: + # Overwrite only the center-disk template key(s). When the + # lattice owns a separate template it is frozen per-position and + # must be preserved from the base state, not clobbered here. + center_keys = {f"components.{self.disk_idx}.template_raw"} + if not getattr(self.lat, "own_template", False) and self.lat_idx is not None: + center_keys.add(f"components.{self.lat_idx}.disk.template_raw") for key in list(out.keys()): - if key.endswith(".template_raw"): + if key.endswith(".template_raw") and key in center_keys: out[key] = t_val.clone() if i_val is not None: @@ -1815,7 +1972,6 @@ def lr_at_step(self, step: int, n_steps: int) -> dict[str, float]: T_max = 1 eta_min = float(spec.get("eta_min", 1e-7)) s = max(step - 1, 0) - import math lr = eta_min + 0.5 * (base_lr - eta_min) * (1.0 + math.cos(math.pi * s / T_max)) out[key] = float(lr) elif t == "exponential": @@ -1846,6 +2002,14 @@ def batched_constraint_loss( template_b = stacked.get("disk.template_raw") if template_b is not None: out = out + self.disk.constraint_loss_batched(ctx, template_raw_b=template_b) + # Per-sample soft L2 prior on the lattice tilt slopes (see + # SyntheticDiskLattice.slope_l2_weight). + if self.lat is not None and float(getattr(self.lat, "slope_l2_weight", 0.0)) > 0.0: + ir = stacked.get("lat.ir") + ic = stacked.get("lat.ic") + if ir is not None and ic is not None: + w = float(self.lat.slope_l2_weight) + out = out + w * (ir * ir + ic * ic).mean(dim=1) return out def resolve_component_keys(self, components: Any) -> list[str]: From 2128aa83deccf3900ea3b7eb91950fcf65cdbd81 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 20 Jul 2026 11:50:31 +0000 Subject: [PATCH 102/113] chore: update lock file --- uv.lock | 354 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/uv.lock b/uv.lock index 1acec1a28..c6842ffe2 100644 --- a/uv.lock +++ b/uv.lock @@ -431,14 +431,14 @@ wheels = [ [[package]] name = "colorlog" -version = "6.10.1" +version = "6.11.0" 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/0a/bf/cb30a51af3aa8ce63735f77e23dcd4fc0720fe0339bcb04f77345659c277/colorlog-6.11.0.tar.gz", hash = "sha256:9d90fb53fa906c8970c18fbe46506bae1fb5f86b513b8f867db37e4ace9be7ae", size = 17734, upload-time = "2026-07-17T12:16:46.59Z" } 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/ec/a1/6b71004ab0fea510230be9ce05a4059029ac847c009fcc80b1b73d6fa5ab/colorlog-6.11.0-py3-none-any.whl", hash = "sha256:f1e27d75aa2cb138f3f640c0e305b65b680ccbef6ecc034eba7e03494ffcd2a1", size = 12016, upload-time = "2026-07-17T12:16:45.3Z" }, ] [[package]] @@ -548,86 +548,86 @@ wheels = [ [[package]] name = "coverage" -version = "7.15.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/83/6051c2a2feab48ae5bd27c84ef047191d2d4a3172f689e38eaa48ed17db1/coverage-7.15.1.tar.gz", hash = "sha256:165e9949eaf222ef1f018635d0d7f368a23bfe0212af558534c40d8c04686d67", size = 927640, upload-time = "2026-07-12T20:58:19.908Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/ee/5095e07a0986930174fc153fd0bb115f7f2724f5344cc672e016d4f23a4e/coverage-7.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6506330b4a8dcf53b95bd84d8d0e817107cdb3fc1438e835029cdf0bc6612eb0", size = 221320, upload-time = "2026-07-12T20:56:16.036Z" }, - { url = "https://files.pythonhosted.org/packages/43/7a/7e2598ce98433a214020a61c0755d5961f9f26df0c630dc73e06b86d3416/coverage-7.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8a51a8ec382c39d939cba0ab07ae949077ae4e842343bd4eed22d432358cea9", size = 221823, upload-time = "2026-07-12T20:56:17.54Z" }, - { url = "https://files.pythonhosted.org/packages/8b/4f/67dac6a69139b490a239d91b6d5ef127982ffb89159769241656ab879ee5/coverage-7.15.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:18ea20e3922d7f8ca9e0ef1084408d08c4ad62d5e531cb9c1f6896a99297ebea", size = 252242, upload-time = "2026-07-12T20:56:18.868Z" }, - { url = "https://files.pythonhosted.org/packages/05/37/5e439725a96de592309725550c8df639c359c209dcb4b152ed46032d4c0d/coverage-7.15.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aab9902a64b8390e3b56e539fddae1d79a267807fe5cb0c18d7d2f544ce867e2", size = 254153, upload-time = "2026-07-12T20:56:20.118Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1c/671ba9e15f9651a99962fb588a1fe4fb267cae4bb85f9bb4ac4413c6f7ef/coverage-7.15.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cc316264317b07a9e90d7f2b4188a15e36e9b54e651081b791b0515fa612a29", size = 256261, upload-time = "2026-07-12T20:56:21.682Z" }, - { url = "https://files.pythonhosted.org/packages/d6/3c/88305322b4d015b4536b7174b8f9b87f850f01af9d89008cdbf984b65ff2/coverage-7.15.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d4e47e7eea81a8ccf060a07627654151d929da62c7b715738387c200905cec89", size = 258222, upload-time = "2026-07-12T20:56:22.962Z" }, - { url = "https://files.pythonhosted.org/packages/8d/20/d02e42333a57f266ded61ed4fb3821da1f2dc143734aaa3dcc9c117204c5/coverage-7.15.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c6829d9a3b55ad2b73ef5fda8302e5be03683789e88b1a079dcf4a773229c21d", size = 252368, upload-time = "2026-07-12T20:56:24.475Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d0/94ebc010926a191d83e83b1f1236f8bd0d14d0eb98b5c324aa4be2589a8e/coverage-7.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:764e045811f9c8cda436641f3f088283351d331a519b5807f19041cd0a68da1c", size = 253953, upload-time = "2026-07-12T20:56:26.036Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a6/2c8553191da0c2da2c43ff294fb9bab57a5b0fae03560304a82a8b5addc3/coverage-7.15.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:09b3b088aa24489c4082bcc35fcc8224281ab94a653dfb6d3f0c8165b0d628ab", size = 252016, upload-time = "2026-07-12T20:56:27.374Z" }, - { url = "https://files.pythonhosted.org/packages/17/99/20f47986d8360fa1a31d9858dd2ba0be009d44ecfa8d02120b1eb93c028b/coverage-7.15.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7911b02f57053adf8164ae63edb1c26574d24dfccabadc5268cf69310a69a358", size = 255783, upload-time = "2026-07-12T20:56:28.921Z" }, - { url = "https://files.pythonhosted.org/packages/17/36/fb61983b5259f78fa5e62ee74e91fe4de4c556fcbc9a3b9c9021c2f8b57b/coverage-7.15.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:664e279ed40599b8ed16f4db18d92a7e212c73129672bec8f5d96d4da48d2404", size = 251735, upload-time = "2026-07-12T20:56:30.465Z" }, - { url = "https://files.pythonhosted.org/packages/6c/2e/d109d8d2d6c726ba2e78125297073918a2a91464f0ea777e5398fa3cf75c/coverage-7.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34fe7cf79d5f1f87f2e8ce7dc1c32950841f50e10d0120f263856acfad66de34", size = 252645, upload-time = "2026-07-12T20:56:32.076Z" }, - { url = "https://files.pythonhosted.org/packages/bc/a0/5b3219cbb46135e14673427f2bf5890c4da4e6f655243692f3448aa3f42c/coverage-7.15.1-cp311-cp311-win32.whl", hash = "sha256:5e2d2536d2f57a354aa382ed303ac0e2e5c9522a508c05b998d26181b94163a7", size = 223414, upload-time = "2026-07-12T20:56:33.432Z" }, - { url = "https://files.pythonhosted.org/packages/5b/80/24e13ade0b6df8090e2492f9c55044bf1423f7ef28c76fc1af8c827a61cb/coverage-7.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:c337da8fca7ea93ab43f3868cfcde6cf6dad32c3906b273cfbad5d7390bc423b", size = 223888, upload-time = "2026-07-12T20:56:34.784Z" }, - { url = "https://files.pythonhosted.org/packages/2d/9e/34659930ae380491af48e2f5f5f9a2e99cd9fb7de3d30f27be5ac5425137/coverage-7.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:db3403fdb7a94d5eb73e099befad8104d2a7d110a0f0d99df0de61c5d1fa756c", size = 223435, upload-time = "2026-07-12T20:56:36.302Z" }, - { url = "https://files.pythonhosted.org/packages/d9/76/32c1826309beaf4604c54accef108fdd611e5e5e93f2f5192f050cd5f6bd/coverage-7.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9476292594309db922cc841dd13b303b3c388f4c25d279884f7e2341c681f80", size = 221497, upload-time = "2026-07-12T20:56:37.628Z" }, - { url = "https://files.pythonhosted.org/packages/db/5c/b88ce0d68fa550c7f3b58617fbf363bce64df5bf8295a01b627e4696e022/coverage-7.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c579056b0de461b3a62318b63d0b6ce90aed7f8158d3f00da094df82f29d189", size = 221854, upload-time = "2026-07-12T20:56:39.033Z" }, - { url = "https://files.pythonhosted.org/packages/0e/fe/8509fd2a66fc4e0a829f76a0f0b1dc3cc163368352435b5f243168658077/coverage-7.15.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:23214bdbe226f2b0e9c66a7d6a1d59d4a88045dcf86e702cf0fe0d0935e3d615", size = 253359, upload-time = "2026-07-12T20:56:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/e5/81/c7009ed7ea9765adb2b9d095054d748266fae5f07ac6c5f925f33715fcde/coverage-7.15.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df164be93b46b4825cc39339440a05edc54c4d1d865ba4a60fd43d151a2a1cd3", size = 256096, upload-time = "2026-07-12T20:56:42.115Z" }, - { url = "https://files.pythonhosted.org/packages/21/52/dc8ee03968a5ba86e2da5aa48ddc9e3747bd65d63825fdce2d96acb9c5ff/coverage-7.15.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a524fca1a6f08927d9dc2d4c873cfb7bd7202c247f08b14bdc02424071b8b304", size = 257211, upload-time = "2026-07-12T20:56:43.513Z" }, - { url = "https://files.pythonhosted.org/packages/b8/27/95d7623908da8937deb53d48efcdbf423907a47540e63c62fa21372c652b/coverage-7.15.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d70f3542cd38de85a9e257dcb1ac4c1ab4b6d7d2c2a645809207556628755d1c", size = 259473, upload-time = "2026-07-12T20:56:44.974Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3b/730d761928de97d585465680b568ae69622fb40716babadeabffe75cb51b/coverage-7.15.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d78aa537237212c4313aabe5e964b66acc86350ed19ebc56a3e202df33b6077b", size = 253759, upload-time = "2026-07-12T20:56:46.615Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fc/6b9277acff1f9484b6c12857af5774689d1a6a95e13265f7405329d2f5da/coverage-7.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a318112bb4f79d9d04766196d5a3388caa825908a6a9b052aa87de3d9aea7c61", size = 255131, upload-time = "2026-07-12T20:56:48.073Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f2/c704f86129594ba34e25a64695d2068c71d51c2b98907184d716c94f4aec/coverage-7.15.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e55d24cada901963eed5bc89fa562aa033f0d84b9d3de4ecf363737c13aed11e", size = 253275, upload-time = "2026-07-12T20:56:49.538Z" }, - { url = "https://files.pythonhosted.org/packages/f6/29/80fee8af47de4a6dce71ccf2938491f444687a756af258a56d8469b8f1b0/coverage-7.15.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3c78f0cea7275342cf2adc2ad5fdd0aafa106ad91e66d573568f2fcf62c41df5", size = 257345, upload-time = "2026-07-12T20:56:51.038Z" }, - { url = "https://files.pythonhosted.org/packages/20/21/a1e7d7ed1b48a8adf8fd5154d9e83fcc5ad8e6ff20ae00e44865057dce8d/coverage-7.15.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:86bd37eabe39977216f630a7fc1b698e7f5e81a191c7186013245c6c3d313f9d", size = 252844, upload-time = "2026-07-12T20:56:52.535Z" }, - { url = "https://files.pythonhosted.org/packages/a7/8c/a4bc26e6ee207d412f3678f04d74be1550e83140563ca0e4997510579712/coverage-7.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6db15c217693bdc3ca0b84de1ba9afafe1c14c26a8a29d77f4ed0de2b6132e2", size = 254716, upload-time = "2026-07-12T20:56:53.968Z" }, - { url = "https://files.pythonhosted.org/packages/11/9d/8ad0266ecfada6353cf6627a1a02294cf55a907521b6ee0bd7b770cfd659/coverage-7.15.1-cp312-cp312-win32.whl", hash = "sha256:359f3fbe09a51500c51966596ee4ee4070b356552c70b3b2420eb200d68e0f76", size = 223554, upload-time = "2026-07-12T20:56:55.583Z" }, - { url = "https://files.pythonhosted.org/packages/81/6d/24224929e06c6e05a93f738bc5f9e8e6ab658f8f1d9b823e7b85430e28b8/coverage-7.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:fa75dc099c126e941a9c0baa8ebd2cbc78bd778687534fe410baf754f6d9e374", size = 224087, upload-time = "2026-07-12T20:56:57.041Z" }, - { url = "https://files.pythonhosted.org/packages/35/23/f81441dd01de88e53c97842e706907b307d9078918c3f4998b11e9ac7250/coverage-7.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:26f89cf6d0634375f454fa71057945ad18edb0f1607a90fecf22c57dc3dc289a", size = 223472, upload-time = "2026-07-12T20:56:58.594Z" }, - { url = "https://files.pythonhosted.org/packages/ca/1e/6fa289d7993a2a39f1b283ddb58c4bfec80f7800be654b8ba8a9f6a07c63/coverage-7.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:71ac4ca1658ca99160fd58cc6967110e989c34b04627f24ed6ec9f70fb24571a", size = 221519, upload-time = "2026-07-12T20:57:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e1/0db4902a0588234a70ab0218073c0b20fbc5c740aa35f91d360160a2ebc9/coverage-7.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26a40cbf2b13bd94af53ee02a424cb3bb96a9edfac0d00834bd068512a62714b", size = 221895, upload-time = "2026-07-12T20:57:01.867Z" }, - { url = "https://files.pythonhosted.org/packages/b4/cb/3719783865092dac5e08df842730305ee9ab1973ae7ddb6fbdf27d401f30/coverage-7.15.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4c5a5eff4ad4f9f7088fd3fc7a66d98d06566ee294b3b053309fb0a3b45be1e", size = 252882, upload-time = "2026-07-12T20:57:03.459Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5e/caf3abbdbb22629626160ffc9c017eb995b7cb11c0be46b974834cef1792/coverage-7.15.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:962aa56c1c9b016d681265880eb6acc9966029d2c4c559319cc43a1abbb9b59a", size = 255479, upload-time = "2026-07-12T20:57:04.984Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f1/d60f375bfe095fef944f0f19427aefdbf9bdd5a9571c41a4bf6e2f5fdb81/coverage-7.15.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1678eb2dc57a8ce67601b029582ef6d41e9e6ca22692aaeccd4107e40f27386c", size = 256715, upload-time = "2026-07-12T20:57:06.446Z" }, - { url = "https://files.pythonhosted.org/packages/d7/17/8b0cbc90d02dc5adad4d9034c1824ec3fa567771b4c39d9c1e3f9b1431b8/coverage-7.15.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1174900a43f6f8c425fee10d7dbddc308adefcdc78aaced32357f5ab750a0e90", size = 258845, upload-time = "2026-07-12T20:57:08.092Z" }, - { url = "https://files.pythonhosted.org/packages/92/29/c5e69f5fb75c322e9a3e4ef64d02eebfc3d66efceccc8514ff80a3c13a56/coverage-7.15.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98847557a6859cadf693792ce89f440cb89692993f60dc6d3a7e35f3d340216f", size = 253098, upload-time = "2026-07-12T20:57:09.636Z" }, - { url = "https://files.pythonhosted.org/packages/64/57/21144252fdd0c01d707d48fbcea13a80b0b7c42ced3f299f885ab8978c3a/coverage-7.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8697b2edb57143546a24389efc11e1b000cd5800fc20d84f04edb601e4a7cfb8", size = 254844, upload-time = "2026-07-12T20:57:11.141Z" }, - { url = "https://files.pythonhosted.org/packages/59/2a/499a28a322b0ce6768328e6c5bb2e2ad00ac068a7c7adb2ecd8533c8c5d9/coverage-7.15.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6827ac0519be3fe91bf96b4060eb00d1d24f82649b29862cd75a3cfca248b02a", size = 252807, upload-time = "2026-07-12T20:57:12.678Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/928a95da5da8b60f2b00e1482c7787b3316188e6d2d227fb8e124ada43a1/coverage-7.15.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2de8ecbbc77c7e4d22572779920ed8979c69168675e96be3a548c996568c6c31", size = 256965, upload-time = "2026-07-12T20:57:14.326Z" }, - { url = "https://files.pythonhosted.org/packages/16/10/889adbc1b8c9f866ed51e18a98bcafc0259fb9d29b81f50a719407c64ea8/coverage-7.15.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b25f0f0fa5260df9d7bb55d47c8bdc23fa3382c1a18f7c9cae122e6c320b1ad", size = 252628, upload-time = "2026-07-12T20:57:15.892Z" }, - { url = "https://files.pythonhosted.org/packages/1a/30/a5e1871e5d93416511f8e359d1ccebfe0cbb050a1bbf7dd20228533ec0cf/coverage-7.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a2effcbd93ae340a58db718fe4181d967f84d352c4cefeaab4ff82ce813901a", size = 254399, upload-time = "2026-07-12T20:57:17.703Z" }, - { url = "https://files.pythonhosted.org/packages/2d/26/c36fbffd549dadbdd1a75827528fb00a4c46aa3187b007b750b1e2cebbf2/coverage-7.15.1-cp313-cp313-win32.whl", hash = "sha256:895e65c96aef0cecea250f6e35e9a32f11375514e1a0cb5210e0fda128c04e8e", size = 223564, upload-time = "2026-07-12T20:57:19.253Z" }, - { url = "https://files.pythonhosted.org/packages/16/fc/becbb9d2c4206d242b9b1e1e8e24a42f7926c0200dd3c788b9fab4bb96d5/coverage-7.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6d0a28b63a0d75f9ed5118105d1154fc3aa40a8605a30d5d87e3d043ad90fe7", size = 224106, upload-time = "2026-07-12T20:57:21.108Z" }, - { url = "https://files.pythonhosted.org/packages/d3/30/1cfc641461369b6858799fca61c0a8b5edc490c519bf7c636ffa6bbf556f/coverage-7.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:b4ee9818e8bae3544379ad2c09b851c4fb886aaa8860d57a1c1316ddcb16db49", size = 223497, upload-time = "2026-07-12T20:57:22.734Z" }, - { url = "https://files.pythonhosted.org/packages/f0/46/81961952e7aebfb38ad0ae4264e8954cc607a7af9e7ac111f9fa986595cc/coverage-7.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a886af95f59edf67d5770fd3564d53f4a8af93f25f8c1d60d27e00d7f5674ee8", size = 221560, upload-time = "2026-07-12T20:57:24.282Z" }, - { url = "https://files.pythonhosted.org/packages/13/d2/ee14d715889f216baf47301d9f469e08fff6995552aaf67e897b282865f6/coverage-7.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:985657ebd707941de90d488d1cbb5efac20bdf81f7b91eba771624ccda4d36f4", size = 221894, upload-time = "2026-07-12T20:57:25.87Z" }, - { url = "https://files.pythonhosted.org/packages/f3/38/f830bc6e6c2c5f23f43847125e6c650d378872f7eeba8d49f1d42193e8a9/coverage-7.15.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5bbe2a06e0a5e1404d9ffbdb49b819bbd6a3bb198ebea4c8dfe7ad9f1e1c2e81", size = 252938, upload-time = "2026-07-12T20:57:27.506Z" }, - { url = "https://files.pythonhosted.org/packages/e1/53/0d3dd963631259d794c898735d5436e68d6a8d40749c419a07ff7c171469/coverage-7.15.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bde0fe24083d0b7b3dbafa7a09f0796410af1afa2523f28f5f208d8340a4aaca", size = 255445, upload-time = "2026-07-12T20:57:29.234Z" }, - { url = "https://files.pythonhosted.org/packages/b1/fd/aabed228557565c958259251b89bab8c5669b31291fa63b3e2154ebb017a/coverage-7.15.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f89f7453d6d46db14cf233e2cd8edcd78de2b9c49d4f1dc109590b4e5dbfbb74", size = 256790, upload-time = "2026-07-12T20:57:30.826Z" }, - { url = "https://files.pythonhosted.org/packages/bc/aa/1cc888e5d3623e603c4e5399653cb25728bb2b40d7519188a3e293d24620/coverage-7.15.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc3656c9ecc27b36bd0907455b77f83c0069ca9ad4a66dec892b76c696eb6047", size = 259104, upload-time = "2026-07-12T20:57:32.63Z" }, - { url = "https://files.pythonhosted.org/packages/5f/61/fc16d5f5e53098dae41efa21e8ccc611a9b4fe922750dd03dc56db552182/coverage-7.15.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:24d8e85a2a45e44883b488c2659f51fa761dad5353fdb319b672a93facbd2ca9", size = 252956, upload-time = "2026-07-12T20:57:34.316Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f3/52384668c3de4519ca770bf1975a89e4d6eb5aa2faf0da0577a14008cba4/coverage-7.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68931b5fe746ed4fdaa8892989cab9e6c35781eeb3b0ab2ded893d561e1b3652", size = 254797, upload-time = "2026-07-12T20:57:35.947Z" }, - { url = "https://files.pythonhosted.org/packages/ce/68/54b807e7c1868178e902fd8360b5d4e559394462f97285c50edf1c4608db/coverage-7.15.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1ce6947e2a95534ecaa5a15e73c21e550514c980d80eda204d064d789a95f6a4", size = 252762, upload-time = "2026-07-12T20:57:37.856Z" }, - { url = "https://files.pythonhosted.org/packages/7b/48/dde8adf0338e3ace738757dccf1ce817e5fdcadfae77e1b48a77e5a3b265/coverage-7.15.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:841befdbc89b9c82435fc25b0f4f41858b6238693e45af758bec4cfc1968171c", size = 257037, upload-time = "2026-07-12T20:57:39.488Z" }, - { url = "https://files.pythonhosted.org/packages/07/f2/179dd88cf60a0aeeee16a970ffe250dccea8b80ed4beab4c5d3f6c41ad4b/coverage-7.15.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5d3de58b837375e7f4c0e1a088ccab5f655efb2fd7427b729df02c862a559633", size = 252577, upload-time = "2026-07-12T20:57:41.363Z" }, - { url = "https://files.pythonhosted.org/packages/2c/37/8a593d69ab521beb6a105a2017cac4ba94425ee0a8349e29c3c0b522d24f/coverage-7.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b1801963f9f44ae0c0f6d737bc7aeb2bbcde7d1fe7e3b43cddc1961af42d3b41", size = 254235, upload-time = "2026-07-12T20:57:43.025Z" }, - { url = "https://files.pythonhosted.org/packages/6d/34/bc9b3bced66f2cdad4bf5e57ae51c54ea226e8aaaebfc9370a9a11877bf3/coverage-7.15.1-cp314-cp314-win32.whl", hash = "sha256:8c7953c4128ef53b6ffb5f90d87c87d4ce26731df294760bb2314eb0e069e44b", size = 223771, upload-time = "2026-07-12T20:57:44.662Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/4d20337bed61915d14349e62b88d5e4144d5a9872b64adbe90e9906db6db/coverage-7.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:6f0bab60a582d415f0fb535ccff13ba334a47a1538f98913330a525d23bd535a", size = 224257, upload-time = "2026-07-12T20:57:46.412Z" }, - { url = "https://files.pythonhosted.org/packages/7b/df/bbfeae4948f3ded516f92b32f2d57952427fc5ecfc0924487bb6ee6a5f38/coverage-7.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:0f410ee8f0ac4ec7db71bc0b7632a8b9994e1cad2755bd1566c17e6a162caa74", size = 223683, upload-time = "2026-07-12T20:57:48.106Z" }, - { url = "https://files.pythonhosted.org/packages/35/65/0b431856064e387d1f5cf474625e4a0465e907024d42f35de6af19ced0be/coverage-7.15.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fc868bab88e049d41fcd41766810d790a8b960053be2a45e060f5ce0d31d258b", size = 222298, upload-time = "2026-07-12T20:57:49.882Z" }, - { url = "https://files.pythonhosted.org/packages/a6/96/50eac9bd49df8a3df5f3d38746d1bf332299dffb554486c94ebd55c9dc49/coverage-7.15.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:206d4ec6028f2773b40932d09f074539d6bcdd8f6b318d40cb04bdbd68ed0b49", size = 222561, upload-time = "2026-07-12T20:57:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/e0/5b/6ba1c4a27e10b8816fd2622b98162c83d3bdf1185097360373611bf96364/coverage-7.15.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:620482ef1c9f4e61f962e159325fe77dea59d16e39d9c9470d069053b244d864", size = 263923, upload-time = "2026-07-12T20:57:53.392Z" }, - { url = "https://files.pythonhosted.org/packages/e4/59/fe03ade97a3ca2d890e98c572cf48a99fda9adba85757c34b823f41efe1e/coverage-7.15.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d385fc9b054e309ad3cecdc77b586d2af0c98aeec2fdb3773544586f366e817c", size = 266043, upload-time = "2026-07-12T20:57:55.095Z" }, - { url = "https://files.pythonhosted.org/packages/16/e0/55c4b1217a572a43e13b39e1eb78d0da29fb23679003bd0cdf22c50b1978/coverage-7.15.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1198bca9c0dd7c188aae1f185b0c0b5fc4f0a2b6909000858c29550320bdb07", size = 268465, upload-time = "2026-07-12T20:57:57.017Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e2/ee47944f76afc03909119b036fe9e0da8cbd274a5141287de79791a0fb6d/coverage-7.15.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d0297e6a070eadb49df7cddd0ab6f420b8b689dd8904c7dd815a323168fa57e", size = 269584, upload-time = "2026-07-12T20:57:58.958Z" }, - { url = "https://files.pythonhosted.org/packages/cb/8a/6b4d9779c7b2e21c3d12c3425e3261aa7411399319e27aa402dfec4db5d0/coverage-7.15.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916fcf2214f56960e409561b37fc32a160a42b6e85483d0652d7b70fa55d707e", size = 263019, upload-time = "2026-07-12T20:58:00.979Z" }, - { url = "https://files.pythonhosted.org/packages/c4/1e/db5c7fa0c8ba5ece390a1e1a3f30db71d440240a80589df28e66a7503c40/coverage-7.15.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f837bae572c7869ffaa502e604c87e182543012831cf87aae4586ad090ac6dcf", size = 265916, upload-time = "2026-07-12T20:58:03.005Z" }, - { url = "https://files.pythonhosted.org/packages/83/53/fe5176682b00709b13fab36addd26883139d0dea430816fea412e69255e2/coverage-7.15.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3ea65e3ee6c7c32349fd00559927a9e577bdd72386087eeed1c42b62dfce9b82", size = 263520, upload-time = "2026-07-12T20:58:04.994Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e7/16f15127be93fbc70c667df5ec5dce934fc76c9b0888d84969a5d5341e2c/coverage-7.15.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:345034976f46a1c54bd17f4e43eb30bb92cb7082fcddff03250cff136cc4eb82", size = 267254, upload-time = "2026-07-12T20:58:06.824Z" }, - { url = "https://files.pythonhosted.org/packages/cd/73/e5119111f6f065376395a525f7ce6e9174d83f3db6d217ea0211a61cca4d/coverage-7.15.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4f051a64eb8f8addb4661c2b41d6eea5b7ebc68ad4b2baea8d9bc54e1956e5f7", size = 262366, upload-time = "2026-07-12T20:58:08.555Z" }, - { url = "https://files.pythonhosted.org/packages/d7/9c/6d0a81182df18a73b081e7a8630f0e2a52b12dfd7898c6ab839551a454d2/coverage-7.15.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a7625770f7720b49bb30d194ad2f8d50fab3c5177874af3d2399676f95f9c594", size = 264680, upload-time = "2026-07-12T20:58:10.359Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a2/bac0cbd4450638f1be2041a464b1766c8cc94abf705a2df6f1c8d4be870d/coverage-7.15.1-cp314-cp314t-win32.whl", hash = "sha256:81e503d130a472ad1bd38199ecd35116b40d92bcd31e27a2cacde035381f2070", size = 224077, upload-time = "2026-07-12T20:58:12.065Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b2/d83c5403155172a43ba47c08641bad3f89822d8405102423a41339d2c857/coverage-7.15.1-cp314-cp314t-win_amd64.whl", hash = "sha256:724e878b213b302ad46e9f2fc872d386613f20ebfc492a211482d917ea76c14f", size = 224908, upload-time = "2026-07-12T20:58:13.956Z" }, - { url = "https://files.pythonhosted.org/packages/cc/41/442b74cad832cc77712080585455482e7cc4f4a9a13192f65731dcd18231/coverage-7.15.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ce2f05c14d077f406fefc4fa5e4f093ad0e0787549f6582535d6e28766f0361b", size = 224219, upload-time = "2026-07-12T20:58:16.315Z" }, - { url = "https://files.pythonhosted.org/packages/34/98/07a67cf1a26e795d617ed5c540c042b0ac87b72f810c30c07f076cf334f3/coverage-7.15.1-py3-none-any.whl", hash = "sha256:717d01e6e00bed56ad13306f19e0dd2f4f645ee8159d2c72c72301d6cfc7090c", size = 213284, upload-time = "2026-07-12T20:58:18.079Z" }, +version = "7.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/3a/54536704f507d4573bf9161c4d0dd3dd59b6d85e48c664e901b6844d8e33/coverage-7.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036", size = 221414, upload-time = "2026-07-15T18:53:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d9/8ba925d29743e3577b21e4d8c11a702b76bc93c41e7fdfd1177af63d4b8d/coverage-7.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660", size = 221913, upload-time = "2026-07-15T18:53:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/a855f3aa0187f2b431ade4e4791b77b56282cfb5d201c83ec26a31b5b36a/coverage-7.15.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589", size = 252332, upload-time = "2026-07-15T18:53:55.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d3/13ac97b4370640ba3452fc8559b06cc2f479ce3ba4a0b632a73e44c38a7d/coverage-7.15.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee", size = 254243, upload-time = "2026-07-15T18:53:57.055Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/5eca144942d8d0659d3f55176517f4a59cdc65eefd17146a0770935a3ebd/coverage-7.15.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0", size = 256352, upload-time = "2026-07-15T18:53:58.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ba/d3db2e01a50fc88cdb4c0f19542bcf6f61489e34dc9aa3538413e2459a38/coverage-7.15.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487", size = 258313, upload-time = "2026-07-15T18:54:00.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/b3/aba83416e9177df28e5186d856c19158c59fc0e7e814aaa61a4a2354ad1b/coverage-7.15.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f", size = 252449, upload-time = "2026-07-15T18:54:02.456Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a5/4b00ecac0194431ab451b0f6710f8e2517d04cef60f821b14dec4637d575/coverage-7.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1", size = 254043, upload-time = "2026-07-15T18:54:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/75/b6/cfa209b4313ee7f1b34da47efcd789ea51c024ad35af390e00f5a3c10a2e/coverage-7.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5", size = 252107, upload-time = "2026-07-15T18:54:06.745Z" }, + { url = "https://files.pythonhosted.org/packages/36/67/e8cac5a6954038c98d7fe7eb9802afe7ab3ecb637bb7cc00e69b4148b56d/coverage-7.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0", size = 255873, upload-time = "2026-07-15T18:54:08.48Z" }, + { url = "https://files.pythonhosted.org/packages/2c/92/395cca9f330a86c3fe3471d73e2c102116c4c58fdc619dbbc125c6e93a54/coverage-7.15.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad", size = 251826, upload-time = "2026-07-15T18:54:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/51/60/3e91b20295439652424f426b7086ec5bf4fbe3f604c73eda22b986c4fd6b/coverage-7.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db", size = 252735, upload-time = "2026-07-15T18:54:11.878Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/8c07839005e5e3c6b3877d3a6e2a80ce766589f31dd2b6882b78d59a7b8c/coverage-7.15.2-cp311-cp311-win32.whl", hash = "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9", size = 223500, upload-time = "2026-07-15T18:54:13.525Z" }, + { url = "https://files.pythonhosted.org/packages/2e/98/59d83c257cd59f0fbaf9d9ddb26b744a576760dfd1ae16e516408894a02b/coverage-7.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688", size = 223973, upload-time = "2026-07-15T18:54:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/ea/09/2d285c8bef5c4f695d120c1c96dc11715638aa8e134069f210bb6a62a9fe/coverage-7.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934", size = 223519, upload-time = "2026-07-15T18:54:16.803Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/eb5bf42e531611a9f8d272556b1ed4de503f84a91413584094487cf69f8f/coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9", size = 221587, upload-time = "2026-07-15T18:54:18.439Z" }, + { url = "https://files.pythonhosted.org/packages/06/d1/da99af464c335d4e023a6efcd7ec30f63b88a43c93745154ab74ffb31cea/coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73", size = 221943, upload-time = "2026-07-15T18:54:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8a/13c42723d61ca447eafa18732e8141dd6a63f2732e1c7e1502c182dd88d7/coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d", size = 253450, upload-time = "2026-07-15T18:54:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/d7/29/99021303f98fbdcb63504b4d07bea4cc025b9b2dd907c4f07c85d50a0dab/coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b", size = 256187, upload-time = "2026-07-15T18:54:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a8/fd503715ed6ca9c5d742923aa5209257340b367a867b2ced0c7d4ba8a0b9/coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296", size = 257301, upload-time = "2026-07-15T18:54:25.183Z" }, + { url = "https://files.pythonhosted.org/packages/da/40/3f4b8fb409810036ebc2857d36adc0498c6e957b5df0290c5036b2e143f1/coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6", size = 259562, upload-time = "2026-07-15T18:54:27.204Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8a/9bdffbef47db77cce3d6b02a28f7e919b19f0106c4b080c2c2246040f885/coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098", size = 253841, upload-time = "2026-07-15T18:54:29.134Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1e/9031efde019d31a06646261fce6dfc5c3c74e951e27a71e5c9a424563178/coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a", size = 255221, upload-time = "2026-07-15T18:54:31.142Z" }, + { url = "https://files.pythonhosted.org/packages/56/db/787acde872389fc84a9ef9d8cd1ccc658e391ab4cb5b28092a714426a394/coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b", size = 253366, upload-time = "2026-07-15T18:54:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9b/6f57bc4b93c842eef1695f8cdaf2318e35e7ba54f5ba80d84be213ab7858/coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2", size = 257434, upload-time = "2026-07-15T18:54:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/b3186a21b2acc83e451118978905c81c7072c3333707804db09a78c096a2/coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440", size = 252935, upload-time = "2026-07-15T18:54:36.548Z" }, + { url = "https://files.pythonhosted.org/packages/20/c2/c9f3376b2e717ea69ed7a6e9a5fcab968fb0b290db6cf4bd9a1fc7541b75/coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e", size = 254807, upload-time = "2026-07-15T18:54:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e1/dfc15401f4a8aaeb486e1ba3e9e3c40522a6e38bd0ecf0b3f29cb8082957/coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd", size = 223641, upload-time = "2026-07-15T18:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/91/40/81b6d809d320cd366ec5bdf8176575e897dcb8efe7fb4b489ef9e93e4d13/coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40", size = 224172, upload-time = "2026-07-15T18:54:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/ef/28/9f14ec438149f7de557f45518f09b4a7917b795cc37083aa7db482693f8c/coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3", size = 223556, upload-time = "2026-07-15T18:54:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d5/f8c838e6b7282976f7c918884b792df7a0c42c5bba5d99c60ad2d221d56d/coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", size = 221606, upload-time = "2026-07-15T18:54:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/97c926376364f66298cc44893b89cdf17b8bc406376497c4061ae4b8a8ff/coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", size = 221982, upload-time = "2026-07-15T18:54:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/a36050a6e83c2135ee0776f452ca3948224befc6d7f26acecc082d0c106a/coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", size = 252972, upload-time = "2026-07-15T18:54:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/06b5f1daf95f0f15ab05bd75f26ba5f3c8b33d0bb72f3aaa3cf41d1bad3a/coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", size = 255569, upload-time = "2026-07-15T18:54:51.098Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/9afb3f8de2b8d36960391c48559a2e3ff96594b58099f115921549ea8d0d/coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", size = 256806, upload-time = "2026-07-15T18:54:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/64/d8/b989f96061a5e32d82fddd1b1b9ff48a7c8f8ae7606f0e80fd9de54b1e33/coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", size = 258936, upload-time = "2026-07-15T18:54:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/f99771f5110457c7b511c1935ca49ddf288218eaa84322e028b9334146ae/coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", size = 253178, upload-time = "2026-07-15T18:54:57.527Z" }, + { url = "https://files.pythonhosted.org/packages/f6/96/c098a6044d119c751ceede7be91035fa8310170ec24a6523aff72f0a5793/coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", size = 254934, upload-time = "2026-07-15T18:54:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a2/1457b3a7a50c8d77500103b97a046db863e2f59a1cf6d2f814595f349885/coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", size = 252898, upload-time = "2026-07-15T18:55:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/76958874c471ecfcdde0d2b2747bb2c61bdbf34a40636f4ce9db9923e643/coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", size = 257056, upload-time = "2026-07-15T18:55:03.243Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7c/3d7c4e3bf58baa40327dc7edc2272b17cf02299366d52763db1b0ca1556a/coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", size = 252718, upload-time = "2026-07-15T18:55:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/1cecffed9ce14fb25be9ba42d37b6bb61485c9a3ddd43cd3dde36b6087d8/coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", size = 254490, upload-time = "2026-07-15T18:55:06.889Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2c/42984561bc7f4c045dca67516a0c50ee5ef8d84352dbeb5559dc86c4823e/coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", size = 223647, upload-time = "2026-07-15T18:55:08.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/9f/39c7c9245efc583beddf89a87683574e663ed93637f3afb6cd7b88405676/coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", size = 224190, upload-time = "2026-07-15T18:55:10.789Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/3a2883cf8a213659280ef4b403059e17a9acaeb7fc7fd4105e1226ff2e6d/coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", size = 223583, upload-time = "2026-07-15T18:55:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/aed265fd7a3551a394f36dfe41868aee709b7f95db4052205b4ad1563ac3/coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", size = 221650, upload-time = "2026-07-15T18:55:14.527Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2c/222ba12a545189017120f8eddfc1a0bd4616b47d5d4a8d99421edb2fe4c6/coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", size = 221988, upload-time = "2026-07-15T18:55:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/aa/38/304b5877ab46e6c290b4292cfcf3fe28245f0e5597cad7f6acc91fc7e0a4/coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", size = 253029, upload-time = "2026-07-15T18:55:18.856Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/821b533b8db9e44cf1d8a97bd525149ced40dde1d0093da02cb78e715244/coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", size = 255536, upload-time = "2026-07-15T18:55:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f2/7aa06604c389d32ea7f0a6a988359a7eafc3cd3f8e7bc2e88cd2fdf0b877/coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", size = 256881, upload-time = "2026-07-15T18:55:23.125Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4f/1ef342339c7916d0096bc5888cc0f653882cc7bc8f897d5cb89143287c9b/coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", size = 259196, upload-time = "2026-07-15T18:55:25.099Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f4/7ed055d7a9c5ec13b161773a115a5ccc6b0081d568c31fad830806306cc7/coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", size = 253036, upload-time = "2026-07-15T18:55:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/14/79/ea82cca18c242a3a38b6c017da39726aa62dcb64aa635abf79b92009975c/coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", size = 254887, upload-time = "2026-07-15T18:55:29.084Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ba/a136db3c0d9562b00e10b72540dbf3a33cd3bc5b95060c9308e247494623/coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", size = 252852, upload-time = "2026-07-15T18:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/ea334246b16b7d059953fad6fdefa11e33c68efbd3fe37b1098120a1fac2/coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", size = 257128, upload-time = "2026-07-15T18:55:33.163Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c3/074fb66d46d607855f710876b117cbda562c5ab08363528e78820449f937/coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", size = 252668, upload-time = "2026-07-15T18:55:35.063Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c1/f620850ada9b36435921c9a3a8057013422b1d964eb4bf37fe138724d192/coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", size = 254325, upload-time = "2026-07-15T18:55:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/cc/31/a729ca3689404493af82ef8e6ff70bd88bdda8da89aeef6ca9b387aeb2b4/coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", size = 223844, upload-time = "2026-07-15T18:55:39.078Z" }, + { url = "https://files.pythonhosted.org/packages/c6/83/5d809dc808fb1698c671f3e372259bb9158e64b7ea526fc6ab7de64de9fe/coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", size = 224331, upload-time = "2026-07-15T18:55:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/4e/35e488548e952795829e129995c4174df33bf432b591d1aa42c8d9e4e7ad/coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", size = 223760, upload-time = "2026-07-15T18:55:43.518Z" }, + { url = "https://files.pythonhosted.org/packages/ed/49/dd2c86cd6374038f6e415fb5bfb86db5218553209c081384a020369dee79/coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", size = 222384, upload-time = "2026-07-15T18:55:45.569Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/173ff17a1c0808e5a438f549f6f145d5ac7528f2791310b63523e3200ac7/coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", size = 222647, upload-time = "2026-07-15T18:55:47.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/f8/b8cba872162356fb44ac79c10309d987206a4461e32072fc29228dad7331/coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", size = 264013, upload-time = "2026-07-15T18:55:49.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/67/a807a7586d0b8cae485308ddd55756f0806c92f8e0b411bacbf23c48edf3/coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a", size = 266135, upload-time = "2026-07-15T18:55:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/cd78771dc985f7e4ebdcc82b1a96d9a932af9e806f01f2f91a89f4c72e80/coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", size = 268555, upload-time = "2026-07-15T18:55:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/18/3e/10134cf81275188c58568f324fc74aedff32c63ca4d5bbc513a91944a6f0/coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", size = 269674, upload-time = "2026-07-15T18:55:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/75/4a/771b77de446cba985dc414bbc5844bd21604da05dbc044286df8318a48a7/coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", size = 263101, upload-time = "2026-07-15T18:55:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b5/70a7011da15f4071943361183aefa27847f3e3aec4fd335f1cb3d3a622b1/coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", size = 266007, upload-time = "2026-07-15T18:56:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/f9547e804ce7ad49646ffeffac26699510efbe6c0f751b66fdc960c4e825/coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", size = 263611, upload-time = "2026-07-15T18:56:02.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/59/f576a396659c0efd351f5c1544f67c3560e89c7761cabf7f65e412beeda5/coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", size = 267344, upload-time = "2026-07-15T18:56:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5d/c2e4fce3579c0cb635024293f1a32bbe26df101b3e3a69f22243d1352b6c/coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", size = 262456, upload-time = "2026-07-15T18:56:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/956287d69436b66094bc4b57ac2da71e43bfd2a5524e958900b9f582fcf8/coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", size = 264771, upload-time = "2026-07-15T18:56:08.795Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5a/6f979530c2734c575de77cf58f5f28d51f7123a94b5030fd9156fe5f363c/coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", size = 224151, upload-time = "2026-07-15T18:56:10.856Z" }, + { url = "https://files.pythonhosted.org/packages/54/7e/27f6b2a74d484742f4017553e710b01e396b23d809df3e95ca0bb9a2824b/coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", size = 224981, upload-time = "2026-07-15T18:56:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/b1/48/284863423aa474240f6842bd00d680da22f4e6ea2e466618ef7c9c9e69a9/coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", size = 224294, upload-time = "2026-07-15T18:56:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, ] [package.optional-dependencies] @@ -723,7 +723,7 @@ wheels = [ [[package]] name = "dask" -version = "2026.7.0" +version = "2026.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -735,9 +735,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/b5/aa50877159f1efd65a744107e0662a15d2b80fa0c427d980313367f49493/dask-2026.7.0.tar.gz", hash = "sha256:b039970642ce97a063070bae2763dada8aae27e3cbd8ff6d894072f920a1e252", size = 11548914, upload-time = "2026-07-06T16:58:36.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/39/cbd21c9133d02b4e60899ed466ad5e876553ea68ebffee6209e7dcafc8d4/dask-2026.7.1.tar.gz", hash = "sha256:5727484427665f051e86bf87d021a64d6411141cdc8a20bfe3c1ad2968cc06b7", size = 11548794, upload-time = "2026-07-14T01:06:22.46Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/aa/ceeb81d9dc886d445416310f0eff5c3978f20224b4143e96e36b7e85b011/dask-2026.7.0-py3-none-any.whl", hash = "sha256:68def8b467529ab8425ef9fcc6735dcaefaaf715d5e19ae219485f99a394dbef", size = 1496878, upload-time = "2026-07-06T16:58:34.597Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5f/7c22733da92b3a6cc4dddcaa8731089d213c2790bbc997e51c429a4e8f8b/dask-2026.7.1-py3-none-any.whl", hash = "sha256:985ffd6c5e9d7979ede515e84ae8d39b647d6aa64f77600f15714ff65f578fe6", size = 1496882, upload-time = "2026-07-14T01:06:20.341Z" }, ] [package.optional-dependencies] @@ -839,11 +839,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.7" +version = "3.31.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/94/00f2059e4835eace3ae8fde680b932c496f8ec7bdc99168dfa53fb2e6b79/filelock-3.29.7.tar.gz", hash = "sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d", size = 71521, upload-time = "2026-07-08T05:46:58.716Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/55/1e19b2b56a24a4b94624f7e819e1bb87fa6c5609dbaf621df3aa6568a761/filelock-3.31.1.tar.gz", hash = "sha256:9e0c4e88ebe90833c1beafd3a547ccbc0bf7f491cd3858c3ec7aed63efe02163", size = 196656, upload-time = "2026-07-20T03:14:32.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/02/be4a57b60c7149b55b9e3b3c13f609cd8eb5307c751f22bd8fb8d262e75b/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51", size = 46036, upload-time = "2026-07-08T05:46:57.53Z" }, + { url = "https://files.pythonhosted.org/packages/88/61/df1d9db18f188d0ae648956a1decadc0e3b77d0571474370fd01f28a82b1/filelock-3.31.1-py3-none-any.whl", hash = "sha256:9ea33146c780161bf67cb20c7cb26b651566820d65ad8dfdd79422602a2dcfc0", size = 97189, upload-time = "2026-07-20T03:14:31.307Z" }, ] [[package]] @@ -1207,16 +1207,16 @@ wheels = [ [[package]] name = "imageio" -version = "2.37.3" +version = "2.37.4" 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.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" } +sdist = { url = "https://files.pythonhosted.org/packages/48/62/aa770a9307508d2a2a2c62d536a49347bffe9e55322db27838d3c93d0b07/imageio-2.37.4.tar.gz", hash = "sha256:e45cbc5e83502047fb138f7f585f7f105a136a57eea5f4b3cfc6ce1b52720bd3", size = 390173, upload-time = "2026-07-20T05:26:11.369Z" } 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/3e/2d/ca050652104bab2cf55e569db2a178b1b61cb041fef28307f2db383f6d9f/imageio-2.37.4-py3-none-any.whl", hash = "sha256:1ab2e22c8debf700f24c3ac43e8f95f3b3a8110c83b93411e97b4b0b2cd1c7e6", size = 318000, upload-time = "2026-07-20T05:26:09.874Z" }, ] [[package]] @@ -1411,15 +1411,15 @@ wheels = [ [[package]] name = "jupyter-builder" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-core" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/df/db5efc4e28803a1421350445e53d85ec571a4e6928e3524ccc39f4e16584/jupyter_builder-1.1.0.tar.gz", hash = "sha256:b996e8af616900f18724fa34883169d869be2205497940bec78a3a1031eb897d", size = 971142, upload-time = "2026-07-11T07:24:02.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/61/47f7ae054f5cd3983c10e1d65a6eb7fcd4b87ebb1056e190ef7d63ff4f19/jupyter_builder-1.1.1.tar.gz", hash = "sha256:1a13977912b08deda77fce2c803940131c27cf77a27ed64b9ffca25aa0ed7e6c", size = 971667, upload-time = "2026-07-17T13:14:47.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/fb/53adbc72931b4429275b160044c3e1bcbc8fbb5ea6b8f318a357834842fb/jupyter_builder-1.1.0-py3-none-any.whl", hash = "sha256:d9d5280e8845f726663545c3a6db55bd205127616eeb8958481091d6cba71b6a", size = 912964, upload-time = "2026-07-11T07:24:00.279Z" }, + { url = "https://files.pythonhosted.org/packages/83/cc/f6a12de1c890ea5dd2816c5c76d5ac6d3ed52db3c37f78691328207d13b9/jupyter_builder-1.1.1-py3-none-any.whl", hash = "sha256:f9c14bc55c0488a073f62af12d468936fcf9ecb7e9dd802f6f9c33de46ad70db", size = 913264, upload-time = "2026-07-17T13:14:45.857Z" }, ] [[package]] @@ -1832,7 +1832,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.11.0" +version = "3.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -1846,53 +1846,53 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -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" }, +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, + { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, + { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3", size = 9456022, upload-time = "2026-07-18T03:39:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319", size = 9285475, upload-time = "2026-07-18T03:39:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f", size = 10847102, upload-time = "2026-07-18T03:39:11.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be", size = 11131087, upload-time = "2026-07-18T03:39:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/97/c5/5e100efdd67abb7de20befaa333612ef9bfc63417fb71398f904f25d083c/matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2", size = 10929036, upload-time = "2026-07-18T03:39:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6", size = 9489571, upload-time = "2026-07-18T03:39:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/facabdc2f1f6caba7e856db64dfedddca25f7608df07d96a1c8fd114fd3b/matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685", size = 9164486, upload-time = "2026-07-18T03:39:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/18da6cd01cf96354534f98c468a25380c68ce582a2c9dd0cae12b04af4f2/matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae", size = 9504876, upload-time = "2026-07-18T03:39:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/f0b63555a18b79d038c81fd6126f35fc4dfce0eaff48d96103348c7cf935/matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda", size = 9336120, upload-time = "2026-07-18T03:39:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/f210ec7c4a6f198d5567237048a93d0811fb5a1f1691f13320e592f95b41/matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb", size = 10858033, upload-time = "2026-07-18T03:39:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/d6d5324507c5fbb316db48e258c09c2807f3de03d9af47017e120070926f/matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2", size = 11141827, upload-time = "2026-07-18T03:39:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/0f/68/3c22e9320bdce2c4d2f1320643ef706db7a24cb7420eea28b97a2d67f5a8/matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d", size = 10943061, upload-time = "2026-07-18T03:39:32.356Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/907ed190ee81a9df581e0ed5456134fc0f7cb55ffcfda2f9e54ca900761c/matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb", size = 9540074, upload-time = "2026-07-18T03:39:34.789Z" }, + { url = "https://files.pythonhosted.org/packages/23/d4/97c19b77e0a6e3b48581185bb65088f431cd20186076cc0f650a1757ea46/matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987", size = 9213472, upload-time = "2026-07-18T03:39:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, ] [[package]] @@ -2523,11 +2523,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.10.0" +version = "4.10.1" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/52/cd/4f25b2f95b23f5d2c9c1fe43e49841bff5800562149b2666afc09309aa8f/platformdirs-4.10.1.tar.gz", hash = "sha256:ceab4084426fe6319ce18e86deada8ab1b7487c7aee7040c55e277c9ae793695", size = 31678, upload-time = "2026-07-18T03:53:43.808Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/ec/73/6fd0bb9ce84138c3857f12e9de63bc901852975a092d545f18087a204aa2/platformdirs-4.10.1-py3-none-any.whl", hash = "sha256:0e4eff26be2d75293977f7cddc153fd9b8eaa7fb0c7b64ffe4076cb443117443", size = 22906, upload-time = "2026-07-18T03:53:42.576Z" }, ] [[package]] @@ -3262,27 +3262,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/36/6f65aa9989acdec45d417192d8f4e7921931d8a6cf87ac74bce3eed98a8e/ruff-0.15.21.tar.gz", hash = "sha256:d0cfc841c572283c36548f82664a54ce6565567f1b0d5b4cf2caac693d8b7500", size = 4769401, upload-time = "2026-07-09T20:01:34.005Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/c6/ede15cac6839f3dbce52565c8f5164a8210e669c7bc4decb03e5bdf47d0d/ruff-0.15.21-py3-none-linux_armv6l.whl", hash = "sha256:63ea0e965e5d73c90e95b2434beeafc70820536717f561b32ab6e777cb9bdf5d", size = 10854342, upload-time = "2026-07-09T20:00:53.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/9d/d825b07ee7ea9e2d61df92a860033c94e06e7300d50a1c2653aac27d24fe/ruff-0.15.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0f212c5d7d54c01bbfe6dcab02b724a39300f3e34ed7acbe995ccb320a2c58bd", size = 11139539, upload-time = "2026-07-09T20:00:57.809Z" }, - { url = "https://files.pythonhosted.org/packages/f5/de/3b107712e642f063c7a9e0887c427b22cb44097de5aab36c05f2e280670c/ruff-0.15.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e6312e41bc96791299614995ea3a977c5857c3b5662b1ecef6755b02b87cb646", size = 10595437, upload-time = "2026-07-09T20:01:00.006Z" }, - { url = "https://files.pythonhosted.org/packages/9a/6f/b4523cc90ba239ede441447a19d0c968846a3012e5a0b0c5b62831a3d5e3/ruff-0.15.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d65b4831c6b2a4ba8ee6faa84049d44d982b7a706e622c4094c509e51673be", size = 10990053, upload-time = "2026-07-09T20:01:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/92/cc/c6a9872a5375f0628875481cf2f66b13d7d865bf3ca2e57f91c7e762d976/ruff-0.15.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c5a913a589120ce67933d5d05fd6ddbcc2481c6a054980ee767f7414c72b4fd", size = 10666096, upload-time = "2026-07-09T20:01:04.299Z" }, - { url = "https://files.pythonhosted.org/packages/ab/97/c621f7a17e097f1790fa3af6374138823b330b2d03fc38337945daca212c/ruff-0.15.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef04b681d02ad4dc9620f00f83ac5c22f652d0e9a9cfe431d219b16ad5ccc41", size = 11537011, upload-time = "2026-07-09T20:01:06.771Z" }, - { url = "https://files.pythonhosted.org/packages/ea/51/d928727e476e25ccc57c6f449ffd80241a651a973ad949d39cfb2a771d28/ruff-0.15.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16d090c0740916594157e75b80d666eab8e78083b39b3b0e1d698f4670a17b86", size = 12347101, upload-time = "2026-07-09T20:01:08.859Z" }, - { url = "https://files.pythonhosted.org/packages/1e/88/8cd62026802b16018ad06931d87997cf795ba2a6239ab659606c87d96bf0/ruff-0.15.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a10e74757dd65004d779b73e2f3c5210156d9980b41224d50d2ebcf1db51e67", size = 11572001, upload-time = "2026-07-09T20:01:11.092Z" }, - { url = "https://files.pythonhosted.org/packages/b2/97/f63084cf55444fc110e8cb985ebfcc592af47f597d44453d778cb81bc156/ruff-0.15.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab0905d2f29e0d9fbc3c373ed23db0095edaa3f71f1f4f519ec15134d9e85c8", size = 11549239, upload-time = "2026-07-09T20:01:13.27Z" }, - { url = "https://files.pythonhosted.org/packages/9d/77/f107da4a2874b7715914b03f09ba9c54424de3ff8a1cc5d015d3ee2ce0ac/ruff-0.15.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:00eca240af5789fec6fe7df74c088cc1f9644ed83027113468efba7c92b94075", size = 11535340, upload-time = "2026-07-09T20:01:15.206Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e9/601deb322d3303a7bf212b0100ead6f2ee3f6a044d89c30f2f92bf83c731/ruff-0.15.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262ab31557a75141325e32d3357f3597645a7f084e732b6b054dde428ecd9341", size = 10964048, upload-time = "2026-07-09T20:01:17.723Z" }, - { url = "https://files.pythonhosted.org/packages/ea/2e/0f2176d1e99c15192caea19c8c3a0a955246b4cb4de795042eeb616345cd/ruff-0.15.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:659c4e7a4212f83306045ec7c5e5a356d16d9a6ef4ae0c7a4d872914fc655d9d", size = 10667055, upload-time = "2026-07-09T20:01:19.73Z" }, - { url = "https://files.pythonhosted.org/packages/48/60/abd74a02e0c4214f12a68becfd30af7165cfdcb0e661ecdc60bbb949c09a/ruff-0.15.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9e866eab611a5f959d36df2d10e446973a3610bc42b0c15b31dc27977d59c233", size = 11242043, upload-time = "2026-07-09T20:01:21.947Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c6/583075d8ccabb4b229345edcaf1545eb3d8d6be90f686a479d7e94088bbf/ruff-0.15.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e89bc93c0d3803ba870b55c29671bad9dc6d94bb1eb181b056b52eb05b52854f", size = 11648064, upload-time = "2026-07-09T20:01:24.023Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3c/37d0ecb729a7cc2d393ea7dce316fc585680f35d93b8d62139d7d0a3700c/ruff-0.15.21-py3-none-win32.whl", hash = "sha256:01f8d5be84823c172b389e123174f781f9daf86d6c58719d603f941932195cdd", size = 10896555, upload-time = "2026-07-09T20:01:26.941Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b8/e43466b2a6067ce91e669068f6e28d6c719a920f014b070d5c8731725de3/ruff-0.15.21-py3-none-win_amd64.whl", hash = "sha256:d4b8d9a2f0f12b816b50447f6eccb9f4bb01a6b82c86b50fb3b5354b458dc6d3", size = 12038772, upload-time = "2026-07-09T20:01:29.497Z" }, - { url = "https://files.pythonhosted.org/packages/dd/75/e90ab9aeece218a9fc5a5bc3ec97d0ee6bb3c4ff95869463c1de58e29a1c/ruff-0.15.21-py3-none-win_arm64.whl", hash = "sha256:6e83115d4b9377c1cbc13abf0e051f069fab0ef815ea0504a8a008cee24dd0a8", size = 11375265, upload-time = "2026-07-09T20:01:31.772Z" }, +version = "0.15.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, + { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, + { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, + { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, + { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, + { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, + { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, + { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, ] [[package]] @@ -3300,7 +3300,7 @@ dependencies = [ { 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'" }, + { name = "tifffile", version = "2026.7.14", 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 = [ @@ -3512,11 +3512,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.4" +version = "2.9" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/80/f1/93422647dd7e461f23d254e6b2bfa687a85b53aeb4903fcdbb74474d4584/soupsieve-2.9.tar.gz", hash = "sha256:acee8417325c5653e1377dc31eccad59eb82cbc65942afe6174c53b3aaad63fc", size = 122122, upload-time = "2026-07-19T01:35:18.425Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/7b/d6/3185ab5ad1280319b31986898f3206dd7227cd75e293d4dba2a5e6bf27a0/soupsieve-2.9-py3-none-any.whl", hash = "sha256:a2b2c76d67df2382d245409fd71e321a571717e58463efa32ace87dcadac2c12", size = 37387, upload-time = "2026-07-19T01:35:17.106Z" }, ] [[package]] @@ -3655,7 +3655,7 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.6.1" +version = "2026.7.14" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -3664,9 +3664,9 @@ resolution-markers = [ dependencies = [ { 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" } +sdist = { url = "https://files.pythonhosted.org/packages/20/2f/e5fe51c8f782241d86fdf7251594b195f0d6c2fcf9d389079de212599246/tifffile-2026.7.14.tar.gz", hash = "sha256:ce2703e5ef22c868f1528d5f5b4ef75eefb019cf628a1c9ec0d17e0afeca8ef5", size = 437660, upload-time = "2026-07-14T23:41:31.737Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/c8/e8/d381de4a3cc4e3682cba0338f43250893508aad0b310af1d0635f7b04413/tifffile-2026.7.14-py3-none-any.whl", hash = "sha256:4eb20372e76edf2c9fed922b1e3a0a0567be3560bd2008336115763bb1f3c034", size = 270614, upload-time = "2026-07-14T23:41:30.078Z" }, ] [[package]] @@ -3864,14 +3864,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.68.4" +version = "4.69.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/57ff8b434839e70dab45601284ea413e947a63799891b7553e5960a793a8/tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520", size = 792418, upload-time = "2026-07-07T09:58:18.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/69/40407dfc835517f058b603dbf37a6df094d8582b015a51eddc988febbcb7/tqdm-4.69.0.tar.gz", hash = "sha256:700c5e85dcd5f009dd6222588a29180a193a748247a5d855b4d67db93d79a53b", size = 792569, upload-time = "2026-07-17T18:09:06.2Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/2a/5e5e750890ada51017d18d0d4c30da696e5b5bd3180947729927628fc3cb/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2", size = 676612, upload-time = "2026-07-07T09:58:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/fe/21/99a0cdaf54eb35e77623c41b5a2c9472ee4404bba687052791fe2aba6773/tqdm-4.69.0-py3-none-any.whl", hash = "sha256:9979978912be667a6ef21fd5d8abf54e324e63d82f7f43c360792ebc2bc4e622", size = 676680, upload-time = "2026-07-17T18:09:04.172Z" }, ] [[package]] From fdb2c8670d0e32db1cfa80a77fae45dd00c22b31 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 27 Jul 2026 12:30:44 +0000 Subject: [PATCH 103/113] chore: update lock file --- uv.lock | 335 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 168 insertions(+), 167 deletions(-) diff --git a/uv.lock b/uv.lock index c6842ffe2..36a6293e2 100644 --- a/uv.lock +++ b/uv.lock @@ -196,11 +196,11 @@ css = [ [[package]] name = "certifi" -version = "2026.6.17" +version = "2026.7.22" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] [[package]] @@ -431,14 +431,14 @@ wheels = [ [[package]] name = "colorlog" -version = "6.11.0" +version = "6.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/bf/cb30a51af3aa8ce63735f77e23dcd4fc0720fe0339bcb04f77345659c277/colorlog-6.11.0.tar.gz", hash = "sha256:9d90fb53fa906c8970c18fbe46506bae1fb5f86b513b8f867db37e4ace9be7ae", size = 17734, upload-time = "2026-07-17T12:16:46.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/55/ba79756cb90c8d69d599d57785398ac87bba7b19c80e87f4e8a562197c93/colorlog-6.12.0.tar.gz", hash = "sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f", size = 18151, upload-time = "2026-07-23T13:40:40.71Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/a1/6b71004ab0fea510230be9ce05a4059029ac847c009fcc80b1b73d6fa5ab/colorlog-6.11.0-py3-none-any.whl", hash = "sha256:f1e27d75aa2cb138f3f640c0e305b65b680ccbef6ecc034eba7e03494ffcd2a1", size = 12016, upload-time = "2026-07-17T12:16:45.3Z" }, + { url = "https://files.pythonhosted.org/packages/d4/19/0b6647bf5e331521e55d2b63bfbdc210bd9cd605189273f03614a05f702d/colorlog-6.12.0-py3-none-any.whl", hash = "sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e", size = 12239, upload-time = "2026-07-23T13:40:39.562Z" }, ] [[package]] @@ -657,10 +657,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.5.6" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/fc/b4/d088047afe39827556df21118cac9ffd20cc3f968c99a7681494d1eb333c/cuda_pathfinder-1.6.0-py3-none-any.whl", hash = "sha256:1503af579d8379c24bdd65528379bc57039b0455be9f5f9686cf8e473a1fce51", size = 54591, upload-time = "2026-07-21T15:03:56.224Z" }, ] [[package]] @@ -830,20 +830,20 @@ wheels = [ [[package]] name = "fastjsonschema" -version = "2.21.2" +version = "2.22.0" 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/f1/11/c802f752919c1fd83d44b3b955c6e7120c79f355d4a448c358198a11178c/fastjsonschema-2.22.0.tar.gz", hash = "sha256:6eb12e8f9900db6166c3d396d178ebdf6a4215fe22a06e19792edd612a20035a", size = 382291, upload-time = "2026-07-25T20:32:35.561Z" } 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/a4/9d/4a0f9355ca3e540b2d22d5f269212e2f227b8f277835b02d8908355245d1/fastjsonschema-2.22.0-py3-none-any.whl", hash = "sha256:60f4c92fda6f93efe3b3261638836478e1e11abc01c647e36e478199f7a86a37", size = 26248, upload-time = "2026-07-25T20:32:33.616Z" }, ] [[package]] name = "filelock" -version = "3.31.1" +version = "3.32.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/55/1e19b2b56a24a4b94624f7e819e1bb87fa6c5609dbaf621df3aa6568a761/filelock-3.31.1.tar.gz", hash = "sha256:9e0c4e88ebe90833c1beafd3a547ccbc0bf7f491cd3858c3ec7aed63efe02163", size = 196656, upload-time = "2026-07-20T03:14:32.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/80/8232b582c4b318b817cf1274ba74976b07b34d35ef439b3eb948f98645a1/filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", size = 213757, upload-time = "2026-07-21T13:17:42.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/61/df1d9db18f188d0ae648956a1decadc0e3b77d0571474370fd01f28a82b1/filelock-3.31.1-py3-none-any.whl", hash = "sha256:9ea33146c780161bf67cb20c7cb26b651566820d65ad8dfdd79422602a2dcfc0", size = 97189, upload-time = "2026-07-20T03:14:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/06/79/b4c714bef36bc4ec2beeae1e0c124f0223888cd8c6feb1cdc56038116920/filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3", size = 97732, upload-time = "2026-07-21T13:17:41.55Z" }, ] [[package]] @@ -969,116 +969,116 @@ wheels = [ [[package]] name = "greenlet" -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" }, +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/74/b13368064b09053253555d3f2839cc2684d22d5aed0d2ccffbf7a6736558/greenlet-3.5.4.tar.gz", hash = "sha256:0232ae1de90a8e07867bb127d7a6ba2301e859145489f25cda8a6096dabe1d20", size = 206538, upload-time = "2026-07-22T12:47:14.468Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/16/71eefcf68267bbf06a9b6bff57d0b222e49432326e85d74348b67694b8d4/greenlet-3.5.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e883de250e299654b1f1680f72a1a9f9ba62c9bd1bce84099c90657349a8dfbb", size = 294266, upload-time = "2026-07-22T11:37:56.142Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/a0b19adfc35d07e10acb626e9d22a3893b95f1309c42c4a20161dec16800/greenlet-3.5.4-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32802705c2c1ff25e8237b3bdacf2594fa02be80af8a66703eb7853ea7e68686", size = 613712, upload-time = "2026-07-22T12:26:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a121978b3337407d05a1ce5f79b4aa5998a43a9d8422f9726029b90b4471/greenlet-3.5.4-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57aa201b351f7c7c75627c60d29e4d5b97a07d37efeb62b903466fca42c097d7", size = 625582, upload-time = "2026-07-22T12:29:00.814Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/080f16cf870e929e592f55767f01d6c98d2ee83bfdc36c3b892f2d0459ab/greenlet-3.5.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3fe76c2cac86b4f7a1e92865ac0a54384deb05c92986287c1a7110d9bd53071", size = 624663, upload-time = "2026-07-22T11:51:08.016Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bb/8f3ca88370b817369008faeceeee85970adc16c92a70a3e5fe5fea495a57/greenlet-3.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1e1a4a684b16c45ba324e60b32a4386a87722bcb815d2a149d2182f9b401ca72", size = 1585010, upload-time = "2026-07-22T12:25:02.539Z" }, + { url = "https://files.pythonhosted.org/packages/51/c2/45877154689709ebce9a0b83c2235e6ca0f31577889b02af308c8cc5f8fb/greenlet-3.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e849e6e139b9671adeac505f72fc05f4af7fd1921faef40295e214fc3b361b59", size = 1651283, upload-time = "2026-07-22T11:51:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/8711a75cb61d85246277c07ff6e1a6504621ba473d808c11ad225ffca43f/greenlet-3.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc418cf4c873357964d6624445ed09472e50def990c65dd4e76fc3ba8cd9cef6", size = 246434, upload-time = "2026-07-22T11:43:15.557Z" }, + { url = "https://files.pythonhosted.org/packages/00/62/e290b3bce433da8f0324ac02da0b128d683482229f1a8b789fa47818a4cd/greenlet-3.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:c38c902a0986eba1f6e7ba1ab39ad5195926abde90f3fe080e08212db62176da", size = 244990, upload-time = "2026-07-22T11:39:22.626Z" }, + { url = "https://files.pythonhosted.org/packages/f3/04/81bd731d6d1e3a469d9a4c36f5eb069bcf0cbb2d5d342c9fec22245b91fc/greenlet-3.5.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3d66250e8b09f182ede05490998c818b5961f7a3640332d44c4927caec7bbfe4", size = 295909, upload-time = "2026-07-22T11:38:09.261Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dd/f5f22903a6ae70f5ea328ed0beaec92ad903f0e3b7d2845133b354abc4b8/greenlet-3.5.4-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c90e930c9c192e5b3ee9fb8bcd920ea3926155e2e3ded39fc697323addecee17", size = 612011, upload-time = "2026-07-22T12:26:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/8e/10/92a4a88d12b915d74ea5b6d288e4afefda4771647caa34442c156f7a454f/greenlet-3.5.4-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:791fdfeeb9c6e0c7b10fa151bf110d2a6974866f13dcb5b1c7efae698245893a", size = 624299, upload-time = "2026-07-22T12:29:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/0b14bb9db2989f32cd9fe7f76afedea01ee8bee3f87c07e69f24adfe7e63/greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f88193799d43dbf8c8a806d6405c9c52fe2af40bf75072a606357b33cc336c7f", size = 621541, upload-time = "2026-07-22T11:51:09.464Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/25e9a2d9eb6b2e8b7ca4e80a3a26cb887cce6c8e0a87c921164f11bc5574/greenlet-3.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7a5f095767c4493afcd06067f2bb3b8716e3f3f9e92b99c88e7e99f885b3d4d", size = 1581444, upload-time = "2026-07-22T12:25:03.818Z" }, + { url = "https://files.pythonhosted.org/packages/b9/96/4c9bf2e2c408dcc0556edce69efa9f802e82223573c53240136a086821f1/greenlet-3.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:42afdc1ab5f66da8c586c32af9224a74a706b4f0ea0dc3a4188a0860a09c65c9", size = 1645842, upload-time = "2026-07-22T11:51:12.295Z" }, + { url = "https://files.pythonhosted.org/packages/b5/41/303ecb26a3a56122c0f4d4073ee078881847bd6b6f463ae0ec57ec20223b/greenlet-3.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:60149df8f462d1b230038e6590c23c3b4768bb5d6c022b3b6e82532b34b0b8a3", size = 247169, upload-time = "2026-07-22T11:38:19.893Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e3/ef56864b4c35fcb3eb3b41b869f6cc46f4cd3f5e2c68e74acde8ac433951/greenlet-3.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:77d6ce04fed0d9aeed42e0f37923cc43eba9b027bdd9c34546bb4ccd143d0fe0", size = 245565, upload-time = "2026-07-22T11:38:27.061Z" }, + { url = "https://files.pythonhosted.org/packages/c0/9a/e51225dcd58713f16ccbdcc501a8da21098ea14515b7870f1f94459e5ff5/greenlet-3.5.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:24e61b88cb7e1b1d794b32a10cc346ac779681d6d74ff137a3e0a444d2bf1f02", size = 294831, upload-time = "2026-07-22T11:38:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ea/de50a50fadf979713ab18b46f22ad5ff5f2dcfc637a3ebdecf669801e1a5/greenlet-3.5.4-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:870d730fec833f5a06906a32596cc099b9161594642a92a520b7a88911c95356", size = 614619, upload-time = "2026-07-22T12:26:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/2aae27fea41205b8650294c301f042a2a4bb6155eea48c995b890a92f2c1/greenlet-3.5.4-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec5ff0d1878df6af3bf9b638a5a92a7d5693291de77c91bff10fa48519c604ef", size = 627021, upload-time = "2026-07-22T12:29:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/79fd826f9ccaae0b84e1b4ef68dabba5e105bb044ffcd448a0b782fcba9a/greenlet-3.5.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d84d993f6e575c950d91a23c1345d18fe1a4310d447bf630849d7809196b52f0", size = 624002, upload-time = "2026-07-22T11:51:11.391Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/27319f97e731298513dcba1a2e91b63e9d8811d9de22130f960b129b1bf1/greenlet-3.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:58023945f421093de5e6fa108c0985a8659d43f49e0216da25099369a121bcbd", size = 1581533, upload-time = "2026-07-22T12:25:05.322Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6d/24240bf562e9786dd2799ee0a4a4dadb4ded22510f41b20245099159ac8c/greenlet-3.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bae2728e1897aa8df8cb1af38cd48b3a743aefe29372de7b8b7a9f532501e69f", size = 1645781, upload-time = "2026-07-22T11:51:14.805Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5a/442ab1a9ef7ca6bf7210e5397a95972206a91a31033a03c8900866a10039/greenlet-3.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:ca5726c0b08ca35ae873557266a78b2c3f3b2b7d7401aa5ff886c2045dd0111c", size = 247133, upload-time = "2026-07-22T11:39:20.661Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e6/9160210222386b1a378ff94db846b9508ca24a121cf684991561fdb69280/greenlet-3.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:7c1303791d603080cac6fc3b34df51c3b75b723739c282c8029e48a0d241672f", size = 245500, upload-time = "2026-07-22T11:40:22.185Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a7/6ab1d4f9cd548d15ab90da29947f2076100130bb179b0bde59f795a459e3/greenlet-3.5.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7e8afa5eac028f8140ceafe5ceec66e6aa127ddcb21452d2a564dcd2900b5f22", size = 295410, upload-time = "2026-07-22T11:40:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7a/422f63b4715cbc0b24385305407adf38b48f6bb68b3e6b04090e994d0f5a/greenlet-3.5.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73b37afe369021423ea53dd3123e04bffa7e93ac64429b9f50835b2e4fcae7cf", size = 661286, upload-time = "2026-07-22T12:26:43.8Z" }, + { url = "https://files.pythonhosted.org/packages/d0/31/5a1cac663bf5582190c5a714ef81364f03cde232227f39748f8ae4c11da5/greenlet-3.5.4-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ef964f56dfcb6f9bbef2a190d9126795eac408716aeae47b5e7c73c32aafca9", size = 673517, upload-time = "2026-07-22T12:29:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/15/4a/2a82a1e3f8aaca020853ac8d12211280ca2b231aa08ea39f636f1060c319/greenlet-3.5.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c53ff01a5c53a40f2c16820ebc56d7c61a77f5fbe009dadd96292d5682f80f8", size = 670917, upload-time = "2026-07-22T11:51:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/68/b0/e379a152b17bfdfa95795af4049e37c0fd1b4d81f020d426db104ed07c77/greenlet-3.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecca4d80d55a01ad6b23b33262662956149fbb7b2c6be2910f1705921958cbf3", size = 1628478, upload-time = "2026-07-22T12:25:06.678Z" }, + { url = "https://files.pythonhosted.org/packages/5e/43/bffdfa64f7317f954c5c1230b5dd5922676ce198689a68c1ac1ed4b1b1a5/greenlet-3.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffbc533e0eaf8e80d8471411646ab88fe58f641d508c0b02b24494479f4d9ec", size = 1692021, upload-time = "2026-07-22T11:51:17.008Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/f799f9637e2c6e9b0b716015e339040598b058cf7654dfc0d67468b177ed/greenlet-3.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:305f69e6c4523d7f6979ed001cff4e5853c063e5da04880296603aa0227e544c", size = 248031, upload-time = "2026-07-22T11:40:11.007Z" }, + { url = "https://files.pythonhosted.org/packages/05/75/625bcdd74d5e6b2dca1ecba3c3ac77bcf8a026c21a649a46cef23e421f97/greenlet-3.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:f260930bbbbcf9caee661211235a5111c86dfe5832fdf6ae4570da1e0995320f", size = 246892, upload-time = "2026-07-22T11:40:27.357Z" }, + { url = "https://files.pythonhosted.org/packages/ec/69/35c62ed49c320cb4d98e14698ccca5467d3bfe683984172be9cb564d9ce3/greenlet-3.5.4-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:41ddab54e4b238f4a6c323f39b4e59e176affd5a94d461a9fb7583dac74240a3", size = 305571, upload-time = "2026-07-22T11:40:31.659Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/64d60216b3640dcb0b62d913dd9e0d80030c09115bb2e4ba70c95d10ca45/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3dabe3e2809013052c68bdf0b7fa5f5f2859c43a80803131ad61af9cabd7867", size = 672568, upload-time = "2026-07-22T12:26:45.298Z" }, + { url = "https://files.pythonhosted.org/packages/5c/de/ba3ab0a96292e53039530333b0d2ae18d9e508f3a325cd7bf15f8172944c/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:27d3f00718634d4520a3a150154ac5da36f257869d41321953375b90bfbbc72c", size = 680076, upload-time = "2026-07-22T12:29:06.125Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/aeada79083c6f1c15f45d77a332f9c441af263ee298e3eb17522cd337d22/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbd60b5763c6543c1827e48faaf14ea9bfbad245f52b1a4d76a2a2d8884c6c66", size = 676733, upload-time = "2026-07-22T11:51:16.027Z" }, + { url = "https://files.pythonhosted.org/packages/e9/10/2392fc3a98948652ef5fd1e7275c04f861dd13f74b78a2b4309f4ee4d090/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f00f910f0e7b35416c63b23ad78b769aeccfc1775f712b43c4ee525624a2eef7", size = 1637327, upload-time = "2026-07-22T12:25:07.879Z" }, + { url = "https://files.pythonhosted.org/packages/55/c6/e7237a3dfa1f205ed0d9ea1e46d70bd2811b32d516266399fb59d28ab90a/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:91c26423753b92caf41ab3f98fd547d7374d4d9fc2d85be041886c1579d9255e", size = 1697493, upload-time = "2026-07-22T11:51:19.214Z" }, + { url = "https://files.pythonhosted.org/packages/55/e3/4ba8154ba2a3d43729e499f72471b4b5c993f3826d3e24da81d5f06d6572/greenlet-3.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:ee032b91fd8ec29ec6c4cea2b8c561b178435134bd0752c7334b94e9c736c132", size = 251637, upload-time = "2026-07-22T11:40:37.44Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/e3f96dfc100261a29545ddc8270cafe58f9195b6651466910e820910de77/greenlet-3.5.4-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:178111881dd7a6c946471fda85485ec796e1043c2b939f694b096e2ecf986809", size = 296076, upload-time = "2026-07-22T11:39:38.364Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3d/da52d208e5c977bce8667e784729e584e38b5785f4c1ec0f4c836e9a1c42/greenlet-3.5.4-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d92df08dd65fede97fc37aad36c2e9dcda3b31c467f8e0c2c096456cb818e927", size = 666870, upload-time = "2026-07-22T12:26:46.691Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8a/7e6dee25cb8a8cf9b362c8e597cc269593378bd916f16c736c059a52e85a/greenlet-3.5.4-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99e8f8c4ebc4fd80aa26c1280ae9ad43a0976e786349703a181cf0bae60413e5", size = 677678, upload-time = "2026-07-22T12:29:07.508Z" }, + { url = "https://files.pythonhosted.org/packages/6c/21/5a38699fa45de749e3857d93b8f07e4c20489e77c2d35d915a2e1c456606/greenlet-3.5.4-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:394de08dad5ffcb1f50c2159d93e398d9d2da3ed437645eaa54771fa720db9f0", size = 676067, upload-time = "2026-07-22T11:51:18.163Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ba/863116ab8ff1ca7a729e327800268939d182db47aa433db70e216e7d9194/greenlet-3.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:c883d61f2282d72c767a14936641b3efcbde9d82f1080712aaea0b1d3126cb88", size = 1633489, upload-time = "2026-07-22T12:25:09.605Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7466ced82818d6132462d7f26b3f83c66ea15d2b193a6c0088d558ed7d95/greenlet-3.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2a924f15d17957e252a810acefcb5942f5ca712298e8b6fcaed9a307d357522c", size = 1696584, upload-time = "2026-07-22T11:51:21.304Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4d/55b638489260065de9ffce606c8b5d04507bef705de4b212a0c3d6a1a0df/greenlet-3.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ed17e5f3420360d5b459de8462efb52060399a5326a613d4cde31cef63ef95da", size = 248297, upload-time = "2026-07-22T11:42:04.055Z" }, + { url = "https://files.pythonhosted.org/packages/bb/08/9dd4ae635da93d41dc268bc34bd62a9d711ed8b8825c5d22ac910c7d6e6d/greenlet-3.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:f908898d6fa484ce4b6f447ce70ea99b52c503fee419e53cf74d60a16bc9e667", size = 247423, upload-time = "2026-07-22T11:44:00.764Z" }, + { url = "https://files.pythonhosted.org/packages/19/66/7c87ed9cdbf1d49c2c6cd1c7b9dd4d16c33b24235ca03972293a1876b30c/greenlet-3.5.4-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:1833637f17d5e7472548a48575c394fe39f1b1890d676d162d86593610f44d8c", size = 306487, upload-time = "2026-07-22T11:41:25.118Z" }, + { url = "https://files.pythonhosted.org/packages/5b/05/0a4201e7c0054866eefc05da234f236dd4c950d0fbf9ca0517141f01b269/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12cda9122e03341f1cb6b8207a19d7a9d375e52f1b4e9243918375f40fd7b4b9", size = 676479, upload-time = "2026-07-22T12:26:48.129Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c0/4b6b8c5a3aec70f0649cd89662d120fdd6421e2bdc8e3b15c3ab5ec568d8/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d83ae0e32d14957ab7170785a20f582635c8474deab1bfbb552b17e769a6ce25", size = 684321, upload-time = "2026-07-22T12:29:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/24/c9/b49c31c9a972eee91e260445770e922244a7efc542697f51a012ec046d0f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f1467de1bb767f75db0aa34c195e3a496d8d1278c796e70c24ce205d3e99cde", size = 681293, upload-time = "2026-07-22T11:51:20.43Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/7f2d4653770500eee667866016d42d7a68e3d3462f80df6b8e3fcd48a0eb/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0fa53040b78b578120eecdc0265e3f1051487cc425d11a2b7c761daadf4feaa8", size = 1642474, upload-time = "2026-07-22T12:25:10.819Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d8/8cba31036a4caae448087ba5d150660ab03b4a0f54d9150f6495a3be7262/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:60e0bc961d367df506660e9ac0177a76bc6d81305300704b0977d1634f76efe2", size = 1701012, upload-time = "2026-07-22T11:51:23.17Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cd/3f77a4cce3bae631b08eb52f53a82a976669600337e21dfdba811cb50267/greenlet-3.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:f680e549edb3eaf21eea4e7fe101e15ec180c74b7879ab46adc080f22d4015d2", size = 251977, upload-time = "2026-07-22T11:41:38.125Z" }, + { url = "https://files.pythonhosted.org/packages/93/e8/65e8707d00fe2a49bf12f609a9b2b39ba6dd23c2810eacad877c4fc94bfe/greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994", size = 250538, upload-time = "2026-07-22T11:40:17.985Z" }, ] [[package]] name = "grpcio" -version = "1.82.1" +version = "1.83.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/bc/656b89387d6f4ed7e0686c7b64c2ae7e554a759aa58122c8e5fb99392c32/grpcio-1.82.1.tar.gz", hash = "sha256:707b24abd90fcb1e45bcc080577da1dbf9971d107490589b9539af8e1e77b4b5", size = 13187300, upload-time = "2026-07-08T12:36:16.588Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/5b/e5092af97fa671ca279b3e373251af4bf87d5fbda7dc85f6a616899562a7/grpcio-1.82.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:0ddb18a9a9e1f46692b3567ae4abb3f8d117ce6afea48650f8eca06d8ab5d06f", size = 6181472, upload-time = "2026-07-08T12:34:31.009Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8f/18053a3a2ca03d0c2a1b8cc7271e705007a16aa5dae84bac00935c5b1a7f/grpcio-1.82.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cf855b1af246720f567b0ce5d0724d45dfa4188eecc3296a2a69257b11b9e94b", size = 11970995, upload-time = "2026-07-08T12:34:33.603Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/21b1acb052876ad00959ec4d1b05fe08607d650bcfa282073bb164c2703c/grpcio-1.82.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb30cb13e25bc13cea70ffc69d6d90c49d36ea6c1d4549e6912f70177834cac", size = 6760127, upload-time = "2026-07-08T12:34:36.122Z" }, - { url = "https://files.pythonhosted.org/packages/3e/12/25eef9c245c54f0061317d13a302357fe8ea03bac240b2b02ececcf54da4/grpcio-1.82.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1e822b2774f719c017cbe700b6e47173b6ae290fb84906f52a5a3c2c60b62e1e", size = 7484377, upload-time = "2026-07-08T12:34:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/1a348767eb9d9bd7765dc4fa8a01723d3bb386d67f981ee5c6f9c02b8b1c/grpcio-1.82.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5dafb1ece8ed45dee7c738f166ec82e19673221ed5ab8967f72858a4685345b2", size = 6924269, upload-time = "2026-07-08T12:34:40.583Z" }, - { url = "https://files.pythonhosted.org/packages/e4/b9/3aae7a03d34c86ea27988db859a6087c186f6c3f53f9b551e07afd989bfa/grpcio-1.82.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06503106e7271e0a49fd5a1ac04747f1e47e87d900476db6fe45bc87ee411f4", size = 7531848, upload-time = "2026-07-08T12:34:43.277Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/3c4afa625d0dac9090707966916284c035fc5b2fb3e2c51e156accee6735/grpcio-1.82.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ff99bc8cafb6a952201c37b995f425e641c93ffa6e072258525feab57290141d", size = 8568217, upload-time = "2026-07-08T12:34:45.502Z" }, - { url = "https://files.pythonhosted.org/packages/20/9c/d8489c628e73e20a3d034e7f66912de7b1acb405f01d388f056a88e47924/grpcio-1.82.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:644ae1b94266ac785330f4590a69e52b6a7eb73029043a02209db81c81397d69", size = 7938771, upload-time = "2026-07-08T12:34:48.323Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b7/0a92cfd1658f3a896d4aa12d4efeb7dd4ddfc723725ae22741a5241ea710/grpcio-1.82.1-cp311-cp311-win32.whl", hash = "sha256:e203d2e19d471630084a16c815616f8211dff21c268ab3c5f5bf38417832e074", size = 4256432, upload-time = "2026-07-08T12:34:50.432Z" }, - { url = "https://files.pythonhosted.org/packages/c7/6a/2872c761b025d9ec74386f22a4a7d59c5a5b00ebf718761b33739ffc45de/grpcio-1.82.1-cp311-cp311-win_amd64.whl", hash = "sha256:0d8299c285fe6cc6a1f56badf8d3bc5078c8d20273ee64bafa3783b4bc29a769", size = 5009633, upload-time = "2026-07-08T12:34:52.67Z" }, - { url = "https://files.pythonhosted.org/packages/dc/88/d1350bf3343a2ed87d801584e40609f6c6bd3087926eeca03de50348cf4a/grpcio-1.82.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:c09bd5fa0d5b1fbd773ec349fe61441c3e4ebf168c229aa7538a820bdfad6a58", size = 6144689, upload-time = "2026-07-08T12:34:55.567Z" }, - { url = "https://files.pythonhosted.org/packages/e6/33/71875cdecd27c24ac1385d4783a09853f01b84a825a36aec2a2bc7d0d080/grpcio-1.82.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1eae24810720734598e3e6a1a528d5de0f265fe3fc86575e9ecce424b9ec7379", size = 11952034, upload-time = "2026-07-08T12:34:58.128Z" }, - { url = "https://files.pythonhosted.org/packages/82/b2/d9125df3d8a140dec12cc82c05b7deafedeababcff6496f28b2fd5634d10/grpcio-1.82.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a6bd5daf5bde7b24d7ad2cbaf8bf9eac620d96222016bb5e7ddde930dec0673f", size = 6710772, upload-time = "2026-07-08T12:35:01.33Z" }, - { url = "https://files.pythonhosted.org/packages/88/9b/69e2d1627398b964f34437dc476a5aff5a2cc8e7f247d26272b5674b5faf/grpcio-1.82.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1ecfde669cb687ac020d31ff76debe5dc7a62213335f02262eb6625628da1c03", size = 7450677, upload-time = "2026-07-08T12:35:03.926Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e7/8f855ca29c294956122a2a73023655b9b02602d5111dad2b9b00e7631c68/grpcio-1.82.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:011c8badee95734dee8bf05ce3464756a0ac3ebb8d443afd20c0e2b5e4640ad9", size = 6886855, upload-time = "2026-07-08T12:35:06.174Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f0/fa87e85f49925f44c479d07e58b051e69bcfef6b6d5fbc6749d140f6730a/grpcio-1.82.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b85f4564926fb23114d239392bdcae200db1e6179629edd7d7ab0ab89c96a197", size = 7501323, upload-time = "2026-07-08T12:35:08.49Z" }, - { url = "https://files.pythonhosted.org/packages/67/55/2e0b10ae1d3ef9dcc480b91dc2158f4931fc4675d3af0a2836e39b2a744f/grpcio-1.82.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2c0c8270833395644c3fe6b6a806397955a2bc0538000a19a78b90c05a6c16e0", size = 8536899, upload-time = "2026-07-08T12:35:10.975Z" }, - { url = "https://files.pythonhosted.org/packages/bd/95/a3d8b0431fa221efc51ee39d73595ede74ba82a43b7c4313192e580face2/grpcio-1.82.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2ba199205ff46c7778290fe1673c91ac8e7e45678dd5c86e9e56fa33ec8788f6", size = 7913892, upload-time = "2026-07-08T12:35:13.944Z" }, - { url = "https://files.pythonhosted.org/packages/b8/92/f2651ec704d9852a56faef394775038afba435b50ce82ab2404d119c3355/grpcio-1.82.1-cp312-cp312-win32.whl", hash = "sha256:06127691866e295c14e84a1fb86356dd962254f6abd0da4ca4b001eea9e89438", size = 4240985, upload-time = "2026-07-08T12:35:16.048Z" }, - { url = "https://files.pythonhosted.org/packages/96/4f/a5fe8bf0d0a1b24855f370293075c931f27de4eb55f0f158786095bf3c11/grpcio-1.82.1-cp312-cp312-win_amd64.whl", hash = "sha256:1fa3223a3a2e1db74f4c2b255189eb7ea875dfba56e221d252ee3fc7b204778e", size = 5001580, upload-time = "2026-07-08T12:35:18.689Z" }, - { url = "https://files.pythonhosted.org/packages/1b/3e/496992d08c0aaa11272eb6228dc8ab947da01fe835de243cd00521bce4c4/grpcio-1.82.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b454a2d97bfab7565683a02345f86bd182ab69fd7c2bdb7414171e7538f266b1", size = 6146068, upload-time = "2026-07-08T12:35:21.365Z" }, - { url = "https://files.pythonhosted.org/packages/e7/8f/f263d6f14fdba6b56cfadd91fd3e158a52682b72c6016d1f8723d435659f/grpcio-1.82.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3dde70abfc80b3be11de53ba0d601c439e7fb2afd3583ad1788d1146bec92fdc", size = 11948600, upload-time = "2026-07-08T12:35:24.312Z" }, - { url = "https://files.pythonhosted.org/packages/8c/14/3a02e6ee49c2d85bc15eaae321e0e11ab3542cad3c5b2de121ecce0c4296/grpcio-1.82.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5523099c98c292ea1ae08e617249db760c56a78f8deae879027fe7d1ffbcbf6", size = 6714591, upload-time = "2026-07-08T12:35:27.027Z" }, - { url = "https://files.pythonhosted.org/packages/69/80/58e3738696f48ab7645347b98d8a7f93d10e00e6218388fbfcd6c9310e3d/grpcio-1.82.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5e5c4dc0a59b0f8490a6bdfd6fc8395b9d8ad8a8407c7d67ca7b5bba15c0877f", size = 7454995, upload-time = "2026-07-08T12:35:29.599Z" }, - { url = "https://files.pythonhosted.org/packages/f3/6c/2557c1a889363072fbf2285ecd0e8c44860d4dbd60f017a32537c5b863e2/grpcio-1.82.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c40d94ba820329cc191981bc22fa6f6eed0799c6d921f3c6709521d59d4a2fd7", size = 6888621, upload-time = "2026-07-08T12:35:32.38Z" }, - { url = "https://files.pythonhosted.org/packages/d2/66/907706ccaff1223f1e10fd5b37fc16faead43392fccb4e786e7e390ac141/grpcio-1.82.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c816180e31e273caaec6f8bd86a8392499d5bbb26f41da44e3dce48bde69095", size = 7505069, upload-time = "2026-07-08T12:35:35.072Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7c/ff97b0d0f635987ee5ec80dfedafa1aad629303745d48e8637d10eec5b80/grpcio-1.82.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e31fd780b261830720cb70b0fd8f0aa51d49e75a66d7464ad2e31d4b765f2580", size = 8535384, upload-time = "2026-07-08T12:35:37.954Z" }, - { url = "https://files.pythonhosted.org/packages/62/9e/a97fddd970a8d1588cade06eca20443761c1858b0ad6590a5c835aa18062/grpcio-1.82.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d76152d7c31d7210d4a106e5d8b64da5bba5d6abf11be30e2f7b0a0c59bbcbf", size = 7910707, upload-time = "2026-07-08T12:35:40.797Z" }, - { url = "https://files.pythonhosted.org/packages/20/e4/eaba1517888af483a88d449eb7566f0f7f63446d46f339c5891798435875/grpcio-1.82.1-cp313-cp313-win32.whl", hash = "sha256:38e9dcb5258226fb3282630b31b16a968df52c8c6ad514af540646e0a4578f8a", size = 4240363, upload-time = "2026-07-08T12:35:43.298Z" }, - { url = "https://files.pythonhosted.org/packages/b0/42/66a98d47732e35290bef722f6149fed3709cd4cf61166f6f53a12f417302/grpcio-1.82.1-cp313-cp313-win_amd64.whl", hash = "sha256:3dbfb52c36d9511ac2b8e6c94fdde837b393ae520cc321f52a333a2deedf5a90", size = 5000980, upload-time = "2026-07-08T12:35:46.262Z" }, - { url = "https://files.pythonhosted.org/packages/b4/cb/cf9ae9e164c6e6dc8a494faa9771763df9da150eefe19671009624d1559f/grpcio-1.82.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:35f990f7784c8fd2872644f07f96ebb4d9e48e145a190ab80d0280af91a1bfb2", size = 6146901, upload-time = "2026-07-08T12:35:49.261Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/eccf26dbcfb7f7cab8027c5490a16c8937c5aa7a2ec20a3eab2cf7a43165/grpcio-1.82.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:46536a4a1f4434df3c851b9254ff6fc7df5705b273681a15ca277d5921c178a0", size = 11954756, upload-time = "2026-07-08T12:35:52.196Z" }, - { url = "https://files.pythonhosted.org/packages/ad/75/3b3b4a3cc9f084b026af96e1d3e539b1af29ec7f41ed0dfff3cb99cc8626/grpcio-1.82.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d6650a7c1ebb7921c70e12a385439a8118efb99e669fa9ed31cf25db1843937c", size = 6723087, upload-time = "2026-07-08T12:35:54.973Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8b/b0f0c9b1400a99a4da4c09b114f101b192f8f11192e76f620b8962f5d90b/grpcio-1.82.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b8e110c66df5204c0506d6c8787b35d48b8b699ef5aa366d6c4d67325c67fe9a", size = 7454542, upload-time = "2026-07-08T12:35:57.586Z" }, - { url = "https://files.pythonhosted.org/packages/b7/bd/428e38868382aa193697a5aa53973f29c58e58ba4268aa0c86a2715ee58b/grpcio-1.82.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f853eae07235a51a27bb5d6a9a175a59ca55dc9b99edc6ce2f76f07332d333ae", size = 6889588, upload-time = "2026-07-08T12:36:00.012Z" }, - { url = "https://files.pythonhosted.org/packages/49/ce/03e01d5e10259bf5c08ee50570cc94724e79c956f61fd2f09b341af0956c/grpcio-1.82.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:60b0f2c95337694fc094b77d9f60f50566c84b5677393e342eb98daeee242d98", size = 7514166, upload-time = "2026-07-08T12:36:02.693Z" }, - { url = "https://files.pythonhosted.org/packages/ff/59/278b4b600329e2ba3849f3c1ea3c820b3a01b38a7ad184ba09595e8d2733/grpcio-1.82.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b064fc444812bdaa9825d33c26f8d732d63ee6a5d78557c1faf92c98687fed27", size = 8536166, upload-time = "2026-07-08T12:36:05.349Z" }, - { url = "https://files.pythonhosted.org/packages/44/27/7ccf2ef00f27a8e47a79d641c8ceaf7d3028c7a03d9a97b4c8a9a783c086/grpcio-1.82.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d7ede11d747b4e1bd05e3bc0260e155b65a88735a895a10f6521f19b889511e", size = 7912572, upload-time = "2026-07-08T12:36:08.393Z" }, - { url = "https://files.pythonhosted.org/packages/0d/be/33742482d2753f2d3a1b7641664b6622262d44f2f3b609f13425dd86d36f/grpcio-1.82.1-cp314-cp314-win32.whl", hash = "sha256:3d21f19838dc255ecbb79321b15ae9b98fbddff4c3d4aedb0a81bdd7f4ab572a", size = 4321856, upload-time = "2026-07-08T12:36:10.899Z" }, - { url = "https://files.pythonhosted.org/packages/cc/67/03329c847172c78ddeb1eb9be6b444fdbc12775a84c958b27e427e7b926d/grpcio-1.82.1-cp314-cp314-win_amd64.whl", hash = "sha256:e20f1edbb15f99e3128ec86433f9785fd5a451d8f115e74fe0056134f092a9d5", size = 5141114, upload-time = "2026-07-08T12:36:13.595Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/f6/3b781cd07a715ea5f5125ae264226e7fc4d87603d6d3955022cabfdc5da2/grpcio-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f", size = 6338720, upload-time = "2026-07-23T15:19:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/21/cc/d14833d15d5984e366f1b027fa78bd038c9b028c66880bffb0f5a4d25ee2/grpcio-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223", size = 12178773, upload-time = "2026-07-23T15:19:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/6b/98/8acbb416544e7871132d8e42a07ed70c802d70e6a16c6009e505a34d32a4/grpcio-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0", size = 6921203, upload-time = "2026-07-23T15:19:17.824Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/0fdbfaf4fc54e5c88f6bce4008a065092fe7fbc4460eb5617ae8b20fd505/grpcio-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d", size = 7648508, upload-time = "2026-07-23T15:19:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ea/107b9dbb2ed3ad14dd774fd3dde7d29ff9938a6c198654becb2c3a0e9a6a/grpcio-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9", size = 7079466, upload-time = "2026-07-23T15:19:21.478Z" }, + { url = "https://files.pythonhosted.org/packages/3b/06/9fa9941089e6fae83b060b6ce61c1e81053e52decae43197245f45e07d36/grpcio-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745", size = 7605583, upload-time = "2026-07-23T15:19:23.74Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/f10fb56062dc2771c630827a82d9ad0ecd05cad572ea3b08d49f6631680a/grpcio-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617", size = 8637810, upload-time = "2026-07-23T15:19:25.536Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/f84927258f6a1b6ea6dea661fdc6de859b35e560c96f3012d15ccd39f85e/grpcio-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969", size = 8008021, upload-time = "2026-07-23T15:19:27.863Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/cdf72161397ccd29d4ca2192f641524536c9cf54ad948c9dd0e0e01138fa/grpcio-1.83.0-cp311-cp311-win32.whl", hash = "sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45", size = 4404376, upload-time = "2026-07-23T15:19:30.137Z" }, + { url = "https://files.pythonhosted.org/packages/df/ed/e0ffeb4c848699c194dc9fb6a29ab29bcb2b6aac8c416bf18c51bfe8242c/grpcio-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49", size = 5164469, upload-time = "2026-07-23T15:19:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/15/2b/51e32514a4e9b715375c99721aadff0f24164cc2049b8269eda4de82a814/grpcio-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930", size = 6303167, upload-time = "2026-07-23T15:19:33.78Z" }, + { url = "https://files.pythonhosted.org/packages/39/33/b5b50fc2c6fbe350e04814047bb2d409feec7b36ef8b170254c050e06bc0/grpcio-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da", size = 12160538, upload-time = "2026-07-23T15:19:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5f/734e72e7b9f79bcf0b2c270b8d3bca0e4ebb97a27a50d06240b145f6d41e/grpcio-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16", size = 6869310, upload-time = "2026-07-23T15:19:38.607Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/a1735f215b2a5cd43c38b79eac072ad197e61be9829905b6b29550abd0db/grpcio-1.83.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2b5e75c34842cd9c1b95285ca395c6a569664b81e3ffa6b714125922942abaaf", size = 7613472, upload-time = "2026-07-23T15:19:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/b2/78/c9e81f806ac704b6b145cb01628db398985b1f8dfdc10e23b55fb0902b3d/grpcio-1.83.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeb339838db07600481ef869507279b75326c75eac6d10f7afa62a0da1d2bcdd", size = 7040616, upload-time = "2026-07-23T15:19:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ba/94cd5af859876049d340480acbb61a959096c84b567f215534faa78d0424/grpcio-1.83.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f47d62808b4c0a97b78bff88a6d4ca283a2a492b9a04a87d814af95ca3b9c19c", size = 7570491, upload-time = "2026-07-23T15:19:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/3e/15/108d30d5a5c964312ae8b9cb0e8cc5b3c1cc68d8f757cca52b3565534d26/grpcio-1.83.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62003babc444a606dcd1f009cd16391ce23669ae4ad6ec267a873da7937a69f5", size = 8605036, upload-time = "2026-07-23T15:19:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/3828ae13c3db8233d123ad612747665817b952d8a954f32390230b582336/grpcio-1.83.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1aa567f8c3f19850ffd5d2858c9a8ea7c80f0db6c01186b71eb31e923ec984f5", size = 7981587, upload-time = "2026-07-23T15:19:48.913Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/77af31228f55f55a2a5112bb0077ad0a1c4d23dbb0c2853a62475bbdcc14/grpcio-1.83.0-cp312-cp312-win32.whl", hash = "sha256:cb2906c61db4f9c64cc360054b5df70eeb81846228e9e56a4944bd415a63dadc", size = 4394004, upload-time = "2026-07-23T15:19:50.618Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/f706e39550e7a3732ce2b9c5926107a93d74a802775b19b642a6df27dc96/grpcio-1.83.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c699bbb20f143c8f2bff219de578aa2dc1f919399d67dc702b038b986ee62df", size = 5158525, upload-time = "2026-07-23T15:19:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/56/eb/135daaa713f32d33b8f99b4153b3f8dc3b2a124996ac15581bf9ebdad3c3/grpcio-1.83.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0", size = 6304480, upload-time = "2026-07-23T15:19:53.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a1/121806ce69f23138dabe06aa595b0e5f1ae051a37e4c1954eed7d692c800/grpcio-1.83.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1", size = 12154419, upload-time = "2026-07-23T15:19:56.3Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e8/d0389e09cd6b4c4d3089b92967ae4e3ffd64795bd349bf2f85cd6656d3da/grpcio-1.83.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867", size = 6873200, upload-time = "2026-07-23T15:19:58.701Z" }, + { url = "https://files.pythonhosted.org/packages/f8/51/f464c1d211fa50d5adbabe1b2e519948d99c13757052bfc9ea7afa28e284/grpcio-1.83.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881", size = 7618811, upload-time = "2026-07-23T15:20:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c0/539fe0832f2dd6500a28f5263071623fb34e8d4867aec632ccf81bd21156/grpcio-1.83.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f", size = 7042310, upload-time = "2026-07-23T15:20:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ca/ccf617d37ffa72567fa8e005ec7090c99da922799be2fb9847c8b21ca18c/grpcio-1.83.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61", size = 7575412, upload-time = "2026-07-23T15:20:04.712Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b9/fd8d5245f823a8e0fd35d90e20ea3aa4acd47f8d5318fa8df307df52dec6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45", size = 8604248, upload-time = "2026-07-23T15:20:06.77Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/f37632fc11db72dfa4bba86c3a43e54358e53030df111ecae5e91a733ad6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c", size = 7977458, upload-time = "2026-07-23T15:20:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/93/b6/d70b69ae5c0cfc341b9ba474980e4ed99cbf05c0e4a14e9eee8cb73db0a5/grpcio-1.83.0-cp313-cp313-win32.whl", hash = "sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf", size = 4393993, upload-time = "2026-07-23T15:20:11.017Z" }, + { url = "https://files.pythonhosted.org/packages/0f/13/45d4cccb555cf4c476226979bf3d2fd0b0254216f7564c3a053e35117efc/grpcio-1.83.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735", size = 5159650, upload-time = "2026-07-23T15:20:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/9c/60/f2cca8147ea213d3e43ae9158d03ad04e020fdf32ff027253e1fe93f921d/grpcio-1.83.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3f351629f6ae16ecc0ec3553e586a6763ffd9f6114044286d0cbec3e09241bfa", size = 6305607, upload-time = "2026-07-23T15:20:15.353Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ab/d3874931d123a95e83a3ebf8aa04537988fb62425cedb8bf3cefc5ad41b2/grpcio-1.83.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d05ff664100d429335b93c91b8b34ddf9e94a112205e7fa06dede309e44a4e4c", size = 12166617, upload-time = "2026-07-23T15:20:17.435Z" }, + { url = "https://files.pythonhosted.org/packages/92/ff/6f18f9426b69306f4e00a9add3b0ee2748da8aad53836ef80cab0d62d04f/grpcio-1.83.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7936f2a56cf04f6514705c0fedf400971de01b6aa1719327e4718f410a765e2b", size = 6880213, upload-time = "2026-07-23T15:20:19.98Z" }, + { url = "https://files.pythonhosted.org/packages/70/21/706d1147c6b93b98f179240c13991fbcc56880eba0c868abb1ad40d8a0a6/grpcio-1.83.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b0a0be840e51b6b7ee9df9269770faf77bdf4b771053c257c21d12bad607714c", size = 7618335, upload-time = "2026-07-23T15:20:22.161Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/1a8443c889115ec9e213a213e86bc93a71ee9088027e5befa09aaa0edd9d/grpcio-1.83.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:009667eaf3dcd5224c713589cdc98e7ca4ed0ff0b61132c6b276e930eb83a2df", size = 7043416, upload-time = "2026-07-23T15:20:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/c6/94e0fee5b12bc1da1370185b680988db6f739d19b42d9959db01a7ea50bf/grpcio-1.83.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb669918fd88936b15599caff4160a77ab74bdeb25f2231f6e45b61282d6107b", size = 7583253, upload-time = "2026-07-23T15:20:26.313Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/de1ccb671fb85575bc5192faedf9ecdbdf5b390d2e6584dcf552bcbd370e/grpcio-1.83.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c19b454d3d3f28db81f2c7c4dbaee96e7f6fd149721733ffe79d6bc530f17404", size = 8605102, upload-time = "2026-07-23T15:20:28.437Z" }, + { url = "https://files.pythonhosted.org/packages/17/0f/0e0ec749a7034ffcbaa050e39779872950ead90c22e7e0116be3f28b2b46/grpcio-1.83.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:61007cd08640abc5c54547ee32505474c482cd733a53cb87551ea81faa6350af", size = 7979826, upload-time = "2026-07-23T15:20:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/83/fa/c3fda157287f64bc65acee6c5aa90c41acf9e0d3a8e69a265eecff6d00a1/grpcio-1.83.0-cp314-cp314-win32.whl", hash = "sha256:32e11c37f5285b0c6fa3042c05fe06903696689749833fc64e67dec71b9bbe33", size = 4471765, upload-time = "2026-07-23T15:20:33.195Z" }, + { url = "https://files.pythonhosted.org/packages/a1/00/b1b26431c9d54eee11724fd6e5585473a2ed47fbc1fb95e5204906a642ce/grpcio-1.83.0-cp314-cp314-win_amd64.whl", hash = "sha256:2bb48cb5e6dd005ca12b89ce4b6ac0b48ff3112c747542ee7986ef611a8ca6d9", size = 5298932, upload-time = "2026-07-23T15:20:35.48Z" }, ] [[package]] @@ -1528,7 +1528,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.6.1" +version = "4.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1544,10 +1544,11 @@ dependencies = [ { name = "packaging" }, { name = "tornado" }, { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/7f/51c0c856ab286bdaf5709cf61ed13584ed9d4bee906479707da45b11b353/jupyterlab-4.6.2.tar.gz", hash = "sha256:e18ce8b34f3de350e93cd5b2c4f3ae884cbe266eb76bf5d6825a4ed34c13bcff", size = 28183650, upload-time = "2026-07-21T12:05:24.051Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/63/1f/e39b248c76bb3736bc05a9491a6aa1315414c32dea12f548ecc1b24c758e/jupyterlab-4.6.2-py3-none-any.whl", hash = "sha256:5964447036629adfcd3fc0969effc1da6f47d2cbd0a60b2c2eea7c31be0ec6a8", size = 17166703, upload-time = "2026-07-21T12:05:19.818Z" }, ] [[package]] @@ -1909,11 +1910,11 @@ wheels = [ [[package]] name = "mistune" -version = "3.3.3" +version = "3.3.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/a5/2dab368d6950e6808904dec98f54c7e726ee7be4a0c6afe00e6e011bd52d/mistune-3.3.3.tar.gz", hash = "sha256:c4c6c0c840b8637a2e9b8b6d607eb7c8f00888bf14c754409bcd339e848c2477", size = 115363, upload-time = "2026-07-09T06:18:05.268Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/92/328a294a6de83bacb95bed01f04e0eaff4e3616ee359fc821a5dfc539b02/mistune-3.3.4.tar.gz", hash = "sha256:58b5c96d6fcb61190dfe5fae498d2b2065f99cf61e9649418fd54cf1ada86dfe", size = 121426, upload-time = "2026-07-22T05:22:30.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/70/b1e4737b84163db5bb1dfde6f216dbfbf32783330a9989c965e121172830/mistune-3.3.3-py3-none-any.whl", hash = "sha256:99de1585e42dcbd826faa9e11a202727a5e202e4e4722a4c69ac1ff615793dd7", size = 63569, upload-time = "2026-07-09T06:18:03.839Z" }, + { url = "https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl", hash = "sha256:ee015381e955e370962968befe1d729ab60fafb6a715ac6751763fbce38c8d4a", size = 66862, upload-time = "2026-07-22T05:22:29.419Z" }, ] [[package]] @@ -2523,11 +2524,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.10.1" +version = "4.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/cd/4f25b2f95b23f5d2c9c1fe43e49841bff5800562149b2666afc09309aa8f/platformdirs-4.10.1.tar.gz", hash = "sha256:ceab4084426fe6319ce18e86deada8ab1b7487c7aee7040c55e277c9ae793695", size = 31678, upload-time = "2026-07-18T03:53:43.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/73/6fd0bb9ce84138c3857f12e9de63bc901852975a092d545f18087a204aa2/platformdirs-4.10.1-py3-none-any.whl", hash = "sha256:0e4eff26be2d75293977f7cddc153fd9b8eaa7fb0c7b64ffe4076cb443117443", size = 22906, upload-time = "2026-07-18T03:53:42.576Z" }, + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, ] [[package]] @@ -2541,7 +2542,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.6.0" +version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -2550,30 +2551,30 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, ] [[package]] name = "prometheus-client" -version = "0.25.0" +version = "0.26.0" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/52/73/f1334c29c2af4cd9dba6c7817e61b611bd0215e2eb5565c6064a4de18802/prometheus_client-0.26.0.tar.gz", hash = "sha256:04a91bcf94e2cf74a44a1a874d651a2e853ed354b6e822f3b7487751465d5c2b", size = 92910, upload-time = "2026-07-24T19:36:41.893Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/eb/a3/b69efbf4143b5b9859b977770bbbabcc2796b702fa69dc40271e45cd5a56/prometheus_client-0.26.0-py3-none-any.whl", hash = "sha256:fa93d06737aa02bacd05794768508bb97d2fbee28cb3bca04eaae92f0ca953d6", size = 64494, upload-time = "2026-07-24T19:36:40.854Z" }, ] [[package]] name = "prompt-toolkit" -version = "3.0.52" +version = "3.0.53" 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/7d/ea/39b988c938f75cb75d7045b5c69f8bfed47ee2152c8837fb403de29d6fb8/prompt_toolkit-3.0.53.tar.gz", hash = "sha256:9ec8a0ad96d5c56148b3f914aa79c1564c3fde5d2e6b876e7bc327e353cf8fa6", size = 435492, upload-time = "2026-07-26T20:56:14.758Z" } 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/54/6f/84908cad2d6aa5144abcf7b42709fe4fdb459bc640ec7ac5786e7693dabc/prompt_toolkit-3.0.53-py3-none-any.whl", hash = "sha256:01c0891d7f9237d5e339f7d3e42cdae80b7534abb1c7c0e3352efba6231492f2", size = 392288, upload-time = "2026-07-26T20:56:12.512Z" }, ] [[package]] @@ -2757,15 +2758,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.4" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/81/58c70036dffeccb7fe7d79d6260c69f7a28272bbd3909c29a01ea9422744/python_discovery-1.4.4.tar.gz", hash = "sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3", size = 72212, upload-time = "2026-07-08T23:06:50.691Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/ae/84bc0d2440c95772272bb6f4b3d09ccf08b2898fce89b3d4f969a9fc74e9/python_discovery-1.4.4-py3-none-any.whl", hash = "sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe", size = 34181, upload-time = "2026-07-08T23:06:49.402Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, ] [[package]] @@ -3262,27 +3263,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, - { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, - { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, - { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, - { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, - { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, ] [[package]] @@ -3512,11 +3513,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.9" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/f1/93422647dd7e461f23d254e6b2bfa687a85b53aeb4903fcdbb74474d4584/soupsieve-2.9.tar.gz", hash = "sha256:acee8417325c5653e1377dc31eccad59eb82cbc65942afe6174c53b3aaad63fc", size = 122122, upload-time = "2026-07-19T01:35:18.425Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/38/e12680bbe6b4f8f3d17adcaf38d26850aa756c85cf4a80e79fc12a018fe8/soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba", size = 122261, upload-time = "2026-07-21T16:57:17.452Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/d6/3185ab5ad1280319b31986898f3206dd7227cd75e293d4dba2a5e6bf27a0/soupsieve-2.9-py3-none-any.whl", hash = "sha256:a2b2c76d67df2382d245409fd71e321a571717e58463efa32ace87dcadac2c12", size = 37387, upload-time = "2026-07-19T01:35:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/437fe806897c2d6cfdc3ee43a18da8bf8e568530a4ae9bac781541ca9896/soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c", size = 37404, upload-time = "2026-07-21T16:57:16.421Z" }, ] [[package]] @@ -3864,14 +3865,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.69.0" +version = "4.70.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/69/40407dfc835517f058b603dbf37a6df094d8582b015a51eddc988febbcb7/tqdm-4.69.0.tar.gz", hash = "sha256:700c5e85dcd5f009dd6222588a29180a193a748247a5d855b4d67db93d79a53b", size = 792569, upload-time = "2026-07-17T18:09:06.2Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/21/99a0cdaf54eb35e77623c41b5a2c9472ee4404bba687052791fe2aba6773/tqdm-4.69.0-py3-none-any.whl", hash = "sha256:9979978912be667a6ef21fd5d8abf54e324e63d82f7f43c360792ebc2bc4e622", size = 676680, upload-time = "2026-07-17T18:09:04.172Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" }, ] [[package]] @@ -3938,7 +3939,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.6.1" +version = "21.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3946,9 +3947,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/d9/b477fddb68840b570af8b22afe9b035cbc277b5fb7b33dea390617a8b10f/virtualenv-21.6.1.tar.gz", hash = "sha256:15f978b7cd329f24855ff4a0c4b4899cc7678589f49adbdcbbb4d3232e641128", size = 5526620, upload-time = "2026-07-10T19:33:53.312Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/7c/4e7225d46d634a0d8d534dd8a6ce0c319d09b4d0cf0337eb314ca4789d8c/virtualenv-21.6.1-py3-none-any.whl", hash = "sha256:afe991df855715a2b2f60edfcc0107ef95a79fdfd8cb4cdaa71603d1c12e463b", size = 5506392, upload-time = "2026-07-10T19:33:51.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, ] [[package]] From 7f2be6f4720fad629e637a63aa68ee06fff4185b Mon Sep 17 00:00:00 2001 From: mitis1 Date: Mon, 27 Jul 2026 23:19:09 -0700 Subject: [PATCH 104/113] fixing visualize, dilation plot, strain panel labels, and model fitting constraints --- src/quantem/core/fitting/base.py | 4 +- src/quantem/diffraction/model_fitting.py | 13 +++--- .../model_fitting_visualizations.py | 42 ++++++++++++++++++- src/quantem/diffraction/strain.py | 18 +++----- .../diffraction/strain_visualization.py | 35 ++++++++-------- 5 files changed, 73 insertions(+), 39 deletions(-) diff --git a/src/quantem/core/fitting/base.py b/src/quantem/core/fitting/base.py index c28b33637..b0a1f620a 100644 --- a/src/quantem/core/fitting/base.py +++ b/src/quantem/core/fitting/base.py @@ -802,8 +802,8 @@ def __init__( def forward(self, pred, target): eps = 1 - pred_modified = (pred - pred.min().detach() + eps) ** self.gamma - target_modified = (target - target.min().detach() + eps) ** self.gamma + pred_modified = (pred - pred.min() + eps) ** self.gamma + target_modified = (target - target.min() + eps) ** self.gamma loss = self.mse_fn(pred_modified, target_modified) return loss diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 768925b71..3dfcdc0b7 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -903,8 +903,8 @@ def fit_individual_diffraction_pattern_batched( if isinstance(loss_fn, SqrtMSELoss): gamma = float(loss_fn.gamma) eps = 1.0 - pred_min = pred.amin(dim=(1, 2), keepdim=True).detach() - tgt_min = targets.amin(dim=(1, 2), keepdim=True).detach() + pred_min = pred.amin(dim=(1, 2), keepdim=True) + tgt_min = targets.amin(dim=(1, 2), keepdim=True) pred_mod = (pred - pred_min + eps) ** gamma tgt_mod = (targets - tgt_min + eps) ** gamma per_sample_loss = ((pred_mod - tgt_mod) ** 2).mean(dim=(1, 2)) @@ -1664,11 +1664,12 @@ def apply_hard_constraints( disk_intensity_frozen = "disk.intensity_raw" in skip_keys # DiskTemplate composite hard constraints - if self.disk is not None and not disk_template_frozen: + if self.disk is not None: template = stacked.get("disk.template_raw") intensity = stacked.get("disk.intensity_raw") cfg = self.disk.constraint_config - if template is not None and intensity is not None: + force_positive = bool(self.disk.hard_constraints.get("force_positive", False)) + if not disk_template_frozen and template is not None and intensity is not None: if bool(self.disk.hard_constraints.get("force_center", False)): self._batched_center_disk(template) if bool(self.disk.hard_constraints.get("force_cutoff", False)): @@ -1677,12 +1678,14 @@ def apply_hard_constraints( self._batched_enforce_circular_mask(template, cfg) if bool(self.disk.hard_constraints.get("force_shrinkage", False)): template.sub_(float(cfg.get("shrinkage_amount", 0.25))) - if bool(self.disk.hard_constraints.get("force_positive", False)): + if force_positive: template.clamp_(min=0.0) if not disk_intensity_frozen and intensity is not None: intensity.clamp_(min=0.0) if bool(self.disk.hard_constraints.get("force_norm", False)): self._batched_enforce_norm(template) + if force_positive and not disk_intensity_frozen and intensity is not None: + intensity.clamp_(min=0.0) for lat_name, lat in zip(self.lat_names, self.lats): key = f"{lat_name}.i0_raw" diff --git a/src/quantem/diffraction/model_fitting_visualizations.py b/src/quantem/diffraction/model_fitting_visualizations.py index c87fefafe..6bc35a09d 100644 --- a/src/quantem/diffraction/model_fitting_visualizations.py +++ b/src/quantem/diffraction/model_fitting_visualizations.py @@ -6,6 +6,11 @@ from quantem.core import config from quantem.core.visualization import show_2d +from quantem.core.visualization.custom_normalizations import ( + CustomNormalization, + _resolve_normalization, +) + if TYPE_CHECKING: from quantem.diffraction.model_fitting import ModelDiffraction @@ -343,8 +348,41 @@ def plot_model( refp = ref if power == 1.0 else np.maximum(ref, 0.0) ** float(power) predp = pred if power == 1.0 else np.maximum(pred, 0.0) ** float(power) - kwargs.setdefault("vmin", float(min(refp.min(), predp.min()))) - kwargs.setdefault("vmax", float(max(refp.max(), predp.max()))) + # kwargs.setdefault("vmin", float(min(refp.min(), predp.min()))) + # kwargs.setdefault("vmax", float(max(refp.max(), predp.max()))) + + norm = kwargs.get("norm", None) + if norm is None: + kwargs.setdefault("vmin", float(min(refp.min(), predp.min()))) + kwargs.setdefault("vmax", float(max(refp.max(), predp.max()))) + else: + # Force both panels onto one shared interval, derived from the + # reference image (the model may be uninitialized -> bad scale). + cfg = _resolve_normalization(norm) + cnorm = CustomNormalization( + interval_type=cfg.interval_type, + stretch_type=cfg.stretch_type, + lower_quantile=cfg.lower_quantile, + upper_quantile=cfg.upper_quantile, + vmin=cfg.vmin, + vmax=cfg.vmax, + vcenter=cfg.vcenter, + half_range=cfg.half_range, + power=cfg.power, + logarithmic_index=cfg.logarithmic_index, + asinh_linear_range=cfg.asinh_linear_range, + ) + vmin_shared, vmax_shared = cnorm.interval.get_limits(np.asarray(refp)) + kwargs["norm"] = { + "interval_type": "manual", + "stretch_type": cfg.stretch_type, + "vmin": float(vmin_shared), + "vmax": float(vmax_shared), + "power": cfg.power, + "logarithmic_index": cfg.logarithmic_index, + "asinh_linear_range": cfg.asinh_linear_range, + } + t1 = kwargs.pop("title", "") fig, ax = show_2d( diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index c99e14313..03605e9b4 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -92,13 +92,6 @@ def __init__( self.ds_sampling = 1.0 if ds_sampling is None else ds_sampling self.ds_units = "pixels" if ds_units is None else ds_units - # Per-position weighting / ROI in [0, 1]. The mask producers - # (BraggVectors.fit_lattice, StrainMapAutocorrelation.create_mask) already emit a - # [0, 1] weight, so a well-formed mask is taken as-is: re-normalizing it here - # would collide with that scaling -- a near-constant mask (e.g. the radial - # cepstral weight) would be squashed to ~0 and blank the strain display. Only a - # mask that falls outside [0, 1] (e.g. a raw-intensity ROI) is rescaled, and a - # constant / empty / all-NaN mask falls back to uniform full weight. m = np.ones(ds_shape[:2], dtype=float) if mask is None else np.asarray(mask, dtype=float) m_lo = np.nanmin(m) m_hi = np.nanmax(m) @@ -212,6 +205,8 @@ def plot_strain_roi( plot_rotation: bool = True, cmap_strain: str = "RdBu_r", cmap_rotation: str = "PiYG", + strain_range_percent: tuple[float, float] | None = None, + rotation_range_degrees: tuple[float, float] | None = None, rotate_strain: bool = False, rotate_title: bool = False, plot_dilation: bool = False, @@ -283,8 +278,8 @@ def plot_strain_roi( self.ds_shape, ds_sampling=self.ds_sampling, ds_units=self.ds_units, - strain_range_percent=(-smax, smax), - rotation_range_degrees=(-rmax, rmax), + strain_range_percent=(-smax, smax) if strain_range_percent is None else strain_range_percent, + rotation_range_degrees=(-rmax, rmax) if rotation_range_degrees is None else rotation_range_degrees, roi=inside, plot_rotation=plot_rotation, cmap_strain=cmap_strain, @@ -304,8 +299,7 @@ def plot_strain_roi( def plot_strain( self, - rotation_angle_deg: float = 0.0, - transpose: bool = False, + rotation_angle: float = 0.0, strain_range_percent: tuple[float, float] = (-3.0, 3.0), rotation_range_degrees: tuple[float, float] = (-2.0, 2.0), mask_range: tuple[float, float] = (0.0, 1.0), @@ -854,7 +848,7 @@ def _strain_tensor( # const = -1 is the reciprocal-space (nanobeam) shear/rotation convention. Both # modalities reduce strain_trans to F.T above, so the convention is shared. - const = -1 + const = 1 if real_space else -1 e_rr = strain_trans[:, :, 0, 0] - 1 e_cc = strain_trans[:, :, 1, 1] - 1 e_rc = strain_trans[:, :, 1, 0] * 0.5 * const + strain_trans[:, :, 0, 1] * 0.5 * const diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index e6cc61139..11cdeaf10 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -124,7 +124,7 @@ def _roi_compose(norm_vals, color_cm): etot_disp = _roi_compose(norm_strain(etot_pct), cm_strain) if rotate_strain: etot_disp = etot_disp.transpose(1,0,2) - ax[0].imshow(euu_disp * mask[:, :, np.newaxis]) + ax[0].imshow(etot_disp * mask[:, :, np.newaxis]) ax[1].imshow(euv_disp * mask[:, :, np.newaxis]) else: ax[0].imshow(euu_disp * mask[:, :, np.newaxis]) @@ -137,23 +137,22 @@ def _roi_compose(norm_vals, color_cm): title_fs = 16 * fs_scale tick_fs = 12 * fs_scale title_val = 'vertical' if rotate_title else 'horizontal' - if panel_titles is None and not plot_dilation: - panel_titles = ( - r"$\epsilon_{uu}$ $\updownarrow$", - r"$\epsilon_{vv}$ $\leftrightarrow$", - r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\searrow$", - ) - ax[0].set_title(panel_titles[0], fontsize=title_fs, rotation=title_val) - ax[1].set_title(panel_titles[1], fontsize=title_fs, rotation=title_val) - ax[2].set_title(panel_titles[2], fontsize=title_fs, rotation=title_val) - if plot_dilation and panel_titles is None: - panel_titles = ( - r"$\epsilon_{uu} + \epsilon_{vv}$", - r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\!\:\searrow$", - "" - ) - ax[0].set_title(panel_titles[0], fontsize=title_fs, rotation=title_val) - ax[1].set_title(panel_titles[1], fontsize=title_fs, rotation=title_val) + if panel_titles is None: + if plot_dilation: + panel_titles = ( + r"$\epsilon_{uu} + \epsilon_{vv}$", + r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\!\:\searrow$", + "", + ) + else: + panel_titles = ( + r"$\epsilon_{uu}$ $\updownarrow$", + r"$\epsilon_{vv}$ $\leftrightarrow$", + r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\searrow$", + ) + # apply to the strain panels whether panel_titles was defaulted or passed in + for i in range(n_strain): + ax[i].set_title(panel_titles[i], fontsize=title_fs, rotation=title_val) if plot_rotation: norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) From aab529222476bdf22a718cf710d128ff7e1bd625 Mon Sep 17 00:00:00 2001 From: Colin Ophus Date: Tue, 28 Jul 2026 14:30:30 -0700 Subject: [PATCH 105/113] Fix crashes when only a subset of scan positions is fitted get_individual_uv_vectors fell through to pos_state.keys() after writing NaNs for an unfitted position, raising AttributeError whenever fit_individual_diffraction_pattern was run with rows=/cols= subsets; skip to the next position instead. reset(reset_to="individual") now raises a clear RuntimeError for an unfitted position rather than failing inside state loading. StrainMapAutocorrelation.diffraction_mask raised a zero-size-array ValueError when edge_blend (default 64) exceeded the kept region's inradius (e.g. any 128x128 detector); clamp edge_blend to the largest feasible feather with a warning, and raise a clear error when the threshold masks every pixel. Co-Authored-By: Claude Fable 5 --- src/quantem/diffraction/model_fitting.py | 10 ++++++-- .../diffraction/strain_autocorrelation.py | 24 ++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 5855d2278..8274224c4 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -605,6 +605,11 @@ def reset( if (individual_row >= self.state_individual_refined.shape[0]) or (individual_col >= self.state_individual_refined.shape[1]): raise ValueError("row and column values not in range") state = self.state_individual_refined[individual_row, individual_col] + if state is None: + raise RuntimeError( + f"No refined state for position ({individual_row}, {individual_col}). " + "Run fit_individual_diffraction_pattern(...) for that row and column first." + ) if reset_history: self._clear_fit_history_all() else: @@ -1007,8 +1012,9 @@ def get_individual_uv_vectors(self) -> "ModelDiffraction": for c in range(scan_c): pos_state = self.state_individual_refined[r,c] if pos_state is None: - self.u_array[r,c,:] = None - self.v_array[r,c,:] = None + self.u_array[r,c,:] = np.nan + self.v_array[r,c,:] = np.nan + continue for key in pos_state.keys(): key_parts = key.split('.') if(key_parts[-1] == 'u_row'): diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index c9d4282af..4ea674ac8 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -227,9 +227,31 @@ def diffraction_mask( mask_init[:, -1] = True mask_init[-1, :] = True + # The feather cannot be wider than the kept region's inradius: otherwise no + # pixel reaches mask ~ 1 and the int_edge reduction below sees an empty + # selection (e.g. edge_blend=64 on a 128x128 detector). + edge_distance = distance_transform_edt(np.logical_not(mask_init)) + max_distance = float(np.max(edge_distance)) + if max_distance <= 0.0: + raise ValueError( + "No detector pixels survive the threshold; lower threshold or " + "threshold_percentile." + ) + if edge_blend > max_distance: + import warnings + + warnings.warn( + f"edge_blend={edge_blend:g} exceeds the kept region's largest distance " + f"from the masked region ({max_distance:g} pixels); clamping to " + f"{max_distance:g}. Pass a smaller edge_blend to silence this warning.", + UserWarning, + stacklevel=2, + ) + edge_blend = max_distance + self.mask_diffraction = np.sin( np.clip( - distance_transform_edt(np.logical_not(mask_init)) / edge_blend, + edge_distance / edge_blend, 0.0, 1.0, ) From dbdd0cd5cfcffa912aaf313b7ba60210f27637ed Mon Sep 17 00:00:00 2001 From: Colin Ophus Date: Tue, 28 Jul 2026 15:06:04 -0700 Subject: [PATCH 106/113] Fix GPU device handling in disk detection and cepstral fitting probe_centroid built its index grids on the CPU, crashing make_template_from_data on a CUDA/MPS device. The cepstral batched fitter forced float64, which MPS does not support at all and consumer CUDA cards run ~30x slower than float32; float32 is ample for the ~0.01 px peak-fit noise floor (verified identical strain output on the LFP tutorial dataset). Co-Authored-By: Claude Fable 5 --- src/quantem/diffraction/disk_detection.py | 4 ++-- src/quantem/diffraction/strain_autocorrelation.py | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/quantem/diffraction/disk_detection.py b/src/quantem/diffraction/disk_detection.py index 05c5600ce..8e26436c5 100644 --- a/src/quantem/diffraction/disk_detection.py +++ b/src/quantem/diffraction/disk_detection.py @@ -215,8 +215,8 @@ def probe_centroid(probe: torch.Tensor) -> tuple[float, float]: total = p.sum() if total <= 0: return (p.shape[0] / 2.0, p.shape[1] / 2.0) - rows = torch.arange(p.shape[0], dtype=torch.float).view(-1, 1) - cols = torch.arange(p.shape[1], dtype=torch.float).view(1, -1) + rows = torch.arange(p.shape[0], dtype=torch.float, device=p.device).view(-1, 1) + cols = torch.arange(p.shape[1], dtype=torch.float, device=p.device).view(1, -1) return (float((p * rows).sum() / total), float((p * cols).sum() / total)) diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 4ea674ac8..07929a825 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -981,8 +981,11 @@ def _fit_lattice_vectors_batched( gamma = float(self.metadata["gamma"]) if mode == "gamma" else None dev = torch.device(device) - mask = torch.as_tensor(self.mask_diffraction, dtype=torch.float64, device=dev) - mask_inv = torch.as_tensor(self.mask_diffraction_inv, dtype=torch.float64, device=dev) + # float32: supported on every backend (MPS has no float64, and float64 is + # ~30x slower on consumer CUDA cards); precision is ample for the ~0.01 px + # peak-fit noise floor. + mask = torch.as_tensor(self.mask_diffraction, dtype=torch.float32, device=dev) + mask_inv = torch.as_tensor(self.mask_diffraction_inv, dtype=torch.float32, device=dev) if refine_all_peaks: # Precompute the fixed pieces of the per-position all-peaks fit (these do not @@ -1017,7 +1020,7 @@ def _fit_lattice_vectors_batched( dps = torch.stack( [ torch.as_tensor( - np.asarray(self.dataset.array[r, c]), dtype=torch.float64, device=dev + np.asarray(self.dataset.array[r, c]), dtype=torch.float32, device=dev ) for r, c in idxs ], From 40c296d7101afb7debe106ac6e51a2df9be8f5d5 Mon Sep 17 00:00:00 2001 From: mitis1 Date: Tue, 28 Jul 2026 16:59:46 -0700 Subject: [PATCH 107/113] initial strain rotation bug fixes and renaming some variables --- src/quantem/diffraction/strain.py | 16 +++++----- .../diffraction/strain_visualization.py | 29 ++++++++++++++++--- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index 03605e9b4..19ebae436 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -207,7 +207,7 @@ def plot_strain_roi( cmap_rotation: str = "PiYG", strain_range_percent: tuple[float, float] | None = None, rotation_range_degrees: tuple[float, float] | None = None, - rotate_strain: bool = False, + transpose_image: bool = False, rotate_title: bool = False, plot_dilation: bool = False, layout: str = "horizontal", @@ -285,7 +285,7 @@ def plot_strain_roi( cmap_strain=cmap_strain, cmap_rotation=cmap_rotation, layout=layout, - rotate_strain = rotate_strain, + transpose_image = transpose_image, rotate_title = rotate_title, plot_dilation = plot_dilation, figsize=figsize, @@ -309,7 +309,8 @@ def plot_strain( cmap_strain: str = "RdBu_r", cmap_rotation: str = "PiYG", layout: str = "horizontal", - rotate_strain: bool = False, + transpose_image: bool = False, + transpose_strain: bool = False, rotate_title: bool = False, plot_dilation: bool = False, figsize: tuple[float, float] | None = None, @@ -322,7 +323,7 @@ def plot_strain( rotation_angle_deg : float, default=0.0 Angle (degrees) by which the strain tensor is rotated into the display frame before plotting. - transpose : bool, default=False + transpose_strain : bool, default=False If ``True``, transpose the detector (row/col) axes before rotating, matching the DPC convention (see :func:`~quantem.diffraction.strain_autocorrelation._raw_vec_to_display`): @@ -364,14 +365,14 @@ def plot_strain( e_cc = self.e_cc.array e_rc = self.e_rc.array phi = self.phi.array - if transpose: + if transpose_strain: # Detector-axis transpose, applied BEFORE the rotation to match the DPC # convention shared across quantem (see _raw_vec_to_display): swapping the # (row, col) axes swaps the normal strains, keeps the shear unchanged, and # reverses the sense of the rotation field. e_rr, e_cc = e_cc, e_rr phi = -phi - e_uu, e_vv, e_uv = _rotate_strain_tensor(e_rr, e_cc, e_rc, rotation_angle_deg) + e_uu, e_vv, e_uv = _rotate_strain_tensor(e_rr, e_cc, e_rc, rotation_angle) return plot_strain_panels( e_uu, e_vv, @@ -392,10 +393,11 @@ def plot_strain( cmap_strain=cmap_strain, cmap_rotation=cmap_rotation, layout=layout, - rotate_strain = rotate_strain, + transpose_image = transpose_image, rotate_title = rotate_title, plot_dilation = plot_dilation, figsize=figsize, + strain_rotation_angle=rotation_angle, **kwargs, ) diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index 11cdeaf10..c17bf8a56 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -31,11 +31,12 @@ def plot_strain_panels( cmap_strain: str = "RdBu_r", cmap_rotation: str = "PiYG", layout: str = "horizontal", - rotate_strain: bool = False, + transpose_image: bool = False, rotate_title: bool = False, plot_dilation: bool = False, figsize: tuple[float, float] | None = None, panel_titles: tuple[str, str, str] | None = None, + strain_rotation_angle: float = 0.0, **kwargs, ): """Render strain (e_uu, e_vv, e_uv) and rotation panels. @@ -113,7 +114,7 @@ def _roi_compose(norm_vals, color_cm): evv_disp = _roi_compose(norm_strain(evv_pct), cm_strain) euv_disp = _roi_compose(norm_strain(euv_pct), cm_strain) - if rotate_strain: + if transpose_image: euu_disp = euu_disp.transpose(1,0,2) evv_disp = evv_disp.transpose(1,0,2) euv_disp = euv_disp.transpose(1,0,2) @@ -122,7 +123,7 @@ def _roi_compose(norm_vals, color_cm): if plot_dilation: etot_pct = (e_uu + e_vv) * 100 etot_disp = _roi_compose(norm_strain(etot_pct), cm_strain) - if rotate_strain: + if transpose_image: etot_disp = etot_disp.transpose(1,0,2) ax[0].imshow(etot_disp * mask[:, :, np.newaxis]) ax[1].imshow(euv_disp * mask[:, :, np.newaxis]) @@ -131,6 +132,18 @@ def _roi_compose(norm_vals, color_cm): ax[1].imshow(evv_disp * mask[:, :, np.newaxis]) ax[2].imshow(euv_disp * mask[:, :, np.newaxis]) + + def _add_title_arrow(ax, angle_deg, x=0.80, y=1.06, length=0.045, + color="black", lw=1.5): + a = np.deg2rad(angle_deg) + dx, dy = length * np.cos(a), length * np.sin(a) + ax.annotate( + "", + xy=(x + dx, y + dy), xytext=(x - dx, y - dy), + xycoords="axes fraction", + annotation_clip=False, + arrowprops=dict(arrowstyle="<->", color=color, lw=lw), + ) ref_dim = figsize[1] if is_horizontal else figsize[0] fs_threshold = 3.0 fs_scale = min(1.0, max(0.5, ref_dim / fs_threshold)) @@ -144,20 +157,28 @@ def _roi_compose(norm_vals, color_cm): r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\!\:\searrow$", "", ) + title_arrow_angles = (None, -45 + strain_rotation_angle, None) else: panel_titles = ( r"$\epsilon_{uu}$ $\updownarrow$", r"$\epsilon_{vv}$ $\leftrightarrow$", r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\searrow$", ) + title_arrow_angles = (90 + strain_rotation_angle, 0 + strain_rotation_angle, -45 + strain_rotation_angle) + else: + title_arrow_angles = (None, None, None) + # apply to the strain panels whether panel_titles was defaulted or passed in for i in range(n_strain): ax[i].set_title(panel_titles[i], fontsize=title_fs, rotation=title_val) + angle = title_arrow_angles[i] + if angle is not None: + _add_title_arrow(ax[i], angle, color="black") if plot_rotation: norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) rot_disp = _roi_compose(norm_rot(rot_deg), cm_rot) - if rotate_strain: rot_disp = rot_disp.transpose(1,0,2) + if transpose_image: rot_disp = rot_disp.transpose(1,0,2) ax[-1].imshow(rot_disp * mask[:, :, np.newaxis]) ax[-1].set_title(r"Rotation $\circlearrowleft$", fontsize=title_fs, rotation=title_val) From e84e46fe99f52eca068f71073cf9f656881945c2 Mon Sep 17 00:00:00 2001 From: Colin Ophus Date: Wed, 29 Jul 2026 11:29:19 -0700 Subject: [PATCH 108/113] Subtract smooth correlation background before disk peak finding The correlation map is relu-clamped before the local-maxima search. With a zero-sum template, a bright unscattered beam imprints a broad negative moat on the correlation map; weak disk peaks ride below zero there and the clamp erased them before any threshold applied -- at quasi-vacuum positions of the LFP tutorial dataset 96% of the map clamped to zero and only 4 of 39 maxima survived, and even on-particle patterns lost their weakest disks. Add background_sigma: a Fourier-domain Gaussian high-pass applied to the correlation product (batched, both fft and rfft paths, also used by the DFT subpixel refinement) that removes the smooth background so peak intensities become prominence above the local baseline. BraggVectors detect_disks / show_detection / correlation_map default to background_sigma="auto" (twice the central-beam radius); pass None for the previous behavior. Co-Authored-By: Claude Fable 5 --- src/quantem/diffraction/bragg_vectors.py | 37 ++++++++++++- src/quantem/diffraction/disk_detection.py | 64 +++++++++++++++++++++-- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/quantem/diffraction/bragg_vectors.py b/src/quantem/diffraction/bragg_vectors.py index a2da2eba2..2eaec234d 100644 --- a/src/quantem/diffraction/bragg_vectors.py +++ b/src/quantem/diffraction/bragg_vectors.py @@ -353,7 +353,9 @@ def template(self) -> np.ndarray | None: return None return torch.fft.fftshift(self._template).detach().cpu().numpy() - def correlation_map(self, row: int, col: int) -> np.ndarray: + def correlation_map( + self, row: int, col: int, background_sigma: float | str | None = "auto" + ) -> np.ndarray: """Cross-correlation map of one diffraction pattern with the template (numpy). Peaks in the returned map sit at absolute disk positions (no fftshift @@ -376,7 +378,9 @@ def correlation_map(self, row: int, col: int) -> np.ndarray: dp = torch.as_tensor( np.asarray(self.dataset.array[row, col]), dtype=torch.float, device=self.device ) - corr, _ = cross_correlation(dp, self._template_ft) + corr, _ = cross_correlation( + dp, self._template_ft, self._resolve_background_sigma(background_sigma) + ) return corr.detach().cpu().numpy() def detect_disks( @@ -389,6 +393,7 @@ def detect_disks( subpixel: str = "upsample", upsample_factor: int = 16, max_num_peaks: int = 1000, + background_sigma: float | str | None = "auto", batch_size: int | None = None, progressbar: bool = True, ) -> Vector: @@ -444,6 +449,7 @@ def detect_disks( subpixel=subpixel, upsample_factor=upsample_factor, max_num_peaks=max_num_peaks, + background_sigma=self._resolve_background_sigma(background_sigma), ) if positions is not None: @@ -1090,6 +1096,7 @@ def show_detection( subpixel: str = "upsample", upsample_factor: int = 16, max_num_peaks: int = 1000, + background_sigma: float | str | None = "auto", image: np.ndarray | None = None, peak_radius: float = 6.0, marker_radius: float | None = None, @@ -1161,6 +1168,7 @@ def show_detection( subpixel=subpixel, upsample_factor=upsample_factor, max_num_peaks=max_num_peaks, + background_sigma=background_sigma, progressbar=False, ) if image is None: @@ -1219,6 +1227,31 @@ def peak_histogram(self, *, returnfig: bool = False, **kwargs): # ---- helpers ---- + def _resolve_background_sigma( + self, background_sigma: float | str | None + ) -> float | None: + """Resolve the ``background_sigma`` argument to a value in pixels. + + ``"auto"`` (the default everywhere) maps to twice the central-beam + radius: wide enough that the disk-scale correlation peaks pass + untouched, narrow enough to remove the zero-sum template's negative + moat around a bright unscattered beam -- which otherwise pushes weak + disk peaks below zero, where the correlation clamp erases them before + peak finding. Pass ``None`` to disable the background subtraction or a + float to set the scale explicitly. + """ + if background_sigma is None: + return None + if isinstance(background_sigma, str): + if background_sigma != "auto": + raise ValueError("background_sigma must be a float, None, or 'auto'.") + radius = self.metadata.get("template", {}).get("radius") + if radius is None: + dp_mean = np.asarray(self.dataset.dp_mean.array) + _, radius = estimate_central_beam(dp_mean) + return 2.0 * float(radius) + return float(background_sigma) + def _set_template( self, probe: torch.Tensor, diff --git a/src/quantem/diffraction/disk_detection.py b/src/quantem/diffraction/disk_detection.py index 8e26436c5..5ec5f00f6 100644 --- a/src/quantem/diffraction/disk_detection.py +++ b/src/quantem/diffraction/disk_detection.py @@ -237,9 +237,52 @@ def template_fourier(template: torch.Tensor) -> torch.Tensor: return torch.conj(torch.fft.fft2(template)) +def _background_highpass( + shape: tuple[int, int], + background_sigma: float, + device, + rfft: bool = False, +) -> torch.Tensor: + """Fourier-domain high-pass ``1 - G`` removing correlation background. + + ``G`` is the transform of a real-space Gaussian of standard deviation + ``background_sigma`` pixels, so multiplying the Fourier product by ``1 - G`` + subtracts a Gaussian-smoothed copy of the correlation map -- the slowly + varying background (the zero-sum template's negative moat around the bright + central beam) that otherwise pushes weak disk peaks below zero, where the + ``relu`` clamp erases them before peak finding. + + Parameters + ---------- + shape : tuple of int + ``(H, W)`` detector shape. + background_sigma : float + Real-space standard deviation of the subtracted background, in pixels. + device + Torch device for the filter tensor. + rfft : bool, default=False + If ``True``, return the ``(H, W // 2 + 1)`` half-plane filter for + ``rfft2`` products instead of the full ``(H, W)`` filter. + + Returns + ------- + torch.Tensor + The ``1 - G`` filter in the requested Fourier layout. + """ + H, W = int(shape[0]), int(shape[1]) + qr = torch.fft.fftfreq(H, device=device, dtype=torch.float)[:, None] + if rfft: + qc = torch.fft.rfftfreq(W, device=device, dtype=torch.float)[None, :] + else: + qc = torch.fft.fftfreq(W, device=device, dtype=torch.float)[None, :] + g = torch.exp(-2.0 * (torch.pi**2) * (float(background_sigma) ** 2) * (qr**2 + qc**2)) + return 1.0 - g + + def cross_correlation( dp: torch.Tensor, template_ft: torch.Tensor, + background_sigma: float | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: """Cross-correlate a diffraction pattern with a template. @@ -261,6 +304,8 @@ def cross_correlation( """ dp = torch.as_tensor(dp) m = torch.fft.fft2(dp) * template_ft + if background_sigma is not None and background_sigma > 0: + m = m * _background_highpass(m.shape[-2:], background_sigma, m.device) corr_map = torch.clamp(torch.fft.ifft2(m).real, min=0.0) return corr_map, m @@ -268,6 +313,7 @@ def cross_correlation( def cross_correlation_batch( dps: torch.Tensor, template_ft: torch.Tensor, + background_sigma: float | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: """Cross-correlate a stack of diffraction patterns with one template. @@ -291,11 +337,17 @@ def cross_correlation_batch( """ dps = torch.as_tensor(dps) m = torch.fft.fft2(dps) * template_ft + if background_sigma is not None and background_sigma > 0: + m = m * _background_highpass(m.shape[-2:], background_sigma, m.device) corr_map = torch.clamp(torch.fft.ifft2(m).real, min=0.0) return corr_map, m -def _corr_map_rfft(dps: torch.Tensor, template_ft: torch.Tensor) -> torch.Tensor: +def _corr_map_rfft( + dps: torch.Tensor, + template_ft: torch.Tensor, + background_sigma: float | None = None, +) -> torch.Tensor: """Real-FFT correlation map(s), used when no Fourier product is needed downstream. For real ``dps`` and a real template the Fourier product is conjugate-symmetric, @@ -318,6 +370,8 @@ def _corr_map_rfft(dps: torch.Tensor, template_ft: torch.Tensor) -> torch.Tensor dps = torch.as_tensor(dps) H, W = dps.shape[-2], dps.shape[-1] prod = torch.fft.rfft2(dps) * template_ft[..., : W // 2 + 1] + if background_sigma is not None and background_sigma > 0: + prod = prod * _background_highpass((H, W), background_sigma, prod.device, rfft=True) corr_map = torch.fft.irfft2(prod, s=(H, W)) return torch.clamp(corr_map, min=0.0) @@ -332,6 +386,7 @@ def detect_disks( subpixel: str = "upsample", upsample_factor: int = 16, max_num_peaks: int = 1000, + background_sigma: float | None = None, ) -> np.ndarray: """Detect Bragg disks in one diffraction pattern by template matching. @@ -366,7 +421,7 @@ def detect_disks( if subpixel not in SUBPIXEL_MODES: raise ValueError(f"subpixel must be in {SUBPIXEL_MODES}, got {subpixel!r}") - corr_map, m = cross_correlation(dp, template_ft) + corr_map, m = cross_correlation(dp, template_ft, background_sigma) peaks = _local_maxima(corr_map, edge_boundary) peaks = _filter_maxima(peaks, min_abs_intensity, min_spacing, max_num_peaks) @@ -393,6 +448,7 @@ def detect_disks_batch( subpixel: str = "upsample", upsample_factor: int = 16, max_num_peaks: int = 1000, + background_sigma: float | None = None, ) -> list[np.ndarray]: """Detect Bragg disks across a stack of diffraction patterns (batched). @@ -437,9 +493,9 @@ def detect_disks_batch( raise ValueError(f"subpixel must be in {SUBPIXEL_MODES}, got {subpixel!r}") if subpixel == "upsample": - corr_map, m = cross_correlation_batch(dps, template_ft) + corr_map, m = cross_correlation_batch(dps, template_ft, background_sigma) else: - corr_map = _corr_map_rfft(dps, template_ft) + corr_map = _corr_map_rfft(dps, template_ft, background_sigma) m = None peaks_all, bidx, counts = _detect_peaks_batched( From ef4c6b56ef93e537f80eb870a9a6cd747299a588 Mon Sep 17 00:00:00 2001 From: Colin Ophus Date: Wed, 29 Jul 2026 13:44:22 -0700 Subject: [PATCH 109/113] Default to a unit-sum positive correlation template Design decision: keep the correlation map strictly positive so peak intensities roughly measure the probe-weighted counts under each disk and min_abs_intensity is always a nonnegative counts-scale threshold. The previous zero-sum default subtracted the pattern-mean from the correlation, pushing weak disk peaks below zero where the relu clamp erased them (the missing-peaks bug on the LFP tutorial data). make_template_* now default subtract_mean=False; background_sigma (the Gaussian high-pass) defaults to None everywhere, remaining available for patterns with a strong smooth background. Co-Authored-By: Claude Fable 5 --- src/quantem/diffraction/bragg_vectors.py | 33 +++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/quantem/diffraction/bragg_vectors.py b/src/quantem/diffraction/bragg_vectors.py index 2eaec234d..6503f56ad 100644 --- a/src/quantem/diffraction/bragg_vectors.py +++ b/src/quantem/diffraction/bragg_vectors.py @@ -211,7 +211,7 @@ def make_template_synthetic( radius: float | None = None, edge: float = 1.0, center: tuple[float, float] | None = None, - subtract_mean: bool = True, + subtract_mean: bool = False, ) -> "BraggVectors": """Build the template from a synthetic soft-edged disk. @@ -227,9 +227,10 @@ def make_template_synthetic( center : tuple of float, optional ``(row, col)`` disk center; defaults to the detector center ``(H // 2, W // 2)``. - subtract_mean : bool, default=True - If ``True``, make the template zero-sum — a band-pass kernel that - suppresses uniform background in the correlation. + subtract_mean : bool, default=False + If ``True``, make the template zero-sum. The default keeps the + unit-sum positive template, so correlation values stay positive and + roughly measure the probe-weighted counts under each peak. Returns ------- @@ -257,7 +258,7 @@ def make_template_synthetic( def make_template_from_data( self, roi: NDArray | None = None, - subtract_mean: bool = True, + subtract_mean: bool = False, center: tuple[float, float] | None = None, ) -> "BraggVectors": """Build the template by averaging diffraction patterns from the data. @@ -269,9 +270,10 @@ def make_template_from_data( ideally a vacuum / single-disk region so the unscattered probe is isolated. ``None`` (default) averages the whole scan (the mean diffraction pattern). - subtract_mean : bool, default=True - If ``True``, make the template zero-sum — a band-pass kernel that - suppresses uniform background in the correlation. + subtract_mean : bool, default=False + If ``True``, make the template zero-sum. The default keeps the + unit-sum positive template, so correlation values stay positive and + roughly measure the probe-weighted counts under each peak. center : tuple of float, optional ``(row, col)`` probe center rolled to the origin; defaults to the probe's intensity centroid. @@ -305,7 +307,7 @@ def make_template_from_probe( self, probe: NDArray | torch.Tensor, center: tuple[float, float] | None = None, - subtract_mean: bool = True, + subtract_mean: bool = False, ) -> "BraggVectors": """Build the template from an explicit probe image (e.g. a measured vacuum probe). @@ -316,9 +318,10 @@ def make_template_from_probe( center : tuple of float, optional ``(row, col)`` probe center rolled to the origin; defaults to the probe's intensity centroid. - subtract_mean : bool, default=True - If ``True``, make the template zero-sum — a band-pass kernel that - suppresses uniform background in the correlation. + subtract_mean : bool, default=False + If ``True``, make the template zero-sum. The default keeps the + unit-sum positive template, so correlation values stay positive and + roughly measure the probe-weighted counts under each peak. Returns ------- @@ -354,7 +357,7 @@ def template(self) -> np.ndarray | None: return torch.fft.fftshift(self._template).detach().cpu().numpy() def correlation_map( - self, row: int, col: int, background_sigma: float | str | None = "auto" + self, row: int, col: int, background_sigma: float | str | None = None ) -> np.ndarray: """Cross-correlation map of one diffraction pattern with the template (numpy). @@ -393,7 +396,7 @@ def detect_disks( subpixel: str = "upsample", upsample_factor: int = 16, max_num_peaks: int = 1000, - background_sigma: float | str | None = "auto", + background_sigma: float | str | None = None, batch_size: int | None = None, progressbar: bool = True, ) -> Vector: @@ -1096,7 +1099,7 @@ def show_detection( subpixel: str = "upsample", upsample_factor: int = 16, max_num_peaks: int = 1000, - background_sigma: float | str | None = "auto", + background_sigma: float | str | None = None, image: np.ndarray | None = None, peak_radius: float = 6.0, marker_radius: float | None = None, From d5812bd6bfff17a8f9da5c7f8c5461191f6b78a6 Mon Sep 17 00:00:00 2001 From: mitis1 Date: Wed, 29 Jul 2026 13:50:18 -0700 Subject: [PATCH 110/113] initial rotation implementation --- src/quantem/diffraction/bragg_vectors.py | 55 ++++- src/quantem/diffraction/model_fitting.py | 38 ++++ src/quantem/diffraction/strain.py | 118 +++++++++-- .../diffraction/strain_autocorrelation.py | 2 + .../diffraction/strain_visualization.py | 193 ++++++++++++------ 5 files changed, 325 insertions(+), 81 deletions(-) diff --git a/src/quantem/diffraction/bragg_vectors.py b/src/quantem/diffraction/bragg_vectors.py index 7b571e04e..344910ba4 100644 --- a/src/quantem/diffraction/bragg_vectors.py +++ b/src/quantem/diffraction/bragg_vectors.py @@ -441,7 +441,8 @@ def detect_disks( detector dimensions. progressbar : bool, default=True If ``True``, show a tqdm progress bar over the full-scan detection. - + save_to_gpu: bool, default = True, + Transfers dataset to gpu for faster calculation and less cpu load. Returns ------- Vector @@ -924,6 +925,8 @@ def calculate_strain_map( u_ref: np.ndarray | None = None, v_ref: np.ndarray | None = None, mask: np.ndarray | None = None, + q_to_r_rotation_ccw_deg: float | None = None, + q_transpose: bool | None = None, ) -> StrainMap: """Build a :class:`StrainMap` from the fitted per-position lattice vectors. @@ -954,8 +957,52 @@ def calculate_strain_map( if mask is None: mask = self.mask_weight - ds_sampling = float(self.dataset.sampling[0]) - ds_units = str(self.dataset.units[0]) + ds_units = None + ds_sampling = None + if hasattr(self.dataset, 'units'): + if isinstance(self.dataset.units, (tuple, list)): + ds_units = str(self.dataset.units[0]) + else: + ds_units = str(self.dataset.units) + if hasattr(self.dataset, 'sampling'): + if isinstance(self.dataset.sampling, (tuple, list, np.ndarray)): + ds_sampling = float(self.dataset.sampling[0]) + else: + ds_sampling = float(self.dataset.sampling) + + parent_rot = self.dataset.metadata.get("q_to_r_rotation_ccw_deg", None) + parent_tr = self.dataset.metadata.get("q_transpose", None) + + used_parent = False + if q_to_r_rotation_ccw_deg is None and parent_rot is not None: + q_to_r_rotation_ccw_deg = parent_rot + used_parent = True + if q_transpose is None and parent_tr is not None: + q_transpose = parent_tr + used_parent = True + + if used_parent: + import warnings + + warnings.warn( + "StrainMapAutocorrelation.preprocess: using parent Dataset4dstem metadata " + f"(q_to_r_rotation_ccw_deg={q_to_r_rotation_ccw_deg or 0.0}, " + f"q_transpose={q_transpose or False}).", + UserWarning, + ) + + if q_to_r_rotation_ccw_deg is None or q_transpose is None: + import warnings + + q_to_r_rotation_ccw_deg = 0.0 if q_to_r_rotation_ccw_deg is None else q_to_r_rotation_ccw_deg + q_transpose = False if q_transpose is None else q_transpose + warnings.warn( + "StrainMapAutocorrelation.preprocess: setting q_to_r_rotation_ccw_deg=0.0 and q_transpose=False.", + UserWarning, + ) + + self.metadata["q_to_r_rotation_ccw_deg"] = q_to_r_rotation_ccw_deg + self.metadata["q_transpose"] = q_transpose return StrainMap( u_array=self.u_array, @@ -967,6 +1014,8 @@ def calculate_strain_map( mask=mask, ds_sampling=ds_sampling, ds_units=ds_units, + q_to_r_rotation_ccw_deg = q_to_r_rotation_ccw_deg, + q_transpose = q_transpose, ) # ---- visualization ---- diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 3dfcdc0b7..5e23135e8 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -1125,6 +1125,8 @@ def calculate_strain_map( u_ref: np.ndarray | None = None, v_ref: np.ndarray | None = None, mask: np.ndarray | None = None, + q_to_r_rotation_ccw_deg: float | None = None, + q_transpose: bool | None = None, ) -> StrainMap: """Build a :class:`StrainMap` from the fitted per-position lattice vectors. @@ -1172,6 +1174,40 @@ def calculate_strain_map( default_sampling = float(self.dataset.sampling[0]) else: default_sampling = float(self.dataset.sampling) + + parent_rot = self.dataset.metadata.get("q_to_r_rotation_ccw_deg", None) + parent_tr = self.dataset.metadata.get("q_transpose", None) + + used_parent = False + if q_to_r_rotation_ccw_deg is None and parent_rot is not None: + q_to_r_rotation_ccw_deg = parent_rot + used_parent = True + if q_transpose is None and parent_tr is not None: + q_transpose = parent_tr + used_parent = True + + if used_parent: + import warnings + + warnings.warn( + "StrainMapAutocorrelation.preprocess: using parent Dataset4dstem metadata " + f"(q_to_r_rotation_ccw_deg={q_to_r_rotation_ccw_deg or 0.0}, " + f"q_transpose={q_transpose or False}).", + UserWarning, + ) + + if q_to_r_rotation_ccw_deg is None or q_transpose is None: + import warnings + + q_to_r_rotation_ccw_deg = 0.0 if q_to_r_rotation_ccw_deg is None else q_to_r_rotation_ccw_deg + q_transpose = False if q_transpose is None else q_transpose + warnings.warn( + "StrainMapAutocorrelation.preprocess: setting q_to_r_rotation_ccw_deg=0.0 and q_transpose=False.", + UserWarning, + ) + + self.metadata["q_to_r_rotation_ccw_deg"] = q_to_r_rotation_ccw_deg + self.metadata["q_transpose"] = q_transpose return StrainMap( u_array = self.u_array, @@ -1183,6 +1219,8 @@ def calculate_strain_map( mask = mask, ds_sampling=default_sampling, ds_units = default_units, + q_to_r_rotation_ccw_deg = q_to_r_rotation_ccw_deg, + q_transpose = q_transpose, ) @property diff --git a/src/quantem/diffraction/strain.py b/src/quantem/diffraction/strain.py index 19ebae436..0c000e364 100644 --- a/src/quantem/diffraction/strain.py +++ b/src/quantem/diffraction/strain.py @@ -4,6 +4,7 @@ import numpy as np from numpy.lib.stride_tricks import sliding_window_view +from numpy.typing import NDArray from quantem.core.datastructures.dataset2d import Dataset2d from quantem.core.io.serialize import AutoSerialize @@ -81,10 +82,18 @@ def __init__( mask: np.ndarray | None = None, ds_sampling: float | None = None, ds_units: str | None = None, + q_to_r_rotation_ccw_deg: float = 0.0, + q_transpose: bool = False, ): super().__init__() self.u_array = u_array self.v_array = v_array + + self.q_to_r_rotation_ccw_deg = q_to_r_rotation_ccw_deg + self.q_transpose = q_transpose + + self.u_array = _raw_vec_to_display(self.u_array, rotation_ccw_deg = q_to_r_rotation_ccw_deg, transpose=q_transpose) + self.v_array = _raw_vec_to_display(self.v_array, rotation_ccw_deg = q_to_r_rotation_ccw_deg, transpose=q_transpose) self.ds_shape = ds_shape self.real_space = real_space @@ -102,8 +111,12 @@ def __init__( self.mask = m # user-supplied reference vectors persist across re-fits (None = use median) - self._u_ref_fixed = None if u_ref is None else np.asarray(u_ref, dtype=float) - self._v_ref_fixed = None if v_ref is None else np.asarray(v_ref, dtype=float) + self._u_ref_fixed = None if u_ref is None else _raw_vec_to_display(np.asarray(u_ref, dtype=float), + rotation_ccw_deg=q_to_r_rotation_ccw_deg, + transpose=q_transpose) + self._v_ref_fixed = None if v_ref is None else _raw_vec_to_display(np.asarray(v_ref, dtype=float), + rotation_ccw_deg=q_to_r_rotation_ccw_deg, + transpose=q_transpose) self.u_ref = None self.v_ref = None @@ -117,6 +130,7 @@ def update_reference( u_ref: np.ndarray | None = None, v_ref: np.ndarray | None = None, plot_strain_roi: bool = False, + define_in_rotated_frame: bool = False, **plot_kwargs, ) -> "StrainMap": """(Re)compute the reference lattice and strain tensor maps. @@ -141,6 +155,8 @@ def update_reference( If ``True``, show the recomputed strain via :meth:`plot_strain_roi` (color-scaled to the ROI) so the chosen reference region can be checked for flatness. + define_in_rotated_frame: bool, default = False + If ''True'' means the u_ref and v_ref passed into the function is defined in the rotated detector frame **plot_kwargs Forwarded to :meth:`plot_strain_roi` when ``plot_strain_roi=True``. @@ -152,14 +168,26 @@ def update_reference( u_med, v_med = _reference_lattice(self.u_array, self.v_array, self.mask, strain_mask) if u_ref is not None: - self.u_ref = np.asarray(u_ref, dtype=float) + if define_in_rotated_frame: + self.u_ref = np.asarray(u_ref, dtype=float) + else: + self.u_ref = _raw_vec_to_display( + np.asarray(u_ref, dtype=float), + rotation_ccw_deg=self.q_to_r_rotation_ccw_deg, + transpose=self.q_transpose) elif self._u_ref_fixed is not None: self.u_ref = self._u_ref_fixed else: self.u_ref = u_med if v_ref is not None: - self.v_ref = np.asarray(v_ref, dtype=float) + if define_in_rotated_frame: + self.v_ref = np.asarray(v_ref, dtype=float) + else: + self.v_ref = _raw_vec_to_display( + np.asarray(v_ref, dtype=float), + rotation_ccw_deg=self.q_to_r_rotation_ccw_deg, + transpose=self.q_transpose) elif self._v_ref_fixed is not None: self.v_ref = self._v_ref_fixed else: @@ -211,6 +239,7 @@ def plot_strain_roi( rotate_title: bool = False, plot_dilation: bool = False, layout: str = "horizontal", + arrow_style: str = "title", figsize: tuple[float, float] | None = None, **kwargs, ): @@ -236,8 +265,21 @@ def plot_strain_roi( Colormap for the strain panels. cmap_rotation : str, default="PiYG" Colormap for the rotation panel. + strain_range_percent : tuple of float, default=(-3.0, 3.0) + Symmetric color range for the strain panels, in percent. + rotation_range_degrees : tuple of float, default=(-2.0, 2.0) + Symmetric color range for the rotation panel, in degrees. + transpose_image: bool, default = False + If ''True'' transpose the real space image before plotting strain + rotate_title: bool, default = False + If ''True'', rotates panel titles by 90 degrees. + plot_dilation: bool, default = False + If ''True'' plots euu + evv, and euv instead of euu, evv, euv layout : {"horizontal", "vertical"}, default="horizontal" Panel arrangement. + arrow_style: str, default="title" + Plots the directional arrows along with the strain titles. + Alternatively can be "legend" where it plots it on the side figsize : tuple of float, optional Figure size in inches; if omitted it is derived from the layout. **kwargs @@ -249,6 +291,10 @@ def plot_strain_roi( tuple ``(fig, ax)`` from :func:`plot_strain_panels`. """ + + if arrow_style not in ("title", "legend"): + raise ValueError("arrow_style must be 'title' or 'legend'") + roi_src = self.mask if strain_mask is None else strain_mask e_rr, e_cc, e_rc, phi = ( self.e_rr.array, @@ -294,6 +340,7 @@ def plot_strain_roi( r"$\epsilon_{cc}$ $\leftrightarrow$", r"$\epsilon_{rc}$ $\nwarrow\!\!\!\!\!\!\!\!\!\:\searrow$", ), + arrow_style = arrow_style, **kwargs, ) @@ -308,11 +355,12 @@ def plot_strain( plot_scalebar: bool = False, cmap_strain: str = "RdBu_r", cmap_rotation: str = "PiYG", - layout: str = "horizontal", transpose_image: bool = False, transpose_strain: bool = False, rotate_title: bool = False, plot_dilation: bool = False, + layout: str = "horizontal", + arrow_style: str = "title", figsize: tuple[float, float] | None = None, **kwargs, ): @@ -320,15 +368,9 @@ def plot_strain( Parameters ---------- - rotation_angle_deg : float, default=0.0 + rotation_angle : float, default=0.0 Angle (degrees) by which the strain tensor is rotated into the display frame before plotting. - transpose_strain : bool, default=False - If ``True``, transpose the detector (row/col) axes before rotating, - matching the DPC convention (see - :func:`~quantem.diffraction.strain_autocorrelation._raw_vec_to_display`): - transpose first, then rotate. This swaps the normal strain components, - leaves the shear unchanged, and reverses the sign of the rotation field. strain_range_percent : tuple of float, default=(-3.0, 3.0) Symmetric color range for the strain panels, in percent. rotation_range_degrees : tuple of float, default=(-2.0, 2.0) @@ -348,10 +390,25 @@ def plot_strain( Colormap for the strain panels. cmap_rotation : str, default="PiYG" Colormap for the rotation panel. + transpose_image: bool, default = False + If ''True'' transpose the real space image before plotting strain + transpose_strain : bool, default=False + If ``True``, transpose the detector (row/col) axes before rotating, + matching the DPC convention (see + :func:`~quantem.diffraction.strain_autocorrelation._raw_vec_to_display`): + transpose first, then rotate. This swaps the normal strain components, + leaves the shear unchanged, and reverses the sign of the rotation field. + rotate_title: bool, default = False + If ''True'', rotates panel titles by 90 degrees. + plot_dilation: bool, default = False + If ''True'' plots euu + evv, and euv instead of euu, evv, euv layout : {"horizontal", "vertical"}, default="horizontal" Panel arrangement. figsize : tuple of float, optional Figure size in inches; if omitted it is derived from the layout. + arrow_style: str, default="title" + Plots the directional arrows along with the strain titles. + Alternatively can be "legend" where it plots it on the side **kwargs Forwarded to :func:`~quantem.diffraction.strain_visualization.plot_strain_panels`. @@ -361,6 +418,9 @@ def plot_strain( tuple ``(fig, ax)`` from :func:`plot_strain_panels`. """ + if arrow_style not in ("title", "legend"): + raise ValueError("arrow_style must be 'title' or 'legend'") + e_rr = self.e_rr.array e_cc = self.e_cc.array e_rc = self.e_rc.array @@ -398,6 +458,7 @@ def plot_strain( plot_dilation = plot_dilation, figsize=figsize, strain_rotation_angle=rotation_angle, + arrow_style = arrow_style, **kwargs, ) @@ -863,6 +924,7 @@ def _rotate_strain_tensor( e_cc: np.ndarray, e_rc: np.ndarray, rotation_angle: float, + real_space=False ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Rotate a 2D strain tensor by ``rotation_angle`` (degrees). @@ -876,7 +938,8 @@ def _rotate_strain_tensor( Row-column (shear) strain component. rotation_angle : float Frame rotation angle, in degrees. - + real_space: bool + Tells whether vector is defined in real or reciprocal space Returns ------- tuple of np.ndarray @@ -885,7 +948,28 @@ def _rotate_strain_tensor( angle = np.deg2rad(rotation_angle) c = np.cos(angle) s = np.sin(angle) - e_uu = e_rr * (c * c) + 2.0 * e_rc * (c * s) + e_cc * (s * s) - e_vv = e_rr * (s * s) - 2.0 * e_rc * (c * s) + e_cc * (c * c) - e_uv = (e_cc - e_rr) * (c * s) + e_rc * (c * c - s * s) - return e_uu, e_vv, e_uv \ No newline at end of file + sign = -1.0 if real_space else 1.0 + e_uu = e_rr * (c * c) + sign * 2.0 * e_rc * (c * s) + e_cc * (s * s) + e_vv = e_rr * (s * s) - sign * 2.0 * e_rc * (c * s) + e_cc * (c * c) + e_uv = sign * (e_cc - e_rr) * (c * s) + e_rc * (c * c - s * s) + return e_uu, e_vv, e_uv + +def _raw_vec_to_display(vec_rc: NDArray, *, rotation_ccw_deg: float, transpose: bool) -> NDArray: + """Map a raw-detector ``(row, col)`` vector into the rotated display frame. + + Applies the optional axis transpose, then a counter-clockwise rotation of + ``rotation_ccw_deg``. Inverse of :func:`_display_vec_to_raw`. + """ + v = np.asarray(vec_rc, dtype=float) + dr, dc = v[..., 0], v[..., 1] + + if transpose: + dr, dc = dc, dr + + theta = np.deg2rad(rotation_ccw_deg) + ct = np.cos(theta) + st = np.sin(theta) + + dr2 = ct * dr - st * dc + dc2 = st * dr + ct * dc + return np.stack((dr2, dc2), axis=-1) \ No newline at end of file diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 0c9a8d14b..5d939f9fb 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -1275,6 +1275,8 @@ def calculate_strain_map( mask=mask, ds_sampling=ds_sampling, ds_units=ds_units, + q_to_r_rotation_ccw_deg = self.metadata['q_to_r_rotation_ccw_deg '], + q_transpose = self.metadata['q_transpose'], ) def plot_lattice_vectors( diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index c17bf8a56..8ad0888f7 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -37,6 +37,7 @@ def plot_strain_panels( figsize: tuple[float, float] | None = None, panel_titles: tuple[str, str, str] | None = None, strain_rotation_angle: float = 0.0, + arrow_style: str = "title", **kwargs, ): """Render strain (e_uu, e_vv, e_uv) and rotation panels. @@ -133,17 +134,6 @@ def _roi_compose(norm_vals, color_cm): ax[2].imshow(euv_disp * mask[:, :, np.newaxis]) - def _add_title_arrow(ax, angle_deg, x=0.80, y=1.06, length=0.045, - color="black", lw=1.5): - a = np.deg2rad(angle_deg) - dx, dy = length * np.cos(a), length * np.sin(a) - ax.annotate( - "", - xy=(x + dx, y + dy), xytext=(x - dx, y - dy), - xycoords="axes fraction", - annotation_clip=False, - arrowprops=dict(arrowstyle="<->", color=color, lw=lw), - ) ref_dim = figsize[1] if is_horizontal else figsize[0] fs_threshold = 3.0 fs_scale = min(1.0, max(0.5, ref_dim / fs_threshold)) @@ -154,39 +144,38 @@ def _add_title_arrow(ax, angle_deg, x=0.80, y=1.06, length=0.045, if plot_dilation: panel_titles = ( r"$\epsilon_{uu} + \epsilon_{vv}$", - r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\!\:\searrow$", + r"$\epsilon_{uv}$", "", ) title_arrow_angles = (None, -45 + strain_rotation_angle, None) else: panel_titles = ( - r"$\epsilon_{uu}$ $\updownarrow$", - r"$\epsilon_{vv}$ $\leftrightarrow$", - r"$\epsilon_{uv}$ $\nwarrow\!\!\!\!\!\!\!\!\searrow$", + r"$\epsilon_{uu}$", + r"$\epsilon_{vv}$", + r"$\epsilon_{uv}$", ) title_arrow_angles = (90 + strain_rotation_angle, 0 + strain_rotation_angle, -45 + strain_rotation_angle) else: title_arrow_angles = (None, None, None) - # apply to the strain panels whether panel_titles was defaulted or passed in - for i in range(n_strain): - ax[i].set_title(panel_titles[i], fontsize=title_fs, rotation=title_val) - angle = title_arrow_angles[i] - if angle is not None: - _add_title_arrow(ax[i], angle, color="black") if plot_rotation: norm_rot = Normalize(vmin=rotation_range_degrees[0], vmax=rotation_range_degrees[1]) rot_disp = _roi_compose(norm_rot(rot_deg), cm_rot) if transpose_image: rot_disp = rot_disp.transpose(1,0,2) ax[-1].imshow(rot_disp * mask[:, :, np.newaxis]) - ax[-1].set_title(r"Rotation $\circlearrowleft$", fontsize=title_fs, rotation=title_val) + if arrow_style == "title": + ax[-1].set_title(r"$\phi$ $\circlearrowleft$", fontsize=title_fs, rotation=title_val) + else: + ax[-1].set_title(r"$\phi$", fontsize=title_fs, rotation=title_val) + for a in ax: a.set_xticks([]) a.set_yticks([]) a.set_facecolor("black") a.set_aspect("equal") + a.set_anchor("W" if not is_horizontal else "C") if plot_scalebar: scalebar_kwargs = {} @@ -236,12 +225,13 @@ def _finalize_layout(): except AttributeError: # matplotlib < 3.5 fig.canvas.draw() + need_side_panel = plot_gvecs or arrow_style == "legend" if is_horizontal: # Reserve a bottom band wide enough for the colorbar + its tick labels and # title (fontsize 16) and a right band for the rotation-panel gap; widen the # right band when the g-vector compass is drawn in it. These keep the figure # usable when saved "as is" (no bbox_inches='tight'). - right = 0.78 if plot_gvecs else 0.93 + right = 0.72 if need_side_panel else 0.93 fig.subplots_adjust(left=0.04, right=right, top=0.88, bottom=0.24, wspace=0.05) if plot_rotation: # nudge the rotation panel right for a visual gap from the strain panels; @@ -269,20 +259,23 @@ def _finalize_layout(): else: # Top band for the panel titles, right band for the vertical colorbars + labels. - fig.subplots_adjust(left=0.04, right=0.80, top=0.92, bottom=0.06, hspace=0.15) + right = 0.55 if need_side_panel else 0.80 + fig.subplots_adjust(left=0.04, right=right, top=0.92, bottom=0.06, hspace=0.15) _finalize_layout() cb_orientation = "vertical" b0 = ax[0].get_position() b2 = ax[n_strain - 1].get_position() - strain_cb_pos = [b0.x1 + cb_pad, b2.y0, cb_size, b0.y1 - b2.y0] + title_gap = 0.15 if arrow_style == "title" else cb_pad + cb_x0 = b0.x1 + title_gap + strain_cb_pos = [cb_x0, b2.y0, cb_size, b0.y1 - b2.y0] if plot_rotation: b3 = ax[-1].get_position() rot_cb_h = max(b3.y1 - b3.y0, cb_min_len) rot_cb_cy = 0.5 * (b3.y0 + b3.y1) rot_cb_y0 = min(max(rot_cb_cy - 0.5 * rot_cb_h, 0.0), 0.99 - rot_cb_h) - rot_cb_pos = [b0.x1 + cb_pad, rot_cb_y0, cb_size, rot_cb_h] + rot_cb_pos = [cb_x0, rot_cb_y0, cb_size, rot_cb_h] last_pos = b3 else: rot_cb_pos = None @@ -306,51 +299,129 @@ def _finalize_layout(): cbar2.update_ticks() cbar2.ax.tick_params(labelsize=tick_fs) - if plot_gvecs: - if u_ref is None or v_ref is None: - print("Warning: u_ref and v_ref not found. Call fit_strain() first.") - return fig, ax - - # The compass goes in the reserved margin beside the last panel; clamp its - # right edge to 0.99 so it never spills off the figure when saved "as is". - if is_horizontal: - ref_left = last_pos.x1 + 0.005 - ref_width = min(last_pos.width, 0.99 - ref_left) - ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) - else: - ref_left = min(last_pos.x1 + 0.18, 0.74) - ref_width = min(last_pos.width, 0.99 - ref_left) - ref_ax = fig.add_axes([ref_left, last_pos.y0, ref_width, last_pos.height]) + def _add_title_arrow(ax, angle_deg, gap_pt=4.0, color="black", fontsize=None): + fs = fontsize if fontsize is not None else title_fs + try: + ax.figure.draw_without_rendering() + except AttributeError: # matplotlib < 3.5 + ax.figure.canvas.draw() + renderer = ax.figure.canvas.get_renderer() + bbox_ax = ax.title.get_window_extent(renderer=renderer).transformed(ax.transAxes.inverted()) + y = 0.5 * (bbox_ax.y0 + bbox_ax.y1) + ax.annotate( + "\u2194", + xy=(bbox_ax.x1, y), xycoords=ax.transAxes, + xytext=(gap_pt + fs / 2.0, 0), textcoords="offset points", + ha="center", va="center", + rotation=angle_deg, rotation_mode="anchor", + fontsize=fs, color=color, + annotation_clip=False, + ) + + def _add_arrow_legend(fig, x0, y_top, entries, plot_rotation, fontsize, color="black"): + fig_w_in, fig_h_in = figsize + row_h_in = fontsize * 1.6 / 72.0 + box_w_in = 1.6 + n_rows = len(entries) + 1 + (2 if plot_rotation else 0) + row_h = row_h_in / fig_h_in + box_h = row_h * n_rows + box_w = min(box_w_in / fig_w_in, 0.99 - x0) + leg_ax = fig.add_axes([x0, y_top - box_h, box_w, box_h]) + leg_ax.set_xlim(0, 1) + leg_ax.set_ylim(0, 1) + leg_ax.axis("off") + + dy = 1.0 / n_rows + y = 1.0 - dy / 2 + leg_ax.text(0.0, y, "Strain", fontsize=fontsize, fontweight="bold", ha="left", va="center") + for label, angle_deg in entries: + y -= dy + leg_ax.text(0.15, y, "\u2194", rotation=angle_deg, rotation_mode="anchor", + ha="center", va="center", fontsize=fontsize, color=color) + leg_ax.text(0.32, y, label, fontsize=fontsize, ha="left", va="center") + + if plot_rotation: + y -= dy + leg_ax.text(0.0, y, "Rotation", fontsize=fontsize, fontweight="bold", ha="left", va="center") + y -= dy + leg_ax.text(0.15, y, "\u21ba", fontsize=fontsize, ha="center", va="center") + leg_ax.text(0.32, y, r"$\phi$", fontsize=fontsize, ha="left", va="center") + return box_h + + for i in range(n_strain): + ax[i].set_title(panel_titles[i], fontsize=title_fs, rotation=title_val) + angle = title_arrow_angles[i] + if arrow_style == "title" and angle is not None: + _add_title_arrow(ax[i], angle, color="black") + + if is_horizontal: + _finalize_layout() + renderer = fig.canvas.get_renderer() + panel_edge = last_pos.x1 + if plot_rotation: + title_edge = ax[-1].title.get_window_extent(renderer=renderer).transformed(fig.transFigure.inverted()).x1 + panel_edge = max(panel_edge, title_edge) + margin_x0 = panel_edge + 0.03 + else: + _finalize_layout() + renderer = fig.canvas.get_renderer() + margin_x0 = cax1.get_tightbbox(renderer).transformed(fig.transFigure.inverted()).x1 + 0.02 + if plot_rotation and rot_cb_pos is not None: + rot_edge = cax2.get_tightbbox(renderer).transformed(fig.transFigure.inverted()).x1 + margin_x0 = max(margin_x0, rot_edge + 0.02) + top_bound = 0.88 if is_horizontal else 0.92 + bottom_bound = 0.24 if is_horizontal else 0.06 + center_y = 0.5 * (top_bound + bottom_bound) + + entries = [] + leg_h = 0.0 + if arrow_style == "legend": + entries = [(panel_titles[i], title_arrow_angles[i]) for i in range(n_strain) + if title_arrow_angles[i] is not None] + n_rows = len(entries) + 1 + (2 if plot_rotation else 0) + leg_h = (title_fs * 1.6 / 72.0 / figsize[1]) * n_rows + + show_gvecs = plot_gvecs and u_ref is not None and v_ref is not None + if plot_gvecs and not show_gvecs: + print("Warning: u_ref and v_ref not found. Call fit_strain() first.") + fig_aspect = figsize[0] / figsize[1] + gvec_w = min(0.99 - margin_x0, 0.15) if show_gvecs else 0.0 + gvec_h = gvec_w * fig_aspect if show_gvecs else 0.0 + + gap = 0.03 if (leg_h > 0 and gvec_h > 0) else 0.0 + total_needed = leg_h + gap + gvec_h + available_span = top_bound - bottom_bound + side_scale = min(1.0, available_span / total_needed) if total_needed > 0 else 1.0 + leg_h *= side_scale + gvec_w *= side_scale + gvec_h *= side_scale + legend_fontsize = title_fs * side_scale + + y_top = center_y + (leg_h + gap * side_scale + gvec_h) / 2.0 + + if leg_h > 0: + _add_arrow_legend(fig, margin_x0, y_top, entries, plot_rotation=plot_rotation, + fontsize=legend_fontsize, color="black") + y_top -= leg_h + gap * side_scale + + if show_gvecs: + ref_ax = fig.add_axes([margin_x0, y_top - gvec_h, gvec_w, gvec_h]) ref_ax.set_xlim(-1.5, 1.5) ref_ax.set_ylim(-1.5, 1.5) ref_ax.set_aspect("equal") ref_ax.axis("off") u_norm = u_ref / np.linalg.norm(u_ref) v_norm = v_ref / np.linalg.norm(v_ref) - u_row, u_col = u_norm v_row, v_col = v_norm arrow_props_ref = dict(arrowstyle="->", lw=3, mutation_scale=25) - - u_arrow = FancyArrowPatch( - (0, 0), (u_col, -u_row), - color="darkred", **arrow_props_ref - ) - ref_ax.add_patch(u_arrow) - - v_arrow = FancyArrowPatch( - (0, 0), (v_col, -v_row), - color="darkblue", **arrow_props_ref - ) - ref_ax.add_patch(v_arrow) - ref_ax.text(u_col * 1.3, -u_row * 1.3, r"$\mathbf{g}_{1}$", - fontsize=14, fontweight="bold", color="darkred", - ha="center", va="center") - - ref_ax.text(v_col * 1.3, -v_row * 1.3, r"$\mathbf{g}_{2}$", - fontsize=14, fontweight="bold", color="darkblue", - ha="center", va="center") + ref_ax.add_patch(FancyArrowPatch((0, 0), (u_col, -u_row), color="darkred", **arrow_props_ref)) + ref_ax.add_patch(FancyArrowPatch((0, 0), (v_col, -v_row), color="darkblue", **arrow_props_ref)) + ref_ax.text(u_col * 1.3, -u_row * 1.3, r"$\mathbf{g}_{1}$", fontsize=14, fontweight="bold", + color="darkred", ha="center", va="center") + ref_ax.text(v_col * 1.3, -v_row * 1.3, r"$\mathbf{g}_{2}$", fontsize=14, fontweight="bold", + color="darkblue", ha="center", va="center") return fig, ax From 01211494c564271b75e11373c3092ff45fe21206 Mon Sep 17 00:00:00 2001 From: Colin Ophus Date: Wed, 29 Jul 2026 14:12:48 -0700 Subject: [PATCH 111/113] Add BraggVectors.correct_peak_origins for descan calibration One-call utility that shifts the detected peak coordinates so every scan position shares a common origin: subtracts the supplied per-position diffraction origins (e.g. the plane-fitted CoM of the central beam) and adds back a reference origin (default: the scan mean), then recomputes the BVM. The raw data is never resampled -- the measurements are calibrated, not the images. Returns a corrected copy by default so repeated calls (re-running a notebook cell) never double-apply the shift; the shift itself is applied vectorized on the flat peak table via Vector.flatten()/set_flattened(). Co-Authored-By: Claude Fable 5 --- src/quantem/diffraction/bragg_vectors.py | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/quantem/diffraction/bragg_vectors.py b/src/quantem/diffraction/bragg_vectors.py index 6503f56ad..e241e58ad 100644 --- a/src/quantem/diffraction/bragg_vectors.py +++ b/src/quantem/diffraction/bragg_vectors.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy as _copy from pathlib import Path from typing import Any, Literal, Sequence, Union @@ -479,6 +480,78 @@ def detect_disks( self.compute_bvm() return peaks + def correct_peak_origins( + self, + origins: NDArray, + origin_ref: NDArray | tuple[float, float] | None = None, + *, + inplace: bool = False, + ) -> "BraggVectors": + """Shift the detected peak coordinates so all positions share one origin. + + Subtracts each scan position's measured diffraction origin (e.g. the + plane-fitted center-of-mass of the central beam -- the descan) from its + peaks and adds back a common reference ``origin_ref``, so the peak + coordinates from every position live in a single detector frame. The + diffraction data itself is untouched: this calibrates the measurements, + not the images. The Bragg vector map is recomputed from the corrected + peaks. + + By default a corrected *copy* of the workflow is returned and ``self`` + keeps the raw detections, so calling this repeatedly (e.g. re-running a + notebook cell) never double-applies the shift. + + Parameters + ---------- + origins : np.ndarray + ``(scan_row, scan_col, 2)`` per-position diffraction origins in + detector pixels (row, col). + origin_ref : array-like of float, optional + ``(row, col)`` common origin the corrected peaks are referred to. + Defaults to the scan-mean of ``origins``. + inplace : bool, default=False + If ``True``, correct ``self`` instead of returning a corrected copy. + + Returns + ------- + BraggVectors + The workflow holding the corrected peaks (a new instance unless + ``inplace=True``); its :attr:`bvm` is recomputed. + """ + if self.peaks is None: + raise ValueError("Run detect_disks() before correct_peak_origins().") + scan_shape = tuple(int(v) for v in self.dataset.shape[:2]) + origins = np.asarray(origins, dtype=float) + if origins.shape != scan_shape + (2,): + raise ValueError(f"origins must have shape {scan_shape + (2,)}, got {origins.shape}.") + if origin_ref is None: + origin_ref = origins.mean(axis=(0, 1)) + origin_ref = np.asarray(origin_ref, dtype=float).reshape(2) + + if inplace: + bv = self + else: + bv = type(self)(dataset=self.dataset, device=self.device, _token=type(self)._token) + bv._template = self._template + bv._template_ft = self._template_ft + bv.metadata = _copy.deepcopy(self.metadata) + bv.peaks = self.peaks.copy() + + peaks = bv.peaks + # rowwise transform on the flat peak table: one shift per scan cell, + # repeated per detected peak (cells and shifts share raster order) + flat = peaks.flatten() + counts = np.asarray(peaks.row_counts(), dtype=int) + shifts = np.repeat(origin_ref[None, :] - origins.reshape(-1, 2), counts, axis=0) + flat[:, :2] += shifts + peaks.set_flattened(flat) + + bv.metadata["origin_correction"] = { + "origin_ref": (float(origin_ref[0]), float(origin_ref[1])), + } + bv.compute_bvm() + return bv + def compute_bvm(self, sampling: float = 1.0) -> Dataset2d: """Accumulate all detected peaks into a Bragg vector map (intensity histogram). From aa7cfdea6717394b1b85086254ee6e9f53a550b5 Mon Sep 17 00:00:00 2001 From: Colin Ophus Date: Wed, 29 Jul 2026 21:07:56 -0700 Subject: [PATCH 112/113] Add origins-based alignment for the model-fitting mean CBED target ModelDiffraction.preprocess(origins=...) builds a descan-free mean CBED: each pattern is shifted by (scan-mean origin - its measured origin), e.g. from the plane-fitted CoM of the central beam, before averaging. Only the image_ref fit target is built from shifted copies -- the raw data is never resampled. The existing align=True cross-correlation self-alignment stays available but is unreliable when diffraction contrast varies across the scan (on the LFP tutorial data it produced +/-68 px shifts against a true +/-5 px descan, chasing pattern content instead of the beam); origins takes precedence when both are given. Co-Authored-By: Claude Fable 5 --- src/quantem/diffraction/model_fitting.py | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 8274224c4..6a295b6fe 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -324,6 +324,7 @@ def preprocess( self, *, align: bool = False, + origins: Any = None, edge_blend: float = 8.0, upsample_factor: int = 32, max_shift: float | None = None, @@ -333,6 +334,27 @@ def preprocess( rows=None, cols = None, ) -> "ModelDiffraction": + """Build the mean-pattern fit target ``image_ref`` (optionally descan-free). + + By default ``image_ref`` is the plain mean over the (optionally + subsetted) scan. With probe descan, that mean is smeared; two ways to + build a sharp mean CBED instead: + + - ``origins``: pass the measured per-position diffraction origins + (``(scan_row, scan_col, 2)``, e.g. the plane-fitted CoM of the + central beam). Each pattern is shifted by ``mean(origins) - + origins[r, c]`` before averaging, so the mean CBED is centered on + the scan-mean beam position. Deterministic and robust -- preferred + when the descan has been measured. + - ``align=True``: self-align the patterns by cross-correlation to a + running mean. Works when the patterns are similar across the scan, + but can chase changing diffraction contrast; ignored when + ``origins`` is given. + + Only ``image_ref`` is built from shifted copies -- ``dataset.array`` + itself is never resampled. The applied shifts are stored in + :attr:`preprocess_shifts`. + """ arr = np.asarray(self.dataset.array) self.mask = np.ones(arr.shape[:2]) @@ -392,6 +414,29 @@ def preprocess( stack = arr.reshape((-1, h, w)).astype(np.float32, copy=False) n = stack.shape[0] + + if origins is not None: + origins = np.asarray(origins, dtype=float) + scan_shape = tuple(int(v) for v in self.dataset.shape[:2]) + if origins.shape != scan_shape + (2,): + raise ValueError( + f"origins must have shape {scan_shape + (2,)}, got {origins.shape}." + ) + origins_sub = origins[np.ix_(rows, cols)].reshape(-1, 2) + shifts = (origins_sub.mean(axis=0) - origins_sub).astype(np.float32) + aligned = np.empty_like(stack, dtype=np.float32) + for i in range(n): + aligned[i] = ndi_shift( + stack[i], + shift=(float(shifts[i, 0]), float(shifts[i, 1])), + order=int(shift_order), + mode="nearest", + prefilter=False, + ) + self.image_ref = np.mean(aligned, axis=0) + self.preprocess_shifts = shifts.reshape(self.index_shape + (2,)) + return self + if not align or n <= 1: self.image_ref = np.mean(stack, axis=0) self.preprocess_shifts = None From 5e660145776f90634fdeff21caac0a585980f3bd Mon Sep 17 00:00:00 2001 From: mitis1 Date: Wed, 29 Jul 2026 22:13:01 -0700 Subject: [PATCH 113/113] fixing subpixel dft fit for cepstral, batched mask implementation for model fitting, and optimizer batched implementation --- src/quantem/diffraction/model_fitting.py | 230 +++++++++++++++--- .../diffraction/strain_autocorrelation.py | 21 +- .../diffraction/strain_visualization.py | 2 +- 3 files changed, 209 insertions(+), 44 deletions(-) diff --git a/src/quantem/diffraction/model_fitting.py b/src/quantem/diffraction/model_fitting.py index 5e23135e8..c5f940a4e 100644 --- a/src/quantem/diffraction/model_fitting.py +++ b/src/quantem/diffraction/model_fitting.py @@ -20,6 +20,7 @@ ) from quantem.core.fitting.diffraction import DiskTemplate, SyntheticDiskLattice from quantem.core.io.serialize import AutoSerialize +from quantem.core.ml.optimizer_mixin import OptimizerParams from quantem.core.utils.imaging_utils import cross_correlation_shift from quantem.diffraction.model_fitting_visualizations import ModelDiffractionVisualizations from quantem.diffraction.strain import StrainMap @@ -830,6 +831,12 @@ def fit_individual_diffraction_pattern_batched( loss_fn = self.loss_fn + fidelity_mask: torch.Tensor | None = None + fidelity_valid_count: torch.Tensor | None = None + if ctx.mask is not None: + fidelity_mask = ctx.mask.bool().to(ctx.device) + fidelity_valid_count = fidelity_mask.sum().clamp(min=1).to(ctx.dtype) + total_steps = len(positions) * n_steps pbar = tqdm(total=total_steps, desc="Fit individual (batched)", disable=not progress) @@ -868,11 +875,12 @@ def fit_individual_diffraction_pattern_batched( stacked = plan.build_stacked_params(B) - adam_state: dict[str, dict[str, torch.Tensor]] = { - name: { - "m": torch.zeros_like(p.detach()), - "v": torch.zeros_like(p.detach()), - } + opt_state: dict[str, dict[str, torch.Tensor]] = { + name: ( + {"m": torch.zeros_like(p.detach()), "v": torch.zeros_like(p.detach())} + if plan.opt_type in ("adam", "adamw") + else {"buf": torch.zeros_like(p.detach())} + ) for name, p in stacked.items() } @@ -903,17 +911,37 @@ def fit_individual_diffraction_pattern_batched( if isinstance(loss_fn, SqrtMSELoss): gamma = float(loss_fn.gamma) eps = 1.0 - pred_min = pred.amin(dim=(1, 2), keepdim=True) - tgt_min = targets.amin(dim=(1, 2), keepdim=True) + if fidelity_mask is not None: + pred_min = pred.masked_fill(~fidelity_mask, float("inf")).amin(dim=(1, 2), keepdim=True) + tgt_min = targets.masked_fill(~fidelity_mask, float("inf")).amin(dim=(1, 2), keepdim=True) + else: + pred_min = pred.amin(dim=(1, 2), keepdim=True) + tgt_min = targets.amin(dim=(1, 2), keepdim=True) pred_mod = (pred - pred_min + eps) ** gamma tgt_mod = (targets - tgt_min + eps) ** gamma - per_sample_loss = ((pred_mod - tgt_mod) ** 2).mean(dim=(1, 2)) + sq = (pred_mod - tgt_mod) ** 2 + if fidelity_mask is not None: + per_sample_loss = (sq * fidelity_mask).sum(dim=(1, 2)) / fidelity_valid_count + else: + per_sample_loss = sq.mean(dim=(1, 2)) elif isinstance(loss_fn, LogMSELoss): - per_sample_loss = ((torch.log1p(pred) - torch.log1p(targets)) ** 2).mean(dim=(1, 2)) + sq = (torch.log1p(pred) - torch.log1p(targets)) ** 2 + if fidelity_mask is not None: + per_sample_loss = (sq * fidelity_mask).sum(dim=(1, 2)) / fidelity_valid_count + else: + per_sample_loss = sq.mean(dim=(1, 2)) elif isinstance(loss_fn, torch.nn.L1Loss): - per_sample_loss = diff2.abs().mean(dim=(1, 2)) + ad = diff2.abs() + if fidelity_mask is not None: + per_sample_loss = (ad * fidelity_mask).sum(dim=(1, 2)) / fidelity_valid_count + else: + per_sample_loss = ad.mean(dim=(1, 2)) else: - per_sample_loss = (diff2 * diff2).mean(dim=(1, 2)) + sq = diff2 * diff2 + if fidelity_mask is not None: + per_sample_loss = (sq * fidelity_mask).sum(dim=(1, 2)) / fidelity_valid_count + else: + per_sample_loss = sq.mean(dim=(1, 2)) # Add per-sample soft constraint loss. if float(constraint_weight) != 0.0: @@ -940,7 +968,10 @@ def fit_individual_diffraction_pattern_batched( t = step + 1 current_lrs = plan.lr_at_step(t, int(n_steps)) - _adam_step_inplace(stacked, tuple(grads_list), adam_state, current_lrs, chunk_trainable, t) + _optimizer_step_inplace( + plan.opt_type, stacked, tuple(grads_list), opt_state, + current_lrs, plan.opt_hparams, chunk_trainable, t, + ) with torch.no_grad(): plan.apply_hard_constraints(stacked, skip_keys=hard_skip_keys) @@ -983,6 +1014,7 @@ def get_individual_uv_vectors(self) -> "ModelDiffraction": if pos_state is None: self.u_array[r,c,:] = None self.v_array[r,c,:] = None + continue for key in pos_state.keys(): key_parts = key.split('.') if(key_parts[-1] == 'u_row'): @@ -1264,52 +1296,156 @@ def _lr_for_component( return float(optimizer_params[class_name].get("lr", component.DEFAULT_LR)) return float(component.DEFAULT_LR) + +def _opt_spec_for_component( + component: RenderComponent, + component_idx: int, + optimizer_params: dict[str, Any], + model: AdditiveRenderModel, +): + """Resolve the full optimizer spec (type + hyperparameters) for one component. + + Mirrors ``_lr_for_component``'s name-then-class lookup, but returns the parsed + ``OptimizerParams`` spec instead of just the learning rate, so batched fitting can + honor a requested optimizer ``type`` (e.g. ``"sgd"``) instead of silently always + running Adam. + """ + name = model._component_constraint_name(component, component_idx) + class_name = component.__class__.__name__ + if name in optimizer_params: + d = dict(optimizer_params[name]) + elif class_name in optimizer_params: + d = dict(optimizer_params[class_name]) + else: + return OptimizerParams.Adam(lr=component.DEFAULT_LR) + d.setdefault("type", "adam") + d.setdefault("lr", component.DEFAULT_LR) + return OptimizerParams.parse_dict(d) + + +def _masked(t: torch.Tensor, mask_expanded: torch.Tensor | None) -> torch.Tensor: + return t if mask_expanded is None else t * mask_expanded + + def _adam_step_inplace( stacked: dict[str, torch.Tensor], grads: tuple[torch.Tensor, ...], adam_state: dict[str, dict[str, torch.Tensor]], lrs: dict[str, float], - chunk_trainable: dict[str, torch.Tensor], # NEW parameter + hparams: dict[str, dict[str, Any]], + chunk_trainable: dict[str, torch.Tensor], t: int, - beta1: float = 0.9, - beta2: float = 0.999, - eps: float = 1e-8, + decoupled_wd: bool = False, ) -> None: """ - In-place Adam step with per-sample trainability support. - + In-place Adam/AdamW step with per-sample trainability support. + Frozen samples (mask=False) will: 1. Not accumulate gradient statistics in moments - 2. Not receive parameter updates + 2. Not receive parameter updates -- including from weight decay, which is + explicitly masked too (unlike a plain zeroed gradient, weight decay would + otherwise still pull frozen samples toward zero every step). """ - bias1 = 1.0 - beta1 ** t - bias2 = 1.0 - beta2 ** t - with torch.no_grad(): for (name, p), g in zip(stacked.items(), grads): if g is None: continue - + + hp = hparams.get(name, {}) + beta1, beta2 = hp.get("betas", (0.9, 0.999)) + eps = hp.get("eps", 1e-8) + wd = hp.get("weight_decay", 0.0) + st = adam_state[name] mask_b = chunk_trainable.get(name) - + mask_expanded = None if mask_b is not None and not bool(mask_b.all()): view_shape = (g.shape[0],) + (1,) * (g.ndim - 1) mask_expanded = mask_b.view(view_shape).to(dtype=g.dtype) - g_masked = g * mask_expanded - - st["m"].mul_(beta1).add_(g_masked, alpha=1.0 - beta1) - st["v"].mul_(beta2).addcmul_(g_masked, g_masked, value=1.0 - beta2) - else: - st["m"].mul_(beta1).add_(g, alpha=1.0 - beta1) - st["v"].mul_(beta2).addcmul_(g, g, value=1.0 - beta2) - - m_hat = st["m"] / bias1 - v_hat = st["v"] / bias2 - + + g_eff = _masked(g, mask_expanded) + if wd != 0: + if decoupled_wd: + lr_ = float(lrs.get(name, 1e-2)) + p.data.sub_(_masked(lr_ * wd * p.data, mask_expanded)) + else: + g_eff = g_eff + _masked(wd * p.data, mask_expanded) + + st["m"].mul_(beta1).add_(g_eff, alpha=1.0 - beta1) + st["v"].mul_(beta2).addcmul_(g_eff, g_eff, value=1.0 - beta2) + + m_hat = st["m"] / (1.0 - beta1 ** t) + v_hat = st["v"] / (1.0 - beta2 ** t) + lr = float(lrs.get(name, 1e-2)) p.data.addcdiv_(m_hat, v_hat.sqrt().add_(eps), value=-lr) + +def _sgd_step_inplace( + stacked: dict[str, torch.Tensor], + grads: tuple[torch.Tensor, ...], + sgd_state: dict[str, dict[str, torch.Tensor]], + lrs: dict[str, float], + hparams: dict[str, dict[str, Any]], + chunk_trainable: dict[str, torch.Tensor], + t: int, +) -> None: + """In-place SGD step (with optional momentum/dampening/nesterov/weight_decay), + matching ``torch.optim.SGD`` exactly, with the same per-sample trainability + masking as :func:`_adam_step_inplace` (including for the weight-decay term).""" + with torch.no_grad(): + for (name, p), g in zip(stacked.items(), grads): + if g is None: + continue + + hp = hparams.get(name, {}) + momentum = hp.get("momentum", 0.0) + dampening = hp.get("dampening", 0.0) + wd = hp.get("weight_decay", 0.0) + nesterov = hp.get("nesterov", False) + + mask_b = chunk_trainable.get(name) + mask_expanded = None + if mask_b is not None and not bool(mask_b.all()): + view_shape = (g.shape[0],) + (1,) * (g.ndim - 1) + mask_expanded = mask_b.view(view_shape).to(dtype=g.dtype) + + d_p = _masked(g, mask_expanded) + if wd != 0: + d_p = d_p + _masked(wd * p.data, mask_expanded) + if momentum != 0: + buf = sgd_state[name]["buf"] + if t == 1: + buf.copy_(d_p) + else: + buf.mul_(momentum).add_(d_p, alpha=1.0 - dampening) + d_p = d_p.add(buf, alpha=momentum) if nesterov else buf + + lr = float(lrs.get(name, 1e-2)) + p.data.add_(d_p, alpha=-lr) + + +def _optimizer_step_inplace( + opt_type: str, + stacked: dict[str, torch.Tensor], + grads: tuple[torch.Tensor, ...], + opt_state: dict[str, dict[str, torch.Tensor]], + lrs: dict[str, float], + hparams: dict[str, dict[str, Any]], + chunk_trainable: dict[str, torch.Tensor], + t: int, +) -> None: + """Dispatch a single in-place optimizer step for the batched fit loop.""" + if opt_type in ("adam", "adamw"): + _adam_step_inplace( + stacked, grads, opt_state, lrs, hparams, chunk_trainable, t, + decoupled_wd=(opt_type == "adamw"), + ) + elif opt_type == "sgd": + _sgd_step_inplace(stacked, grads, opt_state, lrs, hparams, chunk_trainable, t) + else: + raise NotImplementedError(f"Batched fit does not support optimizer type '{opt_type}'.") + class _BatchedPlan: """Resolved layout for the batched per-pattern fit: component refs, lrs, and helpers.""" @@ -1320,6 +1456,8 @@ def __init__(self) -> None: self.disk_idx: int | None = None self.dcbg_idx: int | None = None self.lrs: dict[str, float] = {} + self.opt_type: str = "adam" + self.opt_hparams: dict[str, dict[str, Any]] = {} self.scheduler_specs: dict[str, dict[str, Any]] = {} self.component_keys: dict[str, list[str]] = {} self.lats: list[SyntheticDiskLattice] = [] @@ -1483,6 +1621,28 @@ def from_model( # Default schedulers: constant LR for every key self.scheduler_specs = {k: {"type": "none"} for k in self.lrs} + + # Resolve optimizer type/hyperparameters. All components must agree on the + # optimizer class -- mirroring OptimizerMixin.set_optimizer's own constraint -- + # since a single manual step function runs once per training step over every + # stacked parameter. Keys with no explicit spec fall back to that type's + # library defaults inside _adam_step_inplace/_sgd_step_inplace (e.g. + # "origin.coords", which never has its own optimizer_params entry). + spec_types: set[type] = set() + for idx, comp in enumerate(components_list): + spec = _opt_spec_for_component(comp, idx, optimizer_params, model) + spec_types.add(type(spec)) + canonical_name = model._component_constraint_name(comp, idx) + for key in self.component_keys.get(canonical_name, []): + self.opt_hparams[key] = spec.params() + if len(spec_types) > 1: + raise ValueError( + "All components must use the same optimizer type for batched fitting; " + f"got {sorted(t.__name__ for t in spec_types)}." + ) + if spec_types: + self.opt_type = next(iter(spec_types))._name + return self # ... (keep set_scheduler_params, lr_at_step, batched_constraint_loss, resolve_component_keys as-is) diff --git a/src/quantem/diffraction/strain_autocorrelation.py b/src/quantem/diffraction/strain_autocorrelation.py index 5d939f9fb..5e63e69cb 100644 --- a/src/quantem/diffraction/strain_autocorrelation.py +++ b/src/quantem/diffraction/strain_autocorrelation.py @@ -984,8 +984,8 @@ def _fit_lattice_vectors_batched( self._gpu_cache = None use_cache = False else: - self._gpu_cache = None - use_cache = False + # self._gpu_cache = None + use_cache = True if refine_all_peaks: # Precompute the fixed pieces of the per-position all-peaks fit (these do not @@ -1275,7 +1275,7 @@ def calculate_strain_map( mask=mask, ds_sampling=ds_sampling, ds_units=ds_units, - q_to_r_rotation_ccw_deg = self.metadata['q_to_r_rotation_ccw_deg '], + q_to_r_rotation_ccw_deg = self.metadata['q_to_r_rotation_ccw_deg'], q_transpose = self.metadata['q_transpose'], ) @@ -1698,8 +1698,13 @@ def _refine_peak_subpixel_dft( F = np.fft.fft2(np.fft.fftshift(im)) up = upsample - du = int(np.fix(np.ceil(1.5 * up))) - patch = np.abs(dft_upsample(F, up=up, shift=(r0, c0))) + H, W = im.shape + du = int(np.floor(np.ceil(1.5 * up) / 2.0)) + off_r = -(-H // 2) # ceil(H/2) + off_c = -(-W // 2) # ceil(W/2) + + shift = (du + up * (r0 - off_r), du + up * (c0 - off_c)) + patch = np.abs(dft_upsample(F, up=up, shift=shift)) patch = np.asarray(patch, dtype=float) i0, j0 = np.unravel_index(np.argmax(patch), patch.shape) @@ -1715,9 +1720,9 @@ def _refine_peak_subpixel_dft( dj = _parabolic_vertex_delta(row[0], row[1], row[2]) else: dj = 0.0 - M, N = im.shape - dr = ((float(i0) - du + di)) / up - dc = ((float(j0) - du + dj)) / up + + dr = ((du - float(i0) - di)) / up + dc = ((du - float(j0) - dj)) / up return r0 + dr, c0 + dc diff --git a/src/quantem/diffraction/strain_visualization.py b/src/quantem/diffraction/strain_visualization.py index 8ad0888f7..819891cb7 100644 --- a/src/quantem/diffraction/strain_visualization.py +++ b/src/quantem/diffraction/strain_visualization.py @@ -154,7 +154,7 @@ def _roi_compose(norm_vals, color_cm): r"$\epsilon_{vv}$", r"$\epsilon_{uv}$", ) - title_arrow_angles = (90 + strain_rotation_angle, 0 + strain_rotation_angle, -45 + strain_rotation_angle) + title_arrow_angles = (0 + strain_rotation_angle, 90 + strain_rotation_angle, -45 + strain_rotation_angle) else: title_arrow_angles = (None, None, None)