diff --git a/.gitignore b/.gitignore index b9cf46296..0af80933e 100644 --- a/.gitignore +++ b/.gitignore @@ -155,6 +155,7 @@ settings.json **/datasets/nnunet_raw/** **/datasets/nnunet_preprocessed/** **/datasets/cifar_partitioned_data/** +**/datasets/rxrx1/rxrx1_v1.0/** # logs diff --git a/fl4health/datasets/rxrx1/README.md b/fl4health/datasets/rxrx1/README.md new file mode 100644 index 000000000..e73637067 --- /dev/null +++ b/fl4health/datasets/rxrx1/README.md @@ -0,0 +1,28 @@ +# Fluorescent Microscopy Images Dataset Download and Preprocessing + +This repository provides a set of scripts to download and preprocess RxRx1 datasets for use in federated learning experiments. This dataset include 6-channel fluorescent microscopy images of cells treated with different compounds. The dataset is provided by Recursion Pharmaceuticals and is available on the [RxRx1 Kaggle page](https://www.rxrx.ai/rxrx1). + +## Getting Started + +To start using these datasets, follow the steps below. + + +### Downloading the Datasets +To use the datasets for this project, run the provided shell script to download and unzip the required files. + +```sh +sh fl4health/datasets/rxrx1/download.sh +``` + + +### Preprocessing the Datasets + +Once the datasets are downloaded, preprocess them to generate the required metadata file and prepare the training and testing tensors for each client participating in the federated learning experiments. The following command preprocesses the RxRx1 datasets: + +```sh +python fl4health/datasets/rxrx1/preprocess.py --data_dir +``` + +### Using the Datasets + +After preprocessing, the datasets are ready to be used in the federated learning settings. For examples please refer to the [Rxrx1 experiments](research/rxrx1) directory. diff --git a/fl4health/datasets/rxrx1/download.sh b/fl4health/datasets/rxrx1/download.sh new file mode 100644 index 000000000..c6f9acf03 --- /dev/null +++ b/fl4health/datasets/rxrx1/download.sh @@ -0,0 +1,45 @@ +echo "RxRx1 dataset download." +# Define the URL and the target directory and file name +URL="https://storage.googleapis.com/rxrx/rxrx1" +METADATA_URL="https://storage.googleapis.com/rxrx/rxrx1/rxrx1-metadata.zip" +DIRECTORY="/projects/fl4health/datasets/rxrx1_v1.0/" +IMAGE_FILE_NAME="rxrx1-images.zip" +METADATA_FILE="rxrx1-metadata.zip" +IMAGE_FILE_PATH=${DIRECTORY}${IMAGE_FILE_NAME} +METADATA_FILE_PATH=${DIRECTORY}${METADATA_FILE} + +# Create the directory if it doesn't exist +mkdir -p "$DIRECTORY" + +# Check if the file already exists +if [ -f "$IMAGE_FILE_PATH" ]; then + echo "File $IMAGE_FILE already exists. No download needed." +else + echo "Downloading $IMAGE_FILE_NAME" + wget -O "$IMAGE_FILE_PATH" "$URL/$IMAGE_FILE_NAME" + if [ $? -eq 0 ]; then + echo "Download completed successfully." + else + echo "Download failed." + fi +fi + +mkdir -p ${DIRECTORY}images/ +unzip ${IMAGE_FILE_PATH} -d ${DIRECTORY}images/ + +# Check if the file already exists +if [ -f "$METADATA_FILE_PATH" ]; then + echo "File $METADATA_FILE already exists. No download needed." +else + echo "Downloading $METADATA_FILE" + wget -O "$METADATA_FILE_PATH" "$URL/$METADATA_FILE" + if [ $? -eq 0 ]; then + echo "Download completed successfully." + else + echo "Download failed." + fi +fi + +unzip ${METADATA_FILE_PATH} -d ${DIRECTORY} + +echo "Download completed." diff --git a/fl4health/datasets/rxrx1/load_data.py b/fl4health/datasets/rxrx1/load_data.py new file mode 100644 index 000000000..718520e0f --- /dev/null +++ b/fl4health/datasets/rxrx1/load_data.py @@ -0,0 +1,170 @@ +import copy +import os +import pickle +from collections import defaultdict +from collections.abc import Callable +from logging import INFO +from pathlib import Path + +import numpy as np +import pandas as pd +import torch +from flwr.common.logger import log +from torch.utils.data import DataLoader, Subset + +from fl4health.utils.dataset import TensorDataset + + +def construct_rxrx1_tensor_dataset( + metadata: pd.DataFrame, + data_path: Path, + client_num: int, + dataset_type: str, + transform: Callable | None = None, +) -> tuple[TensorDataset, dict[int, int]]: + """ + Construct a TensorDataset for rxrx1 data. + + Args: + metadata (DataFrame): A DataFrame containing image metadata. + data_path (Path): Root directory which the image data should be loaded. + client_num (int): Client number to load data for. + dataset_type (str): 'train' or 'test' to specify dataset type. + transform (Callable | None): Transformation function to apply to the images. Defaults to None. + + Returns: + tuple[TensorDataset, dict[int, int]]: A TensorDataset containing the processed images and label map. + + """ + + label_map = {label: idx for idx, label in enumerate(sorted(metadata["sirna_id"].unique()))} + original_label_map = {new_label: original_label for original_label, new_label in label_map.items()} + metadata = metadata[metadata["dataset"] == dataset_type] + targets_tensor = torch.Tensor(list(metadata["sirna_id"].map(label_map))).type(torch.long) + data_list = [] + for index in range(len(targets_tensor)): + with open( + os.path.join(data_path, f"clients/{dataset_type}_data_{client_num+1}/image_{index}.pkl"), "rb" + ) as file: + data_list.append(torch.Tensor(pickle.load(file)).unsqueeze(0)) + data_tensor = torch.cat(data_list) + return TensorDataset(data_tensor, targets_tensor, transform), original_label_map + + +def label_frequency(dataset: TensorDataset | Subset, original_label_map: dict[int, int]) -> None: + """ + Prints the frequency of each label in the dataset. + + Args: + dataset (TensorDataset | Subset): The dataset to analyze. + original_label_map (dict[int, int]): A mapping of the original labels to their new labels. + + """ + # Extract metadata and label map + if isinstance(dataset, TensorDataset): + targets = dataset.targets + elif isinstance(dataset, Subset): + assert isinstance(dataset.dataset, TensorDataset), "Subset dataset must be an TensorDataset instance." + targets = dataset.dataset.targets + else: + raise TypeError("Dataset must be of type TensorDataset or Subset containing an TensorDataset.") + + # Count label frequencies + label_to_indices = defaultdict(list) + assert isinstance(targets, torch.Tensor) + for idx, label in enumerate(targets): # Assumes dataset[idx] returns (data, label) + label_to_indices[label].append(idx) + + # Print frequency of labels their names + for label, count in label_to_indices.items(): + assert isinstance(label, int) + original_label = original_label_map.get(label) + log(INFO, f"Label {label} (original: {original_label}): {len(count)} samples") + + +def create_splits( + dataset: TensorDataset, seed: int | None = None, train_fraction: float = 0.8 +) -> tuple[list[int], list[int]]: + """ + Splits the dataset into training and validation sets. + + Args: + dataset (Dataset): The dataset to split. + train_fraction (float): Fraction of data to use for training. + + Returns: + Tuple: (train_dataset, val_dataset) + """ + + # Group indices by label + label_to_indices = defaultdict(list) + assert isinstance(dataset.targets, torch.Tensor) + for idx, label in enumerate(dataset.targets): # Assumes dataset[idx] returns (data, label) + label_to_indices[label.item()].append(idx) + + # Stratified splitting + train_indices, val_indices = [], [] + for label, indices in label_to_indices.items(): + if seed is not None: + np_generator = np.random.default_rng(seed) + np_generator.shuffle(indices) + else: + np.random.shuffle(indices) + split_point = int(len(indices) * train_fraction) + train_indices.extend(indices[:split_point]) + val_indices.extend(indices[split_point:]) + if len(val_indices) == 0: + log(INFO, "Warning: Validation set is empty. Consider changing the train_fraction parameter.") + + return train_indices, val_indices + + +def load_rxrx1_data( + data_path: Path, + client_num: int, + batch_size: int, + seed: int | None = None, + train_val_split: float = 0.8, + num_workers: int = 0, +) -> tuple[DataLoader, DataLoader, dict[str, int]]: + + # Read the CSV file + data = pd.read_csv(f"{data_path}/clients/meta_data_{client_num+1}.csv") + + dataset, _ = construct_rxrx1_tensor_dataset(data, data_path, client_num, "train") + + train_indices, val_indices = create_splits(dataset, seed=seed, train_fraction=train_val_split) + train_set = copy.deepcopy(dataset) + train_set.data = train_set.data[train_indices] + assert train_set.targets is not None + train_set.targets = train_set.targets[train_indices] + + validation_set = copy.deepcopy(dataset) + validation_set.data = validation_set.data[val_indices] + assert validation_set.targets is not None + validation_set.targets = validation_set.targets[val_indices] + + train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=num_workers, pin_memory=True) + validation_loader = DataLoader(validation_set, batch_size=batch_size) + num_examples = { + "train_set": len(train_set.data), + "validation_set": len(validation_set.data), + } + + return train_loader, validation_loader, num_examples + + +def load_rxrx1_test_data( + data_path: Path, client_num: int, batch_size: int, num_workers: int = 0 +) -> tuple[DataLoader, dict[str, int]]: + + # Read the CSV file + data = pd.read_csv(f"{data_path}/clients/meta_data_{client_num+1}.csv") + + dataset, _ = construct_rxrx1_tensor_dataset(data, data_path, client_num, "test") + + evaluation_loader = DataLoader( + dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers, pin_memory=True + ) + num_examples = {"eval_set": len(dataset.data)} + return evaluation_loader, num_examples diff --git a/fl4health/datasets/rxrx1/preprocess.py b/fl4health/datasets/rxrx1/preprocess.py new file mode 100644 index 000000000..2118497e1 --- /dev/null +++ b/fl4health/datasets/rxrx1/preprocess.py @@ -0,0 +1,121 @@ +import argparse +import os +import pickle +from pathlib import Path +from typing import Any + +import pandas as pd +import torch +from PIL import Image +from torchvision.transforms import ToTensor + + +def filter_and_save_data(metadata: pd.DataFrame, top_sirna_ids: list[int], cell_type: str, output_path: Path) -> None: + """ + Filters data for the given cell type and frequency of their sirna_id and saves it to a CSV file. + + Args: + metadata (pd.DataFrame): Metadata containing information about all images. + top_sirna_ids (list[int]): Top sirna_id values to filter by. + cell_type (str): Cell type to filter by. + output_path (Path): Path to save the filtered metadata. + """ + filtered_metadata = metadata[(metadata["sirna_id"].isin(top_sirna_ids)) & (metadata["cell_type"] == cell_type)] + filtered_metadata.to_csv(output_path, index=False) + + +def load_image(row: dict[str, Any], root: Path) -> torch.Tensor: + """ + Load an image tensor for a given row of metadata. + + Args: + row (dict[str, Any]): A row of metadata containing experiment, plate, well, and site information. + root (Path): Root directory containing the image files. + + Returns: + torch.Tensor: The loaded image tensor. + """ + experiment = row["experiment"] + plate = row["plate"] + well = row["well"] + site = row["site"] + + images = [] + # Rxrx1 originally consists of 6 channels, but to reduce the computational cost, we only use 3 channels + # following previous works such as https://github.com/p-lambda/wildYe. + for channel in range(1, 4): + image_path = os.path.join(root, f"images/{experiment}/Plate{plate}/{well}_s{site}_w{channel}.png") + if not Path(image_path).exists(): + raise FileNotFoundError(f"Image not found at {image_path}") + image = ToTensor()(Image.open(image_path).convert("L")) + images.append(image) + + # Concatenate the three channels into one tensor + return torch.cat(images, dim=0) + + +def process_data(metadata: pd.DataFrame, input_dir: Path, output_dir: Path, client_num: int, type_data: str) -> None: + """ + Process the entire dataset, loading image tensors for each row. + + Args: + metadata (pd.DataFrame): Metadata containing information about all images. + input_dir (Path): Input directory containing the image files. + output_dir (Path): Output directory containing the image files. + client_num (int): Client number to load data for. + type_data (str): 'train' or 'test' to specify dataset type. + """ + for i, row in metadata.iterrows(): + image_tensor = load_image(row.to_dict(), Path(input_dir)) + save_to_pkl(image_tensor, os.path.join(output_dir, f"{type_data}_data_{client_num+1}", f"image_{i}.pkl")) + + +def save_to_pkl(data: torch.Tensor, output_path: str) -> None: + """ + Save data to a pickle file. + + Args: + data (torch.Tensor): Data to save. + output_path (str): Path to the output pickle file. + """ + with open(output_path, "wb") as f: + pickle.dump(data, f) + + +def main(dataset_dir: Path) -> None: + metadata_file = os.path.join(dataset_dir, "metadata.csv") + output_dir = os.path.join(dataset_dir, "clients") + + os.makedirs(output_dir, exist_ok=True) + + data = pd.read_csv(metadata_file) + + # Get the top 50 `sirna_id`s by frequency + top_sirna_ids = data["sirna_id"].value_counts().head(50).index.tolist() + + # Define cell types to distribute data based on them for each client + cell_types = ["RPE", "HUVEC", "HEPG2", "U2OS"] + output_files = [os.path.join(output_dir, f"meta_data_{i+1}.csv") for i in range(len(cell_types))] + + # Filter and save data for each client + for cell_type, output_path in zip(cell_types, output_files): + filter_and_save_data(data, top_sirna_ids, cell_type, Path(output_path)) + + for i, metadata_path in enumerate(output_files): + metadata = pd.read_csv(metadata_path) + + # Split the metadata into train and test datasets + train_metadata = metadata[metadata["dataset"] == "train"] + test_metadata = metadata[metadata["dataset"] == "test"] + + process_data(train_metadata, dataset_dir, Path(output_dir), i, "train") + process_data(test_metadata, dataset_dir, Path(output_dir), i, "test") + + +if __name__ == "__main__": + # Argument parsing + parser = argparse.ArgumentParser(description="Filter dataset by the most frequent sirna_id and cell_type.") + parser.add_argument("dataset_dir", type=str, help="Path to the dataset directory containing metadata.csv") + + args = parser.parse_args() + main(args.dataset_dir) diff --git a/poetry.lock b/poetry.lock index c315d477a..25f97dc49 100644 --- a/poetry.lock +++ b/poetry.lock @@ -154,13 +154,13 @@ frozenlist = ">=1.1.0" [[package]] name = "alembic" -version = "1.14.0" +version = "1.14.1" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" files = [ - {file = "alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25"}, - {file = "alembic-1.14.0.tar.gz", hash = "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b"}, + {file = "alembic-1.14.1-py3-none-any.whl", hash = "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5"}, + {file = "alembic-1.14.1.tar.gz", hash = "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213"}, ] [package.dependencies] @@ -169,7 +169,7 @@ SQLAlchemy = ">=1.3.0" typing-extensions = ">=4" [package.extras] -tz = ["backports.zoneinfo"] +tz = ["backports.zoneinfo", "tzdata"] [[package]] name = "antlr4-python3-runtime" @@ -482,13 +482,13 @@ virtualenv = ["virtualenv (>=20.0.35)"] [[package]] name = "cachecontrol" -version = "0.14.1" +version = "0.14.2" description = "httplib2 caching for requests" optional = false python-versions = ">=3.8" files = [ - {file = "cachecontrol-0.14.1-py3-none-any.whl", hash = "sha256:65e3abd62b06382ce3894df60dde9e0deb92aeb734724f68fa4f3b91e97206b9"}, - {file = "cachecontrol-0.14.1.tar.gz", hash = "sha256:06ef916a1e4eb7dba9948cdfc9c76e749db2e02104a9a1277e8b642591a0f717"}, + {file = "cachecontrol-0.14.2-py3-none-any.whl", hash = "sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0"}, + {file = "cachecontrol-0.14.2.tar.gz", hash = "sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2"}, ] [package.dependencies] @@ -766,13 +766,13 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "cloudpickle" -version = "3.1.0" +version = "3.1.1" description = "Pickler class to extend the standard pickle.Pickler functionality" optional = false python-versions = ">=3.8" files = [ - {file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"}, - {file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"}, + {file = "cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e"}, + {file = "cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64"}, ] [[package]] @@ -1176,13 +1176,13 @@ xml-validation = ["lxml (>=4,<5)"] [[package]] name = "databricks-sdk" -version = "0.40.0" +version = "0.41.0" description = "Databricks SDK for Python (Beta)" optional = false python-versions = ">=3.7" files = [ - {file = "databricks_sdk-0.40.0-py3-none-any.whl", hash = "sha256:998a3d118b89abdfd7151a9f0f6065a865a3f84d6ba434118175f4e456d5fa73"}, - {file = "databricks_sdk-0.40.0.tar.gz", hash = "sha256:48c6926ab840bd49e200122bccd72d9e7c823030949fd96a97d903df4fe2c2e7"}, + {file = "databricks-sdk-0.41.0.tar.gz", hash = "sha256:dc6f7f3a81c5183ad9bc64526ca0ccea77249cabdba19189448c20534b3eb861"}, + {file = "databricks_sdk-0.41.0-py3-none-any.whl", hash = "sha256:ea813687889839473dc360b65ae24080c6b91dde5e74a1f57595f18b8e6507ea"}, ] [package.dependencies] @@ -1240,37 +1240,37 @@ vision = ["Pillow (>=9.4.0)"] [[package]] name = "debugpy" -version = "1.8.11" +version = "1.8.12" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.11-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:2b26fefc4e31ff85593d68b9022e35e8925714a10ab4858fb1b577a8a48cb8cd"}, - {file = "debugpy-1.8.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61bc8b3b265e6949855300e84dc93d02d7a3a637f2aec6d382afd4ceb9120c9f"}, - {file = "debugpy-1.8.11-cp310-cp310-win32.whl", hash = "sha256:c928bbf47f65288574b78518449edaa46c82572d340e2750889bbf8cd92f3737"}, - {file = "debugpy-1.8.11-cp310-cp310-win_amd64.whl", hash = "sha256:8da1db4ca4f22583e834dcabdc7832e56fe16275253ee53ba66627b86e304da1"}, - {file = "debugpy-1.8.11-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:85de8474ad53ad546ff1c7c7c89230db215b9b8a02754d41cb5a76f70d0be296"}, - {file = "debugpy-1.8.11-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ffc382e4afa4aee367bf413f55ed17bd91b191dcaf979890af239dda435f2a1"}, - {file = "debugpy-1.8.11-cp311-cp311-win32.whl", hash = "sha256:40499a9979c55f72f4eb2fc38695419546b62594f8af194b879d2a18439c97a9"}, - {file = "debugpy-1.8.11-cp311-cp311-win_amd64.whl", hash = "sha256:987bce16e86efa86f747d5151c54e91b3c1e36acc03ce1ddb50f9d09d16ded0e"}, - {file = "debugpy-1.8.11-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308"}, - {file = "debugpy-1.8.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768"}, - {file = "debugpy-1.8.11-cp312-cp312-win32.whl", hash = "sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b"}, - {file = "debugpy-1.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1"}, - {file = "debugpy-1.8.11-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3"}, - {file = "debugpy-1.8.11-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e"}, - {file = "debugpy-1.8.11-cp313-cp313-win32.whl", hash = "sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28"}, - {file = "debugpy-1.8.11-cp313-cp313-win_amd64.whl", hash = "sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1"}, - {file = "debugpy-1.8.11-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:ad7efe588c8f5cf940f40c3de0cd683cc5b76819446abaa50dc0829a30c094db"}, - {file = "debugpy-1.8.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:189058d03a40103a57144752652b3ab08ff02b7595d0ce1f651b9acc3a3a35a0"}, - {file = "debugpy-1.8.11-cp38-cp38-win32.whl", hash = "sha256:32db46ba45849daed7ccf3f2e26f7a386867b077f39b2a974bb5c4c2c3b0a280"}, - {file = "debugpy-1.8.11-cp38-cp38-win_amd64.whl", hash = "sha256:116bf8342062246ca749013df4f6ea106f23bc159305843491f64672a55af2e5"}, - {file = "debugpy-1.8.11-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:654130ca6ad5de73d978057eaf9e582244ff72d4574b3e106fb8d3d2a0d32458"}, - {file = "debugpy-1.8.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23dc34c5e03b0212fa3c49a874df2b8b1b8fda95160bd79c01eb3ab51ea8d851"}, - {file = "debugpy-1.8.11-cp39-cp39-win32.whl", hash = "sha256:52d8a3166c9f2815bfae05f386114b0b2d274456980d41f320299a8d9a5615a7"}, - {file = "debugpy-1.8.11-cp39-cp39-win_amd64.whl", hash = "sha256:52c3cf9ecda273a19cc092961ee34eb9ba8687d67ba34cc7b79a521c1c64c4c0"}, - {file = "debugpy-1.8.11-py2.py3-none-any.whl", hash = "sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920"}, - {file = "debugpy-1.8.11.tar.gz", hash = "sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57"}, + {file = "debugpy-1.8.12-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:a2ba7ffe58efeae5b8fad1165357edfe01464f9aef25e814e891ec690e7dd82a"}, + {file = "debugpy-1.8.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd4149c4fc5e7d508ece083e78c17442ee13b0e69bfa6bd63003e486770f45"}, + {file = "debugpy-1.8.12-cp310-cp310-win32.whl", hash = "sha256:b202f591204023b3ce62ff9a47baa555dc00bb092219abf5caf0e3718ac20e7c"}, + {file = "debugpy-1.8.12-cp310-cp310-win_amd64.whl", hash = "sha256:9649eced17a98ce816756ce50433b2dd85dfa7bc92ceb60579d68c053f98dff9"}, + {file = "debugpy-1.8.12-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:36f4829839ef0afdfdd208bb54f4c3d0eea86106d719811681a8627ae2e53dd5"}, + {file = "debugpy-1.8.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a28ed481d530e3138553be60991d2d61103ce6da254e51547b79549675f539b7"}, + {file = "debugpy-1.8.12-cp311-cp311-win32.whl", hash = "sha256:4ad9a94d8f5c9b954e0e3b137cc64ef3f579d0df3c3698fe9c3734ee397e4abb"}, + {file = "debugpy-1.8.12-cp311-cp311-win_amd64.whl", hash = "sha256:4703575b78dd697b294f8c65588dc86874ed787b7348c65da70cfc885efdf1e1"}, + {file = "debugpy-1.8.12-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:7e94b643b19e8feb5215fa508aee531387494bf668b2eca27fa769ea11d9f498"}, + {file = "debugpy-1.8.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086b32e233e89a2740c1615c2f775c34ae951508b28b308681dbbb87bba97d06"}, + {file = "debugpy-1.8.12-cp312-cp312-win32.whl", hash = "sha256:2ae5df899732a6051b49ea2632a9ea67f929604fd2b036613a9f12bc3163b92d"}, + {file = "debugpy-1.8.12-cp312-cp312-win_amd64.whl", hash = "sha256:39dfbb6fa09f12fae32639e3286112fc35ae976114f1f3d37375f3130a820969"}, + {file = "debugpy-1.8.12-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:696d8ae4dff4cbd06bf6b10d671e088b66669f110c7c4e18a44c43cf75ce966f"}, + {file = "debugpy-1.8.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:898fba72b81a654e74412a67c7e0a81e89723cfe2a3ea6fcd3feaa3395138ca9"}, + {file = "debugpy-1.8.12-cp313-cp313-win32.whl", hash = "sha256:22a11c493c70413a01ed03f01c3c3a2fc4478fc6ee186e340487b2edcd6f4180"}, + {file = "debugpy-1.8.12-cp313-cp313-win_amd64.whl", hash = "sha256:fdb3c6d342825ea10b90e43d7f20f01535a72b3a1997850c0c3cefa5c27a4a2c"}, + {file = "debugpy-1.8.12-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:b0232cd42506d0c94f9328aaf0d1d0785f90f87ae72d9759df7e5051be039738"}, + {file = "debugpy-1.8.12-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af40506a59450f1315168d47a970db1a65aaab5df3833ac389d2899a5d63b3f"}, + {file = "debugpy-1.8.12-cp38-cp38-win32.whl", hash = "sha256:5cc45235fefac57f52680902b7d197fb2f3650112379a6fa9aa1b1c1d3ed3f02"}, + {file = "debugpy-1.8.12-cp38-cp38-win_amd64.whl", hash = "sha256:557cc55b51ab2f3371e238804ffc8510b6ef087673303890f57a24195d096e61"}, + {file = "debugpy-1.8.12-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:b5c6c967d02fee30e157ab5227706f965d5c37679c687b1e7bbc5d9e7128bd41"}, + {file = "debugpy-1.8.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a77f422f31f170c4b7e9ca58eae2a6c8e04da54121900651dfa8e66c29901a"}, + {file = "debugpy-1.8.12-cp39-cp39-win32.whl", hash = "sha256:a4042edef80364239f5b7b5764e55fd3ffd40c32cf6753da9bda4ff0ac466018"}, + {file = "debugpy-1.8.12-cp39-cp39-win_amd64.whl", hash = "sha256:f30b03b0f27608a0b26c75f0bb8a880c752c0e0b01090551b9d87c7d783e2069"}, + {file = "debugpy-1.8.12-py2.py3-none-any.whl", hash = "sha256:274b6a2040349b5c9864e475284bce5bb062e63dce368a394b8cc865ae3b00c6"}, + {file = "debugpy-1.8.12.tar.gz", hash = "sha256:646530b04f45c830ceae8e491ca1c9320a2d2f0efea3141487c82130aba70dce"}, ] [[package]] @@ -1791,61 +1791,61 @@ simulation = ["ray (==2.10.0)"] [[package]] name = "fonttools" -version = "4.55.3" +version = "4.55.4" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0"}, - {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f"}, - {file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841"}, - {file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674"}, - {file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276"}, - {file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5"}, - {file = "fonttools-4.55.3-cp310-cp310-win32.whl", hash = "sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261"}, - {file = "fonttools-4.55.3-cp310-cp310-win_amd64.whl", hash = "sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5"}, - {file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e"}, - {file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b"}, - {file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90"}, - {file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0"}, - {file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b"}, - {file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765"}, - {file = "fonttools-4.55.3-cp311-cp311-win32.whl", hash = "sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f"}, - {file = "fonttools-4.55.3-cp311-cp311-win_amd64.whl", hash = "sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72"}, - {file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35"}, - {file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c"}, - {file = "fonttools-4.55.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7"}, - {file = "fonttools-4.55.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314"}, - {file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427"}, - {file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a"}, - {file = "fonttools-4.55.3-cp312-cp312-win32.whl", hash = "sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07"}, - {file = "fonttools-4.55.3-cp312-cp312-win_amd64.whl", hash = "sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54"}, - {file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a430178ad3e650e695167cb53242dae3477b35c95bef6525b074d87493c4bf29"}, - {file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:529cef2ce91dc44f8e407cc567fae6e49a1786f2fefefa73a294704c415322a4"}, - {file = "fonttools-4.55.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e75f12c82127486fac2d8bfbf5bf058202f54bf4f158d367e41647b972342ca"}, - {file = "fonttools-4.55.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:859c358ebf41db18fb72342d3080bce67c02b39e86b9fbcf1610cca14984841b"}, - {file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:546565028e244a701f73df6d8dd6be489d01617863ec0c6a42fa25bf45d43048"}, - {file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aca318b77f23523309eec4475d1fbbb00a6b133eb766a8bdc401faba91261abe"}, - {file = "fonttools-4.55.3-cp313-cp313-win32.whl", hash = "sha256:8c5ec45428edaa7022f1c949a632a6f298edc7b481312fc7dc258921e9399628"}, - {file = "fonttools-4.55.3-cp313-cp313-win_amd64.whl", hash = "sha256:11e5de1ee0d95af4ae23c1a138b184b7f06e0b6abacabf1d0db41c90b03d834b"}, - {file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:caf8230f3e10f8f5d7593eb6d252a37caf58c480b19a17e250a63dad63834cf3"}, - {file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b586ab5b15b6097f2fb71cafa3c98edfd0dba1ad8027229e7b1e204a58b0e09d"}, - {file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8c2794ded89399cc2169c4d0bf7941247b8d5932b2659e09834adfbb01589aa"}, - {file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf4fe7c124aa3f4e4c1940880156e13f2f4d98170d35c749e6b4f119a872551e"}, - {file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:86721fbc389ef5cc1e2f477019e5069e8e4421e8d9576e9c26f840dbb04678de"}, - {file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:89bdc5d88bdeec1b15af790810e267e8332d92561dce4f0748c2b95c9bdf3926"}, - {file = "fonttools-4.55.3-cp38-cp38-win32.whl", hash = "sha256:bc5dbb4685e51235ef487e4bd501ddfc49be5aede5e40f4cefcccabc6e60fb4b"}, - {file = "fonttools-4.55.3-cp38-cp38-win_amd64.whl", hash = "sha256:cd70de1a52a8ee2d1877b6293af8a2484ac82514f10b1c67c1c5762d38073e56"}, - {file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bdcc9f04b36c6c20978d3f060e5323a43f6222accc4e7fcbef3f428e216d96af"}, - {file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c3ca99e0d460eff46e033cd3992a969658c3169ffcd533e0a39c63a38beb6831"}, - {file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22f38464daa6cdb7b6aebd14ab06609328fe1e9705bb0fcc7d1e69de7109ee02"}, - {file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed63959d00b61959b035c7d47f9313c2c1ece090ff63afea702fe86de00dbed4"}, - {file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5e8d657cd7326eeaba27de2740e847c6b39dde2f8d7cd7cc56f6aad404ddf0bd"}, - {file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fb594b5a99943042c702c550d5494bdd7577f6ef19b0bc73877c948a63184a32"}, - {file = "fonttools-4.55.3-cp39-cp39-win32.whl", hash = "sha256:dc5294a3d5c84226e3dbba1b6f61d7ad813a8c0238fceea4e09aa04848c3d851"}, - {file = "fonttools-4.55.3-cp39-cp39-win_amd64.whl", hash = "sha256:aedbeb1db64496d098e6be92b2e63b5fac4e53b1b92032dfc6988e1ea9134a4d"}, - {file = "fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977"}, - {file = "fonttools-4.55.3.tar.gz", hash = "sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45"}, + {file = "fonttools-4.55.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b332ea7b7f5f3d99f9bc5a28a23c3824ae72711abf7c4e1d62fa21699fdebe7"}, + {file = "fonttools-4.55.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d8f925909256e62152e7c3e192655dbca3ab8c3cdef7d7b436732727e80feb6"}, + {file = "fonttools-4.55.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a58af9b98e39bcd773aa352b4512be79b472830b799cb1d3cafb2b4796b71cd"}, + {file = "fonttools-4.55.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:736d750d2ab4523067d8058e5294b40b01f2eee521e0fd401bec0d5e21e80b12"}, + {file = "fonttools-4.55.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1a9a2e7e8a9d3bfa9589db3e6c4e4c127fec252493924b2f87a67a25f9430057"}, + {file = "fonttools-4.55.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:87824368e994af34a95cb4279a8c711e51974b3c28d052d39d768531cc9e8e59"}, + {file = "fonttools-4.55.4-cp310-cp310-win32.whl", hash = "sha256:6c36dcbfe64bce38c4d4f1d436cdc6445e969eee96eb98d98be603b5abf8c3f2"}, + {file = "fonttools-4.55.4-cp310-cp310-win_amd64.whl", hash = "sha256:3c53a467e5cf629acdbefc98b0f554859539fb6447bbeae4117b9ab51464ccc5"}, + {file = "fonttools-4.55.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1605b28165c785bf26c2cbd205dc0822463e3f9f56f187049eb214dc5f4a59cb"}, + {file = "fonttools-4.55.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d851d8b2fdb676507365d1430c3285d62c4039d0d7760d8cf2f2e5ea3aa19d73"}, + {file = "fonttools-4.55.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fb3cf1cddf08cec0338f238f950cb76fabab23a324a579e3e1f9b2ef2578329"}, + {file = "fonttools-4.55.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddd3208b06186ca00fbd329c0d0fed5ba209c99017cc46e2c4ea42233c2fbd00"}, + {file = "fonttools-4.55.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9bd98819cb585a894dda9dcb337afeb2601abf17da17de7bfbfc1bc2e4a062c7"}, + {file = "fonttools-4.55.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4877376c10541e8dccf14876c8476d5082338fa5d21103894894382cc245144b"}, + {file = "fonttools-4.55.4-cp311-cp311-win32.whl", hash = "sha256:3a5e466894ec6d8a009b0eb8e02a6eb26959a318d5b7a906280c26bdadce6423"}, + {file = "fonttools-4.55.4-cp311-cp311-win_amd64.whl", hash = "sha256:f595129e6f9c6402965d6295fe8c18c1945d27af0f90bdb52ff426226e647afc"}, + {file = "fonttools-4.55.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b3db72ad2d26a0e9ec694cbfb4485a8da9c095d29f66561cf935dbd19f3efcea"}, + {file = "fonttools-4.55.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87717808fd5953588c3ffaf512e8cab0e43c09c1da04e42ba87fa4c07d8170c7"}, + {file = "fonttools-4.55.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f49dac626ad5bc1a0147b88e6157e3211fd440d00007f0da6c9e5f91dd5cb88e"}, + {file = "fonttools-4.55.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d0ac8656ada8b604ae5da15d9aa075232f2181b95b51a3a2a55195222df7e7"}, + {file = "fonttools-4.55.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:013c8b45873fa77a4ff6d25e43fecf1046cb7e8c6b32f1843117f98f3f8eac60"}, + {file = "fonttools-4.55.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94caad375d254a0332926512f06791f5e66c24a913ebecd6178b14f61d27c62f"}, + {file = "fonttools-4.55.4-cp312-cp312-win32.whl", hash = "sha256:cb3eb4bf3a0c4e431e1ccab7a33ef4f1bb32657133fff4a61dc4fcbd54b94d29"}, + {file = "fonttools-4.55.4-cp312-cp312-win_amd64.whl", hash = "sha256:6914269f6ff6b20c6b5a9b19d0b752880bd8ee218d9a7d6afe9960bbf1922d98"}, + {file = "fonttools-4.55.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:699dd32da7258a89939567a3d71b3f8decf84da54488a2526693f0d981a76479"}, + {file = "fonttools-4.55.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f374b18ac04fbf78f20940418aee7882be3cdcb328ded80e16c3356499f64cf"}, + {file = "fonttools-4.55.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b18792529ca3c24259090b6faa60bd0bdfcc4a06312e8f06d6fccab007f07193"}, + {file = "fonttools-4.55.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e91d25261ebc9ff2143b95e6272f46b9f28e260b8f40feda07c80b66ff7e61d"}, + {file = "fonttools-4.55.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2695781a897395d03504fd24b60c944726b5e7b7af9ea3d922f7319d70c6fc37"}, + {file = "fonttools-4.55.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21de3ef5b8e5361fd01d6aef2c09dda4ede139d6b3a1f5cf621d6bea48840dfd"}, + {file = "fonttools-4.55.4-cp313-cp313-win32.whl", hash = "sha256:0ef33fda14e39aabb892a18ed16805b0b5b4e8a801fd1815a694be9dc7f30024"}, + {file = "fonttools-4.55.4-cp313-cp313-win_amd64.whl", hash = "sha256:e953b1614e32b6da828ae7659c8f330a593b6c4b7a4a31f8f63c01b12f0d3680"}, + {file = "fonttools-4.55.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e2d1bbcaf8ca8c60fbb029982197fbaa487559d5380f1c3098882c5ceb4311c7"}, + {file = "fonttools-4.55.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a885593dbcbfc250ff17831f7dc9316e95c3d046e6cd7ff7ab52ebf673bbf978"}, + {file = "fonttools-4.55.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02cd4ad9b3ab9f9c5b233b3bb6a96a036c9c0ef17487805b5e73cedf6439d188"}, + {file = "fonttools-4.55.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822d46676f794bb6cac055b43f5636792e2a360e18cf0f3a0333c21d79ec0f2d"}, + {file = "fonttools-4.55.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7b195440fe14d8601053a51e06e13c94f725bf9f964611be99dc3cb65497ce8e"}, + {file = "fonttools-4.55.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a0e0a0ec8cc4b8f82f9cf4efa26774dbd93433ba51b8f9bd2b214bf36c5638f6"}, + {file = "fonttools-4.55.4-cp38-cp38-win32.whl", hash = "sha256:ca7e6047fbc995500e0b7459a04d5b92cafd7730b636d5f83334cd7eefdf95c7"}, + {file = "fonttools-4.55.4-cp38-cp38-win_amd64.whl", hash = "sha256:0185983fcf49ae7a826cedc6f64d68b0434a5b7905d89e75bc95fced7fe118c1"}, + {file = "fonttools-4.55.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:dcc08dcb2be554073a72f3a8cecbc4226602ccdd0187b8f37a03a731cb931864"}, + {file = "fonttools-4.55.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7b9b414ce50f09cb692e97ff82b041ea1a21076ed9c1923206560c15ce9ad03a"}, + {file = "fonttools-4.55.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8807a1357d434ef1f4aed9bdfee7077f52dbc040b18ac98f6e417f69a48afbb5"}, + {file = "fonttools-4.55.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a3ec7cba2e71edbc999ce3d48d34ef87cc30a36af6ff90dfc0dbc131f705fc"}, + {file = "fonttools-4.55.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2964b9fe6b4a892a41a8a517bac232072a821cf2288fad1d19c6c1d19c34b0dd"}, + {file = "fonttools-4.55.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0b9f4f032295adeb39a8c0eefb08a7b1e90f4b7571506e5d84bb923a7afa8247"}, + {file = "fonttools-4.55.4-cp39-cp39-win32.whl", hash = "sha256:ee4e86280dc637a17e926cbdd32c2de148c013c3468777ae6e94c8b4449c8e93"}, + {file = "fonttools-4.55.4-cp39-cp39-win_amd64.whl", hash = "sha256:82a03920f0f524abab375dcfac8926d9596986503ee00ae435bdd71b1498f214"}, + {file = "fonttools-4.55.4-py3-none-any.whl", hash = "sha256:d07ad8f31038c6394a0945752458313367a0ef8125d284ee59f99e68393a3c2d"}, + {file = "fonttools-4.55.4.tar.gz", hash = "sha256:9598af0af85073659facbe9612fcc56b071ef2f26e3819ebf9bd8c5d35f958c5"}, ] [package.extras] @@ -2473,13 +2473,13 @@ packaging = "*" [[package]] name = "identify" -version = "2.6.5" +version = "2.6.6" description = "File identification library for Python" optional = false python-versions = ">=3.9" files = [ - {file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"}, - {file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"}, + {file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"}, + {file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"}, ] [package.extras] @@ -2553,13 +2553,13 @@ test = ["bitshuffle", "blosc", "blosc2", "czifile", "lz4", "numcodecs", "pyliblz [[package]] name = "imageio" -version = "2.36.1" +version = "2.37.0" description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." optional = false python-versions = ">=3.9" files = [ - {file = "imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf"}, - {file = "imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62"}, + {file = "imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed"}, + {file = "imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996"}, ] [package.dependencies] @@ -2586,13 +2586,13 @@ tifffile = ["tifffile"] [[package]] name = "importlib-metadata" -version = "8.5.0" +version = "8.6.1" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] [package.dependencies] @@ -2604,7 +2604,7 @@ cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -3094,13 +3094,13 @@ files = [ [[package]] name = "license-expression" -version = "30.4.0" +version = "30.4.1" description = "license-expression is a comprehensive utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." optional = false python-versions = ">=3.9" files = [ - {file = "license_expression-30.4.0-py3-none-any.whl", hash = "sha256:7c8f240c6e20d759cb8455e49cb44a923d9e25c436bf48d7e5b8eea660782c04"}, - {file = "license_expression-30.4.0.tar.gz", hash = "sha256:6464397f8ed4353cc778999caec43b099f8d8d5b335f282e26a9eb9435522f05"}, + {file = "license_expression-30.4.1-py3-none-any.whl", hash = "sha256:679646bc3261a17690494a3e1cada446e5ee342dbd87dcfa4a0c24cc5dce13ee"}, + {file = "license_expression-30.4.1.tar.gz", hash = "sha256:9f02105f9e0fcecba6a85dfbbed7d94ea1c3a70cf23ddbfb5adf3438a6f6fce0"}, ] [package.dependencies] @@ -3144,32 +3144,32 @@ files = [ [[package]] name = "llvmlite" -version = "0.43.0" +version = "0.44.0" description = "lightweight wrapper around basic LLVM functionality" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "llvmlite-0.43.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a289af9a1687c6cf463478f0fa8e8aa3b6fb813317b0d70bf1ed0759eab6f761"}, - {file = "llvmlite-0.43.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4fd101f571a31acb1559ae1af30f30b1dc4b3186669f92ad780e17c81e91bc"}, - {file = "llvmlite-0.43.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d434ec7e2ce3cc8f452d1cd9a28591745de022f931d67be688a737320dfcead"}, - {file = "llvmlite-0.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6912a87782acdff6eb8bf01675ed01d60ca1f2551f8176a300a886f09e836a6a"}, - {file = "llvmlite-0.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:14f0e4bf2fd2d9a75a3534111e8ebeb08eda2f33e9bdd6dfa13282afacdde0ed"}, - {file = "llvmlite-0.43.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8d0618cb9bfe40ac38a9633f2493d4d4e9fcc2f438d39a4e854f39cc0f5f98"}, - {file = "llvmlite-0.43.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0a9a1a39d4bf3517f2af9d23d479b4175ead205c592ceeb8b89af48a327ea57"}, - {file = "llvmlite-0.43.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1da416ab53e4f7f3bc8d4eeba36d801cc1894b9fbfbf2022b29b6bad34a7df2"}, - {file = "llvmlite-0.43.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977525a1e5f4059316b183fb4fd34fa858c9eade31f165427a3977c95e3ee749"}, - {file = "llvmlite-0.43.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5bd550001d26450bd90777736c69d68c487d17bf371438f975229b2b8241a91"}, - {file = "llvmlite-0.43.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f99b600aa7f65235a5a05d0b9a9f31150c390f31261f2a0ba678e26823ec38f7"}, - {file = "llvmlite-0.43.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:35d80d61d0cda2d767f72de99450766250560399edc309da16937b93d3b676e7"}, - {file = "llvmlite-0.43.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eccce86bba940bae0d8d48ed925f21dbb813519169246e2ab292b5092aba121f"}, - {file = "llvmlite-0.43.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6509e1507ca0760787a199d19439cc887bfd82226f5af746d6977bd9f66844"}, - {file = "llvmlite-0.43.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a2872ee80dcf6b5dbdc838763d26554c2a18aa833d31a2635bff16aafefb9c9"}, - {file = "llvmlite-0.43.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cd2a7376f7b3367019b664c21f0c61766219faa3b03731113ead75107f3b66c"}, - {file = "llvmlite-0.43.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18e9953c748b105668487b7c81a3e97b046d8abf95c4ddc0cd3c94f4e4651ae8"}, - {file = "llvmlite-0.43.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74937acd22dc11b33946b67dca7680e6d103d6e90eeaaaf932603bec6fe7b03a"}, - {file = "llvmlite-0.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9efc739cc6ed760f795806f67889923f7274276f0eb45092a1473e40d9b867"}, - {file = "llvmlite-0.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:47e147cdda9037f94b399bf03bfd8a6b6b1f2f90be94a454e3386f006455a9b4"}, - {file = "llvmlite-0.43.0.tar.gz", hash = "sha256:ae2b5b5c3ef67354824fb75517c8db5fbe93bc02cd9671f3c62271626bc041d5"}, + {file = "llvmlite-0.44.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9fbadbfba8422123bab5535b293da1cf72f9f478a65645ecd73e781f962ca614"}, + {file = "llvmlite-0.44.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cccf8eb28f24840f2689fb1a45f9c0f7e582dd24e088dcf96e424834af11f791"}, + {file = "llvmlite-0.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7202b678cdf904823c764ee0fe2dfe38a76981f4c1e51715b4cb5abb6cf1d9e8"}, + {file = "llvmlite-0.44.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40526fb5e313d7b96bda4cbb2c85cd5374e04d80732dd36a282d72a560bb6408"}, + {file = "llvmlite-0.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:41e3839150db4330e1b2716c0be3b5c4672525b4c9005e17c7597f835f351ce2"}, + {file = "llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3"}, + {file = "llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427"}, + {file = "llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1"}, + {file = "llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610"}, + {file = "llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955"}, + {file = "llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad"}, + {file = "llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db"}, + {file = "llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9"}, + {file = "llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d"}, + {file = "llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1"}, + {file = "llvmlite-0.44.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516"}, + {file = "llvmlite-0.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e"}, + {file = "llvmlite-0.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf"}, + {file = "llvmlite-0.44.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc"}, + {file = "llvmlite-0.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930"}, + {file = "llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4"}, ] [[package]] @@ -3624,13 +3624,13 @@ zarr = ["zarr"] [[package]] name = "more-itertools" -version = "10.5.0" +version = "10.6.0" description = "More routines for operating on iterables, beyond itertools" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, - {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, + {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, + {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, ] [[package]] @@ -4057,37 +4057,37 @@ files = [ [[package]] name = "numba" -version = "0.60.0" +version = "0.61.0" description = "compiling Python code using LLVM" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "numba-0.60.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d761de835cd38fb400d2c26bb103a2726f548dc30368853121d66201672e651"}, - {file = "numba-0.60.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:159e618ef213fba758837f9837fb402bbe65326e60ba0633dbe6c7f274d42c1b"}, - {file = "numba-0.60.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1527dc578b95c7c4ff248792ec33d097ba6bef9eda466c948b68dfc995c25781"}, - {file = "numba-0.60.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe0b28abb8d70f8160798f4de9d486143200f34458d34c4a214114e445d7124e"}, - {file = "numba-0.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:19407ced081d7e2e4b8d8c36aa57b7452e0283871c296e12d798852bc7d7f198"}, - {file = "numba-0.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a17b70fc9e380ee29c42717e8cc0bfaa5556c416d94f9aa96ba13acb41bdece8"}, - {file = "numba-0.60.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fb02b344a2a80efa6f677aa5c40cd5dd452e1b35f8d1c2af0dfd9ada9978e4b"}, - {file = "numba-0.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f4fde652ea604ea3c86508a3fb31556a6157b2c76c8b51b1d45eb40c8598703"}, - {file = "numba-0.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4142d7ac0210cc86432b818338a2bc368dc773a2f5cf1e32ff7c5b378bd63ee8"}, - {file = "numba-0.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cac02c041e9b5bc8cf8f2034ff6f0dbafccd1ae9590dc146b3a02a45e53af4e2"}, - {file = "numba-0.60.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7da4098db31182fc5ffe4bc42c6f24cd7d1cb8a14b59fd755bfee32e34b8404"}, - {file = "numba-0.60.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38d6ea4c1f56417076ecf8fc327c831ae793282e0ff51080c5094cb726507b1c"}, - {file = "numba-0.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62908d29fb6a3229c242e981ca27e32a6e606cc253fc9e8faeb0e48760de241e"}, - {file = "numba-0.60.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ebaa91538e996f708f1ab30ef4d3ddc344b64b5227b67a57aa74f401bb68b9d"}, - {file = "numba-0.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:f75262e8fe7fa96db1dca93d53a194a38c46da28b112b8a4aca168f0df860347"}, - {file = "numba-0.60.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:01ef4cd7d83abe087d644eaa3d95831b777aa21d441a23703d649e06b8e06b74"}, - {file = "numba-0.60.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:819a3dfd4630d95fd574036f99e47212a1af41cbcb019bf8afac63ff56834449"}, - {file = "numba-0.60.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b983bd6ad82fe868493012487f34eae8bf7dd94654951404114f23c3466d34b"}, - {file = "numba-0.60.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c151748cd269ddeab66334bd754817ffc0cabd9433acb0f551697e5151917d25"}, - {file = "numba-0.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:3031547a015710140e8c87226b4cfe927cac199835e5bf7d4fe5cb64e814e3ab"}, - {file = "numba-0.60.0.tar.gz", hash = "sha256:5df6158e5584eece5fc83294b949fd30b9f1125df7708862205217e068aabf16"}, -] - -[package.dependencies] -llvmlite = "==0.43.*" -numpy = ">=1.22,<2.1" + {file = "numba-0.61.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9cab9783a700fa428b1a54d65295122bc03b3de1d01fb819a6b9dbbddfdb8c43"}, + {file = "numba-0.61.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:46c5ae094fb3706f5adf9021bfb7fc11e44818d61afee695cdee4eadfed45e98"}, + {file = "numba-0.61.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6fb74e81aa78a2303e30593d8331327dfc0d2522b5db05ac967556a26db3ef87"}, + {file = "numba-0.61.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0ebbd4827091384ab8c4615ba1b3ca8bc639a3a000157d9c37ba85d34cd0da1b"}, + {file = "numba-0.61.0-cp310-cp310-win_amd64.whl", hash = "sha256:43aa4d7d10c542d3c78106b8481e0cbaaec788c39ee8e3d7901682748ffdf0b4"}, + {file = "numba-0.61.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:bf64c2d0f3d161af603de3825172fb83c2600bcb1d53ae8ea568d4c53ba6ac08"}, + {file = "numba-0.61.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de5aa7904741425f28e1028b85850b31f0a245e9eb4f7c38507fb893283a066c"}, + {file = "numba-0.61.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21c2fe25019267a608e2710a6a947f557486b4b0478b02e45a81cf606a05a7d4"}, + {file = "numba-0.61.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:74250b26ed6a1428763e774dc5b2d4e70d93f73795635b5412b8346a4d054574"}, + {file = "numba-0.61.0-cp311-cp311-win_amd64.whl", hash = "sha256:b72bbc8708e98b3741ad0c63f9929c47b623cc4ee86e17030a4f3e301e8401ac"}, + {file = "numba-0.61.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:152146ecdbb8d8176f294e9f755411e6f270103a11c3ff50cecc413f794e52c8"}, + {file = "numba-0.61.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5cafa6095716fcb081618c28a8d27bf7c001e09696f595b41836dec114be2905"}, + {file = "numba-0.61.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ffe9fe373ed30638d6e20a0269f817b2c75d447141f55a675bfcf2d1fe2e87fb"}, + {file = "numba-0.61.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9f25f7fef0206d55c1cfb796ad833cbbc044e2884751e56e798351280038484c"}, + {file = "numba-0.61.0-cp312-cp312-win_amd64.whl", hash = "sha256:550d389573bc3b895e1ccb18289feea11d937011de4d278b09dc7ed585d1cdcb"}, + {file = "numba-0.61.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:b96fafbdcf6f69b69855273e988696aae4974115a815f6818fef4af7afa1f6b8"}, + {file = "numba-0.61.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f6c452dca1de8e60e593f7066df052dd8da09b243566ecd26d2b796e5d3087d"}, + {file = "numba-0.61.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:44240e694d4aa321430c97b21453e46014fe6c7b8b7d932afa7f6a88cc5d7e5e"}, + {file = "numba-0.61.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:764f0e47004f126f58c3b28e0a02374c420a9d15157b90806d68590f5c20cc89"}, + {file = "numba-0.61.0-cp313-cp313-win_amd64.whl", hash = "sha256:074cd38c5b1f9c65a4319d1f3928165f48975ef0537ad43385b2bd908e6e2e35"}, + {file = "numba-0.61.0.tar.gz", hash = "sha256:888d2e89b8160899e19591467e8fdd4970e07606e1fbc248f239c89818d5f925"}, +] + +[package.dependencies] +llvmlite = "==0.44.*" +numpy = ">=1.24,<2.2" [[package]] name = "numexpr" @@ -4392,50 +4392,47 @@ et-xmlfile = "*" [[package]] name = "opentelemetry-api" -version = "1.29.0" +version = "1.16.0" description = "OpenTelemetry Python API" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "opentelemetry_api-1.29.0-py3-none-any.whl", hash = "sha256:5fcd94c4141cc49c736271f3e1efb777bebe9cc535759c54c936cca4f1b312b8"}, - {file = "opentelemetry_api-1.29.0.tar.gz", hash = "sha256:d04a6cf78aad09614f52964ecb38021e248f5714dc32c2e0d8fd99517b4d69cf"}, + {file = "opentelemetry_api-1.16.0-py3-none-any.whl", hash = "sha256:79e8f0cf88dbdd36b6abf175d2092af1efcaa2e71552d0d2b3b181a9707bf4bc"}, + {file = "opentelemetry_api-1.16.0.tar.gz", hash = "sha256:4b0e895a3b1f5e1908043ebe492d33e33f9ccdbe6d02d3994c2f8721a63ddddb"}, ] [package.dependencies] deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=8.5.0" +setuptools = ">=16.0" [[package]] name = "opentelemetry-sdk" -version = "1.29.0" +version = "1.16.0" description = "OpenTelemetry Python SDK" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "opentelemetry_sdk-1.29.0-py3-none-any.whl", hash = "sha256:173be3b5d3f8f7d671f20ea37056710217959e774e2749d984355d1f9391a30a"}, - {file = "opentelemetry_sdk-1.29.0.tar.gz", hash = "sha256:b0787ce6aade6ab84315302e72bd7a7f2f014b0fb1b7c3295b88afe014ed0643"}, + {file = "opentelemetry_sdk-1.16.0-py3-none-any.whl", hash = "sha256:15f03915eec4839f885a5e6ed959cde59b8690c8c012d07c95b4b138c98dc43f"}, + {file = "opentelemetry_sdk-1.16.0.tar.gz", hash = "sha256:4d3bb91e9e209dbeea773b5565d901da4f76a29bf9dbc1c9500be3cabb239a4e"}, ] [package.dependencies] -opentelemetry-api = "1.29.0" -opentelemetry-semantic-conventions = "0.50b0" +opentelemetry-api = "1.16.0" +opentelemetry-semantic-conventions = "0.37b0" +setuptools = ">=16.0" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.50b0" +version = "0.37b0" description = "OpenTelemetry Semantic Conventions" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "opentelemetry_semantic_conventions-0.50b0-py3-none-any.whl", hash = "sha256:e87efba8fdb67fb38113efea6a349531e75ed7ffc01562f65b802fcecb5e115e"}, - {file = "opentelemetry_semantic_conventions-0.50b0.tar.gz", hash = "sha256:02dc6dbcb62f082de9b877ff19a3f1ffaa3c306300fa53bfac761c4567c83d38"}, + {file = "opentelemetry_semantic_conventions-0.37b0-py3-none-any.whl", hash = "sha256:462982278a42dab01f68641cd89f8460fe1f93e87c68a012a76fb426dcdba5ee"}, + {file = "opentelemetry_semantic_conventions-0.37b0.tar.gz", hash = "sha256:087ce2e248e42f3ffe4d9fa2303111de72bb93baa06a0f4655980bc1557c4228"}, ] -[package.dependencies] -deprecated = ">=1.2.6" -opentelemetry-api = "1.29.0" - [[package]] name = "opt-einsum" version = "3.4.0" @@ -4658,19 +4655,19 @@ ptyprocess = ">=0.5" [[package]] name = "picai-eval" -version = "1.4.10" +version = "1.4.13" description = "PICAI Evaluation" optional = false python-versions = ">=3.6" files = [ - {file = "picai_eval-1.4.10-py3-none-any.whl", hash = "sha256:5d6193d6859b8bc26fef90829f90feb197bc9ce71dbd7974f23822913e99ef7b"}, - {file = "picai_eval-1.4.10.tar.gz", hash = "sha256:564862c8c4dfcb7b9a3b6d5662f7c503c0c758dbd38c3501c168f215a36c9e77"}, + {file = "picai_eval-1.4.13-py3-none-any.whl", hash = "sha256:6cb9e8d24d0b85a06e3b02e93b592adbcd9d181c4589bc4aa15f29c902b393a6"}, + {file = "picai_eval-1.4.13.tar.gz", hash = "sha256:88b787547ab9f928d6eb7487d38f91dbf14595eeb4b8c105d38afd9d3be99775"}, ] [package.dependencies] mlxtend = "*" numpy = "*" -report-guided-annotation = ">=0.2.4" +report_guided_annotation = ">=0.2.4" scikit-learn = ">=0.20.3" scipy = "*" SimpleITK = "*" @@ -5001,13 +4998,13 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.48" +version = "3.0.50" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, - {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, + {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, + {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, ] [package.dependencies] @@ -5400,54 +5397,61 @@ xgboost = ["xgboost (>=1.5.2,<2.0.0)"] [[package]] name = "pydantic" -version = "1.10.19" +version = "1.10.21" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a415b9e95fa602b10808113967f72b2da8722061265d6af69268c111c254832d"}, - {file = "pydantic-1.10.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:11965f421f7eb026439d4eb7464e9182fe6d69c3d4d416e464a4485d1ba61ab6"}, - {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5bb81fcfc6d5bff62cd786cbd87480a11d23f16d5376ad2e057c02b3b44df96"}, - {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ee8c9916689f8e6e7d90161e6663ac876be2efd32f61fdcfa3a15e87d4e413"}, - {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0399094464ae7f28482de22383e667625e38e1516d6b213176df1acdd0c477ea"}, - {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b2cf5e26da84f2d2dee3f60a3f1782adedcee785567a19b68d0af7e1534bd1f"}, - {file = "pydantic-1.10.19-cp310-cp310-win_amd64.whl", hash = "sha256:1fc8cc264afaf47ae6a9bcbd36c018d0c6b89293835d7fb0e5e1a95898062d59"}, - {file = "pydantic-1.10.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d7a8a1dd68bac29f08f0a3147de1885f4dccec35d4ea926e6e637fac03cdb4b3"}, - {file = "pydantic-1.10.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07d00ca5ef0de65dd274005433ce2bb623730271d495a7d190a91c19c5679d34"}, - {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad57004e5d73aee36f1e25e4e73a4bc853b473a1c30f652dc8d86b0a987ffce3"}, - {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dce355fe7ae53e3090f7f5fa242423c3a7b53260747aa398b4b3aaf8b25f41c3"}, - {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0d32227ea9a3bf537a2273fd2fdb6d64ab4d9b83acd9e4e09310a777baaabb98"}, - {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e351df83d1c9cffa53d4e779009a093be70f1d5c6bb7068584086f6a19042526"}, - {file = "pydantic-1.10.19-cp311-cp311-win_amd64.whl", hash = "sha256:d8d72553d2f3f57ce547de4fa7dc8e3859927784ab2c88343f1fc1360ff17a08"}, - {file = "pydantic-1.10.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d5b5b7c6bafaef90cbb7dafcb225b763edd71d9e22489647ee7df49d6d341890"}, - {file = "pydantic-1.10.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:570ad0aeaf98b5e33ff41af75aba2ef6604ee25ce0431ecd734a28e74a208555"}, - {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0890fbd7fec9e151c7512941243d830b2d6076d5df159a2030952d480ab80a4e"}, - {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec5c44e6e9eac5128a9bfd21610df3b8c6b17343285cc185105686888dc81206"}, - {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6eb56074b11a696e0b66c7181da682e88c00e5cebe6570af8013fcae5e63e186"}, - {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d7d48fbc5289efd23982a0d68e973a1f37d49064ccd36d86de4543aff21e086"}, - {file = "pydantic-1.10.19-cp312-cp312-win_amd64.whl", hash = "sha256:fd34012691fbd4e67bdf4accb1f0682342101015b78327eaae3543583fcd451e"}, - {file = "pydantic-1.10.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a5d5b877c7d3d9e17399571a8ab042081d22fe6904416a8b20f8af5909e6c8f"}, - {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c46f58ef2df958ed2ea7437a8be0897d5efe9ee480818405338c7da88186fb3"}, - {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d8a38a44bb6a15810084316ed69c854a7c06e0c99c5429f1d664ad52cec353c"}, - {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a82746c6d6e91ca17e75f7f333ed41d70fce93af520a8437821dec3ee52dfb10"}, - {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:566bebdbe6bc0ac593fa0f67d62febbad9f8be5433f686dc56401ba4aab034e3"}, - {file = "pydantic-1.10.19-cp37-cp37m-win_amd64.whl", hash = "sha256:22a1794e01591884741be56c6fba157c4e99dcc9244beb5a87bd4aa54b84ea8b"}, - {file = "pydantic-1.10.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:076c49e24b73d346c45f9282d00dbfc16eef7ae27c970583d499f11110d9e5b0"}, - {file = "pydantic-1.10.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d4320510682d5a6c88766b2a286d03b87bd3562bf8d78c73d63bab04b21e7b4"}, - {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e66aa0fa7f8aa9d0a620361834f6eb60d01d3e9cea23ca1a92cda99e6f61dac"}, - {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d216f8d0484d88ab72ab45d699ac669fe031275e3fa6553e3804e69485449fa0"}, - {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f28a81978e936136c44e6a70c65bde7548d87f3807260f73aeffbf76fb94c2f"}, - {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d3449633c207ec3d2d672eedb3edbe753e29bd4e22d2e42a37a2c1406564c20f"}, - {file = "pydantic-1.10.19-cp38-cp38-win_amd64.whl", hash = "sha256:7ea24e8614f541d69ea72759ff635df0e612b7dc9d264d43f51364df310081a3"}, - {file = "pydantic-1.10.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:573254d844f3e64093f72fcd922561d9c5696821ff0900a0db989d8c06ab0c25"}, - {file = "pydantic-1.10.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff09600cebe957ecbb4a27496fe34c1d449e7957ed20a202d5029a71a8af2e35"}, - {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4739c206bfb6bb2bdc78dcd40bfcebb2361add4ceac6d170e741bb914e9eff0f"}, - {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfb5b378b78229119d66ced6adac2e933c67a0aa1d0a7adffbe432f3ec14ce4"}, - {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f31742c95e3f9443b8c6fa07c119623e61d76603be9c0d390bcf7e888acabcb"}, - {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c6444368b651a14c2ce2fb22145e1496f7ab23cbdb978590d47c8d34a7bc0289"}, - {file = "pydantic-1.10.19-cp39-cp39-win_amd64.whl", hash = "sha256:945407f4d08cd12485757a281fca0e5b41408606228612f421aa4ea1b63a095d"}, - {file = "pydantic-1.10.19-py3-none-any.whl", hash = "sha256:2206a1752d9fac011e95ca83926a269fb0ef5536f7e053966d058316e24d929f"}, - {file = "pydantic-1.10.19.tar.gz", hash = "sha256:fea36c2065b7a1d28c6819cc2e93387b43dd5d3cf5a1e82d8132ee23f36d1f10"}, + {file = "pydantic-1.10.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:245e486e0fec53ec2366df9cf1cba36e0bbf066af7cd9c974bbbd9ba10e1e586"}, + {file = "pydantic-1.10.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c54f8d4c151c1de784c5b93dfbb872067e3414619e10e21e695f7bb84d1d1fd"}, + {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b64708009cfabd9c2211295144ff455ec7ceb4c4fb45a07a804309598f36187"}, + {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a148410fa0e971ba333358d11a6dea7b48e063de127c2b09ece9d1c1137dde4"}, + {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:36ceadef055af06e7756eb4b871cdc9e5a27bdc06a45c820cd94b443de019bbf"}, + {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0501e1d12df6ab1211b8cad52d2f7b2cd81f8e8e776d39aa5e71e2998d0379f"}, + {file = "pydantic-1.10.21-cp310-cp310-win_amd64.whl", hash = "sha256:c261127c275d7bce50b26b26c7d8427dcb5c4803e840e913f8d9df3f99dca55f"}, + {file = "pydantic-1.10.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b6350b68566bb6b164fb06a3772e878887f3c857c46c0c534788081cb48adf4"}, + {file = "pydantic-1.10.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:935b19fdcde236f4fbf691959fa5c3e2b6951fff132964e869e57c70f2ad1ba3"}, + {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6a04efdcd25486b27f24c1648d5adc1633ad8b4506d0e96e5367f075ed2e0b"}, + {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1ba253eb5af8d89864073e6ce8e6c8dec5f49920cff61f38f5c3383e38b1c9f"}, + {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:57f0101e6c97b411f287a0b7cf5ebc4e5d3b18254bf926f45a11615d29475793"}, + {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e85834f0370d737c77a386ce505c21b06bfe7086c1c568b70e15a568d9670d"}, + {file = "pydantic-1.10.21-cp311-cp311-win_amd64.whl", hash = "sha256:6a497bc66b3374b7d105763d1d3de76d949287bf28969bff4656206ab8a53aa9"}, + {file = "pydantic-1.10.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ed4a5f13cf160d64aa331ab9017af81f3481cd9fd0e49f1d707b57fe1b9f3ae"}, + {file = "pydantic-1.10.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b7693bb6ed3fbe250e222f9415abb73111bb09b73ab90d2d4d53f6390e0ccc1"}, + {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185d5f1dff1fead51766da9b2de4f3dc3b8fca39e59383c273f34a6ae254e3e2"}, + {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38e6d35cf7cd1727822c79e324fa0677e1a08c88a34f56695101f5ad4d5e20e5"}, + {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1d7c332685eafacb64a1a7645b409a166eb7537f23142d26895746f628a3149b"}, + {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c9b782db6f993a36092480eeaab8ba0609f786041b01f39c7c52252bda6d85f"}, + {file = "pydantic-1.10.21-cp312-cp312-win_amd64.whl", hash = "sha256:7ce64d23d4e71d9698492479505674c5c5b92cda02b07c91dfc13633b2eef805"}, + {file = "pydantic-1.10.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0067935d35044950be781933ab91b9a708eaff124bf860fa2f70aeb1c4be7212"}, + {file = "pydantic-1.10.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5e8148c2ce4894ce7e5a4925d9d3fdce429fb0e821b5a8783573f3611933a251"}, + {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4973232c98b9b44c78b1233693e5e1938add5af18042f031737e1214455f9b8"}, + {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:662bf5ce3c9b1cef32a32a2f4debe00d2f4839fefbebe1d6956e681122a9c839"}, + {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98737c3ab5a2f8a85f2326eebcd214510f898881a290a7939a45ec294743c875"}, + {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0bb58bbe65a43483d49f66b6c8474424d551a3fbe8a7796c42da314bac712738"}, + {file = "pydantic-1.10.21-cp313-cp313-win_amd64.whl", hash = "sha256:e622314542fb48542c09c7bd1ac51d71c5632dd3c92dc82ede6da233f55f4848"}, + {file = "pydantic-1.10.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d356aa5b18ef5a24d8081f5c5beb67c0a2a6ff2a953ee38d65a2aa96526b274f"}, + {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08caa8c0468172d27c669abfe9e7d96a8b1655ec0833753e117061febaaadef5"}, + {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c677aa39ec737fec932feb68e4a2abe142682f2885558402602cd9746a1c92e8"}, + {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:79577cc045d3442c4e845df53df9f9202546e2ba54954c057d253fc17cd16cb1"}, + {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:b6b73ab347284719f818acb14f7cd80696c6fdf1bd34feee1955d7a72d2e64ce"}, + {file = "pydantic-1.10.21-cp37-cp37m-win_amd64.whl", hash = "sha256:46cffa24891b06269e12f7e1ec50b73f0c9ab4ce71c2caa4ccf1fb36845e1ff7"}, + {file = "pydantic-1.10.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:298d6f765e3c9825dfa78f24c1efd29af91c3ab1b763e1fd26ae4d9e1749e5c8"}, + {file = "pydantic-1.10.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2f4a2305f15eff68f874766d982114ac89468f1c2c0b97640e719cf1a078374"}, + {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35b263b60c519354afb3a60107d20470dd5250b3ce54c08753f6975c406d949b"}, + {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e23a97a6c2f2db88995496db9387cd1727acdacc85835ba8619dce826c0b11a6"}, + {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3c96fed246ccc1acb2df032ff642459e4ae18b315ecbab4d95c95cfa292e8517"}, + {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b92893ebefc0151474f682e7debb6ab38552ce56a90e39a8834734c81f37c8a9"}, + {file = "pydantic-1.10.21-cp38-cp38-win_amd64.whl", hash = "sha256:b8460bc256bf0de821839aea6794bb38a4c0fbd48f949ea51093f6edce0be459"}, + {file = "pydantic-1.10.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d387940f0f1a0adb3c44481aa379122d06df8486cc8f652a7b3b0caf08435f7"}, + {file = "pydantic-1.10.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:266ecfc384861d7b0b9c214788ddff75a2ea123aa756bcca6b2a1175edeca0fe"}, + {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61da798c05a06a362a2f8c5e3ff0341743e2818d0f530eaac0d6898f1b187f1f"}, + {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a621742da75ce272d64ea57bd7651ee2a115fa67c0f11d66d9dcfc18c2f1b106"}, + {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9e3e4000cd54ef455694b8be9111ea20f66a686fc155feda1ecacf2322b115da"}, + {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f198c8206640f4c0ef5a76b779241efb1380a300d88b1bce9bfe95a6362e674d"}, + {file = "pydantic-1.10.21-cp39-cp39-win_amd64.whl", hash = "sha256:e7f0cda108b36a30c8fc882e4fc5b7eec8ef584aa43aa43694c6a7b274fb2b56"}, + {file = "pydantic-1.10.21-py3-none-any.whl", hash = "sha256:db70c920cba9d05c69ad4a9e7f8e9e83011abb2c6490e561de9ae24aee44925c"}, + {file = "pydantic-1.10.21.tar.gz", hash = "sha256:64b48e2b609a6c22178a56c408ee1215a7206077ecb8a193e2fda31858b2362a"}, ] [package.dependencies] @@ -5500,6 +5504,20 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pympler" +version = "1.1" +description = "A development tool to measure, monitor and analyze the memory behavior of Python objects." +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pympler-1.1-py3-none-any.whl", hash = "sha256:5b223d6027d0619584116a0cbc28e8d2e378f7a79c1e5e024f9ff3b673c58506"}, + {file = "pympler-1.1.tar.gz", hash = "sha256:1eaa867cb8992c218430f1708fdaccda53df064144d1c5656b1e6f1ee6000424"}, +] + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + [[package]] name = "pyparsing" version = "3.2.1" @@ -6085,13 +6103,13 @@ all = ["numpy"] [[package]] name = "rdflib" -version = "7.1.1" +version = "7.1.3" description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information." optional = false python-versions = "<4.0.0,>=3.8.1" files = [ - {file = "rdflib-7.1.1-py3-none-any.whl", hash = "sha256:e590fa9a2c34ba33a667818b5a84be3fb8a4d85868f8038f17912ec84f912a25"}, - {file = "rdflib-7.1.1.tar.gz", hash = "sha256:164de86bd3564558802ca983d84f6616a4a1a420c7a17a8152f5016076b2913e"}, + {file = "rdflib-7.1.3-py3-none-any.whl", hash = "sha256:5402310a9f0f3c07d453d73fd0ad6ba35616286fe95d3670db2b725f3f539673"}, + {file = "rdflib-7.1.3.tar.gz", hash = "sha256:f3dcb4c106a8cd9e060d92f43d593d09ebc3d07adc244f4c7315856a12e383ee"}, ] [package.dependencies] @@ -6210,13 +6228,13 @@ files = [ [[package]] name = "report-guided-annotation" -version = "0.3.2" +version = "0.3.4" description = "Report Guided Annotation" optional = false python-versions = ">=3.7" files = [ - {file = "report_guided_annotation-0.3.2-py3-none-any.whl", hash = "sha256:bec7929ebba0827707801695b474afdf36ad0dbfa233ff1b6c6daffb64feb0b0"}, - {file = "report_guided_annotation-0.3.2.tar.gz", hash = "sha256:f3e6fb43c60f7ca5fa85c90dc11158b0457d4bac1182199a5f5068d6b106b5f5"}, + {file = "report_guided_annotation-0.3.4-py3-none-any.whl", hash = "sha256:21d569c1fcbdc72355526b178bf868ed71b6ee1adc993728558d21e7ab3c2c63"}, + {file = "report_guided_annotation-0.3.4.tar.gz", hash = "sha256:bb48b6dd865b2c6c64f1fca7d675d7f57e9bff01f90362a3b28087563e5a5578"}, ] [package.dependencies] @@ -6316,26 +6334,26 @@ pyasn1 = ">=0.1.3" [[package]] name = "safetensors" -version = "0.5.0" +version = "0.5.2" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "safetensors-0.5.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c683b9b485bee43422ba2855f72777c37647190281e03da4c8d2a69fa5336558"}, - {file = "safetensors-0.5.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6106aa835deb7263f7014f74c05842ab828d6c11d789f2e7e98f26b1a305e72d"}, - {file = "safetensors-0.5.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1349611f74f55c5ee1c1c144c536a2743c38f7d8bf60b9fc8267e0efc0591a2"}, - {file = "safetensors-0.5.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d936028ac799e18644b08a91fd98b4b62ae3dcd0440b1cfcb56535785589f1"}, - {file = "safetensors-0.5.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f26afada2233576ffea6b80042c2c0a8105c164254af56168ec14299ad3122"}, - {file = "safetensors-0.5.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20067e7a5e63f0cbc88457b2a1161e70ff73af4cc3a24bce90309430cd6f6e7e"}, - {file = "safetensors-0.5.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649d6a4aa34d5174ae87289068ccc2fec2a1a998ecf83425aa5a42c3eff69bcf"}, - {file = "safetensors-0.5.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:debff88f41d569a3e93a955469f83864e432af35bb34b16f65a9ddf378daa3ae"}, - {file = "safetensors-0.5.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:bdf6a3e366ea8ba1a0538db6099229e95811194432c684ea28ea7ae28763b8dc"}, - {file = "safetensors-0.5.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0371afd84c200a80eb7103bf715108b0c3846132fb82453ae018609a15551580"}, - {file = "safetensors-0.5.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5ec7fc8c3d2f32ebf1c7011bc886b362e53ee0a1ec6d828c39d531fed8b325d6"}, - {file = "safetensors-0.5.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:53715e4ea0ef23c08f004baae0f609a7773de7d4148727760417c6760cfd6b76"}, - {file = "safetensors-0.5.0-cp38-abi3-win32.whl", hash = "sha256:b85565bc2f0456961a788d2f11d9d892eec46603db0e4923aa9512c2355aa727"}, - {file = "safetensors-0.5.0-cp38-abi3-win_amd64.whl", hash = "sha256:f451941f8aa11e7be5c3fa450e264609a2b1e65fa38ae590a74e55a94d646b76"}, - {file = "safetensors-0.5.0.tar.gz", hash = "sha256:c47b34c549fa1e0c655c4644da31332c61332c732c47c8dd9399347e9aac69d1"}, + {file = "safetensors-0.5.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:45b6092997ceb8aa3801693781a71a99909ab9cc776fbc3fa9322d29b1d3bef2"}, + {file = "safetensors-0.5.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6d0d6a8ee2215a440e1296b843edf44fd377b055ba350eaba74655a2fe2c4bae"}, + {file = "safetensors-0.5.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86016d40bcaa3bcc9a56cd74d97e654b5f4f4abe42b038c71e4f00a089c4526c"}, + {file = "safetensors-0.5.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:990833f70a5f9c7d3fc82c94507f03179930ff7d00941c287f73b6fcbf67f19e"}, + {file = "safetensors-0.5.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dfa7c2f3fe55db34eba90c29df94bcdac4821043fc391cb5d082d9922013869"}, + {file = "safetensors-0.5.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46ff2116150ae70a4e9c490d2ab6b6e1b1b93f25e520e540abe1b81b48560c3a"}, + {file = "safetensors-0.5.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab696dfdc060caffb61dbe4066b86419107a24c804a4e373ba59be699ebd8d5"}, + {file = "safetensors-0.5.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03c937100f38c9ff4c1507abea9928a6a9b02c9c1c9c3609ed4fb2bf413d4975"}, + {file = "safetensors-0.5.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a00e737948791b94dad83cf0eafc09a02c4d8c2171a239e8c8572fe04e25960e"}, + {file = "safetensors-0.5.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:d3a06fae62418ec8e5c635b61a8086032c9e281f16c63c3af46a6efbab33156f"}, + {file = "safetensors-0.5.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:1506e4c2eda1431099cebe9abf6c76853e95d0b7a95addceaa74c6019c65d8cf"}, + {file = "safetensors-0.5.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5c5b5d9da594f638a259fca766046f44c97244cc7ab8bef161b3e80d04becc76"}, + {file = "safetensors-0.5.2-cp38-abi3-win32.whl", hash = "sha256:fe55c039d97090d1f85277d402954dd6ad27f63034fa81985a9cc59655ac3ee2"}, + {file = "safetensors-0.5.2-cp38-abi3-win_amd64.whl", hash = "sha256:78abdddd03a406646107f973c7843276e7b64e5e32623529dc17f3d94a20f589"}, + {file = "safetensors-0.5.2.tar.gz", hash = "sha256:cb4a8d98ba12fa016f4241932b1fc5e702e5143f5374bba0bbcf7ddc1c4cf2b8"}, ] [package.extras] @@ -6446,51 +6464,51 @@ tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc ( [[package]] name = "scipy" -version = "1.15.0" +version = "1.15.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" files = [ - {file = "scipy-1.15.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:aeac60d3562a7bf2f35549bdfdb6b1751c50590f55ce7322b4b2fc821dc27fca"}, - {file = "scipy-1.15.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5abbdc6ede5c5fed7910cf406a948e2c0869231c0db091593a6b2fa78be77e5d"}, - {file = "scipy-1.15.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:eb1533c59f0ec6c55871206f15a5c72d1fae7ad3c0a8ca33ca88f7c309bbbf8c"}, - {file = "scipy-1.15.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:de112c2dae53107cfeaf65101419662ac0a54e9a088c17958b51c95dac5de56d"}, - {file = "scipy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2240e1fd0782e62e1aacdc7234212ee271d810f67e9cd3b8d521003a82603ef8"}, - {file = "scipy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35aef233b098e4de88b1eac29f0df378278e7e250a915766786b773309137c4"}, - {file = "scipy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b29e4fc02e155a5fd1165f1e6a73edfdd110470736b0f48bcbe48083f0eee37"}, - {file = "scipy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e5b34f8894f9904cc578008d1a9467829c1817e9f9cb45e6d6eeb61d2ab7731"}, - {file = "scipy-1.15.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:46e91b5b16909ff79224b56e19cbad65ca500b3afda69225820aa3afbf9ec020"}, - {file = "scipy-1.15.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:82bff2eb01ccf7cea8b6ee5274c2dbeadfdac97919da308ee6d8e5bcbe846443"}, - {file = "scipy-1.15.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:9c8254fe21dd2c6c8f7757035ec0c31daecf3bb3cffd93bc1ca661b731d28136"}, - {file = "scipy-1.15.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:c9624eeae79b18cab1a31944b5ef87aa14b125d6ab69b71db22f0dbd962caf1e"}, - {file = "scipy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d13bbc0658c11f3d19df4138336e4bce2c4fbd78c2755be4bf7b8e235481557f"}, - {file = "scipy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdca4c7bb8dc41307e5f39e9e5d19c707d8e20a29845e7533b3bb20a9d4ccba0"}, - {file = "scipy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f376d7c767731477bac25a85d0118efdc94a572c6b60decb1ee48bf2391a73b"}, - {file = "scipy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:61513b989ee8d5218fbeb178b2d51534ecaddba050db949ae99eeb3d12f6825d"}, - {file = "scipy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5beb0a2200372b7416ec73fdae94fe81a6e85e44eb49c35a11ac356d2b8eccc6"}, - {file = "scipy-1.15.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fde0f3104dfa1dfbc1f230f65506532d0558d43188789eaf68f97e106249a913"}, - {file = "scipy-1.15.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:35c68f7044b4e7ad73a3e68e513dda946989e523df9b062bd3cf401a1a882192"}, - {file = "scipy-1.15.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:52475011be29dfcbecc3dfe3060e471ac5155d72e9233e8d5616b84e2b542054"}, - {file = "scipy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5972e3f96f7dda4fd3bb85906a17338e65eaddfe47f750e240f22b331c08858e"}, - {file = "scipy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00169cf875bed0b3c40e4da45b57037dc21d7c7bf0c85ed75f210c281488f1"}, - {file = "scipy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:161f80a98047c219c257bf5ce1777c574bde36b9d962a46b20d0d7e531f86863"}, - {file = "scipy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:327163ad73e54541a675240708244644294cb0a65cca420c9c79baeb9648e479"}, - {file = "scipy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0fcb16eb04d84670722ce8d93b05257df471704c913cb0ff9dc5a1c31d1e9422"}, - {file = "scipy-1.15.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:767e8cf6562931f8312f4faa7ddea412cb783d8df49e62c44d00d89f41f9bbe8"}, - {file = "scipy-1.15.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:37ce9394cdcd7c5f437583fc6ef91bd290014993900643fdfc7af9b052d1613b"}, - {file = "scipy-1.15.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6d26f17c64abd6c6c2dfb39920f61518cc9e213d034b45b2380e32ba78fde4c0"}, - {file = "scipy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e2448acd79c6374583581a1ded32ac71a00c2b9c62dfa87a40e1dd2520be111"}, - {file = "scipy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36be480e512d38db67f377add5b759fb117edd987f4791cdf58e59b26962bee4"}, - {file = "scipy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ccb6248a9987193fe74363a2d73b93bc2c546e0728bd786050b7aef6e17db03c"}, - {file = "scipy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:952d2e9eaa787f0a9e95b6e85da3654791b57a156c3e6609e65cc5176ccfe6f2"}, - {file = "scipy-1.15.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b1432102254b6dc7766d081fa92df87832ac25ff0b3d3a940f37276e63eb74ff"}, - {file = "scipy-1.15.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:4e08c6a36f46abaedf765dd2dfcd3698fa4bd7e311a9abb2d80e33d9b2d72c34"}, - {file = "scipy-1.15.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ec915cd26d76f6fc7ae8522f74f5b2accf39546f341c771bb2297f3871934a52"}, - {file = "scipy-1.15.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:351899dd2a801edd3691622172bc8ea01064b1cada794f8641b89a7dc5418db6"}, - {file = "scipy-1.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9baff912ea4f78a543d183ed6f5b3bea9784509b948227daaf6f10727a0e2e5"}, - {file = "scipy-1.15.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cd9d9198a7fd9a77f0eb5105ea9734df26f41faeb2a88a0e62e5245506f7b6df"}, - {file = "scipy-1.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:129f899ed275c0515d553b8d31696924e2ca87d1972421e46c376b9eb87de3d2"}, - {file = "scipy-1.15.0.tar.gz", hash = "sha256:300742e2cc94e36a2880ebe464a1c8b4352a7b0f3e36ec3d2ac006cdbe0219ac"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:4b17d4220df99bacb63065c76b0d1126d82bbf00167d1730019d2a30d6ae01ea"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:63b9b6cd0333d0eb1a49de6f834e8aeaefe438df8f6372352084535ad095219e"}, + {file = "scipy-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f151e9fb60fbf8e52426132f473221a49362091ce7a5e72f8aa41f8e0da4f25"}, + {file = "scipy-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e10b1dd56ce92fba3e786007322542361984f8463c6d37f6f25935a5a6ef52"}, + {file = "scipy-1.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5dff14e75cdbcf07cdaa1c7707db6017d130f0af9ac41f6ce443a93318d6c6e0"}, + {file = "scipy-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:f82fcf4e5b377f819542fbc8541f7b5fbcf1c0017d0df0bc22c781bf60abc4d8"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:5bd8d27d44e2c13d0c1124e6a556454f52cd3f704742985f6b09e75e163d20d2"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:be3deeb32844c27599347faa077b359584ba96664c5c79d71a354b80a0ad0ce0"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:5eb0ca35d4b08e95da99a9f9c400dc9f6c21c424298a0ba876fdc69c7afacedf"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:74bb864ff7640dea310a1377d8567dc2cb7599c26a79ca852fc184cc851954ac"}, + {file = "scipy-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:667f950bf8b7c3a23b4199db24cb9bf7512e27e86d0e3813f015b74ec2c6e3df"}, + {file = "scipy-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395be70220d1189756068b3173853029a013d8c8dd5fd3d1361d505b2aa58fa7"}, + {file = "scipy-1.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce3a000cd28b4430426db2ca44d96636f701ed12e2b3ca1f2b1dd7abdd84b39a"}, + {file = "scipy-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fe1d95944f9cf6ba77aa28b82dd6bb2a5b52f2026beb39ecf05304b8392864b"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c09aa9d90f3500ea4c9b393ee96f96b0ccb27f2f350d09a47f533293c78ea776"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0ac102ce99934b162914b1e4a6b94ca7da0f4058b6d6fd65b0cef330c0f3346f"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:09c52320c42d7f5c7748b69e9f0389266fd4f82cf34c38485c14ee976cb8cb04"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:cdde8414154054763b42b74fe8ce89d7f3d17a7ac5dd77204f0e142cdc9239e9"}, + {file = "scipy-1.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c9d8fc81d6a3b6844235e6fd175ee1d4c060163905a2becce8e74cb0d7554ce"}, + {file = "scipy-1.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb57b30f0017d4afa5fe5f5b150b8f807618819287c21cbe51130de7ccdaed2"}, + {file = "scipy-1.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491d57fe89927fa1aafbe260f4cfa5ffa20ab9f1435025045a5315006a91b8f5"}, + {file = "scipy-1.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:900f3fa3db87257510f011c292a5779eb627043dd89731b9c461cd16ef76ab3d"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:100193bb72fbff37dbd0bf14322314fc7cbe08b7ff3137f11a34d06dc0ee6b85"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:2114a08daec64980e4b4cbdf5bee90935af66d750146b1d2feb0d3ac30613692"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6b3e71893c6687fc5e29208d518900c24ea372a862854c9888368c0b267387ab"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:837299eec3d19b7e042923448d17d95a86e43941104d33f00da7e31a0f715d3c"}, + {file = "scipy-1.15.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82add84e8a9fb12af5c2c1a3a3f1cb51849d27a580cb9e6bd66226195142be6e"}, + {file = "scipy-1.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070d10654f0cb6abd295bc96c12656f948e623ec5f9a4eab0ddb1466c000716e"}, + {file = "scipy-1.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55cc79ce4085c702ac31e49b1e69b27ef41111f22beafb9b49fea67142b696c4"}, + {file = "scipy-1.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:c352c1b6d7cac452534517e022f8f7b8d139cd9f27e6fbd9f3cbd0bfd39f5bef"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0458839c9f873062db69a03de9a9765ae2e694352c76a16be44f93ea45c28d2b"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:af0b61c1de46d0565b4b39c6417373304c1d4f5220004058bdad3061c9fa8a95"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:71ba9a76c2390eca6e359be81a3e879614af3a71dfdabb96d1d7ab33da6f2364"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14eaa373c89eaf553be73c3affb11ec6c37493b7eaaf31cf9ac5dffae700c2e0"}, + {file = "scipy-1.15.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f735bc41bd1c792c96bc426dece66c8723283695f02df61dcc4d0a707a42fc54"}, + {file = "scipy-1.15.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2722a021a7929d21168830790202a75dbb20b468a8133c74a2c0230c72626b6c"}, + {file = "scipy-1.15.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc7136626261ac1ed988dca56cfc4ab5180f75e0ee52e58f1e6aa74b5f3eacd5"}, + {file = "scipy-1.15.1.tar.gz", hash = "sha256:033a75ddad1463970c96a88063a1df87ccfddd526437136b6ee81ff0312ebdf6"}, ] [package.dependencies] @@ -6620,13 +6638,13 @@ doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "sentry-sdk" -version = "2.19.2" +version = "2.20.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" files = [ - {file = "sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84"}, - {file = "sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d"}, + {file = "sentry_sdk-2.20.0-py2.py3-none-any.whl", hash = "sha256:c359a1edf950eb5e80cffd7d9111f3dbeef57994cb4415df37d39fda2cf22364"}, + {file = "sentry_sdk-2.20.0.tar.gz", hash = "sha256:afa82713a92facf847df3c6f63cec71eb488d826a50965def3d7722aa6f0fdab"}, ] [package.dependencies] @@ -6671,6 +6689,7 @@ sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=6)"] +unleash = ["UnleashClient (>=6.0.1)"] [[package]] name = "setproctitle" @@ -6771,13 +6790,13 @@ test = ["pytest"] [[package]] name = "setuptools" -version = "75.7.0" +version = "75.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" files = [ - {file = "setuptools-75.7.0-py3-none-any.whl", hash = "sha256:84fb203f278ebcf5cd08f97d3fb96d3fbed4b629d500b29ad60d11e00769b183"}, - {file = "setuptools-75.7.0.tar.gz", hash = "sha256:886ff7b16cd342f1d1defc16fc98c9ce3fde69e087a4e1983d7ab634e5f41f4f"}, + {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, + {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, ] [package.extras] @@ -6802,31 +6821,31 @@ files = [ [[package]] name = "simpleitk" -version = "2.4.0" +version = "2.4.1" description = "SimpleITK is a simplified interface to the Insight Toolkit (ITK) for image registration and segmentation" optional = false python-versions = "*" files = [ - {file = "SimpleITK-2.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a0493cf49291c6fee067463f2c353690878666500d4799c1bd0facf83302b9a"}, - {file = "SimpleITK-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aedea771980e558940f0c5ef1ee180a822ebcdbf3b65faf609bfaf45c8b96fc1"}, - {file = "SimpleITK-2.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bac63ed45ac8b7c9b6983e0e4216a217af3b86dd5fb2ba9343b30e33e6d6a3e"}, - {file = "SimpleITK-2.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6797a540f50d80b128232a940438dff4c994b8a55eac8e96075ccc80e59f1db"}, - {file = "SimpleITK-2.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:8b6ce8e6b8d81e9340cc895ec604d6ede5ce38141fa84173287e0be5e76b0319"}, - {file = "SimpleITK-2.4.0-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bcdcdb14cc4da7bcf3b00dbbe5b8d478e6b0e59962406c2c480b6bb0441fa1dc"}, - {file = "SimpleITK-2.4.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:09eb7982638b049ca36cea9f8612071af2c3f0c74776aad35c7a5aebb4a3f90e"}, - {file = "SimpleITK-2.4.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db42b4a7e934df21ad051706612da2cdcc4fdd3d8d360948878d27d0d92129b4"}, - {file = "SimpleITK-2.4.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a8eaec0383d39f5a39b4307d0310611dad08182e709dd0fe1e788f80f24b35"}, - {file = "SimpleITK-2.4.0-cp311-abi3-win_amd64.whl", hash = "sha256:f9681520793a36b229f1f177790264ab7503180a6ea9c38b2c1d219d40f87994"}, - {file = "SimpleITK-2.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:009f337515566accb28971378c85dc96797584ea411a3470fa038958249fa47d"}, - {file = "SimpleITK-2.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ddeb39e41f441c0fc41d07fb5cf89da5d259f05a58d30a62833de15c17f9b69d"}, - {file = "SimpleITK-2.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab00edbb7a7b961deec4b4ad7ee105721997b56622f3df2d70732e20720ef4b"}, - {file = "SimpleITK-2.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4bc7ad223253df50436123861925210c9139191e50d9caec80446370052866e"}, - {file = "SimpleITK-2.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:e5c78984eb9390a0e3963235ac80d1d8e097ad154cade0a84b895e2d34e094b0"}, - {file = "SimpleITK-2.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:752022971d18604fbf6fe7737d1231cf8de866b6a98532aece8d389c3a6e613d"}, - {file = "SimpleITK-2.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:989143803527b2ab983cf53274b1a7ec05586a55801f73fbe9d6767486c55831"}, - {file = "SimpleITK-2.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fc64ef6ba63832ff5dee4112bcc45367d6f2124cdad187f5daf3552bdf2a2d7"}, - {file = "SimpleITK-2.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:888ee5e04c9e4e02e7d31f0555fdd88240b7a7a9e883cf40780c51d45aaf3950"}, - {file = "SimpleITK-2.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:f3ff657a58ce515c5742eedcd711ddeddb1673b8bac71be725b3182a936e29ff"}, + {file = "SimpleITK-2.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:682fe4de005688a61e226b552399d3d17ca343b5211d0b81b66078bf657ba8a9"}, + {file = "SimpleITK-2.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:634cfdace29ad0a6252b3ed9ede5e5ccf37d7c8ae609c39781187cef49ada75e"}, + {file = "SimpleITK-2.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:997b2e702992f0a90567d15271fc762513486e4a34de2cc5ee8a217a8e2e1ac0"}, + {file = "SimpleITK-2.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ebc7ae651cb5d774c68d8978b4d770a5f46897f1b5c714a68a63716de0961e7"}, + {file = "SimpleITK-2.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f121665a870b1c1f2afc82d9ca6e41f28662850d7b04496f01cd2def6f37e0fd"}, + {file = "SimpleITK-2.4.1-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f48acf72a13dc5955b9b8e9853560666c978b3166e2ccaf4d9ba352024218080"}, + {file = "SimpleITK-2.4.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:204a5a87f8f2dd0b018a304f4dfb302adbf8fb4804b75c231340a37a36dd59de"}, + {file = "SimpleITK-2.4.1-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8347e491e1e387d192d924b6b3091a3db7998c41dee15f863b662f71c8423633"}, + {file = "SimpleITK-2.4.1-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a963e107138611a062c00d9513357772dbf33e0f6fa7214231d392f63c8ffaa"}, + {file = "SimpleITK-2.4.1-cp311-abi3-win_amd64.whl", hash = "sha256:e98389f74b557bf90bf2baa6fc16e8d5b7e149251e92868724cee94ca744b1b5"}, + {file = "SimpleITK-2.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45c8f099170c89e1f2cfb4d03e8142d0c4a53fd2e1390f4113e55e0fbf549026"}, + {file = "SimpleITK-2.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:624fce1b190117e50c6000232fc9bd45cf01cebef40e5a5db5de1ad0e74fdbdb"}, + {file = "SimpleITK-2.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df74bb128e2043d75bd0071b67179acb20beb6b7520c1d3b210de94e59971c4e"}, + {file = "SimpleITK-2.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db35e5159269a35295efa78f7f78da549c742c0fcee613eafbcab61ec43e42af"}, + {file = "SimpleITK-2.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:c9d8ef6b8fcaf6be48affc81136e7f5898b27702172a9b254ac4bbab0bef5ef0"}, + {file = "SimpleITK-2.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:12216fec03af70a2d2382d56518878d21f329256bf8bf96d74f366a450a292f5"}, + {file = "SimpleITK-2.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbbdcc14aece4f5e25bc43ac51dff7622a0f58aea29264f2c12d249adf48049"}, + {file = "SimpleITK-2.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e8db85764e39a585b38dad19161d5820225ad5d78c2ac917180cfba22caa36a"}, + {file = "SimpleITK-2.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa619181d82d82913129dfa8b300204071402c689fded79e80d279f1ebed215d"}, + {file = "SimpleITK-2.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:d109c654e87ac543ac6b381cf56bf6352a110746b44bff9961212d1e86edd4d7"}, ] [[package]] @@ -6892,72 +6911,72 @@ test = ["pyshacl", "pytest", "tzdata"] [[package]] name = "sqlalchemy" -version = "2.0.36" +version = "2.0.37" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, - {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, - {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-win32.whl", hash = "sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-win_amd64.whl", hash = "sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-win32.whl", hash = "sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-win_amd64.whl", hash = "sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-win32.whl", hash = "sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-win_amd64.whl", hash = "sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-win32.whl", hash = "sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-win_amd64.whl", hash = "sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-win32.whl", hash = "sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-win_amd64.whl", hash = "sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b"}, + {file = "SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1"}, + {file = "sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] @@ -7234,21 +7253,21 @@ files = [ [[package]] name = "tifffile" -version = "2024.12.12" +version = "2025.1.10" description = "Read and write TIFF files" optional = false python-versions = ">=3.10" files = [ - {file = "tifffile-2024.12.12-py3-none-any.whl", hash = "sha256:6ff0f196a46a75c8c0661c70995e06ea4d08a81fe343193e69f1673f4807d508"}, - {file = "tifffile-2024.12.12.tar.gz", hash = "sha256:c38e929bf74c04b6c8708d87f16b32c85c6d7c2514b99559ea3db8003ba4edda"}, + {file = "tifffile-2025.1.10-py3-none-any.whl", hash = "sha256:ed24cf4c99fb13b4f5fb29f8a0d5605e60558c950bccbdca2a6470732a27cfb3"}, + {file = "tifffile-2025.1.10.tar.gz", hash = "sha256:baaf0a3b87bf7ec375fa1537503353f70497eabe1bdde590f2e41cc0346e612f"}, ] [package.dependencies] numpy = "*" [package.extras] -all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib", "zarr (<3)"] -codecs = ["imagecodecs (>=2023.8.12)"] +all = ["defusedxml", "fsspec", "imagecodecs (>=2024.12.30)", "lxml", "matplotlib", "zarr (<3)"] +codecs = ["imagecodecs (>=2024.12.30)"] plot = ["matplotlib"] test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "roifile", "xarray", "zarr (<3)"] xml = ["defusedxml", "lxml"] @@ -7661,13 +7680,13 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "transformers" -version = "4.47.1" +version = "4.48.1" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" optional = false python-versions = ">=3.9.0" files = [ - {file = "transformers-4.47.1-py3-none-any.whl", hash = "sha256:d2f5d19bb6283cd66c893ec7e6d931d6370bbf1cc93633326ff1f41a40046c9c"}, - {file = "transformers-4.47.1.tar.gz", hash = "sha256:6c29c05a5f595e278481166539202bf8641281536df1c42357ee58a45d0a564a"}, + {file = "transformers-4.48.1-py3-none-any.whl", hash = "sha256:24be0564b0a36d9e433d9a65de248f1545b6f6edce1737669605eb6a8141bbbb"}, + {file = "transformers-4.48.1.tar.gz", hash = "sha256:7c1931facc3ee8adcbf86fc7a87461d54c1e40eca3bb57fef1ee9f3ecd32187e"}, ] [package.dependencies] @@ -7684,16 +7703,16 @@ tqdm = ">=4.27" [package.extras] accelerate = ["accelerate (>=0.26.0)"] -agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch"] -all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch", "torchaudio", "torchvision"] +agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch (>=2.0)"] +all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av (==9.2.0)", "codecarbon (>=2.8.1)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.0)", "torchaudio", "torchvision"] audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] benchmark = ["optimum-benchmark (>=0.3.0)"] -codecarbon = ["codecarbon (==1.2.0)"] +codecarbon = ["codecarbon (>=2.8.1)"] deepspeed = ["accelerate (>=0.26.0)", "deepspeed (>=0.9.3)"] -deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.26.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "nltk (<=3.8.1)", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "nltk (<=3.8.1)", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.21,<0.22)", "urllib3 (<2.0.0)"] -dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "libcst", "librosa", "nltk (<=3.8.1)", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.26.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (>=2.8.1)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "nltk (<=3.8.1)", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "nltk (<=3.8.1)", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-rich", "pytest-timeout", "pytest-xdist", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.21,<0.22)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "beautifulsoup4", "codecarbon (>=2.8.1)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "libcst", "librosa", "nltk (<=3.8.1)", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)", "scipy (<1.13.0)"] flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] ftfy = ["ftfy"] @@ -7714,17 +7733,17 @@ serving = ["fastapi", "pydantic", "starlette", "uvicorn"] sigopt = ["sigopt"] sklearn = ["scikit-learn"] speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] tf = ["keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<0.24)", "tensorflow-text (<2.16)", "tf2onnx"] tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] tiktoken = ["blobfile", "tiktoken"] timm = ["timm (<=1.0.11)"] tokenizers = ["tokenizers (>=0.21,<0.22)"] -torch = ["accelerate (>=0.26.0)", "torch"] +torch = ["accelerate (>=0.26.0)", "torch (>=2.0)"] torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] -torchhub = ["filelock", "huggingface-hub (>=0.24.0,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.21,<0.22)", "torch", "tqdm (>=4.27)"] +torchhub = ["filelock", "huggingface-hub (>=0.24.0,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.21,<0.22)", "torch (>=2.0)", "tqdm (>=4.27)"] video = ["av (==9.2.0)"] vision = ["Pillow (>=10.0.1,<=15.0)"] @@ -7753,13 +7772,13 @@ tutorials = ["matplotlib", "pandas", "tabulate", "torch"] [[package]] name = "trove-classifiers" -version = "2025.1.6.15" +version = "2025.1.15.22" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" files = [ - {file = "trove_classifiers-2025.1.6.15-py3-none-any.whl", hash = "sha256:cd8e14d53158c72b68f7adcd119e3b4c7271d282999d3a8ec7486733a6202e4d"}, - {file = "trove_classifiers-2025.1.6.15.tar.gz", hash = "sha256:49ab0326e000c54be6adc945d8fd618265c27de1296cf17caf9106fef96d2da4"}, + {file = "trove_classifiers-2025.1.15.22-py3-none-any.whl", hash = "sha256:5f19c789d4f17f501d36c94dbbf969fb3e8c2784d008e6f5164dd2c3d6a2b07c"}, + {file = "trove_classifiers-2025.1.15.22.tar.gz", hash = "sha256:90af74358d3a01b3532bc7b3c88d8c6a094c2fd50a563d13d9576179326d7ed9"}, ] [[package]] @@ -7952,13 +7971,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.28.1" +version = "20.29.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"}, - {file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"}, + {file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"}, + {file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"}, ] [package.dependencies] @@ -8169,69 +8188,81 @@ files = [ [[package]] name = "xattr" -version = "1.1.0" +version = "1.1.4" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" files = [ - {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef2fa0f85458736178fd3dcfeb09c3cf423f0843313e25391db2cfd1acec8888"}, - {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccab735d0632fe71f7d72e72adf886f45c18b7787430467ce0070207882cfe25"}, - {file = "xattr-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9013f290387f1ac90bccbb1926555ca9aef75651271098d99217284d9e010f7c"}, - {file = "xattr-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcd5dfbcee73c7be057676ecb900cabb46c691aff4397bf48c579ffb30bb963"}, - {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6480589c1dac7785d1f851347a32c4a97305937bf7b488b857fe8b28a25de9e9"}, - {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08f61cbed52dc6f7c181455826a9ff1e375ad86f67dd9d5eb7663574abb32451"}, - {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:918e1f83f2e8a072da2671eac710871ee5af337e9bf8554b5ce7f20cdb113186"}, - {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0f06e0c1e4d06b4e0e49aaa1184b6f0e81c3758c2e8365597918054890763b53"}, - {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a641ac038a9f53d2f696716147ca4dbd6a01998dc9cd4bc628801bc0df7f4d"}, - {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e4ca0956fd11679bb2e0c0d6b9cdc0f25470cc00d8da173bb7656cc9a9cf104"}, - {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6881b120f9a4b36ccd8a28d933bc0f6e1de67218b6ce6e66874e0280fc006844"}, - {file = "xattr-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab29d9288aa28e68a6f355ddfc3f0a7342b40c9012798829f3e7bd765e85c2c"}, - {file = "xattr-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c80bbf55339c93770fc294b4b6586b5bf8e85ec00a4c2d585c33dbd84b5006"}, - {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1418705f253b6b6a7224b69773842cac83fcbcd12870354b6e11dd1cd54630f"}, - {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687e7d18611ef8d84a6ecd8f4d1ab6757500c1302f4c2046ce0aa3585e13da3f"}, - {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6ceb9efe0657a982ccb8b8a2efe96b690891779584c901d2f920784e5d20ae3"}, - {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b489b7916f239100956ea0b39c504f3c3a00258ba65677e4c8ba1bd0b5513446"}, - {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0a9c431b0e66516a078125e9a273251d4b8e5ba84fe644b619f2725050d688a0"}, - {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1a5921ea3313cc1c57f2f53b63ea8ca9a91e48f4cc7ebec057d2447ec82c7efe"}, - {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6ad2a7bd5e6cf71d4a862413234a067cf158ca0ae94a40d4b87b98b62808498"}, - {file = "xattr-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0683dae7609f7280b0c89774d00b5957e6ffcb181c6019c46632b389706b77e6"}, - {file = "xattr-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cb15cd94e5ef8a0ef02309f1bf973ba0e13c11e87686e983f371948cfee6af"}, - {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff6223a854229055e803c2ad0c0ea9a6da50c6be30d92c198cf5f9f28819a921"}, - {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d44e8f955218638c9ab222eed21e9bd9ab430d296caf2176fb37abe69a714e5c"}, - {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:caab2c2986c30f92301f12e9c50415d324412e8e6a739a52a603c3e6a54b3610"}, - {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d6eb7d5f281014cd44e2d847a9107491af1bf3087f5afeded75ed3e37ec87239"}, - {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:47a3bdfe034b4fdb70e5941d97037405e3904accc28e10dbef6d1c9061fb6fd7"}, - {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00d2b415cf9d6a24112d019e721aa2a85652f7bbc9f3b9574b2d1cd8668eb491"}, - {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:78b377832dd0ee408f9f121a354082c6346960f7b6b1480483ed0618b1912120"}, - {file = "xattr-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6461a43b585e5f2e049b39bcbfcb6391bfef3c5118231f1b15d10bdb89ef17fe"}, - {file = "xattr-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24d97f0d28f63695e3344ffdabca9fcc30c33e5c8ccc198c7524361a98d526f2"}, - {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad47d89968c9097900607457a0c89160b4771601d813e769f68263755516065"}, - {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc53cab265f6e8449bd683d5ee3bc5a191e6dd940736f3de1a188e6da66b0653"}, - {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cd11e917f5b89f2a0ad639d9875943806c6c9309a3dd02da5a3e8ef92db7bed9"}, - {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9c5a78c7558989492c4cb7242e490ffb03482437bf782967dfff114e44242343"}, - {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cebcf8a303a44fbc439b68321408af7267507c0d8643229dbb107f6c132d389c"}, - {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b0d73150f2f9655b4da01c2369eb33a294b7f9d56eccb089819eafdbeb99f896"}, - {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:793c01deaadac50926c0e1481702133260c7cb5e62116762f6fe1543d07b826f"}, - {file = "xattr-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e189e440bcd04ccaad0474720abee6ee64890823ec0db361fb0a4fb5e843a1bf"}, - {file = "xattr-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afacebbc1fa519f41728f8746a92da891c7755e6745164bd0d5739face318e86"}, - {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b1664edf003153ac8d1911e83a0fc60db1b1b374ee8ac943f215f93754a1102"}, - {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda2684228798e937a7c29b0e1c7ef3d70e2b85390a69b42a1c61b2039ba81de"}, - {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b735ac2625a4fc2c9343b19f806793db6494336338537d2911c8ee4c390dda46"}, - {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fa6a7af7a4ada43f15ccc58b6f9adcdbff4c36ba040013d2681e589e07ae280a"}, - {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1059b2f726e2702c8bbf9bbf369acfc042202a4cc576c2dec6791234ad5e948"}, - {file = "xattr-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2255f36ebf2cb2dbf772a7437ad870836b7396e60517211834cf66ce678b595"}, - {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba4f80b9855cc98513ddf22b7ad8551bc448c70d3147799ea4f6c0b758fb466"}, - {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb70c16e7c3ae6ba0ab6c6835c8448c61d8caf43ea63b813af1f4dbe83dd156"}, - {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83652910ef6a368b77b00825ad67815e5c92bfab551a848ca66e9981d14a7519"}, - {file = "xattr-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7a92aff66c43fa3e44cbeab7cbeee66266c91178a0f595e044bf3ce51485743b"}, - {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f71b673339aeaae1f6ea9ef8ea6c9643c8cd0df5003b9a0eaa75403e2e06c"}, - {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a20de1c47b5cd7b47da61799a3b34e11e5815d716299351f82a88627a43f9a96"}, - {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23705c7079b05761ff2fa778ad17396e7599c8759401abc05b312dfb3bc99f69"}, - {file = "xattr-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27272afeba8422f2a9d27e1080a9a7b807394e88cce73db9ed8d2dde3afcfb87"}, - {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd43978966de3baf4aea367c99ffa102b289d6c2ea5f3d9ce34a203dc2f2ab73"}, - {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded771eaf27bb4eb3c64c0d09866460ee8801d81dc21097269cf495b3cac8657"}, - {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca300c0acca4f0cddd2332bb860ef58e1465d376364f0e72a1823fdd58e90d"}, - {file = "xattr-1.1.0.tar.gz", hash = "sha256:fecbf3b05043ed3487a28190dec3e4c4d879b2fcec0e30bafd8ec5d4b6043630"}, + {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb85b6249e9f3ea10cbb56df1021d43f4027212f0d004304bc9075dc7f54769"}, + {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a848ab125c0fafdc501ccd83b4c9018bba576a037a4ca5960a22f39e295552e"}, + {file = "xattr-1.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:467ee77471d26ae5187ee7081b82175b5ca56ead4b71467ec2e6119d1b08beed"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd35f46cb0154f7033f9d5d0960f226857acb0d1e0d71fd7af18ed84663007c"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d956478e9bb98a1efd20ebc6e5703497c1d2d690d5a13c4df4abf59881eed50"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f25dfdcd974b700fb04a40e14a664a80227ee58e02ea062ac241f0d7dc54b4e"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33b63365c1fcbc80a79f601575bac0d6921732e0245b776876f3db3fcfefe22d"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:544542be95c9b49e211f0a463758f200de88ba6d5a94d3c4f42855a484341acd"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac14c9893f3ea046784b7702be30889b200d31adcd2e6781a8a190b6423f9f2d"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bb4bbe37ba95542081890dd34fa5347bef4651e276647adaa802d5d0d7d86452"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3da489ecef798705f9a39ea8cea4ead0d1eeed55f92c345add89740bd930bab6"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:798dd0cbe696635a6f74b06fc430818bf9c3b24314e1502eadf67027ab60c9b0"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2b6361626efad5eb5a6bf8172c6c67339e09397ee8140ec41258737bea9681"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7fa20a0c9ce022d19123b1c5b848d00a68b837251835a7929fe041ee81dcd0"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20eeb08e2c57fc7e71f050b1cfae35cbb46105449853a582bf53fd23c5379e"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:477370e75821bded901487e5e752cffe554d1bd3bd4839b627d4d1ee8c95a093"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a8682091cd34a9f4a93c8aaea4101aae99f1506e24da00a3cc3dd2eca9566f21"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2e079b3b1a274ba2121cf0da38bbe5c8d2fb1cc49ecbceb395ce20eb7d69556d"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ae6579dea05bf9f335a082f711d5924a98da563cac72a2d550f5b940c401c0e9"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd6038ec9df2e67af23c212693751481d5f7e858156924f14340376c48ed9ac7"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:608b2877526674eb15df4150ef4b70b7b292ae00e65aecaae2f192af224be200"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54dad1a6a998c6a23edfd25e99f4d38e9b942d54e518570044edf8c767687ea"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0dab6ff72bb2b508f3850c368f8e53bd706585012676e1f71debba3310acde8"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3c54c6af7cf09432b2c461af257d5f4b1cb2d59eee045f91bacef44421a46d"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e346e05a158d554639fbf7a0db169dc693c2d2260c7acb3239448f1ff4a9d67f"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3ff6d9e2103d0d6e5fcd65b85a2005b66ea81c0720a37036445faadc5bbfa424"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a2ee4563c6414dfec0d1ac610f59d39d5220531ae06373eeb1a06ee37cd193f"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878df1b38cfdadf3184ad8c7b0f516311128d5597b60ac0b3486948953658a83"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c9b8350244a1c5454f93a8d572628ff71d7e2fc2f7480dcf4c4f0e8af3150fe"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a46bf48fb662b8bd745b78bef1074a1e08f41a531168de62b5d7bd331dadb11a"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83fc3c07b583777b1dda6355329f75ca6b7179fe0d1002f1afe0ef96f7e3b5de"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6308b19cff71441513258699f0538394fad5d66e1d324635207a97cb076fd439"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48c00ddc15ddadc9c729cd9504dabf50adb3d9c28f647d4ac9a3df45a046b1a0"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a06136196f26293758e1b244200b73156a0274af9a7349fa201c71c7af3bb9e8"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8fc2631a3c6cfcdc71f7f0f847461839963754e76a2015de71e7e71e3304abc0"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6e1e835f9c938d129dd45e7eb52ebf7d2d6816323dab93ce311bf331f7d2328"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:60dea2d369a6484e8b7136224fc2971e10e2c46340d83ab780924afe78c90066"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85c2b778b09d919523f80f244d799a142302582d76da18903dc693207c4020b0"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ee0abba9e1b890d39141714ff43e9666864ca635ea8a5a2194d989e6b17fe862"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e4174ba7f51f46b95ea7918d907c91cd579575d59e6a2f22ca36a0551026737"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2b05e52e99d82d87528c54c2c5c8c5fb0ba435f85ac6545511aeea136e49925"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a3696fad746be37de34eb73c60ea67144162bd08106a5308a90ce9dea9a3287"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3a7149439a26b68904c14fdc4587cde4ac7d80303e9ff0fefcfd893b698c976"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:507b36a126ce900dbfa35d4e2c2db92570c933294cba5d161ecd6a89f7b52f43"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9392b417b54923e031041940d396b1d709df1d3779c6744454e1f1c1f4dad4f5"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e9f00315e6c02943893b77f544776b49c756ac76960bea7cb8d7e1b96aefc284"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8f98775065260140efb348b1ff8d50fd66ddcbf0c685b76eb1e87b380aaffb3"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b471c6a515f434a167ca16c5c15ff34ee42d11956baa749173a8a4e385ff23e7"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee0763a1b7ceb78ba2f78bee5f30d1551dc26daafcce4ac125115fa1def20519"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:099e6e9ce7999b403d36d9cf943105a3d25d8233486b54ec9d1b78623b050433"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e56faef9dde8d969f0d646fb6171883693f88ae39163ecd919ec707fbafa85"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:328156d4e594c9ae63e1072503c168849e601a153ad37f0290743544332d6b6f"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a57a55a27c7864d6916344c9a91776afda6c3b8b2209f8a69b79cdba93fbe128"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c19cdde08b040df1e99d2500bf8a9cff775ab0e6fa162bf8afe6d84aa93ed04"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c72667f19d3a9acf324aed97f58861d398d87e42314731e7c6ab3ac7850c971"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:67ae934d75ea2563fc48a27c5945749575c74a6de19fdd38390917ddcb0e4f24"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1b0c348dd8523554dc535540d2046c0c8a535bb086561d8359f3667967b6ca"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22284255d2a8e8f3da195bd8e8d43ce674dbc7c38d38cb6ecfb37fae7755d31f"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38aac5ef4381c26d3ce147ca98fba5a78b1e5bcd6be6755b4908659f2705c6d"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:803f864af528f6f763a5be1e7b1ccab418e55ae0e4abc8bda961d162f850c991"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:40354ebfb5cecd60a5fbb9833a8a452d147486b0ffec547823658556625d98b5"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2abaf5d06be3361bfa8e0db2ee123ba8e92beab5bceed5e9d7847f2145a32e04"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e638e5ffedc3565242b5fa3296899d35161bad771f88d66277b58f03a1ba9fe"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0597e919d116ec39997804288d77bec3777228368efc0f2294b84a527fc4f9c2"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee9455c501d19f065527afda974418b3ef7c61e85d9519d122cd6eb3cb7a00"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:89ed62ce430f5789e15cfc1ccabc172fd8b349c3a17c52d9e6c64ecedf08c265"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b824f4b9259cd8bb6e83c4873cf8bf080f6e4fa034a02fe778e07aba8d345"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fba66faa0016dfc0af3dd7ac5782b5786a1dfb851f9f3455e266f94c2a05a04"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ec4b0c3e0a7bcd103f3cf31dd40c349940b2d4223ce43d384a3548992138ef1"}, + {file = "xattr-1.1.4.tar.gz", hash = "sha256:b7b02ecb2270da5b7e7deaeea8f8b528c17368401c2b9d5f63e91f545b45d372"}, ] [package.dependencies] @@ -8516,4 +8547,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.10.0,<3.11" -content-hash = "af78fc2b3b7e96499e3e41429295a73baaf0408ba67c4fb4d670207fd0b16327" +content-hash = "941b24e7756f9d19310016d8d00f1fe13f6a8683e4e0304c0f231b3d8cecdb1a" diff --git a/pyproject.toml b/pyproject.toml index 8f96fbbc0..26c188e7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ scikit-learn = "1.5.0" # Pin as it was causing issues with nnunet # See https://github.com/adap/flower/pull/3853 # https://github.com/grpc/grpc/issues/37162 tornado = ">=6.4.2" +pympler = "^1.1" [tool.poetry.group.dev.dependencies] # locked the 2.15 version because of restrictions with tensorflow-io diff --git a/research/rxrx1/central/__init__.py b/research/rxrx1/central/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/rxrx1/central/run_fold_experiment.slrm b/research/rxrx1/central/run_fold_experiment.slrm new file mode 100644 index 000000000..63653d35e --- /dev/null +++ b/research/rxrx1/central/run_fold_experiment.slrm @@ -0,0 +1,125 @@ +#!/bin/bash + +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=8 +#SBATCH --gres=gpu:1 +#SBATCH --mem=32G +#SBATCH --partition=t4v2 +#SBATCH --qos=normal +#SBATCH --job-name=central_five_fold_exp +#SBATCH --output=%j_%x.out +#SBATCH --error=%j_%x.err +#SBATCH --time=12:00:00 + +############################################### +# Usage: +# +# sbatch research/rxrx1/central/run_fold_experiment.slrm \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ +# +# Example: +# sbatch research/rxrx1/central/run_fold_experiment.slrm \ +# research/rxrx1/central/results/ \ +# /projects//fl4health/datasets/rxrx1_v1.0 \ +# /h/demerson/vector_repositories/fl4health_env/ +# +# Notes: +# 1) The sbatch command above should be run from the top level directory of the repository. +# 2) This example runs centralized dataset training. As such the data paths and python launch commands are hardcoded. +# If you want to change the example you run, you need to explicitly modify the code below. +# 3) The logging directories need to ALREADY EXIST. The script does not create them. +############################################### + +# Note: +# ntasks: Total number of processes to use across world +# ntasks-per-node: How many processes each node should create + +# Set NCCL options +# export NCCL_DEBUG=INFO +# NCCL backend to communicate between GPU workers is not provided in vector's cluster. +# Disable this option in slurm. +export NCCL_IB_DISABLE=1 + +if [[ "${SLURM_JOB_PARTITION}" == "t4v2" ]] || \ + [[ "${SLURM_JOB_PARTITION}" == "rtx6000" ]]; then + echo export NCCL_SOCKET_IFNAME=bond0 on "${SLURM_JOB_PARTITION}" + export NCCL_SOCKET_IFNAME=bond0 +fi + +# Process Inputs + +ARTIFACT_DIR=$1 +DATASET_DIR=$2 +VENV_PATH=$3 +LR=$4 + +# Create the artifact directory +mkdir "${ARTIFACT_DIR}" + +RUN_NAMES=( "Run1" "Run2" "Run3" "Run4" "Run5" ) + +echo "Python Venv Path: ${VENV_PATH}" + +echo "World size: ${SLURM_NTASKS}" +echo "Number of nodes: ${SLURM_NNODES}" +NUM_GPUs=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l) +echo "GPUs per node: ${NUM_GPUs}" + +# Source the environment +source ${VENV_PATH}bin/activate +echo "Active Environment:" +which python + +for RUN_NAME in "${RUN_NAMES[@]}"; +do + # create the run directory + RUN_DIR="${ARTIFACT_DIR}${RUN_NAME}/" + echo "Starting Run and logging artifacts at ${RUN_DIR}" + if [ -d "${RUN_DIR}" ] + then + # Directory already exists, we check if the done.out file exists + if [ -f "${RUN_DIR}done.out" ] + then + # Done file already exists so we skip this run + echo "Run already completed. Skipping Run." + continue + else + # Done file doesn't exists (assume pre-emption happened) + # Delete the partially finished contents and start over + echo "Run did not finished correctly. Re-running." + rm -r "${RUN_DIR}" + mkdir "${RUN_DIR}" + fi + else + # Directory doesn't exist yet, so we create it. + echo "Run directory does not exist. Creating it." + mkdir "${RUN_DIR}" + fi + + OUTPUT_FILE="${RUN_DIR}central.out" + + # Start the Trainer, divert the outputs to a trainer file + + echo "Trainer logging at: ${OUTPUT_FILE}" + echo "Launching Trainer" + + nohup python -m research.rxrx1.central.train \ + --artifact_dir ${ARTIFACT_DIR} \ + --run_name ${RUN_NAME} \ + --dataset_dir ${DATASET_DIR} \ + --lr ${LR} \ + > ${OUTPUT_FILE} 2>&1 & + + echo "Centralized Training Running" + + wait + + # Create a file that verifies that the Run concluded properly + touch "${RUN_DIR}done.out" + echo "Finished Centralized Training Processes" + +done diff --git a/research/rxrx1/central/train.py b/research/rxrx1/central/train.py new file mode 100644 index 000000000..02d7f32fe --- /dev/null +++ b/research/rxrx1/central/train.py @@ -0,0 +1,113 @@ +import argparse +import copy +from logging import INFO +from pathlib import Path + +import torch +import torch.nn as nn +from flwr.common.logger import log +from torch.utils.data import DataLoader +from torchvision import models + +from fl4health.datasets.rxrx1.load_data import load_rxrx1_data +from fl4health.utils.dataset import TensorDataset +from fl4health.utils.metrics import Accuracy, MetricManager +from research.rxrx1.single_node_trainer import SingleNodeTrainer + +NUM_CLIENTS = 4 +EPOCHS = 100 + + +class Rxrx1CentralizedTrainer(SingleNodeTrainer): + def __init__( + self, + device: torch.device, + checkpoint_stub: str, + dataset_dir: str, + run_name: str = "", + lr: float = 0.001, + ) -> None: + super().__init__(device, checkpoint_stub, dataset_dir, run_name) + + for client_number in range(NUM_CLIENTS): + train_loader, val_loader, num_examples = load_rxrx1_data( + data_path=Path(dataset_dir), client_num=client_number, batch_size=32 + ) + assert isinstance(train_loader.dataset, TensorDataset), "Expected TensorDataset." + assert isinstance(val_loader.dataset, TensorDataset), "Expected TensorDataset." + + if client_number == 0: + aggregated_train_dataset = copy.deepcopy(train_loader.dataset) + aggregated_val_dataset = copy.deepcopy(val_loader.dataset) + else: + assert aggregated_train_dataset.data is not None + aggregated_train_dataset.data = torch.cat((aggregated_train_dataset.data, train_loader.dataset.data)) + assert train_loader.dataset.targets is not None and aggregated_train_dataset.targets is not None + aggregated_train_dataset.targets = torch.cat( + (aggregated_train_dataset.targets, train_loader.dataset.targets) + ) + + assert aggregated_val_dataset.data is not None + aggregated_val_dataset.data = torch.cat((aggregated_val_dataset.data, val_loader.dataset.data)) + assert val_loader.dataset.targets is not None and aggregated_val_dataset.targets is not None + aggregated_val_dataset.targets = torch.cat( + (aggregated_val_dataset.targets, val_loader.dataset.targets) + ) + + log(INFO, f"Aggregated train dataset size: {len(aggregated_train_dataset.data)}") + log(INFO, f"Aggregated val dataset size: {len(aggregated_val_dataset.data)}") + + self.train_loader = DataLoader(aggregated_train_dataset, batch_size=32, shuffle=True) + self.val_loader = DataLoader(aggregated_val_dataset, batch_size=32, shuffle=False) + + self.model: nn.Module = models.resnet18(pretrained=True).to(self.device) + # NOTE: The class weights specified by alpha in this baseline loss are precomputed based on the weights of + # the pool dataset. This is a bit of cheating but FLamby does it in their paper. + self.criterion = torch.nn.CrossEntropyLoss() + self.optimizer = torch.optim.AdamW(self.model.parameters(), lr=lr) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Centralized Training Main") + parser.add_argument( + "--artifact_dir", + action="store", + type=str, + help="Path to save artifacts such as logs and model checkpoints", + required=True, + ) + parser.add_argument( + "--dataset_dir", + action="store", + type=str, + help="Path to the preprocessed Rxrx1 Dataset", + required=True, + ) + parser.add_argument( + "--run_name", + action="store", + help="Name of the run, model checkpoints will be saved under a subfolder with this name", + required=True, + ) + parser.add_argument( + "--lr", + action="store", + help="Learning rate for the optimizer", + required=True, + ) + args = parser.parse_args() + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + log(INFO, f"Device to be used: {device}") + + trainer = Rxrx1CentralizedTrainer( + device, + args.artifact_dir, + args.dataset_dir, + args.run_name, + ) + metrics = [Accuracy("Rxrx1_accuracy")] + train_metric_mngr = MetricManager(metrics, "train_meter") + val_metric_mngr = MetricManager(metrics, "val_meter") + # Central and local models in FLamby for FedISic are trained for 20 epochs + trainer.train_by_epochs(EPOCHS, train_metric_mngr, val_metric_mngr) diff --git a/research/rxrx1/ditto/__init__.py b/research/rxrx1/ditto/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/rxrx1/ditto/client.py b/research/rxrx1/ditto/client.py new file mode 100644 index 000000000..cff140590 --- /dev/null +++ b/research/rxrx1/ditto/client.py @@ -0,0 +1,176 @@ +import argparse +import os +from collections.abc import Sequence +from logging import INFO +from pathlib import Path + +import flwr as fl +import torch +import torch.nn as nn +from flwr.common.logger import log +from flwr.common.typing import Config +from torch.nn.modules.loss import _Loss +from torch.optim import Optimizer +from torch.utils.data import DataLoader +from torchvision import models + +from fl4health.checkpointing.checkpointer import BestLossTorchModuleCheckpointer, LatestTorchModuleCheckpointer +from fl4health.checkpointing.client_module import ClientCheckpointAndStateModule +from fl4health.clients.ditto_client import DittoClient +from fl4health.datasets.rxrx1.load_data import load_rxrx1_data, load_rxrx1_test_data +from fl4health.reporting.base_reporter import BaseReporter +from fl4health.utils.config import narrow_dict_type +from fl4health.utils.losses import LossMeterType +from fl4health.utils.metrics import Accuracy, Metric +from fl4health.utils.random import set_all_random_seeds + + +class Rxrx1DittoClient(DittoClient): + def __init__( + self, + data_path: Path, + metrics: Sequence[Metric], + device: torch.device, + client_number: int, + learning_rate: float, + loss_meter_type: LossMeterType = LossMeterType.AVERAGE, + checkpoint_and_state_module: ClientCheckpointAndStateModule | None = None, + reporters: Sequence[BaseReporter] | None = None, + progress_bar: bool = False, + client_name: str | None = None, + ) -> None: + super().__init__( + data_path=data_path, + metrics=metrics, + device=device, + loss_meter_type=loss_meter_type, + checkpoint_and_state_module=checkpoint_and_state_module, + reporters=reporters, + progress_bar=progress_bar, + client_name=client_name, + ) + self.client_number = client_number + self.learning_rate: float = learning_rate + + log(INFO, f"Client Name: {self.client_name}, Client Number: {self.client_number}") + + def setup_client(self, config: Config) -> None: + # Check if the client number is within the range of the total number of clients + num_clients = narrow_dict_type(config, "n_clients", int) + assert 0 <= self.client_number < num_clients + super().setup_client(config) + + def get_data_loaders(self, config: Config) -> tuple[DataLoader, DataLoader]: + batch_size = narrow_dict_type(config, "batch_size", int) + train_loader, val_loader, _ = load_rxrx1_data( + data_path=self.data_path, client_num=self.client_number, batch_size=batch_size, seed=self.client_number + ) + + return train_loader, val_loader + + def get_test_data_loader(self, config: Config) -> DataLoader | None: + batch_size = narrow_dict_type(config, "batch_size", int) + test_loader, _ = load_rxrx1_test_data( + data_path=self.data_path, client_num=self.client_number, batch_size=batch_size + ) + + return test_loader + + def get_criterion(self, config: Config) -> _Loss: + return torch.nn.CrossEntropyLoss() + + def get_optimizer(self, config: Config) -> dict[str, Optimizer]: + global_optimizer = torch.optim.AdamW(self.global_model.parameters(), lr=self.learning_rate) + local_optimizer = torch.optim.AdamW(self.model.parameters(), lr=self.learning_rate) + return {"global": global_optimizer, "local": local_optimizer} + + def get_model(self, config: Config) -> nn.Module: + return models.resnet18(pretrained=True).to(self.device) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="FL Client Main") + parser.add_argument( + "--artifact_dir", + action="store", + type=str, + help="Path to save client artifacts such as logs and model checkpoints", + required=True, + ) + parser.add_argument( + "--dataset_dir", + action="store", + type=str, + help="Path to the preprocessed Rxrx1 Dataset", + required=True, + ) + parser.add_argument( + "--run_name", + action="store", + help="Name of the run, model checkpoints will be saved under a subfolder with this name", + required=True, + ) + parser.add_argument( + "--server_address", + action="store", + type=str, + help="Server Address for the clients to communicate with the server through", + default="0.0.0.0:8080", + ) + parser.add_argument( + "--client_number", + action="store", + type=int, + help="Number of the client for dataset loading (should be 0-3 for Rxrx1)", + required=True, + ) + parser.add_argument( + "--learning_rate", action="store", type=float, help="Learning rate for local optimization", default=0.1 + ) + parser.add_argument( + "--seed", + action="store", + type=int, + help="Seed for the random number generators across python, torch, and numpy", + required=False, + ) + args = parser.parse_args() + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + log(INFO, f"Device to be used: {device}") + log(INFO, f"Server Address: {args.server_address}") + log(INFO, f"Learning Rate: {args.learning_rate}") + + # Set the random seed for reproducibility + set_all_random_seeds(args.seed) + + # Adding extensive checkpointing for the client + checkpoint_dir = os.path.join(args.artifact_dir, args.run_name) + pre_aggregation_best_checkpoint_name = f"pre_aggregation_client_{args.client_number}_best_model.pkl" + pre_aggregation_last_checkpoint_name = f"pre_aggregation_client_{args.client_number}_last_model.pkl" + post_aggregation_best_checkpoint_name = f"post_aggregation_client_{args.client_number}_best_model.pkl" + post_aggregation_last_checkpoint_name = f"post_aggregation_client_{args.client_number}_last_model.pkl" + checkpoint_and_state_module = ClientCheckpointAndStateModule( + pre_aggregation=[ + BestLossTorchModuleCheckpointer(checkpoint_dir, pre_aggregation_best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, pre_aggregation_last_checkpoint_name), + ], + post_aggregation=[ + BestLossTorchModuleCheckpointer(checkpoint_dir, post_aggregation_best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, post_aggregation_last_checkpoint_name), + ], + ) + + data_path = Path(args.dataset_dir) + client = Rxrx1DittoClient( + data_path=data_path, + metrics=[Accuracy("accuracy")], + device=device, + client_number=args.client_number, + learning_rate=args.learning_rate, + checkpoint_and_state_module=checkpoint_and_state_module, + ) + + fl.client.start_client(server_address=args.server_address, client=client.to_client()) + # Shutdown the client gracefully + client.shutdown() diff --git a/research/rxrx1/ditto/config.yaml b/research/rxrx1/ditto/config.yaml new file mode 100644 index 000000000..09e723ad3 --- /dev/null +++ b/research/rxrx1/ditto/config.yaml @@ -0,0 +1,7 @@ +# Parameters that describe server +n_server_rounds: 10 # The number of rounds to run FL + +# Parameters that describe clients +n_clients: 4 # The number of clients in the FL experiment +local_epochs: 5 # The number of epochs to complete for client +batch_size: 32 # The batch size for client training diff --git a/research/rxrx1/ditto/run_fold_experiment.slrm b/research/rxrx1/ditto/run_fold_experiment.slrm new file mode 100644 index 000000000..4ef8f9c39 --- /dev/null +++ b/research/rxrx1/ditto/run_fold_experiment.slrm @@ -0,0 +1,163 @@ +#!/bin/bash + +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=1 +#SBATCH --gres=gpu:1 +#SBATCH --mem=64G +#SBATCH --partition=a40 +#SBATCH --qos=m2 +#SBATCH --job-name=fl_five_fold_exp +#SBATCH --output=%j_%x.out +#SBATCH --error=%j_%x.err +#SBATCH --time=4:00:00 + +############################################### +# Usage: +# +# sbatch research/rxrx1/ditto/run_fold_experiment.slrm \ +# path_to_config.yaml \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ \ +# client_side_learning_rate_value \ +# lambda value \ +# server_address +# +# Example: +# sbatch research/rxrx1/ditto/run_fold_experiment.slrm \ +# research/rxrx1/ditto/config.yaml \ +# research/rxrx1/ditto/hp_results/ \ +# /datasets/rxrx1 \ +# /h/demerson/vector_repositories/fl4health_env/ \ +# 0.0001 \ +# 0.01 \ +# 0.0.0.0:8080 +# +# Notes: +# 1) The sbatch command above should be run from the top level directory of the repository. +# 2) This example runs ditto. As such the data paths and python launch commands are hardcoded. If you want to change +# the example you run, you need to explicitly modify the code below. +# 3) The logging directories need to ALREADY EXIST. The script does not create them. +############################################### + +# Note: +# ntasks: Total number of processes to use across world +# ntasks-per-node: How many processes each node should create + +# Set NCCL options +# export NCCL_DEBUG=INFO +# NCCL backend to communicate between GPU workers is not provided in vector's cluster. +# Disable this option in slurm. +export NCCL_IB_DISABLE=1 + +if [[ "${SLURM_JOB_PARTITION}" == "t4v2" ]] || \ + [[ "${SLURM_JOB_PARTITION}" == "rtx6000" ]]; then + echo export NCCL_SOCKET_IFNAME=bond0 on "${SLURM_JOB_PARTITION}" + export NCCL_SOCKET_IFNAME=bond0 +fi + + +export CUBLAS_WORKSPACE_CONFIG=:4096:8 +# Process Inputs + +SERVER_CONFIG_PATH=$1 +ARTIFACT_DIR=$2 +DATASET_DIR=$3 +VENV_PATH=$4 +CLIENT_LR=$5 +SERVER_ADDRESS=$6 + +# Create the artifact directory +mkdir "${ARTIFACT_DIR}" + +RUN_NAMES=( "Run1" "Run2" "Run3" "Run4" "Run5" ) +SEEDS=( 2021 2022 2023 2024 2025 ) + +echo "Python Venv Path: ${VENV_PATH}" + +echo "World size: ${SLURM_NTASKS}" +echo "Number of nodes: ${SLURM_NNODES}" +NUM_GPUs=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l) +echo "GPUs per node: ${NUM_GPUs}" + +# Source the environment +source ${VENV_PATH}bin/activate +echo "Active Environment:" +which python + +for ((i=0; i<${#RUN_NAMES[@]}; i++)); +do + RUN_NAME="${RUN_NAMES[i]}" + SEED="${SEEDS[i]}" + # create the run directory + RUN_DIR="${ARTIFACT_DIR}${RUN_NAME}/" + echo "Starting Run and logging artifcats at ${RUN_DIR}" + if [ -d "${RUN_DIR}" ] + then + # Directory already exists, we check if the done.out file exists + if [ -f "${RUN_DIR}done.out" ] + then + # Done file already exists so we skip this run + echo "Run already completed. Skipping Run." + continue + else + # Done file doesn't exists (assume pre-emption happened) + # Delete the partially finished contents and start over + echo "Run did not finished correctly. Re-running." + rm -r "${RUN_DIR}" + mkdir "${RUN_DIR}" + fi + else + # Directory doesn't exist yet, so we create it. + echo "Run directory does not exist. Creating it." + mkdir "${RUN_DIR}" + fi + + SERVER_OUTPUT_FILE="${RUN_DIR}server.out" + + # Start the server, divert the outputs to a server file + + echo "Server logging at: ${SERVER_OUTPUT_FILE}" + echo "Launching Server" + + nohup python -m research.rxrx1.ditto.server \ + --config_path ${SERVER_CONFIG_PATH} \ + --server_address ${SERVER_ADDRESS} \ + --seed ${SEED} \ + --lam ${LAM_VALUE} \ + > ${SERVER_OUTPUT_FILE} 2>&1 & + + # Sleep for 20 seconds to allow the server to come up. + sleep 20 + + # Start n number of clients and divert the outputs to their own files + n_clients=4 + for (( c=0; c<${n_clients}; c++ )) + do + CLIENT_NAME="client_${c}" + echo "Launching ${CLIENT_NAME}" + + CLIENT_LOG_PATH="${RUN_DIR}${CLIENT_NAME}.out" + echo "${CLIENT_NAME} logging at: ${CLIENT_LOG_PATH}" + nohup python -m research.rxrx1.ditto.client \ + --artifact_dir ${ARTIFACT_DIR} \ + --dataset_dir ${DATASET_DIR} \ + --run_name ${RUN_NAME} \ + --client_number ${c} \ + --learning_rate ${CLIENT_LR} \ + --server_address ${SERVER_ADDRESS} \ + --seed ${SEED} \ + > ${CLIENT_LOG_PATH} 2>&1 & + done + + echo "FL Processes Running" + + wait + + # Create a file that verifies that the Run concluded properly + touch "${RUN_DIR}done.out" + echo "Finished FL Processes" + +done diff --git a/research/rxrx1/ditto/run_hp_sweep.sh b/research/rxrx1/ditto/run_hp_sweep.sh new file mode 100755 index 000000000..22f66ab9f --- /dev/null +++ b/research/rxrx1/ditto/run_hp_sweep.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +############################################### +# Usage: +# +# ./research/rxrx1/ditto/run_hp_sweep.sh \ +# path_to_config.yaml \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ +# +# Example: +# ./research/rxrx1/ditto/run_hp_sweep.sh \ +# research/rxrx1/ditto/config.yaml \ +# research/rxrx1/ditto \ +# /datasets/rxrx1 \ +# /h/demerson/vector_repositories/fl4health_env/ +# +# Notes: +# 1) The bash command above should be run from the top level directory of the repository. +############################################### + +SERVER_CONFIG_PATH=$1 +ARTIFACT_DIR=$2 +DATASET_DIR=$3 +VENV_PATH=$4 + +LR_VALUES=( 0.00001 0.0001 0.001 0.01 0.1 ) +LAM_VALUES=( 0.01 0.1 1.0 ) + +SERVER_PORT=8100 + +# Create sweep folder +SWEEP_DIRECTORY="${ARTIFACT_DIR}hp_sweep_results" +echo "Creating sweep folder at ${SWEEP_DIRECTORY}" +mkdir ${SWEEP_DIRECTORY} + +for LR_VALUE in "${LR_VALUES[@]}"; +do + for LAM_VALUE in "${LAM_VALUES[@]}"; + do + EXPERIMENT_NAME="lr_${LR_VALUE}_lam_${LAM_VALUE}" + echo "Beginning Experiment ${EXPERIMENT_NAME}" + EXPERIMENT_DIRECTORY="${SWEEP_DIRECTORY}/${EXPERIMENT_NAME}/" + echo "Creating experiment folder ${EXPERIMENT_DIRECTORY}" + mkdir "${EXPERIMENT_DIRECTORY}" + SERVER_ADDRESS="0.0.0.0:${SERVER_PORT}" + echo "Server Address: ${SERVER_ADDRESS}" + SBATCH_COMMAND="research/rxrx1/ditto/run_fold_experiment.slrm \ + ${SERVER_CONFIG_PATH} \ + ${EXPERIMENT_DIRECTORY} \ + ${DATASET_DIR} \ + ${VENV_PATH} \ + ${LR_VALUE} \ + ${LAM_VALUE} \ + ${SERVER_ADDRESS}" + echo "Running sbatch command ${SBATCH_COMMAND}" + sbatch ${SBATCH_COMMAND} + ((SERVER_PORT=SERVER_PORT+1)) + done +done +echo Experiments Launched diff --git a/research/rxrx1/ditto/server.py b/research/rxrx1/ditto/server.py new file mode 100644 index 000000000..96c3b044e --- /dev/null +++ b/research/rxrx1/ditto/server.py @@ -0,0 +1,114 @@ +import argparse +from functools import partial +from logging import INFO +from typing import Any + +import flwr as fl +from flwr.common.logger import log +from flwr.common.typing import Config +from flwr.server.client_manager import SimpleClientManager +from torchvision import models + +from fl4health.strategies.fedavg_with_adaptive_constraint import FedAvgWithAdaptiveConstraint +from fl4health.utils.config import load_config +from fl4health.utils.metric_aggregation import evaluate_metrics_aggregation_fn, fit_metrics_aggregation_fn +from fl4health.utils.parameter_extraction import get_all_model_parameters +from fl4health.utils.random import set_all_random_seeds +from research.rxrx1.personal_server import PersonalServer + + +def fit_config( + batch_size: int, + local_epochs: int, + n_server_rounds: int, + n_clients: int, + current_server_round: int, +) -> Config: + return { + "batch_size": batch_size, + "local_epochs": local_epochs, + "n_server_rounds": n_server_rounds, + "n_clients": n_clients, + "current_server_round": current_server_round, + } + + +def main(config: dict[str, Any], server_address: str, lam: float) -> None: + # This function will be used to produce a config that is sent to each client to initialize their own environment + fit_config_fn = partial( + fit_config, + config["batch_size"], + config["local_epochs"], + config["n_server_rounds"], + config["n_clients"], + ) + + client_manager = SimpleClientManager() + # Initializing the model on the server side + model = models.resnet18(pretrained=True) + # Server performs simple FedAveraging as its server-side optimization strategy + strategy = FedAvgWithAdaptiveConstraint( + min_fit_clients=config["n_clients"], + min_evaluate_clients=config["n_clients"], + # Server waits for min_available_clients before starting FL rounds + min_available_clients=config["n_clients"], + on_fit_config_fn=fit_config_fn, + # We use the same fit config function, as nothing changes for eval + on_evaluate_config_fn=fit_config_fn, + fit_metrics_aggregation_fn=fit_metrics_aggregation_fn, + evaluate_metrics_aggregation_fn=evaluate_metrics_aggregation_fn, + initial_parameters=get_all_model_parameters(model), + initial_loss_weight=lam, + ) + + server = PersonalServer(client_manager=client_manager, fl_config=config, strategy=strategy) + + fl.server.start_server( + server=server, + server_address=server_address, + config=fl.server.ServerConfig(num_rounds=config["n_server_rounds"]), + ) + + log(INFO, "Training Complete") + log(INFO, f"Best Aggregated (Weighted) Loss seen by the Server: \n{server.best_aggregated_loss}") + + # Shutdown the server gracefully + server.shutdown() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="FL Server Main") + parser.add_argument( + "--config_path", + action="store", + type=str, + help="Path to configuration file.", + default="config.yaml", + ) + parser.add_argument( + "--server_address", + action="store", + type=str, + help="Server Address to be used to communicate with the clients", + default="0.0.0.0:8080", + ) + parser.add_argument( + "--seed", + action="store", + type=int, + help="Seed for the random number generators across python, torch, and numpy", + required=False, + ) + parser.add_argument( + "--lam", action="store", type=float, help="Ditto loss weight for local model training", default=0.01 + ) + args = parser.parse_args() + + config = load_config(args.config_path) + log(INFO, f"Server Address: {args.server_address}") + log(INFO, f"Lambda: {args.lam}") + + # Set the random seed for reproducibility + set_all_random_seeds(args.seed) + + main(config, args.server_address, args.lam) diff --git a/research/rxrx1/ditto_deep_mmd/__init__.py b/research/rxrx1/ditto_deep_mmd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/rxrx1/ditto_deep_mmd/client.py b/research/rxrx1/ditto_deep_mmd/client.py new file mode 100644 index 000000000..91d957784 --- /dev/null +++ b/research/rxrx1/ditto_deep_mmd/client.py @@ -0,0 +1,208 @@ +import argparse +import os +from collections import OrderedDict +from collections.abc import Sequence +from logging import INFO +from pathlib import Path + +import flwr as fl +import torch +import torch.nn as nn +from flwr.common.logger import log +from flwr.common.typing import Config +from torch.nn.modules.loss import _Loss +from torch.optim import Optimizer +from torch.utils.data import DataLoader +from torchvision import models + +from fl4health.checkpointing.checkpointer import BestLossTorchModuleCheckpointer, LatestTorchModuleCheckpointer +from fl4health.checkpointing.client_module import ClientCheckpointAndStateModule +from fl4health.clients.deep_mmd_clients.ditto_deep_mmd_client import DittoDeepMmdClient +from fl4health.datasets.rxrx1.load_data import load_rxrx1_data, load_rxrx1_test_data +from fl4health.reporting.base_reporter import BaseReporter +from fl4health.utils.config import narrow_dict_type +from fl4health.utils.losses import LossMeterType +from fl4health.utils.metrics import Accuracy, Metric +from fl4health.utils.random import set_all_random_seeds + +BASELINE_LAYERS: OrderedDict[str, int] = OrderedDict() +BASELINE_LAYERS["layer1"] = 1048576 +BASELINE_LAYERS["layer2"] = 524288 +BASELINE_LAYERS["layer3"] = 262144 +BASELINE_LAYERS["layer4"] = 131072 +BASELINE_LAYERS["avgpool"] = 512 + + +class Rxrx1DittoClient(DittoDeepMmdClient): + def __init__( + self, + data_path: Path, + metrics: Sequence[Metric], + device: torch.device, + client_number: int, + learning_rate: float, + loss_meter_type: LossMeterType = LossMeterType.AVERAGE, + checkpoint_and_state_module: ClientCheckpointAndStateModule | None = None, + reporters: Sequence[BaseReporter] | None = None, + progress_bar: bool = False, + client_name: str | None = None, + deep_mmd_loss_weight: float = 10, + deep_mmd_loss_depth: int = 1, + ) -> None: + feature_extraction_layers_with_size = OrderedDict(list(BASELINE_LAYERS.items())[-1 * deep_mmd_loss_depth :]) + super().__init__( + data_path=data_path, + metrics=metrics, + device=device, + loss_meter_type=loss_meter_type, + checkpoint_and_state_module=checkpoint_and_state_module, + reporters=reporters, + progress_bar=progress_bar, + client_name=client_name, + deep_mmd_loss_weight=deep_mmd_loss_weight, + feature_extraction_layers_with_size=feature_extraction_layers_with_size, + ) + self.client_number = client_number + self.learning_rate: float = learning_rate + + log(INFO, f"Client Name: {self.client_name}, Client Number: {self.client_number}") + + def setup_client(self, config: Config) -> None: + # Check if the client number is within the range of the total number of clients + num_clients = narrow_dict_type(config, "n_clients", int) + assert 0 <= self.client_number < num_clients + super().setup_client(config) + + def get_data_loaders(self, config: Config) -> tuple[DataLoader, DataLoader]: + batch_size = narrow_dict_type(config, "batch_size", int) + train_loader, val_loader, _ = load_rxrx1_data( + data_path=self.data_path, client_num=self.client_number, batch_size=batch_size, seed=self.client_number + ) + + return train_loader, val_loader + + def get_test_data_loader(self, config: Config) -> DataLoader | None: + batch_size = narrow_dict_type(config, "batch_size", int) + test_loader, _ = load_rxrx1_test_data( + data_path=self.data_path, client_num=self.client_number, batch_size=batch_size + ) + + return test_loader + + def get_criterion(self, config: Config) -> _Loss: + return torch.nn.CrossEntropyLoss() + + def get_optimizer(self, config: Config) -> dict[str, Optimizer]: + global_optimizer = torch.optim.AdamW(self.global_model.parameters(), lr=self.learning_rate) + local_optimizer = torch.optim.AdamW(self.model.parameters(), lr=self.learning_rate) + return {"global": global_optimizer, "local": local_optimizer} + + def get_model(self, config: Config) -> nn.Module: + return models.resnet18(pretrained=True).to(self.device) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="FL Client Main") + parser.add_argument( + "--artifact_dir", + action="store", + type=str, + help="Path to save client artifacts such as logs and model checkpoints", + required=True, + ) + parser.add_argument( + "--dataset_dir", + action="store", + type=str, + help="Path to the preprocessed Rxrx1 Dataset", + required=True, + ) + parser.add_argument( + "--run_name", + action="store", + help="Name of the run, model checkpoints will be saved under a subfolder with this name", + required=True, + ) + parser.add_argument( + "--server_address", + action="store", + type=str, + help="Server Address for the clients to communicate with the server through", + default="0.0.0.0:8080", + ) + parser.add_argument( + "--client_number", + action="store", + type=int, + help="Number of the client for dataset loading (should be 0-3 for Rxrx1)", + required=True, + ) + parser.add_argument( + "--learning_rate", action="store", type=float, help="Learning rate for local optimization", default=0.1 + ) + parser.add_argument( + "--seed", + action="store", + type=int, + help="Seed for the random number generators across python, torch, and numpy", + required=False, + ) + parser.add_argument( + "--mu", + action="store", + type=float, + help="Weight for the Deep MMD losses", + required=False, + ) + parser.add_argument( + "--deep_mmd_loss_depth", + action="store", + type=int, + help="Depth of applying the Deep MMD loss", + required=False, + default=1, + ) + args = parser.parse_args() + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + log(INFO, f"Device to be used: {device}") + log(INFO, f"Server Address: {args.server_address}") + log(INFO, f"Learning Rate: {args.learning_rate}") + log(INFO, f"Mu: {args.mu}") + log(INFO, f"DEEP MMD Loss Depth: {args.deep_mmd_loss_depth}") + + # Set the random seed for reproducibility + set_all_random_seeds(args.seed) + + # Adding extensive checkpointing for the client + checkpoint_dir = os.path.join(args.artifact_dir, args.run_name) + pre_aggregation_best_checkpoint_name = f"pre_aggregation_client_{args.client_number}_best_model.pkl" + pre_aggregation_last_checkpoint_name = f"pre_aggregation_client_{args.client_number}_last_model.pkl" + post_aggregation_best_checkpoint_name = f"post_aggregation_client_{args.client_number}_best_model.pkl" + post_aggregation_last_checkpoint_name = f"post_aggregation_client_{args.client_number}_last_model.pkl" + checkpoint_and_state_module = ClientCheckpointAndStateModule( + pre_aggregation=[ + BestLossTorchModuleCheckpointer(checkpoint_dir, pre_aggregation_best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, pre_aggregation_last_checkpoint_name), + ], + post_aggregation=[ + BestLossTorchModuleCheckpointer(checkpoint_dir, post_aggregation_best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, post_aggregation_last_checkpoint_name), + ], + ) + + data_path = Path(args.dataset_dir) + client = Rxrx1DittoClient( + data_path=data_path, + metrics=[Accuracy("accuracy")], + device=device, + client_number=args.client_number, + learning_rate=args.learning_rate, + checkpoint_and_state_module=checkpoint_and_state_module, + deep_mmd_loss_depth=args.deep_mmd_loss_depth, + deep_mmd_loss_weight=args.mu, + ) + + fl.client.start_client(server_address=args.server_address, client=client.to_client()) + # Shutdown the client gracefully + client.shutdown() diff --git a/research/rxrx1/ditto_deep_mmd/config.yaml b/research/rxrx1/ditto_deep_mmd/config.yaml new file mode 100644 index 000000000..09e723ad3 --- /dev/null +++ b/research/rxrx1/ditto_deep_mmd/config.yaml @@ -0,0 +1,7 @@ +# Parameters that describe server +n_server_rounds: 10 # The number of rounds to run FL + +# Parameters that describe clients +n_clients: 4 # The number of clients in the FL experiment +local_epochs: 5 # The number of epochs to complete for client +batch_size: 32 # The batch size for client training diff --git a/research/rxrx1/ditto_deep_mmd/run_fold_experiment.slrm b/research/rxrx1/ditto_deep_mmd/run_fold_experiment.slrm new file mode 100644 index 000000000..6dfd95f91 --- /dev/null +++ b/research/rxrx1/ditto_deep_mmd/run_fold_experiment.slrm @@ -0,0 +1,172 @@ +#!/bin/bash + +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=1 +#SBATCH --gres=gpu:1 +#SBATCH --mem=64G +#SBATCH --partition=a40 +#SBATCH --qos=m2 +#SBATCH --job-name=fl_five_fold_exp +#SBATCH --output=%j_%x.out +#SBATCH --error=%j_%x.err +#SBATCH --time=4:00:00 + +############################################### +# Usage: +# +# sbatch research/rxrx1/ditto_deep_mmd/run_fold_experiment.slrm \ +# path_to_config.yaml \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ \ +# client_side_learning_rate_value \ +# lambda value \ +# mu value \ +# deep_mmd_loss_depth \ +# server_address +# +# Example: +# sbatch research/rxrx1/ditto_deep_mmd/run_fold_experiment.slrm \ +# research/rxrx1/ditto_deep_mmd/config.yaml \ +# research/rxrx1/ditto_deep_mmd/hp_results/ \ +# /datasets/rxrx1 \ +# /h/demerson/vector_repositories/fl4health_env/ \ +# 0.0001 \ +# 0.01 \ +# 0.1 \ +# 2 \ +# 0.0.0.0:8080 +# +# Notes: +# 1) The sbatch command above should be run from the top level directory of the repository. +# 2) This example runs ditto Deep MMD. As such the data paths and python launch commands are hardcoded. If you want to change +# the example you run, you need to explicitly modify the code below. +# 3) The logging directories need to ALREADY EXIST. The script does not create them. +############################################### + +# Note: +# ntasks: Total number of processes to use across world +# ntasks-per-node: How many processes each node should create + +# Set NCCL options +# export NCCL_DEBUG=INFO +# NCCL backend to communicate between GPU workers is not provided in vector's cluster. +# Disable this option in slurm. +export NCCL_IB_DISABLE=1 + +if [[ "${SLURM_JOB_PARTITION}" == "t4v2" ]] || \ + [[ "${SLURM_JOB_PARTITION}" == "rtx6000" ]]; then + echo export NCCL_SOCKET_IFNAME=bond0 on "${SLURM_JOB_PARTITION}" + export NCCL_SOCKET_IFNAME=bond0 +fi + + +export CUBLAS_WORKSPACE_CONFIG=:4096:8 +# Process Inputs + +SERVER_CONFIG_PATH=$1 +ARTIFACT_DIR=$2 +DATASET_DIR=$3 +VENV_PATH=$4 +CLIENT_LR=$5 +LAM_VALUE=$6 +MU_VALUE=$7 +DEEP_MMD_LOSS_DEPTH=$8 +SERVER_ADDRESS=$9 + +# Create the artifact directory +mkdir "${ARTIFACT_DIR}" + +RUN_NAMES=( "Run1" "Run2" "Run3" "Run4" "Run5" ) +SEEDS=( 2021 2022 2023 2024 2025 ) + +echo "Python Venv Path: ${VENV_PATH}" + +echo "World size: ${SLURM_NTASKS}" +echo "Number of nodes: ${SLURM_NNODES}" +NUM_GPUs=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l) +echo "GPUs per node: ${NUM_GPUs}" + +# Source the environment +source ${VENV_PATH}bin/activate +echo "Active Environment:" +which python + +for ((i=0; i<${#RUN_NAMES[@]}; i++)); +do + RUN_NAME="${RUN_NAMES[i]}" + SEED="${SEEDS[i]}" + # create the run directory + RUN_DIR="${ARTIFACT_DIR}${RUN_NAME}/" + echo "Starting Run and logging artifcats at ${RUN_DIR}" + if [ -d "${RUN_DIR}" ] + then + # Directory already exists, we check if the done.out file exists + if [ -f "${RUN_DIR}done.out" ] + then + # Done file already exists so we skip this run + echo "Run already completed. Skipping Run." + continue + else + # Done file doesn't exists (assume pre-emption happened) + # Delete the partially finished contents and start over + echo "Run did not finished correctly. Re-running." + rm -r "${RUN_DIR}" + mkdir "${RUN_DIR}" + fi + else + # Directory doesn't exist yet, so we create it. + echo "Run directory does not exist. Creating it." + mkdir "${RUN_DIR}" + fi + + SERVER_OUTPUT_FILE="${RUN_DIR}server.out" + + # Start the server, divert the outputs to a server file + + echo "Server logging at: ${SERVER_OUTPUT_FILE}" + echo "Launching Server" + + nohup python -m research.rxrx1.ditto_deep_mmd.server \ + --config_path ${SERVER_CONFIG_PATH} \ + --server_address ${SERVER_ADDRESS} \ + --seed ${SEED} \ + --lam ${LAM_VALUE} \ + > ${SERVER_OUTPUT_FILE} 2>&1 & + + # Sleep for 20 seconds to allow the server to come up. + sleep 20 + + # Start n number of clients and divert the outputs to their own files + n_clients=5 + for (( c=0; c<${n_clients}; c++ )) + do + CLIENT_NAME="client_${c}" + echo "Launching ${CLIENT_NAME}" + + CLIENT_LOG_PATH="${RUN_DIR}${CLIENT_NAME}.out" + echo "${CLIENT_NAME} logging at: ${CLIENT_LOG_PATH}" + nohup python -m research.rxrx1.ditto_deep_mmd.client \ + --artifact_dir ${ARTIFACT_DIR} \ + --dataset_dir ${DATASET_DIR} \ + --run_name ${RUN_NAME} \ + --client_number ${c} \ + --learning_rate ${CLIENT_LR} \ + --mu ${MU_VALUE} \ + --deep_mmd_loss_depth ${DEEP_MMD_LOSS_DEPTH} \ + --server_address ${SERVER_ADDRESS} \ + --seed ${SEED} \ + > ${CLIENT_LOG_PATH} 2>&1 & + done + + echo "FL Processes Running" + + wait + + # Create a file that verifies that the Run concluded properly + touch "${RUN_DIR}done.out" + echo "Finished FL Processes" + +done diff --git a/research/rxrx1/ditto_deep_mmd/run_hp_sweep.sh b/research/rxrx1/ditto_deep_mmd/run_hp_sweep.sh new file mode 100755 index 000000000..667e3ab9a --- /dev/null +++ b/research/rxrx1/ditto_deep_mmd/run_hp_sweep.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +############################################### +# Usage: +# +# ./research/rxrx1/ditto_deep_mmd/run_hp_sweep.sh \ +# path_to_config.yaml \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ +# +# Example: +# ./research/rxrx1/ditto_deep_mmd/run_hp_sweep.sh \ +# research/rxrx1/ditto_deep_mmd/config.yaml \ +# research/rxrx1/ditto_deep_mmd \ +# /datasets/rxrx1 \ +# /h/demerson/vector_repositories/fl4health_env/ +# +# Notes: +# 1) The bash command above should be run from the top level directory of the repository. +############################################### + +SERVER_CONFIG_PATH=$1 +ARTIFACT_DIR=$2 +DATASET_DIR=$3 +VENV_PATH=$4 + +LR_VALUES=( 0.00001 0.0001 0.001 0.01 0.1 ) +LAM_VALUES=( 0.01 0.1 1.0 ) +MU_VALUES=( 0.01 0.1 1.0 ) +DEEP_MMD_LOSS_DEPTHS=( 1 2 3 ) + +SERVER_PORT=8100 + +# Create sweep folder +SWEEP_DIRECTORY="${ARTIFACT_DIR}hp_sweep_results" +echo "Creating sweep folder at ${SWEEP_DIRECTORY}" +mkdir ${SWEEP_DIRECTORY} + +for LR_VALUE in "${LR_VALUES[@]}"; +do + for LAM_VALUE in "${LAM_VALUES[@]}"; + do + for MU_VALUE in "${MU_VALUES[@]}"; + do + for DEEP_MMD_LOSS_DEPTH in "${DEEP_MMD_LOSS_DEPTHS[@]}"; + do + EXPERIMENT_NAME="lr_${LR_VALUE}_lam_${LAM_VALUE}_mu_${MU_VALUE}_depth_${DEEP_MMD_LOSS_DEPTH}" + echo "Beginning Experiment ${EXPERIMENT_NAME}" + EXPERIMENT_DIRECTORY="${SWEEP_DIRECTORY}/${EXPERIMENT_NAME}/" + echo "Creating experiment folder ${EXPERIMENT_DIRECTORY}" + mkdir "${EXPERIMENT_DIRECTORY}" + SERVER_ADDRESS="0.0.0.0:${SERVER_PORT}" + echo "Server Address: ${SERVER_ADDRESS}" + SBATCH_COMMAND="research/rxrx1/ditto_deep_mmd/run_fold_experiment.slrm \ + ${SERVER_CONFIG_PATH} \ + ${EXPERIMENT_DIRECTORY} \ + ${DATASET_DIR} \ + ${VENV_PATH} \ + ${LR_VALUE} \ + ${LAM_VALUE} \ + ${MU_VALUE} \ + ${DEEP_MMD_LOSS_DEPTH} \ + ${SERVER_ADDRESS}" + echo "Running sbatch command ${SBATCH_COMMAND}" + sbatch ${SBATCH_COMMAND} + ((SERVER_PORT=SERVER_PORT+1)) + done + done + done +done + +echo Experiments Launched diff --git a/research/rxrx1/ditto_deep_mmd/server.py b/research/rxrx1/ditto_deep_mmd/server.py new file mode 100644 index 000000000..96c3b044e --- /dev/null +++ b/research/rxrx1/ditto_deep_mmd/server.py @@ -0,0 +1,114 @@ +import argparse +from functools import partial +from logging import INFO +from typing import Any + +import flwr as fl +from flwr.common.logger import log +from flwr.common.typing import Config +from flwr.server.client_manager import SimpleClientManager +from torchvision import models + +from fl4health.strategies.fedavg_with_adaptive_constraint import FedAvgWithAdaptiveConstraint +from fl4health.utils.config import load_config +from fl4health.utils.metric_aggregation import evaluate_metrics_aggregation_fn, fit_metrics_aggregation_fn +from fl4health.utils.parameter_extraction import get_all_model_parameters +from fl4health.utils.random import set_all_random_seeds +from research.rxrx1.personal_server import PersonalServer + + +def fit_config( + batch_size: int, + local_epochs: int, + n_server_rounds: int, + n_clients: int, + current_server_round: int, +) -> Config: + return { + "batch_size": batch_size, + "local_epochs": local_epochs, + "n_server_rounds": n_server_rounds, + "n_clients": n_clients, + "current_server_round": current_server_round, + } + + +def main(config: dict[str, Any], server_address: str, lam: float) -> None: + # This function will be used to produce a config that is sent to each client to initialize their own environment + fit_config_fn = partial( + fit_config, + config["batch_size"], + config["local_epochs"], + config["n_server_rounds"], + config["n_clients"], + ) + + client_manager = SimpleClientManager() + # Initializing the model on the server side + model = models.resnet18(pretrained=True) + # Server performs simple FedAveraging as its server-side optimization strategy + strategy = FedAvgWithAdaptiveConstraint( + min_fit_clients=config["n_clients"], + min_evaluate_clients=config["n_clients"], + # Server waits for min_available_clients before starting FL rounds + min_available_clients=config["n_clients"], + on_fit_config_fn=fit_config_fn, + # We use the same fit config function, as nothing changes for eval + on_evaluate_config_fn=fit_config_fn, + fit_metrics_aggregation_fn=fit_metrics_aggregation_fn, + evaluate_metrics_aggregation_fn=evaluate_metrics_aggregation_fn, + initial_parameters=get_all_model_parameters(model), + initial_loss_weight=lam, + ) + + server = PersonalServer(client_manager=client_manager, fl_config=config, strategy=strategy) + + fl.server.start_server( + server=server, + server_address=server_address, + config=fl.server.ServerConfig(num_rounds=config["n_server_rounds"]), + ) + + log(INFO, "Training Complete") + log(INFO, f"Best Aggregated (Weighted) Loss seen by the Server: \n{server.best_aggregated_loss}") + + # Shutdown the server gracefully + server.shutdown() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="FL Server Main") + parser.add_argument( + "--config_path", + action="store", + type=str, + help="Path to configuration file.", + default="config.yaml", + ) + parser.add_argument( + "--server_address", + action="store", + type=str, + help="Server Address to be used to communicate with the clients", + default="0.0.0.0:8080", + ) + parser.add_argument( + "--seed", + action="store", + type=int, + help="Seed for the random number generators across python, torch, and numpy", + required=False, + ) + parser.add_argument( + "--lam", action="store", type=float, help="Ditto loss weight for local model training", default=0.01 + ) + args = parser.parse_args() + + config = load_config(args.config_path) + log(INFO, f"Server Address: {args.server_address}") + log(INFO, f"Lambda: {args.lam}") + + # Set the random seed for reproducibility + set_all_random_seeds(args.seed) + + main(config, args.server_address, args.lam) diff --git a/research/rxrx1/ditto_mkmmd/__init__.py b/research/rxrx1/ditto_mkmmd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/rxrx1/ditto_mkmmd/client.py b/research/rxrx1/ditto_mkmmd/client.py new file mode 100644 index 000000000..e5994987b --- /dev/null +++ b/research/rxrx1/ditto_mkmmd/client.py @@ -0,0 +1,227 @@ +import argparse +import os +from collections.abc import Sequence +from logging import INFO +from pathlib import Path + +import flwr as fl +import torch +import torch.nn as nn +from flwr.common.logger import log +from flwr.common.typing import Config +from torch.nn.modules.loss import _Loss +from torch.optim import Optimizer +from torch.utils.data import DataLoader +from torchvision import models + +from fl4health.checkpointing.checkpointer import BestLossTorchModuleCheckpointer, LatestTorchModuleCheckpointer +from fl4health.checkpointing.client_module import ClientCheckpointAndStateModule +from fl4health.clients.mkmmd_clients.ditto_mkmmd_client import DittoMkMmdClient +from fl4health.datasets.rxrx1.load_data import load_rxrx1_data, load_rxrx1_test_data +from fl4health.reporting.base_reporter import BaseReporter +from fl4health.utils.config import narrow_dict_type +from fl4health.utils.losses import LossMeterType +from fl4health.utils.metrics import Accuracy, Metric +from fl4health.utils.random import set_all_random_seeds + +BASELINE_LAYERS = ["layer1", "layer2", "layer3", "layer4", "avgpool"] + + +class Rxrx1DittoClient(DittoMkMmdClient): + def __init__( + self, + data_path: Path, + metrics: Sequence[Metric], + device: torch.device, + client_number: int, + learning_rate: float, + loss_meter_type: LossMeterType = LossMeterType.AVERAGE, + mkmmd_loss_weight: float = 10, + feature_l2_norm_weight: float = 1, + mkmmd_loss_depth: int = 1, + beta_global_update_interval: int = 20, + checkpoint_and_state_module: ClientCheckpointAndStateModule | None = None, + reporters: Sequence[BaseReporter] | None = None, + progress_bar: bool = False, + client_name: str | None = None, + ) -> None: + super().__init__( + data_path=data_path, + metrics=metrics, + device=device, + loss_meter_type=loss_meter_type, + checkpoint_and_state_module=checkpoint_and_state_module, + reporters=reporters, + progress_bar=progress_bar, + client_name=client_name, + mkmmd_loss_weight=mkmmd_loss_weight, + feature_extraction_layers=BASELINE_LAYERS[-1 * mkmmd_loss_depth :], + feature_l2_norm_weight=feature_l2_norm_weight, + beta_global_update_interval=beta_global_update_interval, + ) + self.client_number = client_number + self.learning_rate: float = learning_rate + + log(INFO, f"Client Name: {self.client_name}, Client Number: {self.client_number}") + + # Number of batches to accumulate before updating the kernel betas + self.num_accumulating_batches = 50 + + def setup_client(self, config: Config) -> None: + # Check if the client number is within the range of the total number of clients + num_clients = narrow_dict_type(config, "n_clients", int) + assert 0 <= self.client_number < num_clients + super().setup_client(config) + + def get_data_loaders(self, config: Config) -> tuple[DataLoader, DataLoader]: + batch_size = narrow_dict_type(config, "batch_size", int) + train_loader, val_loader, _ = load_rxrx1_data( + data_path=self.data_path, client_num=self.client_number, batch_size=batch_size, seed=self.client_number + ) + + return train_loader, val_loader + + def get_test_data_loader(self, config: Config) -> DataLoader | None: + batch_size = narrow_dict_type(config, "batch_size", int) + test_loader, _ = load_rxrx1_test_data( + data_path=self.data_path, client_num=self.client_number, batch_size=batch_size + ) + + return test_loader + + def get_criterion(self, config: Config) -> _Loss: + return torch.nn.CrossEntropyLoss() + + def get_optimizer(self, config: Config) -> dict[str, Optimizer]: + global_optimizer = torch.optim.AdamW(self.global_model.parameters(), lr=self.learning_rate) + local_optimizer = torch.optim.AdamW(self.model.parameters(), lr=self.learning_rate) + return {"global": global_optimizer, "local": local_optimizer} + + def get_model(self, config: Config) -> nn.Module: + return models.resnet18(pretrained=True).to(self.device) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="FL Client Main") + parser.add_argument( + "--artifact_dir", + action="store", + type=str, + help="Path to save client artifacts such as logs and model checkpoints", + required=True, + ) + parser.add_argument( + "--dataset_dir", + action="store", + type=str, + help="Path to the preprocessed Rxrx1 Dataset", + required=True, + ) + parser.add_argument( + "--run_name", + action="store", + help="Name of the run, model checkpoints will be saved under a subfolder with this name", + required=True, + ) + parser.add_argument( + "--server_address", + action="store", + type=str, + help="Server Address for the clients to communicate with the server through", + default="0.0.0.0:8080", + ) + parser.add_argument( + "--client_number", + action="store", + type=int, + help="Number of the client for dataset loading (should be 0-3 for Rxrx1)", + required=True, + ) + parser.add_argument( + "--learning_rate", action="store", type=float, help="Learning rate for local optimization", default=0.1 + ) + parser.add_argument( + "--seed", + action="store", + type=int, + help="Seed for the random number generators across python, torch, and numpy", + required=False, + ) + parser.add_argument( + "--mu", + action="store", + type=float, + help="Weight for the mkmmd losses", + required=False, + ) + parser.add_argument( + "--l2", + action="store", + type=float, + help="Weight for the feature l2 norm loss as a regularizer", + required=False, + ) + parser.add_argument( + "--mkmmd_loss_depth", + action="store", + type=int, + help="Depth of applying the mkmmd loss", + required=False, + default=1, + ) + parser.add_argument( + "--beta_update_interval", + action="store", + type=int, + help="Interval for updating the beta values of mkmmd loss", + required=False, + default=20, + ) + args = parser.parse_args() + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + log(INFO, f"Device to be used: {device}") + log(INFO, f"Server Address: {args.server_address}") + log(INFO, f"Learning Rate: {args.learning_rate}") + log(INFO, f"Mu: {args.mu}") + log(INFO, f"Feature L2 Norm Weight: {args.l2}") + log(INFO, f"MKMMD Loss Depth: {args.mkmmd_loss_depth}") + log(INFO, f"Beta Update Interval: {args.beta_update_interval}") + + # Set the random seed for reproducibility + set_all_random_seeds(args.seed) + + # Adding extensive checkpointing for the client + checkpoint_dir = os.path.join(args.artifact_dir, args.run_name) + pre_aggregation_best_checkpoint_name = f"pre_aggregation_client_{args.client_number}_best_model.pkl" + pre_aggregation_last_checkpoint_name = f"pre_aggregation_client_{args.client_number}_last_model.pkl" + post_aggregation_best_checkpoint_name = f"post_aggregation_client_{args.client_number}_best_model.pkl" + post_aggregation_last_checkpoint_name = f"post_aggregation_client_{args.client_number}_last_model.pkl" + checkpoint_and_state_module = ClientCheckpointAndStateModule( + pre_aggregation=[ + BestLossTorchModuleCheckpointer(checkpoint_dir, pre_aggregation_best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, pre_aggregation_last_checkpoint_name), + ], + post_aggregation=[ + BestLossTorchModuleCheckpointer(checkpoint_dir, post_aggregation_best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, post_aggregation_last_checkpoint_name), + ], + ) + + data_path = Path(args.dataset_dir) + client = Rxrx1DittoClient( + data_path=data_path, + metrics=[Accuracy("accuracy")], + device=device, + client_number=args.client_number, + learning_rate=args.learning_rate, + checkpoint_and_state_module=checkpoint_and_state_module, + feature_l2_norm_weight=args.l2, + mkmmd_loss_depth=args.mkmmd_loss_depth, + mkmmd_loss_weight=args.mu, + beta_global_update_interval=args.beta_update_interval, + ) + + fl.client.start_client(server_address=args.server_address, client=client.to_client()) + # Shutdown the client gracefully + client.shutdown() diff --git a/research/rxrx1/ditto_mkmmd/config.yaml b/research/rxrx1/ditto_mkmmd/config.yaml new file mode 100644 index 000000000..09e723ad3 --- /dev/null +++ b/research/rxrx1/ditto_mkmmd/config.yaml @@ -0,0 +1,7 @@ +# Parameters that describe server +n_server_rounds: 10 # The number of rounds to run FL + +# Parameters that describe clients +n_clients: 4 # The number of clients in the FL experiment +local_epochs: 5 # The number of epochs to complete for client +batch_size: 32 # The batch size for client training diff --git a/research/rxrx1/ditto_mkmmd/run_fold_experiment.slrm b/research/rxrx1/ditto_mkmmd/run_fold_experiment.slrm new file mode 100644 index 000000000..a34a9bb45 --- /dev/null +++ b/research/rxrx1/ditto_mkmmd/run_fold_experiment.slrm @@ -0,0 +1,180 @@ +#!/bin/bash + +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=1 +#SBATCH --gres=gpu:1 +#SBATCH --mem=64G +#SBATCH --partition=a40 +#SBATCH --qos=m2 +#SBATCH --job-name=fl_five_fold_exp +#SBATCH --output=%j_%x.out +#SBATCH --error=%j_%x.err +#SBATCH --time=4:00:00 + +############################################### +# Usage: +# +# sbatch research/rxrx1/ditto_mkmmd/run_fold_experiment.slrm \ +# path_to_config.yaml \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ \ +# client_side_learning_rate_value \ +# lambda value \ +# mu value \ +# l2 value \ +# mkmmd_loss_depth \ +# beta_update_interval \ +# server_address +# +# Example: +# sbatch research/rxrx1/ditto_mkmmd/run_fold_experiment.slrm \ +# research/rxrx1/ditto_mkmmd/config.yaml \ +# research/rxrx1/ditto_mkmmd/hp_results/ \ +# /datasets/rxrx1 \ +# /h/demerson/vector_repositories/fl4health_env/ \ +# 0.0001 \ +# 0.01 \ +# 0.1 \ +# 0.1 \ +# 2 \ +# 20 \ +# 0.0.0.0:8080 +# +# Notes: +# 1) The sbatch command above should be run from the top level directory of the repository. +# 2) This example runs ditto MkMMD. As such the data paths and python launch commands are hardcoded. If you want to change +# the example you run, you need to explicitly modify the code below. +# 3) The logging directories need to ALREADY EXIST. The script does not create them. +############################################### + +# Note: +# ntasks: Total number of processes to use across world +# ntasks-per-node: How many processes each node should create + +# Set NCCL options +# export NCCL_DEBUG=INFO +# NCCL backend to communicate between GPU workers is not provided in vector's cluster. +# Disable this option in slurm. +export NCCL_IB_DISABLE=1 + +if [[ "${SLURM_JOB_PARTITION}" == "t4v2" ]] || \ + [[ "${SLURM_JOB_PARTITION}" == "rtx6000" ]]; then + echo export NCCL_SOCKET_IFNAME=bond0 on "${SLURM_JOB_PARTITION}" + export NCCL_SOCKET_IFNAME=bond0 +fi + + +export CUBLAS_WORKSPACE_CONFIG=:4096:8 +# Process Inputs + +SERVER_CONFIG_PATH=$1 +ARTIFACT_DIR=$2 +DATASET_DIR=$3 +VENV_PATH=$4 +CLIENT_LR=$5 +LAM_VALUE=$6 +MU_VALUE=$7 +L2_VALUE=$8 +MKMMD_LOSS_DEPTH=$9 +BETA_INTERVAL=${10} +SERVER_ADDRESS=${11} + +# Create the artifact directory +mkdir "${ARTIFACT_DIR}" + +RUN_NAMES=( "Run1" "Run2" "Run3" "Run4" "Run5" ) +SEEDS=( 2021 2022 2023 2024 2025 ) + +echo "Python Venv Path: ${VENV_PATH}" + +echo "World size: ${SLURM_NTASKS}" +echo "Number of nodes: ${SLURM_NNODES}" +NUM_GPUs=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l) +echo "GPUs per node: ${NUM_GPUs}" + +# Source the environment +source ${VENV_PATH}bin/activate +echo "Active Environment:" +which python + +for ((i=0; i<${#RUN_NAMES[@]}; i++)); +do + RUN_NAME="${RUN_NAMES[i]}" + SEED="${SEEDS[i]}" + # create the run directory + RUN_DIR="${ARTIFACT_DIR}${RUN_NAME}/" + echo "Starting Run and logging artifcats at ${RUN_DIR}" + if [ -d "${RUN_DIR}" ] + then + # Directory already exists, we check if the done.out file exists + if [ -f "${RUN_DIR}done.out" ] + then + # Done file already exists so we skip this run + echo "Run already completed. Skipping Run." + continue + else + # Done file doesn't exists (assume pre-emption happened) + # Delete the partially finished contents and start over + echo "Run did not finished correctly. Re-running." + rm -r "${RUN_DIR}" + mkdir "${RUN_DIR}" + fi + else + # Directory doesn't exist yet, so we create it. + echo "Run directory does not exist. Creating it." + mkdir "${RUN_DIR}" + fi + + SERVER_OUTPUT_FILE="${RUN_DIR}server.out" + + # Start the server, divert the outputs to a server file + + echo "Server logging at: ${SERVER_OUTPUT_FILE}" + echo "Launching Server" + + nohup python -m research.rxrx1.ditto_mkmmd.server \ + --config_path ${SERVER_CONFIG_PATH} \ + --server_address ${SERVER_ADDRESS} \ + --seed ${SEED} \ + --lam ${LAM_VALUE} \ + > ${SERVER_OUTPUT_FILE} 2>&1 & + + # Sleep for 20 seconds to allow the server to come up. + sleep 20 + + # Start n number of clients and divert the outputs to their own files + n_clients=5 + for (( c=0; c<${n_clients}; c++ )) + do + CLIENT_NAME="client_${c}" + echo "Launching ${CLIENT_NAME}" + + CLIENT_LOG_PATH="${RUN_DIR}${CLIENT_NAME}.out" + echo "${CLIENT_NAME} logging at: ${CLIENT_LOG_PATH}" + nohup python -m research.rxrx1.ditto_mkmmd.client \ + --artifact_dir ${ARTIFACT_DIR} \ + --dataset_dir ${DATASET_DIR} \ + --run_name ${RUN_NAME} \ + --client_number ${c} \ + --learning_rate ${CLIENT_LR} \ + --mu ${MU_VALUE} \ + --l2 ${L2_VALUE} \ + --mkmmd_loss_depth ${MKMMD_LOSS_DEPTH} \ + --beta_update_interval ${BETA_INTERVAL} \ + --server_address ${SERVER_ADDRESS} \ + --seed ${SEED} \ + > ${CLIENT_LOG_PATH} 2>&1 & + done + + echo "FL Processes Running" + + wait + + # Create a file that verifies that the Run concluded properly + touch "${RUN_DIR}done.out" + echo "Finished FL Processes" + +done diff --git a/research/rxrx1/ditto_mkmmd/run_hp_sweep.sh b/research/rxrx1/ditto_mkmmd/run_hp_sweep.sh new file mode 100755 index 000000000..8073d97c9 --- /dev/null +++ b/research/rxrx1/ditto_mkmmd/run_hp_sweep.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +############################################### +# Usage: +# +# ./research/rxrx1/ditto_mkmmd/run_hp_sweep.sh \ +# path_to_config.yaml \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ +# +# Example: +# ./research/rxrx1/ditto_mkmmd/run_hp_sweep.sh \ +# research/rxrx1/ditto_mkmmd/config.yaml \ +# research/rxrx1/ditto_mkmmd \ +# /datasets/rxrx1 \ +# /h/demerson/vector_repositories/fl4health_env/ +# +# Notes: +# 1) The bash command above should be run from the top level directory of the repository. +############################################### + +SERVER_CONFIG_PATH=$1 +ARTIFACT_DIR=$2 +DATASET_DIR=$3 +VENV_PATH=$4 + +LR_VALUES=( 0.00001 0.0001 0.001 0.01 0.1 ) +LAM_VALUES=( 0.01 0.1 1.0 ) +MU_VALUES=( 0.01 0.1 1.0 ) +L2_VALUES=( 0.01 0.1 1.0 ) +MKMMD_LOSS_DEPTHS=( 1 2 3 ) +BETA_INTERVALS=( -1 0 20 ) + +SERVER_PORT=8100 + +# Create sweep folder +SWEEP_DIRECTORY="${ARTIFACT_DIR}hp_sweep_results" +echo "Creating sweep folder at ${SWEEP_DIRECTORY}" +mkdir ${SWEEP_DIRECTORY} + +for LR_VALUE in "${LR_VALUES[@]}"; +do + for LAM_VALUE in "${LAM_VALUES[@]}"; + do + for MU_VALUE in "${MU_VALUES[@]}"; + do + for L2_VALUE in "${L2_VALUES[@]}"; + do + for MKMMD_LOSS_DEPTH in "${MKMMD_LOSS_DEPTHS[@]}"; + do + for BETA_INTERVAL in "${BETA_INTERVALS[@]}"; + do + EXPERIMENT_NAME="lr_${LR_VALUE}_lam_${LAM_VALUE}_mu_${MU_VALUE}_l2_${L2_VALUE}_depth_${MKMMD_LOSS_DEPTH}_interval_${BETA_INTERVAL}" + echo "Beginning Experiment ${EXPERIMENT_NAME}" + EXPERIMENT_DIRECTORY="${SWEEP_DIRECTORY}/${EXPERIMENT_NAME}/" + echo "Creating experiment folder ${EXPERIMENT_DIRECTORY}" + mkdir "${EXPERIMENT_DIRECTORY}" + SERVER_ADDRESS="0.0.0.0:${SERVER_PORT}" + echo "Server Address: ${SERVER_ADDRESS}" + SBATCH_COMMAND="research/rxrx1/ditto_mkmmd/run_fold_experiment.slrm \ + ${SERVER_CONFIG_PATH} \ + ${EXPERIMENT_DIRECTORY} \ + ${DATASET_DIR} \ + ${VENV_PATH} \ + ${LR_VALUE} \ + ${LAM_VALUE} \ + ${MU_VALUE} \ + ${L2_VALUE} \ + ${MKMMD_LOSS_DEPTH} \ + ${BETA_INTERVAL} \ + ${SERVER_ADDRESS}" + echo "Running sbatch command ${SBATCH_COMMAND}" + sbatch ${SBATCH_COMMAND} + ((SERVER_PORT=SERVER_PORT+1)) + done + done + done + done + done +done + +echo Experiments Launched diff --git a/research/rxrx1/ditto_mkmmd/server.py b/research/rxrx1/ditto_mkmmd/server.py new file mode 100644 index 000000000..96c3b044e --- /dev/null +++ b/research/rxrx1/ditto_mkmmd/server.py @@ -0,0 +1,114 @@ +import argparse +from functools import partial +from logging import INFO +from typing import Any + +import flwr as fl +from flwr.common.logger import log +from flwr.common.typing import Config +from flwr.server.client_manager import SimpleClientManager +from torchvision import models + +from fl4health.strategies.fedavg_with_adaptive_constraint import FedAvgWithAdaptiveConstraint +from fl4health.utils.config import load_config +from fl4health.utils.metric_aggregation import evaluate_metrics_aggregation_fn, fit_metrics_aggregation_fn +from fl4health.utils.parameter_extraction import get_all_model_parameters +from fl4health.utils.random import set_all_random_seeds +from research.rxrx1.personal_server import PersonalServer + + +def fit_config( + batch_size: int, + local_epochs: int, + n_server_rounds: int, + n_clients: int, + current_server_round: int, +) -> Config: + return { + "batch_size": batch_size, + "local_epochs": local_epochs, + "n_server_rounds": n_server_rounds, + "n_clients": n_clients, + "current_server_round": current_server_round, + } + + +def main(config: dict[str, Any], server_address: str, lam: float) -> None: + # This function will be used to produce a config that is sent to each client to initialize their own environment + fit_config_fn = partial( + fit_config, + config["batch_size"], + config["local_epochs"], + config["n_server_rounds"], + config["n_clients"], + ) + + client_manager = SimpleClientManager() + # Initializing the model on the server side + model = models.resnet18(pretrained=True) + # Server performs simple FedAveraging as its server-side optimization strategy + strategy = FedAvgWithAdaptiveConstraint( + min_fit_clients=config["n_clients"], + min_evaluate_clients=config["n_clients"], + # Server waits for min_available_clients before starting FL rounds + min_available_clients=config["n_clients"], + on_fit_config_fn=fit_config_fn, + # We use the same fit config function, as nothing changes for eval + on_evaluate_config_fn=fit_config_fn, + fit_metrics_aggregation_fn=fit_metrics_aggregation_fn, + evaluate_metrics_aggregation_fn=evaluate_metrics_aggregation_fn, + initial_parameters=get_all_model_parameters(model), + initial_loss_weight=lam, + ) + + server = PersonalServer(client_manager=client_manager, fl_config=config, strategy=strategy) + + fl.server.start_server( + server=server, + server_address=server_address, + config=fl.server.ServerConfig(num_rounds=config["n_server_rounds"]), + ) + + log(INFO, "Training Complete") + log(INFO, f"Best Aggregated (Weighted) Loss seen by the Server: \n{server.best_aggregated_loss}") + + # Shutdown the server gracefully + server.shutdown() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="FL Server Main") + parser.add_argument( + "--config_path", + action="store", + type=str, + help="Path to configuration file.", + default="config.yaml", + ) + parser.add_argument( + "--server_address", + action="store", + type=str, + help="Server Address to be used to communicate with the clients", + default="0.0.0.0:8080", + ) + parser.add_argument( + "--seed", + action="store", + type=int, + help="Seed for the random number generators across python, torch, and numpy", + required=False, + ) + parser.add_argument( + "--lam", action="store", type=float, help="Ditto loss weight for local model training", default=0.01 + ) + args = parser.parse_args() + + config = load_config(args.config_path) + log(INFO, f"Server Address: {args.server_address}") + log(INFO, f"Lambda: {args.lam}") + + # Set the random seed for reproducibility + set_all_random_seeds(args.seed) + + main(config, args.server_address, args.lam) diff --git a/research/rxrx1/evaluate_on_test.py b/research/rxrx1/evaluate_on_test.py new file mode 100644 index 000000000..0fa247c9f --- /dev/null +++ b/research/rxrx1/evaluate_on_test.py @@ -0,0 +1,697 @@ +import argparse +import copy +from logging import INFO +from pathlib import Path + +import torch +from flwr.common.logger import log + +from fl4health.datasets.rxrx1.load_data import load_rxrx1_test_data +from fl4health.utils.dataset import TensorDataset +from fl4health.utils.metrics import Accuracy +from research.rxrx1.utils import ( + evaluate_rxrx1_model, + get_all_run_folders, + get_metric_avg_std, + load_best_global_model, + load_eval_best_post_aggregation_local_model, + load_eval_best_pre_aggregation_local_model, + load_eval_last_post_aggregation_local_model, + load_eval_last_pre_aggregation_local_model, + load_last_global_model, + write_measurement_results, +) + +NUM_CLIENTS = 4 +BATCH_SIZE = 32 + + +def main( + artifact_dir: str, + dataset_dir: str, + eval_write_path: str, + eval_best_pre_aggregation_local_models: bool, + eval_last_pre_aggregation_local_models: bool, + eval_best_post_aggregation_local_models: bool, + eval_last_post_aggregation_local_models: bool, + eval_best_global_model: bool, + eval_last_global_model: bool, + eval_over_aggregated_test_data: bool, +) -> None: + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + all_run_folder_dir = get_all_run_folders(artifact_dir) + test_results: dict[str, float] = {} + metrics = [Accuracy("rxrx1_accuracy")] + + all_pre_best_local_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + all_pre_last_local_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + all_post_best_local_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + all_post_last_local_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + + all_best_server_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + all_last_server_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + + if eval_over_aggregated_test_data: + all_pre_best_local_agg_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + all_pre_last_local_agg_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + all_post_best_local_agg_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + all_post_last_local_agg_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + + all_best_server_agg_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + all_last_server_agg_test_metrics = {run_folder_dir: 0.0 for run_folder_dir in all_run_folder_dir} + + if eval_over_aggregated_test_data: + for client_number in range(NUM_CLIENTS): + test_loader, num_examples = load_rxrx1_test_data( + data_path=Path(dataset_dir), client_num=client_number, batch_size=BATCH_SIZE + ) + assert isinstance(test_loader.dataset, TensorDataset), "Expected TensorDataset." + + if client_number == 0: + aggregated_dataset = copy.deepcopy(test_loader.dataset) + else: + assert aggregated_dataset.data is not None and test_loader.dataset.data is not None + aggregated_dataset.data = torch.cat((aggregated_dataset.data, test_loader.dataset.data)) + assert aggregated_dataset.targets is not None and test_loader.dataset.targets is not None + aggregated_dataset.targets = torch.cat((aggregated_dataset.targets, test_loader.dataset.targets)) + + aggregated_test_loader = torch.utils.data.DataLoader(aggregated_dataset, batch_size=BATCH_SIZE, shuffle=False) + aggregated_num_examples = len(aggregated_dataset) + + for client_number in range(NUM_CLIENTS): + test_loader, _ = load_rxrx1_test_data( + data_path=Path(dataset_dir), client_num=client_number, batch_size=BATCH_SIZE + ) + + pre_best_local_test_metrics = [] + pre_last_local_test_metrics = [] + post_best_local_test_metrics = [] + post_last_local_test_metrics = [] + best_server_test_metrics = [] + last_server_test_metrics = [] + + if eval_over_aggregated_test_data: + pre_best_local_agg_test_metrics = [] + pre_last_local_agg_test_metrics = [] + post_best_local_agg_test_metrics = [] + post_last_local_agg_test_metrics = [] + best_server_agg_test_metrics = [] + last_server_agg_test_metrics = [] + + for run_folder_dir in all_run_folder_dir: + if eval_best_pre_aggregation_local_models: + local_model = load_eval_best_pre_aggregation_local_model(run_folder_dir, client_number) + local_run_metric = evaluate_rxrx1_model(local_model, test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Best Pre-aggregation Local Model Test Performance: {local_run_metric}", + ) + pre_best_local_test_metrics.append(local_run_metric) + # Perform weighted average of the local model performance across all clients based on the number + # of examples in the evaluation set + all_pre_best_local_test_metrics[run_folder_dir] += ( + local_run_metric * num_examples["eval_set"] / aggregated_num_examples + ) + if eval_over_aggregated_test_data: + + agg_local_run_metric = evaluate_rxrx1_model(local_model, aggregated_test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Best Pre-aggregation Local Model Aggregated Test Performance: {agg_local_run_metric}", + ) + pre_best_local_agg_test_metrics.append(agg_local_run_metric) + all_pre_best_local_agg_test_metrics[run_folder_dir] += agg_local_run_metric / NUM_CLIENTS + + if eval_last_pre_aggregation_local_models: + local_model = load_eval_last_pre_aggregation_local_model(run_folder_dir, client_number) + local_run_metric = evaluate_rxrx1_model(local_model, test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Last Pre-aggregation Local Model Test Performance: {local_run_metric}", + ) + pre_last_local_test_metrics.append(local_run_metric) + # Perform weighted average of the local model performance across all clients based on the number + # of examples in the evaluation set + all_pre_last_local_test_metrics[run_folder_dir] += ( + local_run_metric * num_examples["eval_set"] / aggregated_num_examples + ) + + if eval_over_aggregated_test_data: + + agg_local_run_metric = evaluate_rxrx1_model(local_model, aggregated_test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Last Pre-aggregation Local Model Aggregated Test Performance: {agg_local_run_metric}", + ) + pre_last_local_agg_test_metrics.append(agg_local_run_metric) + all_pre_last_local_agg_test_metrics[run_folder_dir] += agg_local_run_metric / NUM_CLIENTS + + if eval_best_post_aggregation_local_models: + local_model = load_eval_best_post_aggregation_local_model(run_folder_dir, client_number) + local_run_metric = evaluate_rxrx1_model(local_model, test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Best Post-aggregation Local Model Test Performance: {local_run_metric}", + ) + post_best_local_test_metrics.append(local_run_metric) + # Perform weighted average of the local model performance across all clients based on the number + # of examples in the evaluation set + all_post_best_local_test_metrics[run_folder_dir] += ( + local_run_metric * num_examples["eval_set"] / aggregated_num_examples + ) + + if eval_over_aggregated_test_data: + + agg_local_run_metric = evaluate_rxrx1_model(local_model, aggregated_test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Best Post-aggregation Local Model Aggregated Test Performance: {agg_local_run_metric}", + ) + post_best_local_agg_test_metrics.append(agg_local_run_metric) + all_post_best_local_agg_test_metrics[run_folder_dir] += agg_local_run_metric / NUM_CLIENTS + + if eval_last_post_aggregation_local_models: + local_model = load_eval_last_post_aggregation_local_model(run_folder_dir, client_number) + local_run_metric = evaluate_rxrx1_model(local_model, test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Last Post-aggregation Local Model Test Performance: {local_run_metric}", + ) + post_last_local_test_metrics.append(local_run_metric) + # Perform weighted average of the local model performance across all clients based on the number + # of examples in the evaluation set + all_post_last_local_test_metrics[run_folder_dir] += ( + local_run_metric * num_examples["eval_set"] / aggregated_num_examples + ) + + if eval_over_aggregated_test_data: + + agg_local_run_metric = evaluate_rxrx1_model(local_model, aggregated_test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Last Post-aggregation Local Model Aggregated Test Performance: {agg_local_run_metric}", + ) + post_last_local_agg_test_metrics.append(agg_local_run_metric) + all_post_last_local_agg_test_metrics[run_folder_dir] += agg_local_run_metric / NUM_CLIENTS + + if eval_best_global_model: + server_model = load_best_global_model(run_folder_dir) + server_run_metric = evaluate_rxrx1_model(server_model, test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Server Best Model Test Performance: {server_run_metric}", + ) + best_server_test_metrics.append(server_run_metric) + # Perform weighted average of the server model performance across all clients based on the number + # of examples in the evaluation set + all_best_server_test_metrics[run_folder_dir] += ( + server_run_metric * num_examples["eval_set"] / aggregated_num_examples + ) + + if eval_over_aggregated_test_data: + + agg_server_run_metric = evaluate_rxrx1_model(server_model, aggregated_test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Server Best Model Aggregated Test Performance: {agg_server_run_metric}", + ) + best_server_agg_test_metrics.append(agg_server_run_metric) + all_best_server_agg_test_metrics[run_folder_dir] += agg_server_run_metric / NUM_CLIENTS + + if eval_last_global_model: + server_model = load_last_global_model(run_folder_dir) + server_run_metric = evaluate_rxrx1_model(server_model, test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Server Last Model Test Performance: {server_run_metric}", + ) + last_server_test_metrics.append(server_run_metric) + # Perform weighted average of the server model performance across all clients based on the number + # of examples in the evaluation set + all_last_server_test_metrics[run_folder_dir] += ( + server_run_metric * num_examples["eval_set"] / aggregated_num_examples + ) + + if eval_over_aggregated_test_data: + + agg_server_run_metric = evaluate_rxrx1_model(server_model, aggregated_test_loader, metrics, device) + log( + INFO, + f"Client Number {client_number}, Run folder: {run_folder_dir}: " + f"Server Last Model Aggregated Test Performance: {agg_server_run_metric}", + ) + last_server_agg_test_metrics.append(agg_server_run_metric) + all_last_server_agg_test_metrics[run_folder_dir] += agg_server_run_metric / NUM_CLIENTS + + # Write the results for each client + if eval_best_pre_aggregation_local_models: + avg_test_metric, std_test_metric = get_metric_avg_std(pre_best_local_test_metrics) + log( + INFO, + f"""Client {client_number} Pre-aggregation Best Model Average Test + Performance on own Data: {avg_test_metric}""", + ) + log( + INFO, + f"""Client {client_number} Pre-aggregation Best Model St. Dev. Test + Performance on own Data: {std_test_metric}""", + ) + test_results[f"client_{client_number}_pre_best_model_local_avg"] = avg_test_metric + test_results[f"client_{client_number}_pre_best_model_local_std"] = std_test_metric + + if eval_over_aggregated_test_data: + avg_test_metric, std_test_metric = get_metric_avg_std(pre_best_local_agg_test_metrics) + log( + INFO, + f"""Client {client_number} Pre-aggregation Best Model Average Aggregated Test + Performance: {avg_test_metric}""", + ) + log( + INFO, + f"""Client {client_number} Pre-aggregation Best Model St. Dev. Aggregated Test + Performance: {std_test_metric}""", + ) + test_results[f"agg_client_{client_number}_pre_best_model_local_avg"] = avg_test_metric + test_results[f"agg_client_{client_number}_pre_best_model_local_std"] = std_test_metric + + if eval_last_pre_aggregation_local_models: + avg_test_metric, std_test_metric = get_metric_avg_std(pre_last_local_test_metrics) + log( + INFO, + f"""Client {client_number} Pre-aggregation Last Model Average Test + Performance on own Data: {avg_test_metric}""", + ) + log( + INFO, + f"""Client {client_number} Pre-aggregation Last Model St. Dev. Test + Performance on own Data: {std_test_metric}""", + ) + test_results[f"client_{client_number}_pre_last_model_local_avg"] = avg_test_metric + test_results[f"client_{client_number}_pre_last_model_local_std"] = std_test_metric + if eval_over_aggregated_test_data: + avg_test_metric, std_test_metric = get_metric_avg_std(pre_last_local_agg_test_metrics) + log( + INFO, + f"""Client {client_number} Pre-aggregation Last Model Average Aggregated Test + Performance: {avg_test_metric}""", + ) + log( + INFO, + f"""Client {client_number} Pre-aggregation Last Model St. Dev. Aggregated Test + Performance: {std_test_metric}""", + ) + test_results[f"agg_client_{client_number}_pre_last_model_local_avg"] = avg_test_metric + test_results[f"agg_client_{client_number}_pre_last_model_local_std"] = std_test_metric + + if eval_best_post_aggregation_local_models: + avg_test_metric, std_test_metric = get_metric_avg_std(post_best_local_test_metrics) + log( + INFO, + f"""Client {client_number} Post-aggregation Best Model Average Test + Performance on own Data: {avg_test_metric}""", + ) + log( + INFO, + f"""Client {client_number} Post-aggregation Best Model St. Dev. Test + Performance on own Data: {std_test_metric}""", + ) + test_results[f"client_{client_number}_post_best_model_local_avg"] = avg_test_metric + test_results[f"client_{client_number}_post_best_model_local_std"] = std_test_metric + + if eval_over_aggregated_test_data: + avg_test_metric, std_test_metric = get_metric_avg_std(post_best_local_agg_test_metrics) + log( + INFO, + f"""Client {client_number} Post-aggregation Best Model Average Aggregated Test + Performance: {avg_test_metric}""", + ) + log( + INFO, + f"""Client {client_number} Post-aggregation Best Model St. Dev. Aggregated Test + Performance: {std_test_metric}""", + ) + test_results[f"agg_client_{client_number}_post_best_model_local_avg"] = avg_test_metric + test_results[f"agg_client_{client_number}_post_best_model_local_std"] = std_test_metric + + if eval_last_post_aggregation_local_models: + avg_test_metric, std_test_metric = get_metric_avg_std(post_last_local_test_metrics) + log( + INFO, + f"""Client {client_number} Post-aggregation Last Model Average Test + Performance on own Data: {avg_test_metric}""", + ) + log( + INFO, + f"""Client {client_number} Post-aggregation Last Model St. Dev. Test + Performance on own Data: {std_test_metric}""", + ) + test_results[f"client_{client_number}_post_last_model_local_avg"] = avg_test_metric + test_results[f"client_{client_number}_post_last_model_local_std"] = std_test_metric + + if eval_over_aggregated_test_data: + avg_test_metric, std_test_metric = get_metric_avg_std(post_last_local_agg_test_metrics) + log( + INFO, + f"""Client {client_number} Post-aggregation Last Model Average Aggregated Test + Performance: {avg_test_metric}""", + ) + log( + INFO, + f"""Client {client_number} Post-aggregation Last Model St. Dev. Aggregated Test + Performance: {std_test_metric}""", + ) + test_results[f"agg_client_{client_number}_post_last_model_local_avg"] = avg_test_metric + test_results[f"agg_client_{client_number}_post_last_model_local_std"] = std_test_metric + + if eval_best_global_model: + avg_server_test_global_metric, std_server_test_global_metric = get_metric_avg_std(best_server_test_metrics) + log( + INFO, + f"Server Best model Average Test Performance on Client {client_number} " + f"Data: {avg_server_test_global_metric}", + ) + log( + INFO, + f"Server Best model St. Dev. Test Performance on Client {client_number} " + f"Data: {std_server_test_global_metric}", + ) + test_results[f"server_best_model_client_{client_number}_avg"] = avg_server_test_global_metric + test_results[f"server_best_model_client_{client_number}_std"] = std_server_test_global_metric + + if eval_last_global_model: + avg_server_test_global_metric, std_server_test_global_metric = get_metric_avg_std(last_server_test_metrics) + log( + INFO, + f"Server Last model Average Test Performance on Client {client_number} " + f"Data: {avg_server_test_global_metric}", + ) + log( + INFO, + f"Server Last model St. Dev. Test Performance on Client {client_number} " + f"Data: {std_server_test_global_metric}", + ) + test_results[f"server_last_model_client_{client_number}_avg"] = avg_server_test_global_metric + test_results[f"server_last_model_client_{client_number}_std"] = std_server_test_global_metric + + if eval_over_aggregated_test_data: + if eval_best_global_model: + avg_server_test_global_metric, std_server_test_global_metric = get_metric_avg_std( + best_server_agg_test_metrics + ) + log( + INFO, + f"Server Best model Average Test Performance on Aggregated Client Data" + f"Data: {avg_server_test_global_metric}", + ) + log( + INFO, + f"Server Best model St. Dev. Test Performance on Aggregated Client Data" + f"Data: {std_server_test_global_metric}", + ) + test_results["agg_server_best_model_client_avg"] = avg_server_test_global_metric + test_results["agg_server_best_model_client_std"] = std_server_test_global_metric + + if eval_last_global_model: + avg_server_test_global_metric, std_server_test_global_metric = get_metric_avg_std( + last_server_agg_test_metrics + ) + log( + INFO, + f"Server Last model Average Test Performance on Aggregated Client Data" + f"Data: {avg_server_test_global_metric}", + ) + log( + INFO, + f"Server Last model St. Dev. Test Performance on Aggregated Client Data" + f"Data: {std_server_test_global_metric}", + ) + test_results["agg_server_last_model_client_avg"] = avg_server_test_global_metric + test_results["agg_server_last_model_client_std"] = std_server_test_global_metric + + if eval_best_pre_aggregation_local_models: + all_avg_test_metric, all_std_test_metric = get_metric_avg_std(list(all_pre_best_local_test_metrics.values())) + test_results["avg_pre_best_local_model_avg_across_clients"] = all_avg_test_metric + test_results["std_pre_best_local_model_avg_across_clients"] = all_std_test_metric + log(INFO, f"Avg Pre-aggregation Best Local Model Test Performance Over all clients: {all_avg_test_metric}") + log( + INFO, + f"Std. Dev. Pre-aggregation Best Local Model Test Performance Over all clients: {all_std_test_metric}", + ) + if eval_over_aggregated_test_data: + all_avg_test_metric, all_std_test_metric = get_metric_avg_std( + list(all_pre_best_local_agg_test_metrics.values()) + ) + test_results["agg_avg_pre_best_local_model_avg_across_clients"] = all_avg_test_metric + test_results["agg_std_pre_best_local_model_avg_across_clients"] = all_std_test_metric + log( + INFO, + f"""Avg Pre-aggregation Best Local Model Test + Performance Over Aggregated clients: {all_avg_test_metric}""", + ) + log( + INFO, + f"""Std. Dev. Pre-aggregation Best Local Model Test + Performance Over Aggregated clients: {all_std_test_metric}""", + ) + + if eval_last_pre_aggregation_local_models: + all_avg_test_metric, all_std_test_metric = get_metric_avg_std(list(all_pre_last_local_test_metrics.values())) + test_results["avg_pre_last_local_model_avg_across_clients"] = all_avg_test_metric + test_results["std_pre_last_local_model_avg_across_clients"] = all_std_test_metric + log(INFO, f"Avg Pre-aggregation Last Local Model Test Performance Over all clients: {all_avg_test_metric}") + log( + INFO, + f"Std. Dev. Pre-aggregation Last Local Model Test Performance Over all clients: {all_std_test_metric}", + ) + if eval_over_aggregated_test_data: + all_avg_test_metric, all_std_test_metric = get_metric_avg_std( + list(all_pre_last_local_agg_test_metrics.values()) + ) + test_results["agg_avg_pre_last_local_model_avg_across_clients"] = all_avg_test_metric + test_results["agg_std_pre_last_local_model_avg_across_clients"] = all_std_test_metric + log( + INFO, + f"""Avg Pre-aggregation Last Local Model Test + Performance Over Aggregated clients: {all_avg_test_metric}""", + ) + log( + INFO, + f"""Std. Dev. Pre-aggregation Last Local Model Test + Performance Over Aggregated clients: {all_std_test_metric}""", + ) + + if eval_best_post_aggregation_local_models: + all_avg_test_metric, all_std_test_metric = get_metric_avg_std(list(all_post_best_local_test_metrics.values())) + test_results["avg_post_best_local_model_avg_across_clients"] = all_avg_test_metric + test_results["std_post_best_local_model_avg_across_clients"] = all_std_test_metric + log(INFO, f"Avg Post-aggregation Best Local Model Test Performance Over all clients: {all_avg_test_metric}") + log( + INFO, + f"Std. Dev. Post-aggregation Best Local Model Test Performance Over all clients: {all_std_test_metric}", + ) + if eval_over_aggregated_test_data: + all_avg_test_metric, all_std_test_metric = get_metric_avg_std( + list(all_post_best_local_agg_test_metrics.values()) + ) + test_results["agg_avg_post_best_local_model_avg_across_clients"] = all_avg_test_metric + test_results["agg_std_post_best_local_model_avg_across_clients"] = all_std_test_metric + log( + INFO, + f"""Avg Post-aggregation Best Local Model Test + Performance Over Aggregated clients: {all_avg_test_metric}""", + ) + log( + INFO, + f"""Std. Dev. Post-aggregation Best Local Model Test + Performance Over Aggregated clients: {all_std_test_metric}""", + ) + + if eval_last_post_aggregation_local_models: + all_avg_test_metric, all_std_test_metric = get_metric_avg_std(list(all_post_last_local_test_metrics.values())) + test_results["avg_post_last_local_model_avg_across_clients"] = all_avg_test_metric + test_results["std_post_last_local_model_avg_across_clients"] = all_std_test_metric + log(INFO, f"Avg Post-aggregation Last Local Model Test Performance Over all clients: {all_avg_test_metric}") + log( + INFO, + f"Std. Dev. Post-aggregation Last Local Model Test Performance Over all clients: {all_std_test_metric}", + ) + if eval_over_aggregated_test_data: + all_avg_test_metric, all_std_test_metric = get_metric_avg_std( + list(all_post_last_local_agg_test_metrics.values()) + ) + test_results["agg_avg_post_last_local_model_avg_across_clients"] = all_avg_test_metric + test_results["agg_std_post_last_local_model_avg_across_clients"] = all_std_test_metric + log( + INFO, + f"""Avg Post-aggregation Last Local Model Test + Performance Over Aggregated clients: {all_avg_test_metric}""", + ) + log( + INFO, + f"""Std. Dev. Post-aggregation Last Local Model Test + Performance Over Aggregated clients: {all_std_test_metric}""", + ) + + if eval_best_global_model: + all_server_avg_test_metric, all_server_std_test_metric = get_metric_avg_std( + list(all_best_server_test_metrics.values()) + ) + test_results["avg_best_server_model_avg_across_clients"] = all_server_avg_test_metric + test_results["std_best_server_model_avg_across_clients"] = all_server_std_test_metric + log(INFO, f"Avg. Best Server Model Test Performance Over all clients: {all_server_avg_test_metric}") + log(INFO, f"Std. Dev. Best Server Model Test Performance Over all clients: {all_server_std_test_metric}") + + if eval_over_aggregated_test_data: + all_server_avg_test_metric, all_server_std_test_metric = get_metric_avg_std( + list(all_best_server_agg_test_metrics.values()) + ) + test_results["agg_avg_best_server_model_avg_across_clients"] = all_server_avg_test_metric + test_results["agg_std_best_server_model_avg_across_clients"] = all_server_std_test_metric + log( + INFO, + f"""Avg. Best Server Model Test Performance Over Aggregated + clients: {all_server_avg_test_metric}""", + ) + log( + INFO, + f"""Std. Dev. Best Server Model Test Performance Over Aggregated + clients: {all_server_std_test_metric}""", + ) + + if eval_last_global_model: + all_server_avg_test_metric, all_server_std_test_metric = get_metric_avg_std( + list(all_last_server_test_metrics.values()) + ) + test_results["avg_last_server_model_avg_across_clients"] = all_server_avg_test_metric + test_results["std_last_server_model_avg_across_clients"] = all_server_std_test_metric + log(INFO, f"Avg. Last Server Model Test Performance Over all clients: {all_server_avg_test_metric}") + log(INFO, f"Std. Dev. Last Server Model Test Performance Over all clients: {all_server_std_test_metric}") + + if eval_over_aggregated_test_data: + all_server_avg_test_metric, all_server_std_test_metric = get_metric_avg_std( + list(all_last_server_agg_test_metrics.values()) + ) + test_results["agg_avg_last_server_model_avg_across_clients"] = all_server_avg_test_metric + test_results["agg_std_last_server_model_avg_across_clients"] = all_server_std_test_metric + log( + INFO, + f"""Avg. Last Server Model Test Performance Over Aggregated + clients: {all_server_avg_test_metric}""", + ) + log( + INFO, + f"""Std. Dev. Last Server Model Test Performance Over Aggregated + clients: {all_server_std_test_metric}""", + ) + + write_measurement_results(eval_write_path, test_results) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Evaluate Trained Models on Test Data") + parser.add_argument( + "--artifact_dir", + action="store", + type=str, + help="Path to saved model artifacts to be evaluated", + required=True, + ) + parser.add_argument( + "--dataset_dir", + action="store", + type=str, + help="Path to the preprocessed Rxrx1 Dataset (ex. path/to/rxrx1)", + required=True, + ) + parser.add_argument( + "--eval_write_path", + action="store", + type=str, + help="Path to write the evaluation results file", + required=True, + ) + parser.add_argument( + "--eval_best_global_model", + action="store_true", + help="boolean to indicate whether to search for and evaluate best server model in addition to client models", + ) + parser.add_argument( + "--eval_last_global_model", + action="store_true", + help="boolean to indicate whether to search for and evaluate last server model in addition to client models", + ) + parser.add_argument( + "--eval_best_pre_aggregation_local_models", + action="store_true", + help="""boolean to indicate whether to search for and evaluate best pre-aggregation local models in addition + to the server model""", + ) + parser.add_argument( + "--eval_best_post_aggregation_local_models", + action="store_true", + help="""boolean to indicate whether to search for and evaluate best post-aggregation local models in addition + to the server model""", + ) + parser.add_argument( + "--eval_last_pre_aggregation_local_models", + action="store_true", + help="""boolean to indicate whether to search for and evaluate last pre-aggregation local models in addition + to the server model""", + ) + parser.add_argument( + "--eval_last_post_aggregation_local_models", + action="store_true", + help="""boolean to indicate whether to search for and evaluate last post-aggregation local models in addition + to the server model""", + ) + parser.add_argument( + "--eval_over_aggregated_test_data", + action="store_true", + help="""boolean to indicate whether to evaluate all the models on the over-aggregated test data as well as + client specific data""", + ) + + args = parser.parse_args() + log(INFO, f"Artifact Directory: {args.artifact_dir}") + log(INFO, f"Dataset Directory: {args.dataset_dir}") + log(INFO, f"Eval Write Path: {args.eval_write_path}") + + log(INFO, f"Run Best Global Model: {args.eval_best_global_model}") + log(INFO, f"Run Last Global Model: {args.eval_last_global_model}") + log(INFO, f"Run Best Pre-aggregation Local Model: {args.eval_best_pre_aggregation_local_models}") + log(INFO, f"Run Last Pre-aggregation Local Model: {args.eval_last_pre_aggregation_local_models}") + log(INFO, f"Run Best Post-aggregation Local Model: {args.eval_best_post_aggregation_local_models}") + log(INFO, f"Run Last Post-aggregation Local Model: {args.eval_last_post_aggregation_local_models}") + log(INFO, f"Run Eval Over Aggregated Test Data: {args.eval_over_aggregated_test_data}") + + assert ( + args.eval_best_global_model + or args.eval_last_global_model + or args.eval_best_pre_aggregation_local_models + or args.eval_last_pre_aggregation_local_models + or args.eval_best_post_aggregation_local_models + or args.eval_last_post_aggregation_local_models + ) + main( + args.artifact_dir, + args.dataset_dir, + args.eval_write_path, + args.eval_best_pre_aggregation_local_models, + args.eval_last_pre_aggregation_local_models, + args.eval_best_post_aggregation_local_models, + args.eval_last_post_aggregation_local_models, + args.eval_best_global_model, + args.eval_last_global_model, + args.eval_over_aggregated_test_data, + ) diff --git a/research/rxrx1/fedavg/__init__.py b/research/rxrx1/fedavg/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/rxrx1/fedavg/client.py b/research/rxrx1/fedavg/client.py new file mode 100644 index 000000000..460c9b545 --- /dev/null +++ b/research/rxrx1/fedavg/client.py @@ -0,0 +1,174 @@ +import argparse +import os +from collections.abc import Sequence +from logging import INFO +from pathlib import Path + +import flwr as fl +import torch +import torch.nn as nn +from flwr.common.logger import log +from flwr.common.typing import Config +from torch.nn.modules.loss import _Loss +from torch.optim import Optimizer +from torch.utils.data import DataLoader +from torchvision import models + +from fl4health.checkpointing.checkpointer import BestLossTorchModuleCheckpointer, LatestTorchModuleCheckpointer +from fl4health.checkpointing.client_module import ClientCheckpointAndStateModule +from fl4health.clients.basic_client import BasicClient +from fl4health.datasets.rxrx1.load_data import load_rxrx1_data, load_rxrx1_test_data +from fl4health.reporting.base_reporter import BaseReporter +from fl4health.utils.config import narrow_dict_type +from fl4health.utils.losses import LossMeterType +from fl4health.utils.metrics import Accuracy, Metric +from fl4health.utils.random import set_all_random_seeds + + +class Rxrx1FedAvgClient(BasicClient): + def __init__( + self, + data_path: Path, + metrics: Sequence[Metric], + device: torch.device, + client_number: int, + learning_rate: float, + loss_meter_type: LossMeterType = LossMeterType.AVERAGE, + checkpoint_and_state_module: ClientCheckpointAndStateModule | None = None, + reporters: Sequence[BaseReporter] | None = None, + progress_bar: bool = False, + client_name: str | None = None, + ) -> None: + super().__init__( + data_path=data_path, + metrics=metrics, + device=device, + loss_meter_type=loss_meter_type, + checkpoint_and_state_module=checkpoint_and_state_module, + reporters=reporters, + progress_bar=progress_bar, + client_name=client_name, + ) + self.client_number = client_number + self.learning_rate: float = learning_rate + + log(INFO, f"Client Name: {self.client_name}, Client Number: {self.client_number}") + + def setup_client(self, config: Config) -> None: + # Check if the client number is within the range of the total number of clients + num_clients = narrow_dict_type(config, "n_clients", int) + assert 0 <= self.client_number < num_clients + super().setup_client(config) + + def get_data_loaders(self, config: Config) -> tuple[DataLoader, DataLoader]: + batch_size = narrow_dict_type(config, "batch_size", int) + train_loader, val_loader, _ = load_rxrx1_data( + data_path=self.data_path, client_num=self.client_number, batch_size=batch_size, seed=self.client_number + ) + + return train_loader, val_loader + + def get_test_data_loader(self, config: Config) -> DataLoader | None: + batch_size = narrow_dict_type(config, "batch_size", int) + test_loader, _ = load_rxrx1_test_data( + data_path=self.data_path, client_num=self.client_number, batch_size=batch_size + ) + + return test_loader + + def get_criterion(self, config: Config) -> _Loss: + return torch.nn.CrossEntropyLoss() + + def get_optimizer(self, config: Config) -> Optimizer: + return torch.optim.AdamW(self.model.parameters(), lr=self.learning_rate) + + def get_model(self, config: Config) -> nn.Module: + return models.resnet18(pretrained=True).to(self.device) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="FL Client Main") + parser.add_argument( + "--artifact_dir", + action="store", + type=str, + help="Path to save client artifacts such as logs and model checkpoints", + required=True, + ) + parser.add_argument( + "--dataset_dir", + action="store", + type=str, + help="Path to the preprocessed Rxrx1 Dataset", + required=True, + ) + parser.add_argument( + "--run_name", + action="store", + help="Name of the run, model checkpoints will be saved under a subfolder with this name", + required=True, + ) + parser.add_argument( + "--server_address", + action="store", + type=str, + help="Server Address for the clients to communicate with the server through", + default="0.0.0.0:8080", + ) + parser.add_argument( + "--client_number", + action="store", + type=int, + help="Number of the client for dataset loading (should be 0-3 for Rxrx1)", + required=True, + ) + parser.add_argument( + "--learning_rate", action="store", type=float, help="Learning rate for local optimization", default=0.1 + ) + parser.add_argument( + "--seed", + action="store", + type=int, + help="Seed for the random number generators across python, torch, and numpy", + required=False, + ) + args = parser.parse_args() + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + log(INFO, f"Device to be used: {device}") + log(INFO, f"Server Address: {args.server_address}") + log(INFO, f"Learning Rate: {args.learning_rate}") + + # Set the random seed for reproducibility + set_all_random_seeds(args.seed) + + # Adding extensive checkpointing for the client + checkpoint_dir = os.path.join(args.artifact_dir, args.run_name) + pre_aggregation_best_checkpoint_name = f"pre_aggregation_client_{args.client_number}_best_model.pkl" + pre_aggregation_last_checkpoint_name = f"pre_aggregation_client_{args.client_number}_last_model.pkl" + post_aggregation_best_checkpoint_name = f"post_aggregation_client_{args.client_number}_best_model.pkl" + post_aggregation_last_checkpoint_name = f"post_aggregation_client_{args.client_number}_last_model.pkl" + checkpoint_and_state_module = ClientCheckpointAndStateModule( + pre_aggregation=[ + BestLossTorchModuleCheckpointer(checkpoint_dir, pre_aggregation_best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, pre_aggregation_last_checkpoint_name), + ], + post_aggregation=[ + BestLossTorchModuleCheckpointer(checkpoint_dir, post_aggregation_best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, post_aggregation_last_checkpoint_name), + ], + ) + + data_path = Path(args.dataset_dir) + client = Rxrx1FedAvgClient( + data_path=data_path, + metrics=[Accuracy("accuracy")], + device=device, + client_number=args.client_number, + learning_rate=args.learning_rate, + checkpoint_and_state_module=checkpoint_and_state_module, + ) + + fl.client.start_client(server_address=args.server_address, client=client.to_client()) + # Shutdown the client gracefully + client.shutdown() diff --git a/research/rxrx1/fedavg/config.yaml b/research/rxrx1/fedavg/config.yaml new file mode 100644 index 000000000..09e723ad3 --- /dev/null +++ b/research/rxrx1/fedavg/config.yaml @@ -0,0 +1,7 @@ +# Parameters that describe server +n_server_rounds: 10 # The number of rounds to run FL + +# Parameters that describe clients +n_clients: 4 # The number of clients in the FL experiment +local_epochs: 5 # The number of epochs to complete for client +batch_size: 32 # The batch size for client training diff --git a/research/rxrx1/fedavg/run_fold_experiment.slrm b/research/rxrx1/fedavg/run_fold_experiment.slrm new file mode 100644 index 000000000..123d80a65 --- /dev/null +++ b/research/rxrx1/fedavg/run_fold_experiment.slrm @@ -0,0 +1,162 @@ +#!/bin/bash + +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=1 +#SBATCH --gres=gpu:1 +#SBATCH --mem=64G +#SBATCH --partition=a40 +#SBATCH --qos=m2 +#SBATCH --job-name=fl_five_fold_exp +#SBATCH --output=%j_%x.out +#SBATCH --error=%j_%x.err +#SBATCH --time=4:00:00 + +############################################### +# Usage: +# +# sbatch research/rxrx1/fedavg/run_fold_experiment.slrm \ +# path_to_config.yaml \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ \ +# client_side_learning_rate_value \ +# server_address +# +# Example: +# sbatch research/rxrx1/fedavg/run_fold_experiment.slrm \ +# research/rxrx1/fedavg/config.yaml \ +# research/rxrx1/fedavg/hp_results/ \ +# /datasets/rxrx1 \ +# /h/demerson/vector_repositories/fl4health_env/ \ +# 0.0001 \ +# 0.0.0.0:8080 +# +# Notes: +# 1) The sbatch command above should be run from the top level directory of the repository. +# 2) This example runs FedAvg. As such the data paths and python launch commands are hardcoded. If you want to change +# the example you run, you need to explicitly modify the code below. +# 3) The logging directories need to ALREADY EXIST. The script does not create them. +############################################### + +# Note: +# ntasks: Total number of processes to use across world +# ntasks-per-node: How many processes each node should create + +# Set NCCL options +# export NCCL_DEBUG=INFO +# NCCL backend to communicate between GPU workers is not provided in vector's cluster. +# Disable this option in slurm. +export NCCL_IB_DISABLE=1 + +if [[ "${SLURM_JOB_PARTITION}" == "t4v2" ]] || \ + [[ "${SLURM_JOB_PARTITION}" == "rtx6000" ]]; then + echo export NCCL_SOCKET_IFNAME=bond0 on "${SLURM_JOB_PARTITION}" + export NCCL_SOCKET_IFNAME=bond0 +fi + + +export CUBLAS_WORKSPACE_CONFIG=:4096:8 +# Process Inputs + +SERVER_CONFIG_PATH=$1 +ARTIFACT_DIR=$2 +DATASET_DIR=$3 +VENV_PATH=$4 +CLIENT_LR=$5 +SERVER_ADDRESS=$6 + +# Create the artifact directory +mkdir "${ARTIFACT_DIR}" + +RUN_NAMES=( "Run1" "Run2" "Run3" "Run4" "Run5" ) +SEEDS=( 2021 2022 2023 2024 2025 ) + +echo "Python Venv Path: ${VENV_PATH}" + +echo "World size: ${SLURM_NTASKS}" +echo "Number of nodes: ${SLURM_NNODES}" +NUM_GPUs=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l) +echo "GPUs per node: ${NUM_GPUs}" + +# Source the environment +source ${VENV_PATH}bin/activate +echo "Active Environment:" +which python + +for ((i=0; i<${#RUN_NAMES[@]}; i++)); +do + RUN_NAME="${RUN_NAMES[i]}" + SEED="${SEEDS[i]}" + # create the run directory + RUN_DIR="${ARTIFACT_DIR}${RUN_NAME}/" + echo "Starting Run and logging artifcats at ${RUN_DIR}" + if [ -d "${RUN_DIR}" ] + then + # Directory already exists, we check if the done.out file exists + if [ -f "${RUN_DIR}done.out" ] + then + # Done file already exists so we skip this run + echo "Run already completed. Skipping Run." + continue + else + # Done file doesn't exists (assume pre-emption happened) + # Delete the partially finished contents and start over + echo "Run did not finished correctly. Re-running." + rm -r "${RUN_DIR}" + mkdir "${RUN_DIR}" + fi + else + # Directory doesn't exist yet, so we create it. + echo "Run directory does not exist. Creating it." + mkdir "${RUN_DIR}" + fi + + SERVER_OUTPUT_FILE="${RUN_DIR}server.out" + + # Start the server, divert the outputs to a server file + + echo "Server logging at: ${SERVER_OUTPUT_FILE}" + echo "Launching Server" + + nohup python -m research.rxrx1.fedavg.server \ + --config_path ${SERVER_CONFIG_PATH} \ + --artifact_dir ${ARTIFACT_DIR} \ + --run_name ${RUN_NAME} \ + --server_address ${SERVER_ADDRESS} \ + --seed ${SEED} \ + > ${SERVER_OUTPUT_FILE} 2>&1 & + + # Sleep for 20 seconds to allow the server to come up. + sleep 20 + + # Start n number of clients and divert the outputs to their own files + n_clients=4 + for (( c=0; c<${n_clients}; c++ )) + do + CLIENT_NAME="client_${c}" + echo "Launching ${CLIENT_NAME}" + + CLIENT_LOG_PATH="${RUN_DIR}${CLIENT_NAME}.out" + echo "${CLIENT_NAME} logging at: ${CLIENT_LOG_PATH}" + nohup python -m research.rxrx1.fedavg.client \ + --artifact_dir ${ARTIFACT_DIR} \ + --dataset_dir ${DATASET_DIR} \ + --run_name ${RUN_NAME} \ + --client_number ${c} \ + --learning_rate ${CLIENT_LR} \ + --server_address ${SERVER_ADDRESS} \ + --seed ${SEED} \ + > ${CLIENT_LOG_PATH} 2>&1 & + done + + echo "FL Processes Running" + + wait + + # Create a file that verifies that the Run concluded properly + touch "${RUN_DIR}done.out" + echo "Finished FL Processes" + +done diff --git a/research/rxrx1/fedavg/run_hp_sweep.sh b/research/rxrx1/fedavg/run_hp_sweep.sh new file mode 100755 index 000000000..af8d7dd89 --- /dev/null +++ b/research/rxrx1/fedavg/run_hp_sweep.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +############################################### +# Usage: +# +# ./research/rxrx1/fedavg/run_hp_sweep.sh \ +# path_to_config.yaml \ +# path_to_folder_for_artifacts/ \ +# path_to_folder_for_dataset/ \ +# path_to_desired_venv/ +# +# Example: +# ./research/rxrx1/fedavg/run_hp_sweep.sh \ +# research/rxrx1/fedavg/config.yaml \ +# research/rxrx1/fedavg \ +# /datasets/rxrx1 \ +# /h/demerson/vector_repositories/fl4health_env/ +# +# Notes: +# 1) The bash command above should be run from the top level directory of the repository. +############################################### + +SERVER_CONFIG_PATH=$1 +ARTIFACT_DIR=$2 +DATASET_DIR=$3 +VENV_PATH=$4 + +LR_VALUES=( 0.00001 0.0001 0.001 0.01 0.1 ) + +SERVER_PORT=8100 + +# Create sweep folder +SWEEP_DIRECTORY="${ARTIFACT_DIR}hp_sweep_results" +echo "Creating sweep folder at ${SWEEP_DIRECTORY}" +mkdir ${SWEEP_DIRECTORY} + +for LR_VALUE in "${LR_VALUES[@]}"; +do + EXPERIMENT_NAME="lr_${LR_VALUE}" + echo "Beginning Experiment ${EXPERIMENT_NAME}" + EXPERIMENT_DIRECTORY="${SWEEP_DIRECTORY}/${EXPERIMENT_NAME}/" + echo "Creating experiment folder ${EXPERIMENT_DIRECTORY}" + mkdir "${EXPERIMENT_DIRECTORY}" + SERVER_ADDRESS="0.0.0.0:${SERVER_PORT}" + echo "Server Address: ${SERVER_ADDRESS}" + SBATCH_COMMAND="research/rxrx1/fedavg/run_fold_experiment.slrm \ + ${SERVER_CONFIG_PATH} \ + ${EXPERIMENT_DIRECTORY} \ + ${DATASET_DIR} \ + ${VENV_PATH} \ + ${LR_VALUE} \ + ${SERVER_ADDRESS}" + echo "Running sbatch command ${SBATCH_COMMAND}" + sbatch ${SBATCH_COMMAND} + ((SERVER_PORT=SERVER_PORT+1)) +done +echo Experiments Launched diff --git a/research/rxrx1/fedavg/server.py b/research/rxrx1/fedavg/server.py new file mode 100644 index 000000000..659782718 --- /dev/null +++ b/research/rxrx1/fedavg/server.py @@ -0,0 +1,142 @@ +import argparse +import os +from functools import partial +from logging import INFO +from typing import Any + +import flwr as fl +from flwr.common.logger import log +from flwr.common.typing import Config +from flwr.server.client_manager import SimpleClientManager +from flwr.server.strategy import FedAvg +from torchvision import models + +from fl4health.checkpointing.checkpointer import BestLossTorchModuleCheckpointer, LatestTorchModuleCheckpointer +from fl4health.checkpointing.server_module import BaseServerCheckpointAndStateModule +from fl4health.parameter_exchange.full_exchanger import FullParameterExchanger +from fl4health.servers.base_server import FlServer +from fl4health.utils.config import load_config +from fl4health.utils.metric_aggregation import evaluate_metrics_aggregation_fn, fit_metrics_aggregation_fn +from fl4health.utils.parameter_extraction import get_all_model_parameters +from fl4health.utils.random import set_all_random_seeds + + +def fit_config( + batch_size: int, + local_epochs: int, + n_server_rounds: int, + n_clients: int, + current_server_round: int, +) -> Config: + return { + "batch_size": batch_size, + "local_epochs": local_epochs, + "n_server_rounds": n_server_rounds, + "n_clients": n_clients, + "current_server_round": current_server_round, + } + + +def main(config: dict[str, Any], server_address: str, checkpoint_stub: str, run_name: str) -> None: + # This function will be used to produce a config that is sent to each client to initialize their own environment + fit_config_fn = partial( + fit_config, + config["batch_size"], + config["local_epochs"], + config["n_server_rounds"], + config["n_clients"], + ) + # Initializing the model on the server side + model = models.resnet18(pretrained=True) + parameter_exchanger = FullParameterExchanger() + checkpoint_dir = os.path.join(checkpoint_stub, run_name) + best_checkpoint_name = "server_best_model.pkl" + last_checkpoint_name = "server_last_model.pkl" + checkpointers = [ + BestLossTorchModuleCheckpointer(checkpoint_dir, best_checkpoint_name), + LatestTorchModuleCheckpointer(checkpoint_dir, last_checkpoint_name), + ] + + checkpoint_and_state_module = BaseServerCheckpointAndStateModule( + model=model, parameter_exchanger=parameter_exchanger, model_checkpointers=checkpointers + ) + + client_manager = SimpleClientManager() + # Server performs simple FedAveraging as its server-side optimization strategy + strategy = FedAvg( + min_fit_clients=config["n_clients"], + min_evaluate_clients=config["n_clients"], + # Server waits for min_available_clients before starting FL rounds + min_available_clients=config["n_clients"], + on_fit_config_fn=fit_config_fn, + # We use the same fit config function, as nothing changes for eval + on_evaluate_config_fn=fit_config_fn, + fit_metrics_aggregation_fn=fit_metrics_aggregation_fn, + evaluate_metrics_aggregation_fn=evaluate_metrics_aggregation_fn, + initial_parameters=get_all_model_parameters(model), + ) + server = FlServer( + client_manager=client_manager, + fl_config=config, + strategy=strategy, + checkpoint_and_state_module=checkpoint_and_state_module, + ) + + fl.server.start_server( + server=server, + server_address=server_address, + config=fl.server.ServerConfig(num_rounds=config["n_server_rounds"]), + ) + + assert isinstance(checkpointers[0], BestLossTorchModuleCheckpointer) + log(INFO, f"Best Aggregated (Weighted) Loss seen by the Server: \n{checkpointers[0].best_score}") + + # Shutdown the server gracefully + server.shutdown() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="FL Server Main") + parser.add_argument( + "--artifact_dir", + action="store", + type=str, + help="Path to save server artifacts such as logs and model checkpoints", + required=True, + ) + parser.add_argument( + "--run_name", + action="store", + help="Name of the run, model checkpoints will be saved under a subfolder with this name", + required=True, + ) + parser.add_argument( + "--config_path", + action="store", + type=str, + help="Path to configuration file.", + default="config.yaml", + ) + parser.add_argument( + "--server_address", + action="store", + type=str, + help="Server Address to be used to communicate with the clients", + default="0.0.0.0:8080", + ) + parser.add_argument( + "--seed", + action="store", + type=int, + help="Seed for the random number generators across python, torch, and numpy", + required=False, + ) + args = parser.parse_args() + + config = load_config(args.config_path) + log(INFO, f"Server Address: {args.server_address}") + + # Set the random seed for reproducibility + set_all_random_seeds(args.seed) + + main(config, args.server_address, args.artifact_dir, args.run_name) diff --git a/research/rxrx1/find_best_hp.py b/research/rxrx1/find_best_hp.py new file mode 100644 index 000000000..2737bb4fc --- /dev/null +++ b/research/rxrx1/find_best_hp.py @@ -0,0 +1,59 @@ +import argparse +import os +from logging import INFO + +import numpy as np +from flwr.common.logger import log + + +def get_hp_folders(hp_sweep_dir: str) -> list[str]: + paths_in_hp_sweep_dir = [os.path.join(hp_sweep_dir, contents) for contents in os.listdir(hp_sweep_dir)] + return [hp_folder for hp_folder in paths_in_hp_sweep_dir if os.path.isdir(hp_folder)] + + +def get_run_folders(hp_dir: str) -> list[str]: + run_folder_names = [folder_name for folder_name in os.listdir(hp_dir) if "Run" in folder_name] + return [os.path.join(hp_dir, run_folder_name) for run_folder_name in run_folder_names] + + +def get_weighted_loss_from_server_log(run_folder_path: str) -> float: + server_log_path = os.path.join(run_folder_path, "server.out") + with open(server_log_path, "r") as handle: + files_lines = handle.readlines() + line_to_convert = files_lines[-1].strip() + return float(line_to_convert) + + +def main(hp_sweep_dir: str) -> None: + hp_folders = get_hp_folders(hp_sweep_dir) + best_avg_loss: float | None = None + best_folder = "" + for hp_folder in hp_folders: + run_folders = get_run_folders(hp_folder) + hp_losses = [] + for run_folder in run_folders: + run_loss = get_weighted_loss_from_server_log(run_folder) + hp_losses.append(run_loss) + current_avg_loss = float(np.mean(hp_losses)) + if best_avg_loss is None or current_avg_loss <= best_avg_loss: + log(INFO, f"Current Loss: {current_avg_loss} is lower than Best Loss: {best_avg_loss}") + log(INFO, f"Best Folder: {hp_folder}, Previous Best: {best_folder}") + best_avg_loss = current_avg_loss + best_folder = hp_folder + log(INFO, f"Best Loss: {best_avg_loss}") + log(INFO, f"Best Folder: {best_folder}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Evaluate Holdout Global") + parser.add_argument( + "--hp_sweep_dir", + action="store", + type=str, + help="Path to the artifacts of the hyper-parameter sweep script", + required=True, + ) + args = parser.parse_args() + + log(INFO, f"Hyperparameter Sweep Directory: {args.hp_sweep_dir}") + main(args.hp_sweep_dir) diff --git a/research/rxrx1/personal_server.py b/research/rxrx1/personal_server.py new file mode 100644 index 000000000..ae3469497 --- /dev/null +++ b/research/rxrx1/personal_server.py @@ -0,0 +1,65 @@ +from logging import INFO + +from flwr.common.logger import log +from flwr.common.typing import Config, Scalar +from flwr.server.client_manager import ClientManager +from flwr.server.server import EvaluateResultsAndFailures +from flwr.server.strategy import Strategy + +from fl4health.servers.base_server import FlServer + + +class PersonalServer(FlServer): + """ + The PersonalServer class is used for FL approaches that only have a sense of a PERSONAL model that is checkpointed + and valid only on the client size of the FL training framework. FL approaches like APFL and FENDA fall under this + category. Each client will have its own model that is specific to its own training. Personal models may have + shared components but the full model is specific to each client. As such, there is no sense of a GLOBAL model + to be checkpointed on the server-side that is shared by all clients. We eliminate the possibility of + checkpointing, but still consider the aggregated loss as a means of hyper-parameter tuning. + """ + + def __init__( + self, + client_manager: ClientManager, + fl_config: Config, + strategy: Strategy | None = None, + ) -> None: + # Personal approaches don't train a "server" model. Rather, each client trains a client specific model with + # some globally shared weights. So we don't checkpoint a global model + super().__init__( + client_manager=client_manager, fl_config=fl_config, strategy=strategy, checkpoint_and_state_module=None + ) + self.best_aggregated_loss: float | None = None + + def evaluate_round( + self, + server_round: int, + timeout: float | None, + ) -> tuple[float | None, dict[str, Scalar], EvaluateResultsAndFailures] | None: + # loss_aggregated is the aggregated validation per step loss + # aggregated over each client (weighted by num examples) + eval_round_results = super().evaluate_round(server_round, timeout) + assert eval_round_results is not None + loss_aggregated, metrics_aggregated, (results, failures) = eval_round_results + assert loss_aggregated is not None + + if self.best_aggregated_loss: + if self.best_aggregated_loss >= loss_aggregated: + log( + INFO, + f"Best Aggregated Loss: {self.best_aggregated_loss} " + f"is larger than current aggregated loss: {loss_aggregated}", + ) + self.best_aggregated_loss = loss_aggregated + else: + log( + INFO, + f"Best Aggregated Loss: {self.best_aggregated_loss} " + f"is smaller than current aggregated loss: {loss_aggregated}", + ) + else: + log(INFO, f"Saving Best Aggregated Loss: {loss_aggregated} as it is currently None") + self.best_aggregated_loss = loss_aggregated + + return loss_aggregated, metrics_aggregated, (results, failures) diff --git a/research/rxrx1/single_node_trainer.py b/research/rxrx1/single_node_trainer.py new file mode 100644 index 000000000..39531b7c5 --- /dev/null +++ b/research/rxrx1/single_node_trainer.py @@ -0,0 +1,107 @@ +import os +from logging import INFO + +import torch +import torch.nn as nn +from flwr.common.logger import log +from flwr.common.typing import Scalar +from torch.nn.modules.loss import _Loss +from torch.utils.data import DataLoader + +from fl4health.checkpointing.checkpointer import BestLossTorchModuleCheckpointer, LatestTorchModuleCheckpointer +from fl4health.utils.metrics import MetricManager + + +class SingleNodeTrainer: + def __init__( + self, + device: torch.device, + checkpoint_stub: str, + dataset_dir: str, + run_name: str = "", + ) -> None: + self.device = device + checkpoint_dir = os.path.join(checkpoint_stub, run_name) + # This is called the "server model" so that it can be found by the evaluate_on_holdout.py script + self.checkpointer = BestLossTorchModuleCheckpointer(checkpoint_dir, "server_best_model.pkl") + self.last_checkpointer = LatestTorchModuleCheckpointer(checkpoint_dir, "server_last_model.pkl") + self.dataset_dir = dataset_dir + self.model: nn.Module + self.criterion: _Loss + self.optimizer: torch.optim.Optimizer + self.train_loader: DataLoader + self.val_loader: DataLoader + + def _maybe_checkpoint(self, loss: float, metrics: dict[str, Scalar]) -> None: + if self.checkpointer: + self.checkpointer.maybe_checkpoint(self.model, loss, metrics) + + def _handle_reporting( + self, + loss: float, + metrics_dict: dict[str, Scalar], + is_validation: bool = False, + ) -> None: + metric_string = "\t".join([f"{key}: {str(val)}" for key, val in metrics_dict.items()]) + metric_prefix = "Validation" if is_validation else "Training" + log( + INFO, + f"Centralized {metric_prefix} Loss: {loss} \n" f"Centralized {metric_prefix} Metrics: {metric_string}", + ) + + def train_step(self, input: torch.Tensor, target: torch.Tensor) -> tuple[torch.Tensor, dict[str, torch.Tensor]]: + # forward pass on the model + preds = self.model(input) + loss = self.criterion(preds, target) + + self.optimizer.zero_grad() + loss.backward() + self.optimizer.step() + + return loss, {"predictions": preds} + + def train_by_epochs( + self, + epochs: int, + train_metric_mngr: MetricManager, + val_metric_mngr: MetricManager, + ) -> None: + self.model.train() + + for local_epoch in range(epochs): + train_metric_mngr.clear() + running_loss = 0.0 + for input, target in self.train_loader: + input, target = input.to(self.device), target.to(self.device) + batch_loss, preds = self.train_step(input, target) + running_loss += batch_loss.item() + train_metric_mngr.update(preds, target) + + log(INFO, f"Local Epoch: {local_epoch}") + running_loss = running_loss / len(self.train_loader) + metrics = train_metric_mngr.compute() + self._handle_reporting(running_loss, metrics) + + # After each epoch run a validation pass + self.validate(val_metric_mngr) + # Checkpoint the model at the end of training + self.last_checkpointer.maybe_checkpoint(self.model, 0.0, {}) + + def validate(self, val_metric_mngr: MetricManager) -> None: + self.model.eval() + running_loss = 0.0 + val_metric_mngr.clear() + + with torch.no_grad(): + for input, target in self.val_loader: + input, target = input.to(self.device), target.to(self.device) + + preds = {"predictions": self.model(input)} + batch_loss = self.criterion(preds["predictions"], target) + running_loss += batch_loss.item() + val_metric_mngr.update(preds, target) + + running_loss = running_loss / len(self.val_loader) + metrics = val_metric_mngr.compute() + self._handle_reporting(running_loss, metrics, is_validation=True) + self._maybe_checkpoint(running_loss, metrics) diff --git a/research/rxrx1/utils.py b/research/rxrx1/utils.py new file mode 100644 index 000000000..5c5a1ce12 --- /dev/null +++ b/research/rxrx1/utils.py @@ -0,0 +1,91 @@ +import os +from collections.abc import Sequence + +import numpy as np +import torch +from torch import nn +from torch.utils.data import DataLoader + +from fl4health.utils.metrics import Metric, MetricManager + + +def get_all_run_folders(artifact_dir: str) -> list[str]: + run_folder_names = [folder_name for folder_name in os.listdir(artifact_dir) if "Run" in folder_name] + return [os.path.join(artifact_dir, run_folder_name) for run_folder_name in run_folder_names] + + +def load_best_global_model(run_folder_dir: str) -> nn.Module: + model_checkpoint_path = os.path.join(run_folder_dir, "server_best_model.pkl") + model = torch.load(model_checkpoint_path) + return model + + +def load_last_global_model(run_folder_dir: str) -> nn.Module: + model_checkpoint_path = os.path.join(run_folder_dir, "server_last_model.pkl") + model = torch.load(model_checkpoint_path) + return model + + +def get_metric_avg_std(metrics: list[float]) -> tuple[float, float]: + mean = float(np.mean(metrics)) + std = float(np.std(metrics, ddof=1)) + return mean, std + + +def write_measurement_results(eval_write_path: str, results: dict[str, float]) -> None: + with open(eval_write_path, "w") as f: + for key, metric_value in results.items(): + f.write(f"{key}: {metric_value}\n") + + +def evaluate_rxrx1_model( + model: nn.Module, dataset: DataLoader, metrics: Sequence[Metric], device: torch.device +) -> float: + meter = evaluate_model_on_dataset(model, dataset, metrics, device) + + computed_metrics = meter.compute() + assert "test_meter - prediction - rxrx1_accuracy" in computed_metrics + accuracy = computed_metrics["test_meter - prediction - rxrx1_accuracy"] + assert isinstance(accuracy, float) + return accuracy + + +def load_eval_best_pre_aggregation_local_model(run_folder_dir: str, client_number: int) -> nn.Module: + model_checkpoint_path = os.path.join(run_folder_dir, f"pre_aggregation_client_{client_number}_best_model.pkl") + model = torch.load(model_checkpoint_path) + return model + + +def load_eval_last_pre_aggregation_local_model(run_folder_dir: str, client_number: int) -> nn.Module: + model_checkpoint_path = os.path.join(run_folder_dir, f"pre_aggregation_client_{client_number}_last_model.pkl") + model = torch.load(model_checkpoint_path) + return model + + +def load_eval_best_post_aggregation_local_model(run_folder_dir: str, client_number: int) -> nn.Module: + model_checkpoint_path = os.path.join(run_folder_dir, f"post_aggregation_client_{client_number}_best_model.pkl") + model = torch.load(model_checkpoint_path) + return model + + +def load_eval_last_post_aggregation_local_model(run_folder_dir: str, client_number: int) -> nn.Module: + model_checkpoint_path = os.path.join(run_folder_dir, f"post_aggregation_client_{client_number}_last_model.pkl") + model = torch.load(model_checkpoint_path) + return model + + +def evaluate_model_on_dataset( + model: nn.Module, dataset: DataLoader, metrics: Sequence[Metric], device: torch.device +) -> MetricManager: + model.to(device).eval() + meter = MetricManager(metrics, "test_meter") + + with torch.no_grad(): + for input, target in dataset: + input, target = input.to(device), target.to(device) + preds = model(input) + if isinstance(preds, tuple): + preds = preds[0] + preds = preds if isinstance(preds, dict) else {"prediction": preds} + meter.update(preds, target) + return meter