diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..36dce4d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.npy filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 7d778a3..89b9d3b 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -22,6 +22,7 @@ jobs: ref: ${{ inputs.sha }} fetch-depth: 0 fetch-tags: true + lfs: true - uses: ./.github/actions/setup-environment id: runner-context - name: check diff --git a/.gitignore b/.gitignore index 1b4b92b..3628940 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,9 @@ bld/ [Oo]ut/ [Ll]og/ [Ll]ogs/ +lightning_logs/ +run/ +.run/ # Build results on 'Bin' directories **/[Bb]in/* diff --git a/Privacy.md b/PRIVACY.md similarity index 100% rename from Privacy.md rename to PRIVACY.md diff --git a/docs/.pages b/docs/.pages new file mode 100644 index 0000000..1db3223 --- /dev/null +++ b/docs/.pages @@ -0,0 +1,8 @@ +title: SNRAware +nav: + - overview.md + - attention_layers.md + - block_and_cell.md + - backbone.md + - examples_and_tests.md + - mri_denoising.md \ No newline at end of file diff --git a/docs/attention_layers.md b/docs/attention_layers.md new file mode 100644 index 0000000..68ddf65 --- /dev/null +++ b/docs/attention_layers.md @@ -0,0 +1,49 @@ + +# Attention layers + +Given the 5D tensor, different imaging attention modules are developed to capture the signal correlation across spatial and frame/temporal dimensions. All these modules receive a 5D tensor and output a 5D tensor. Unlike the token based attention, these imaging attention modules do not convert pixel data to tokens. That is, the computation is performed in the pixel space, not in token space. + +![motivation](images/ifm_model_index.png) + +Full attention across three dimensions (F, H, W) can incur high computing costs and may not be optimal as the locality of information was not exploited. Instead, intra- and inter-frame attention can be separately computed. For spatial (along with H, W) attention, a target patch (marked in yellow) takes in information from both close neighbors (marked in green) and remote patches (marked in pink). + +![attention](images/ifm_model_three_attentions.png) + +Let the image size be $[H, W]$, the windows size be $[w, w]$ and patch size be $[k, k]$. The number of windows will be $[\frac {H}{w}, \frac{W}{w}]$. The number of patches is $[\frac{H}{k}, \frac{W}{k}]$. For example, for an $256 \times 256$ images, $w=32$ and $k=16$, we will have $8 \times 8$ windows and each window will have $2 \times 2$ patches. + +**Spatial Local attention (L)** + +Local attention is computed by attending to the all patches in a window for images or feature maps. The attention matrix is $\frac {w}{k} \times \frac {w}{k}$. + +**Spatial Global attention (G)** + +While the local attention only explores neighboring pixels, global attention looks at more remote pixels. This will help model learn global information over larger field-of-view. All patches with the same color are attended together. So for every patch, attention matrix size is $w^2 \times w^2$ (that is, the number of windows). + +**Frame or temporal attention (F)** + +All frames can attend each other to compute the output values. In this case, the attention matrix size is ${F}\times{F}$. + +These **F, G, T** attention mechanisms are implemented as **Attention** modules. The implemented version supports multi-heads. + +**Spatial Local 3d attention** + +Previous spatial local attention works on every 2D frame independently. For some applications where data acquisition is 3D, it may require to perform 3D attention. In this case, the [F, H, W] volume is split into 3D windows. Every window includes 3D patches. All patches within a window are attended together. + +**Spatial Global 3d attention** + +Similar to the 2D global attention, the tensor is split to 3D windows and patches. Corresponding patches from all windows attend each other. + +## Other attentions + +**Spatial ViT attention** + +The vision transformer method splits the image into windows. All pixels in a window are flattened into a feature vector. Features from all windows are inputted into the attention. This layer works on every 2D frame, as the spatial local attention. + +**ViT 3d attention** + +The image is split to 3D window to compute the attention. + +**Swin 3d attention** + +The shifted window attention is adapted to 3D. The image is split to 3D windows. Every 3d window is split to 3D patches. The attention is computed among all patches within a window or a shifted window. + diff --git a/docs/backbone.md b/docs/backbone.md new file mode 100644 index 0000000..36b4837 --- /dev/null +++ b/docs/backbone.md @@ -0,0 +1,42 @@ + +# Backbone + +Attention - Cell - Block are three levels of building blocks for a model. We can connect many blocks to assemble a backbone. Here the backbone means an "almost" full model. Different pre- and post-processing task heads can be added to a backbone, given a specific application. + +In the LLMs, the stack of attentions proves to be very effective. For imaging, previous researches had explored similar architecture (e.g. [3B swin v2](https://arxiv.org/abs/2111.09883) and [20B ViT](https://arxiv.org/abs/2302.05442)). On the other hand, there are very intuitive model architectures were invented for different convolution models. Comparing to the LLM type backbone, these CNN derived architectures are more tuned for image data format, by utilizing resolution pyramid, up and downsampling, and long range skip connections. + +Different backbone models are implemented by borrowing successful architectures from CNNs. + +### backbone U-Net + +![unet](images/Unet.jpg) + +Here every resolution stage includes one block containing multiple cells. Model can specify number of feature maps (Channel dimension C) at each resolution stage. + +Return tensor of this backbone is the final output tensor after U0 layer. + +The [Unet with attention](https://arxiv.org/abs/1804.03999) is implemented here. Downsample and upsample are implemented with interpolation. + +![Attention_in_Unet](images/Attention_in_Unet.jpg) + +### backbone HR-Net + +This network is modified from the [high-resolution architecture](https://www.microsoft.com/en-us/research/blog/high-resolution-network-a-universal-neural-architecture-for-visual-recognition/). + +![stcnnt_hrnet](images/HRNet.jpg) + +The network is defined as levels and stages. Every block is numbered by its level and stage indexes (starting from 0). The downsample and upsample modules are added to link different blocks. Different up/downsample modules are implemented, with TLG attentions or 1x1 CONV. Bilinear interpolation is used to alter spatial resolution. + +After the fusion stage, the model outputs per-level tensors and the aggregated tensor as a list. This backbone returns a list of tensors. The outputs from every resolution level and the final aggregated tensor are returned as a list *res*. *res[0]* is the output with the highest resolution (*y_hat_0*). *res[1]* is the *y_hat_1* etc. The *res[-1]* is the aggregated tensor. + +### backbone Stack-of-attention (SOANet) + +The classical stack-of-attention model is defined by the stages. Each stage contains one block with many cells. The downsampling layers are optional between stages. + +We can instantiate the typical Swin or ViT model by inserting corresponding cells to SOANet. For example, a four-stage model with block strings ["S3ShS3Sh", "S3ShS3Sh", "S3ShS3ShS3ShS3ShS3ShS3Sh", "S3ShS3Sh"] is a replica of published model in the [swin paper](https://arxiv.org/abs/2103.14030).A one stage model ["V2V2V2V2V2V2V2V2V2V2V2V2"] is a version of 2D ViT base model with 12 cells. Since the backbone takes in the 5D tensor, it is often better to instantiate 3D ViT model, e.g. ["V3V3V3","V3V3V3V3V3V3", "V3V3V3V3V3V3V3V3V3V3V3V3"] for a 3 stage 3D ViT. + +This backbone returns a list of tensors containing outputs from all stages. *res[0]* is the first stage and *res[-1]* has the lowest resolution (if downsampling). + +![soanet](images/SOANet.jpg) + +**Please ref to [examples and tests](./examples_and_tests.md) for how to instantiate the backbone.** \ No newline at end of file diff --git a/docs/block_and_cell.md b/docs/block_and_cell.md new file mode 100644 index 0000000..bec9d2c --- /dev/null +++ b/docs/block_and_cell.md @@ -0,0 +1,69 @@ + +# Cell and Block + +## Cell +A cell is a container to host an attention layer, as the basic processing unit for the 5D tensor. As the following, a cell can host either F, L or G or other attentions. Input tensor goes through the norm layers and attention. The mixer consists of another norm and two convolutions. + +![Cell](./images/ifm_model_cell.png) + +Many attention modules are supported. These modules are represented by its name string: + +| Name | Description | +| ----------- | ----------- | +| V2 | 2D ViT attention, working on [H, W], with the mixer | +| V3 | 3D ViT attention, working on [D/Z/T, H, W], with the mixer | +| L1 | spatial local attention on [H, W], with mixer | +| L0 | spatial local attention on [H, W], without mixer | +| L3 | local 3D attention on [D/Z/T, H, W], with mixer | +| G1 | spatial global attention on [H, W], with mixer | +| G0 | spatial global attention on [H, W], without mixer | +| G3 | global 3D attention on [D/Z/T, H, W], with mixer | +| S3 | swin 3D attention over [D/Z/T, H, W], with the mixer| +| Sh | swin 3D attention over [D/Z/T, H, W], with the mixer, shifted window| + +#### Norms + +Different $Norm$ can be configured: + +| Norm | Description | +| ----------- | ----------- | +| LayerNorm | normalize over [C, H, W] | +| BatchNorm 2D | normalize over [H, W], B*T are batch dimension | +| BatchNorm 3D | normalize over [T, H, W] | +| InstanceNorm 2D | normalize over [H, W]| +| InstanceNorm 3D | normalize over [T, H, W]| + +Except the *layer* norm, all other norms support flexible image sizes, when using with the $CONV$ in attention layers. + +#### Mixers + +The mixers are added after attention layers to increase model power. Two types of mixers are implemented. *Linear* mixer are the same mixer type used in the conventional transformer, with `torch.linear` layers. The "conv" mixer replaced Linear layer with the convolution layer, to better fit for imaging tensors. The conv is performed along the $[C, H, W]$: + +``` +self.mlp = nn.Sequential( + Conv2DExt(C_out, 4*C_out, kernel_size=kernel_size, stride=stride, padding=padding, bias=True), + nn.GELU(), + Conv2DExt(4*C_out, C_out, kernel_size=kernel_size, stride=stride, padding=padding, bias=True), + nn.Dropout(dropout_p), + ) +``` + +User can specify whether a cell has mixer or not. + +## Block + +Many cells are concatenated to enhance the model power. To standardize this configuration, the Block is introduced: + +![Block](./images/ifm_model_block.png) + +A block contains any number of cells and can be scaled up by inserting more cells. + +A block is coded by the acronyms of attention layers in each cell. For example, a block is coded as the block string "L1G1T1". The letter "L", "G" or "T" means the local, global and Frame/Temporal Cell. "1" means the mixer is added on top of the attention mechanism (if "0", mixer is not added; we can have a stack of attention only layers). As an example, "L0L1G0G1T1" means a block with 5 attention layers. Mixers are not added to the first and third attentions, but added after the second and fourth attentions. The last cell is a temporal attention with its mixer added. This method of "block string specification" gives a good amount of flexibility to assemble and test different attention configurations. Depending on the image resolutions and number of feature maps, different blocks in a model can have different attention configuration. Note the convolution layers are formatted into this framework as the "C2" or "C3" cells. "C2" means the 2D conv is used in the cell. "C3" means the 3D conv is used (over F, H, W). By changing the block string from attentions to convs, we can get identical architectures with only differences being the cell structure. + +The block string can also be "S2" or "S3" for 2D or 3D SWIN module. Each SWIN module includes two parts for concatenated normal and shifted window attentions. These attention modules can be concatenated in any order of mixing. For example, *L1G1T1* or *S3V2L1G1T1*, or *G1G3S3V2T1* etc. *S3ShS3ShS3Sh* is the SWIN transformer with normal and shifted window attentions interleaved. *V3V3V3V3* is the ViT transformer. By separating the backbone architecture with attention modules, it is very flexible to instantiate many types of models, tailored to the nature of datasets and amount of computing. + +Two cell layouts were implemented: sequential and parallel: + +![Cell](./images/Cell.jpg) + +The reference for parallel cell is [here](https://arxiv.org/abs/2302.05442). \ No newline at end of file diff --git a/docs/examples_and_tests.md b/docs/examples_and_tests.md new file mode 100644 index 0000000..1ffd435 --- /dev/null +++ b/docs/examples_and_tests.md @@ -0,0 +1,89 @@ + +# Examples and tests + +The modules and backbones in this package can be used to build models for different applications. The key design is to support 5D tensor [B, C, T/D/F/Z, H, W] for 2D, 2D+T, and 3D usecases. + +## Attention modules + +All attention modules are implemented as pytorch modules. They can be defined in the very straight-forward way as the following: + +```python +import numpy as np +import torch +from snraware.components.setup import start_timer, end_timer, get_device +from snraware.components.model.attention import ViT3DAttention + +# find the device, e.g. 'cuda:0' +device = get_device() + +# define an input tensor +B, C, T, H, W = 1, 2, 16, 32, 32 +C_out = 8 +test_in = torch.rand(B, C, T, H, W).to(device=device) +print(test_in.shape) + +# define a vit3d module + +spacial_vit = ViT3DAttention(window_size=None, + num_wind=[8, 8, 4], # here we choose to set number of windows; alternatively, we can set window_size to be [2, 2, 4] + attention_type="conv", # use convolution to compute Q/K/V in the attention + C_in=2, # input channel, 2 + C_out=8, # output channel, 8 + H=H, W=W, D=T, # tensor sizes + stride_qk=[1,1,1], # stride is 1 when computing Q and K + cosine_att=True, # use the cosine attention + normalize_Q_K=False, # normalize Q and K before computing attention matrix; if cosine_att is True, this option + att_with_relative_position_bias=True, + att_with_output_proj=True) + +spacial_vit.to(device=device) + +test_out = spacial_vit(test_in) + +print(f"ViT3DAttention, input tensor {test_in.shape}, output tensor {test_out.shape}") + +``` + +## Backbones + +The backbone models contain multiple Blocks and Cells. They are configured by supplying the block_str to define the attention structures. + +Unlike the attention modules where parameters are listed out, the model has many more parameters. We use the [hydra](https://hydra.cc/) to manage the parameters. The parameter configuration files are in the `src/ifm/configs` folder. To define the model parameters for a backbone model: + +``` +from hydra import initialize, compose +with initialize(version_base=None, config_path="../src/snraware/components/configs"): + config = compose(config_name="config") +``` + +The backbone parameters are in `config.backbone`. The block and cell parameters are in `config.block` and `config.cell`. + +```python + +import torch +from snraware.components.setup import get_device +import hydra +from hydra import initialize, compose + +# get the device, e.g. 'cuda:0' +device = get_device() + +# define the input tensor +B, C, T, H, W = 1, 2, 32, 128, 128 +test_in = torch.rand(B, C, T, H, W).to(device=device) + +# get the mode parameter; note user can override the default configuration +# here we order a hrnet backbone +with initialize(version_base=None, config_path="../src/snraware/components/configs"): + cfg = compose(config_name="config") + +config = hydra.utils.instantiate(cfg.backbone) + +# define the model +model = HRnet(config=config, input_feature_channels=C, H=H, W=W, D=T) +model = model.to(device=device) + +# perform the forward pass +test_out = model(test_in) +print(f"{Fore.YELLOW}The output tensor shape is {test_out[-1].shape}.{Style.RESET_ALL}") +``` \ No newline at end of file diff --git a/docs/images/Attention_in_Unet.jpg b/docs/images/Attention_in_Unet.jpg new file mode 100644 index 0000000..ba884fa Binary files /dev/null and b/docs/images/Attention_in_Unet.jpg differ diff --git a/docs/images/Cell.jpg b/docs/images/Cell.jpg new file mode 100644 index 0000000..1d92b1c Binary files /dev/null and b/docs/images/Cell.jpg differ diff --git a/docs/images/HRNet.jpg b/docs/images/HRNet.jpg new file mode 100644 index 0000000..38781cf Binary files /dev/null and b/docs/images/HRNet.jpg differ diff --git a/docs/images/LLM.jpg b/docs/images/LLM.jpg new file mode 100644 index 0000000..3e605d6 Binary files /dev/null and b/docs/images/LLM.jpg differ diff --git a/docs/images/PSNR_small_medium_large.png b/docs/images/PSNR_small_medium_large.png new file mode 100644 index 0000000..b0333c9 Binary files /dev/null and b/docs/images/PSNR_small_medium_large.png differ diff --git a/docs/images/SOANet.jpg b/docs/images/SOANet.jpg new file mode 100644 index 0000000..71a876b Binary files /dev/null and b/docs/images/SOANet.jpg differ diff --git a/docs/images/Unet.jpg b/docs/images/Unet.jpg new file mode 100644 index 0000000..359a8e4 Binary files /dev/null and b/docs/images/Unet.jpg differ diff --git a/docs/images/ch4.gif b/docs/images/ch4.gif new file mode 100644 index 0000000..c90c7e6 Binary files /dev/null and b/docs/images/ch4.gif differ diff --git a/docs/images/ifm_model_block.png b/docs/images/ifm_model_block.png new file mode 100644 index 0000000..396e62b Binary files /dev/null and b/docs/images/ifm_model_block.png differ diff --git a/docs/images/ifm_model_cell.png b/docs/images/ifm_model_cell.png new file mode 100644 index 0000000..ad9f98a Binary files /dev/null and b/docs/images/ifm_model_cell.png differ diff --git a/docs/images/ifm_model_index.png b/docs/images/ifm_model_index.png new file mode 100644 index 0000000..6a64768 Binary files /dev/null and b/docs/images/ifm_model_index.png differ diff --git a/docs/images/ifm_model_three_attentions.png b/docs/images/ifm_model_three_attentions.png new file mode 100644 index 0000000..55c02c3 Binary files /dev/null and b/docs/images/ifm_model_three_attentions.png differ diff --git a/docs/images/mri_denoising_res2.gif b/docs/images/mri_denoising_res2.gif new file mode 100644 index 0000000..0450395 Binary files /dev/null and b/docs/images/mri_denoising_res2.gif differ diff --git a/docs/images/phantom.gif b/docs/images/phantom.gif new file mode 100644 index 0000000..9ec4f40 Binary files /dev/null and b/docs/images/phantom.gif differ diff --git a/docs/images/phantom_std.png b/docs/images/phantom_std.png new file mode 100644 index 0000000..31fbc32 Binary files /dev/null and b/docs/images/phantom_std.png differ diff --git a/docs/images/training_denoising_samples.gif b/docs/images/training_denoising_samples.gif new file mode 100644 index 0000000..a455c0d Binary files /dev/null and b/docs/images/training_denoising_samples.gif differ diff --git a/docs/images/wandb.png b/docs/images/wandb.png new file mode 100644 index 0000000..d07b08f Binary files /dev/null and b/docs/images/wandb.png differ diff --git a/docs/index.md b/docs/index.md index ad380de..108ec21 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,79 @@ ---- -title: about ---- # SNRAware -Deep Learning MR Denoising Traning and Imaging Transformer Models +This repository contains the Pytorch code in our paper [SNRAware: Improved Deep Learning MRI Denoising with Signal-to-noise Ratio Unit Training and G-factor Map Augmentation](https://pubs.rsna.org/doi/full/10.1148/ryai.250227) published at the Radiology: Artificial Intelligence: + +```latex +@article{ + doi:10.1148/ryai.250227, + author = {Xue, Hui and Hooper, Sarah M. and Pierce, Iain and Davies, Rhodri H. and Stairs, John and Naegele, Joseph and Campbell-Washburn, Adrienne E. and Manisty, Charlotte and Moon, James C. and Treibel, Thomas A. and Hansen, Michael S. and Kellman, Peter}, + title = {SNRAware: Improved Deep Learning MRI Denoising with Signal-to-noise Ratio Unit Training and G-factor Map Augmentation}, + journal = {Radiology: Artificial Intelligence}, + volume = {0}, + number = {ja}, + pages = {e250227}, + year = {0}, + doi = {10.1148/ryai.250227}, + note ={PMID: 41123451}, + URL = {https://doi.org/10.1148/ryai.250227} +} +``` + +- Model type: Imaging AI, non-generative +- License: MIT + +## Get started + +[just](https://github.com/casey/just) is used in this project. If not, please install this tool: + +```bash +# install just +wget -qO - 'https://proget.makedeb.org/debian-feeds/prebuilt-mpr.pub' | gpg --dearmor | sudo tee /usr/share/keyrings/prebuilt-mpr-archive-keyring.gpg 1> /dev/null +echo "deb [arch=all,$(dpkg --print-architecture) signed-by=/usr/share/keyrings/prebuilt-mpr-archive-keyring.gpg] https://proget.makedeb.org prebuilt-mpr $(lsb_release -cs)" | sudo tee /etc/apt/sources.list.d/prebuilt-mpr.list +sudo apt update +sudo apt install just -y +``` + +Then, please set up the virtual environment and run tests: + +```bash +# show the list +just --list + +# set up virtual environment +just setup-env + +# review documentation +just serve-docs + +# run test +just test +``` + +## Data +Dataset for MR denoising training is not opened at this moment. + +## Model +Three models are released at https://huggingface.co/microsoft/SNRAware + +- SNRAware-small: 27.7million parameters +- SNRAware-medium: 55.1million parameters +- SNRAware-large: 109million parameters + +## Direct intended uses +SNRAware is shared for research and technical development purposes only, to denoise MR images. + +## License and Usage Notices +The data, code, and model checkpoints described in this repository is provided for research and technical development use +only. The data, code, and model checkpoints are not intended for use in clinical use. + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft +trademarks or logos is subject to and must follow +[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. +Any use of third-party trademarks or logos are subject to those third-party's policies. + +## Documentation + +Please find documentation in the [docs/overview](./docs/overview.md). diff --git a/docs/mri_denoising.md b/docs/mri_denoising.md new file mode 100644 index 0000000..7269fcb --- /dev/null +++ b/docs/mri_denoising.md @@ -0,0 +1,58 @@ +# MRI Denoising with SNRUnit + +## SNRUnit + +Central to our MRI model is the concept of signal-to-noise ratio (SNR) and unitary noise level. When the noise level is statistically 1.0 (aka unitary), image intensity directly reflects pixel-wise SNR. This fixed noise floor helps the model distinguish signal from noise. To demonstrate, we scanned a water phantom 40 times: + +![phantom](./images/phantom.gif) + +The per-pixel noise standard deviation (SD) is estimated as `std = np.std(im, axis=2)`, where `im` is the image tensor `[H, W, T]`. The SNRUnit scheme ensures the mean noise level (measured by averaging the SD inside the circle) is 1.0. The mean SD can be measured by drawing a region-of-interest and computing the mean: + +![SDmap](./images/phantom_std.png) + +The mean SD measured is 1.01. + +A set of functions are developed to simulate colored noise in MR imaging. The simulated noise is added to the clean images to generate paired low- and high-quality samples. These functions are carefully validated to maintain a unitary noise level. The corresponding code is located in the `src/snraware/projects/mri/snr` folder, with unit tests in [`test/test_snr_noise.py`](../test/test_snr_noise.py). + +## Denoising + +The SNRAware method is applied to MRI denoising. Correlated MR noise is generated and added to high-quality complex images. The imaging transformer model is instantiated to predict the high quality output. + +In the visualization below, the left panel shows the generated noisy sample, the middle panel displays the ground-truth high-quality data, and the right panel presents the g-factor map. +![training_samples](./images/training_denoising_samples.gif) + +A set of imaging loss functions are implemented in `src/snraware/projects/loss/imaging_loss.py`. Different combination of losses can be configured to train the model. To use these loss functions, simply import from imaging_loss, e.g. `from snraware.projects.loss.imaging_loss import SSIM_loss`. + +The project source files reside in `src/snraware/projects/mri/denoising`. The main driver file is `run.py`. To start a training: +```bash +python3 ./src/snraware/projects/mri/denoising/run.py logging.use_wandb=True trainer.max_epochs=32 batch_size=1 +``` + +To train the release small and medium models: +```bash +# a small model, 27.7 M parameters +python3 ./src/ifm/mri/denoising/run.py logging.use_wandb=True backbone.block_str=\[\'T1L1G1\',\ \'T1L1G1\'\] trainer.max_epochs=320 + +# a medium model, 55.1 M parameters +python3 ./src/ifm/mri/denoising/run.py logging.use_wandb=True backbone.block_str=\[\'T1L1G1T1L1G1\',\ \'T1L1G1T1L1G1\'\] trainer.max_epochs=320 + +# a large model, 109 M parameters +python3 ./src/ifm/mri/denoising/run.py logging.use_wandb=True backbone.block_str=\[\'T1L1G1T1L1G1T1L1G1T1L1G1\',\ \'T1L1G1T1L1G1T1L1G1T1L1G1\'\] trainer.max_epochs=320 +``` + +User needs to log into the wandb as `wandb login`. Training and validation samples will be uploaded to wandb as videos. +![wandb](./images/wandb.png) + +# Run inference + +After training the model, user can run inference with `run_inference.py` in the `src/snraware/projects/mri/denoising` folder: + +```bash +export model_file="SNRAware-small.pts" +export config_file="SNRAware-small_config.yaml" +export res_dir="res" +export data_dir="/data" + +python3 ./src/snraware/projects/mri/denoising/run_inference.py --input_dir ${data_dir} --output_dir ${data_dir}/${res_dir} --saved_model_path $model_file --saved_config_path $config_file --batch_size 1 --input_fname input --gmap_fname gmap + +``` \ No newline at end of file diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..0d5a753 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,20 @@ + +# Overview + +Data formats in imaging and related domains can be flexible. A x-ray image is a 2D picture with `[C, H, W]` for channel (often being 1), and height and width. A MRI image to capture beating heart is a 2D+T imagery `[C, F, H, W]` with the frame dimension. A CT volume can be isotropic 3D data `[C, D, H, W]`. All these diversity can be captured by a 5D tensor `[B, C, F, H, W]`. For the 2D case, frame dimension is 1. For the dynamic imaging, frame dimension is the time. For the 3D case, frame dimension is the depth into the volume. + +As an example, this is a `2D+T` case. Here we have F=30 frames to capture the beating heart. The image `H` and `W` is 192 and 256. So the tensor shape is `[B, 1, 30, 192, 256]`. + +![ch4](images/ch4.gif) + +For a 2D case, such as the chest x-ray image, the F is 1. + +This package implemented building blocks to help develop AI models to process the 5D tensors. To standardize and simplify this process, all components take in a 5D tensor and output another 5D tensor. The number of channels can be increased or decreased, but most components will keep the `F, H, W` unchanged. The explicit down/upsampling layers are inserted into the model to alter these dimensions if needed. + +# What is in this package + +- [Attention layers](./attention_layers.md) : the modified attention layers to capture local, global and inter-frame signal and noise information. +- [Cell and Block](./block_and_cell.md): the container objects to hold the attention layers. +- [Backbone models](./backbone.md): the backbone models consist of multiple blocks. It is the main body of a model. +- [Examples and tests](./examples_and_tests.md): examples to use these components and how to run the tests. +- [MR denoising](./mri_denoising.md): A MR denoising trianing using the imaging transformers. diff --git a/justfile b/justfile index b114349..9e4f666 100644 --- a/justfile +++ b/justfile @@ -11,6 +11,10 @@ set shell := ['bash', '-ceuo', 'pipefail'] @test: pytest +@fix: + ruff check --fix + ruff format + @build-package: uv build diff --git a/pyproject.toml b/pyproject.toml index aafb28d..51dcbd5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "snraware" -description = "" +description = "A deep learning imaging AI framework" version = "0.1.0" readme = "README.md" requires-python = ">=3.12,<4.0" @@ -9,7 +9,43 @@ authors = [ ] keywords = [] license = "MIT" -dependencies = [] +dependencies = [ + "mkl-fft>=2.0.0", + "mkl-service>=2.4.2", + "typing_extensions", + "click", + "h5py", + "opencv-python", + "pathlib", + "PyWavelets", + "pytorch-wavelets", + "torchmetrics==1.7.3", + "torch-interpol", + "piq", + "imageio", + "pyyaml", + "wandb", + "wandb[media]", + "scipy", + "cffi", + "numpy>=2.2.6", + "torch>=2.8.0", + "setuptools", + "torchvision", + "torchinfo", + "prettytable", + "scikit-image", + "nibabel", + "matplotlib", + "einops", + "pandas", + "lightning", + "hydra-core", + "omegaconf", + "hydra_colorlog", + "pytest>=8.4.2", + "dvc[azure]", +] [project.urls] repository = "https://github.com/microsoft/SNRAware" @@ -42,6 +78,15 @@ build-backend = "uv_build" [tool.uv.build-backend] module-name = ["snraware"] +[[tool.uv.index]] +name = "mkl" +url = "https://urob.github.io/numpy-mkl" + +[tool.uv.sources] +numpy = { index = "mkl" } +scipy = { index = "mkl" } +mkl-service = { index = "mkl" } + [project.scripts] [tool.pyright] @@ -49,25 +94,16 @@ module-name = ["snraware"] include = ["src", "test"] exclude = ["**/__pycache__"] -strictListInference = true -strictDictionaryInference = true -strictSetInference = true deprecateTypingAliases = true reportPropertyTypeMismatch = true reportUnusedImport = true -reportUnusedClass = true -reportUnusedVariable = true reportDuplicateImport = true reportUntypedFunctionDecorator = true reportUntypedClassDecorator = true reportUntypedBaseClass = true reportUntypedNamedTuple = true -reportConstantRedefinition = true reportDeprecated = true reportInconsistentConstructor = true -reportUnknownParameterType = true -reportMissingParameterType = true -reportMissingTypeArgument = true reportUnnecessaryCast = true reportUnnecessaryContains = true reportImplicitStringConcatenation = true @@ -75,26 +111,44 @@ reportInvalidStubStatement = true reportIncompleteStub = true reportUnnecessaryTypeIgnoreComment = true reportMatchNotExhaustive = true -reportShadowedImports = true reportMissingTypeStubs = false reportImportCycles = true reportUnusedFunction = false reportMissingSuperCall = false -reportPrivateUsage = true -reportUninitializedInstanceVariable = false # disabled so we can check test directories -reportUnknownArgumentType = true reportUnknownLambdaType = true -reportUnknownVariableType = true -reportUnknownMemberType = true reportCallInDefaultInitializer = true reportUnnecessaryIsInstance = true -reportUnnecessaryComparison = true reportUnusedCallResult = false reportUnusedExpression = true reportImplicitOverride = false reportIncompatibleVariableOverride = true -reportIncompatibleMethodOverride = true +reportCallIssue = false +reportGeneralTypeIssues = false +reportIndexIssue = false +reportOptionalIterable = false +reportOperatorIssue = false +reportOptionalCall = false +reportMissingTypeArgument = false + +reportIncompatibleMethodOverride = false +reportUninitializedInstanceVariable = false +reportMissingParameterType = false +reportUnknownArgumentType = false +reportUnknownMemberType = false +reportConstantRedefinition = false +reportUnknownVariableType = false +reportUnknownParameterType = false +reportArgumentType = false +reportUnusedVariable = false +reportPossiblyUnboundVariable = false +reportAttributeAccessIssue = false +reportOptionalMemberAccess = false +reportPrivateUsage = false +reportOptionalSubscript = false +reportUnusedClass = false +reportUndefinedVariable = false +reportUnnecessaryComparison = false [tool.ruff] cache-dir = ".tmp/.ruff_cache" @@ -124,12 +178,37 @@ select = [ "PTH", # Path "RUF", # Ruff "UP", # Unittest - "D213", # D213: Multi-line docstring summary should start at the second line ] ignore = [ + "E501", # E501: Line too long ({width} > {limit}) "D212", # D212: Multi-line docstring summary should start at the second line # This rule seems buggy: https://github.com/astral-sh/ruff/issues/2480 "PD011", # PD011: Use `.to_numpy()` instead of `.values` + # TODO: re-enable these when we can fix the errors + "B007", # Loop control variable `loader_ind` not used within loop body + "B008", # Do not perform function call `torch.device` in argument defaults + "B016", # Cannot raise a literal. Did you intend to return it or raise an Exception? + "D100", # Missing docstrings + "D101", # Missing docstrings + "D102", # Missing docstrings + "D103", # Missing docstrings + "D104", # Missing docstrings + "D105", # Missing docstrings + "D107", # Missing docstrings + "D205", # 1 blank line required between summary line and description + "D417", # Missing argument descriptions in the docstring + "E722", # Do not use bare `except` + "F403", # Do not use `*` imports + "F405", # X may be undefined, or defined from star imports + "PTH110", # `os.path.exists()` should be replaced by `Path.exists()` + "PTH112", # `os.path.isdir()` should be replaced by `Path.is_dir()` + "PTH113", # `os.path.isfile()` should be replaced by `Path.is_file()` + "PTH118", # `os.path.join()` should be replaced by `Path` with `/` operator + "PTH103", # `os.makedirs()` should be replaced by `Path.mkdir(parents=True)` + "PTH123", # `open()` should be replaced by `Path.open()` + "PTH208", # Use `pathlib.Path.iterdir()` instead. + "PTH120", # `os.path.dirname()` should be replaced by `Path.parent` + "NPY002", ] # Allow fix for all enabled rules (when `--fix`) is provided. @@ -146,6 +225,9 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" "D102", # Missing docstring in public method "D103", # Missing docstring in public function "D104", # Missing docstring in public package + "E501", # Line too long, + "D205", # blank line required between summary line and description + "D107", # Missing docstring in `__init__` ] [tool.ruff.lint.pydocstyle] @@ -185,4 +267,10 @@ addopts = """ --capture=no --cov-branch --cache-clear + -m 'not gpu' + -s """ +markers = [ + "gpu: a test needs gpus to run", + "slow: mark a test as slow", +] diff --git a/src/snraware/components/configs/backbone/base_backbone.yaml b/src/snraware/components/configs/backbone/base_backbone.yaml new file mode 100644 index 0000000..b6e4eac --- /dev/null +++ b/src/snraware/components/configs/backbone/base_backbone.yaml @@ -0,0 +1,5 @@ + # number of channels in main body of backbone + num_of_channels: 32 + + # block string to define the attention layers in blocks; if multiple strings are given, each is for a resolution level + block_str: ["T1L1G1"] \ No newline at end of file diff --git a/src/snraware/components/configs/backbone/block/block_params.yaml b/src/snraware/components/configs/backbone/block/block_params.yaml new file mode 100644 index 0000000..d8f3ad0 --- /dev/null +++ b/src/snraware/components/configs/backbone/block/block_params.yaml @@ -0,0 +1,11 @@ + +_target_: snraware.components.model.config.BlockConfig + +defaults: + - cell: cell_params + +# sequential or parallel cells +cell_type: sequential + +# whether to add dense connections between cells in a block +block_dense_connection: False \ No newline at end of file diff --git a/src/snraware/components/configs/backbone/block/cell/base_cell.yaml b/src/snraware/components/configs/backbone/block/cell/base_cell.yaml new file mode 100644 index 0000000..32b3bce --- /dev/null +++ b/src/snraware/components/configs/backbone/block/cell/base_cell.yaml @@ -0,0 +1,46 @@ +# conv or lin, type of attention in the spatial attention modules +attention_type: conv + +# conv or lin, type of mixer in the spatial attention modules; only conv is possible for the temporal attention +mixer_type: conv + +# size of window for spatial attention. This is the number of pixels in a window. Given image height and weight H and W, number of windows is H/windows_size[0] * W/windows_size[1]; for 3D usecase, window_size order is [H, W, D] +window_size: [16, 16, 1] + +# size of patch for spatial attention. This is the number of pixels in a patch. An image is first split into windows. Every window is further split into patches. +patch_size: [2, 2, 1] + +# method to adjust window_size between resolution levels, "keep_window_size", "keep_num_window", "mixed". +# "keep_window_size" means number of pixels in a window is kept after down/upsample the image; "keep_num_window" means the number of windows is kept after down/upsample the image; "mixed" means interleave both methods. +window_sizing_method: mixed + +# number of transformer heads +n_head: 16 + +# the scaling ratio to increase/decrease dimensions in the mixer of an attention layer +scale_ratio_in_mixer: 4.0 + +# whether to normalize Q and K before computing attention matrix +normalize_Q_K: True +# whether to use cosine attention for computing attention matrix +cosine_att: True +# whether to add relative position bias to the attention matrix +att_with_relative_position_bias: True +# dropout probability for attention matrix +att_dropout_p: 0.0 +# dropout probability after applying the attention matrix; aka stochastic residual connections +dropout_p: 0.1 +# whether to add output projection in attention layer +att_with_output_proj: True + +# normalization mode +norm_mode: instance2d # "layer", "batch2d", "instance2d", "batch3d", "instance3d" + +# nonlinear activation function, "elu", "relu", "leakyrelu", "prelu", "relu6", "selu", "celu", "gelu" +activation_func: prelu + +# upsampling method in backbone, "NN", "linear", "bspline" +upsample_method: linear + +# whether to do timing in the attention layer +with_timer: False \ No newline at end of file diff --git a/src/snraware/components/configs/backbone/block/cell/cell_params.yaml b/src/snraware/components/configs/backbone/block/cell/cell_params.yaml new file mode 100644 index 0000000..8e27f6e --- /dev/null +++ b/src/snraware/components/configs/backbone/block/cell/cell_params.yaml @@ -0,0 +1,33 @@ +defaults: + - /backbone/block/cell/base_cell@_here_ + +temporal: + _target_: snraware.components.model.config.TemporalAttentionConfig + stride_qk: [1, 1, 1] + +spatial_local: + _target_: snraware.components.model.config.SpatialLocalConfig + +spatial_global: + _target_: snraware.components.model.config.SpatialGlobalConfig + shuffle_in_window: False + +convoluation: + _target_: snraware.components.model.config.ConvolutionConfig + conv_type: "conv3d" + +spatial_local_3d: + _target_: snraware.components.model.config.SpatialLocal3DConfig + +spatial_global_3d: + _target_: snraware.components.model.config.SpatialGlobal3DConfig + shuffle_in_window: False + +spatial_vit: + _target_: snraware.components.model.config.SpatialViTConfig + +vit_3d: + _target_: snraware.components.model.config.ViT3DConfig + +swin_3d: + _target_: snraware.components.model.config.Swin3DConfig diff --git a/src/snraware/components/configs/backbone/hrnet.yaml b/src/snraware/components/configs/backbone/hrnet.yaml new file mode 100644 index 0000000..ae259a3 --- /dev/null +++ b/src/snraware/components/configs/backbone/hrnet.yaml @@ -0,0 +1,11 @@ + +_target_: snraware.components.model.config.HRNetConfig + +defaults: + - /backbone/base_backbone@_here_ + - block: block_params + +name: "HRnet" + +# number of resolution levels; image size reduce by x2 for every level +num_resolution_levels: 2 \ No newline at end of file diff --git a/src/snraware/components/configs/backbone/soanet.yaml b/src/snraware/components/configs/backbone/soanet.yaml new file mode 100644 index 0000000..f2079d6 --- /dev/null +++ b/src/snraware/components/configs/backbone/soanet.yaml @@ -0,0 +1,14 @@ +_target_: snraware.components.model.config.SOANetConfig + +defaults: + - /backbone/base_backbone@_here_ + - block: block_params + +name: "SOAnet" + +# number of attentin stages in the soanet backbone +# optionally, tensor size can be reduced by x2 for every stage +num_stages: 2 + +# whether to downsample between stages +downsample: True \ No newline at end of file diff --git a/src/snraware/components/configs/backbone/unet.yaml b/src/snraware/components/configs/backbone/unet.yaml new file mode 100644 index 0000000..542ba43 --- /dev/null +++ b/src/snraware/components/configs/backbone/unet.yaml @@ -0,0 +1,14 @@ + +_target_: snraware.components.model.config.UNetConfig + +defaults: + - /backbone/base_backbone@_here_ + - block: block_params + +name: "Unet" + +# number of resolution levels; image size reduce by x2 for every level +num_resolution_levels: 2 + +# whether to add unet channel attention on the skip connections between resolution levels +use_unet_attention: True \ No newline at end of file diff --git a/src/snraware/components/configs/config.yaml b/src/snraware/components/configs/config.yaml new file mode 100644 index 0000000..89dfb54 --- /dev/null +++ b/src/snraware/components/configs/config.yaml @@ -0,0 +1,8 @@ +# Configuration for the imaging fm model + +defaults: + - backbone: hrnet + - optim: sophia + - scheduler: OneCycleLR + - heads: base_heads + - _self_ \ No newline at end of file diff --git a/src/snraware/components/configs/heads/base_heads.yaml b/src/snraware/components/configs/heads/base_heads.yaml new file mode 100644 index 0000000..0c6b48a --- /dev/null +++ b/src/snraware/components/configs/heads/base_heads.yaml @@ -0,0 +1,7 @@ +# parameters for model heads + +PoolLinear: + add_tanh: false + +PreConv2D: + conv_with_bias: true \ No newline at end of file diff --git a/src/snraware/components/configs/optim/base_optim.yaml b/src/snraware/components/configs/optim/base_optim.yaml new file mode 100644 index 0000000..f318393 --- /dev/null +++ b/src/snraware/components/configs/optim/base_optim.yaml @@ -0,0 +1,5 @@ +# learning rate (used for params not sorted into pre/backbone/post) +lr: 1e-4 + +# Weight decay regularization +weight_decay: 0.0 \ No newline at end of file diff --git a/src/snraware/components/configs/optim/sophia.yaml b/src/snraware/components/configs/optim/sophia.yaml new file mode 100644 index 0000000..c096369 --- /dev/null +++ b/src/snraware/components/configs/optim/sophia.yaml @@ -0,0 +1,8 @@ +defaults: + - /optim/base_optim@_here_ + +name: "sophia" +beta1: 0.9 +beta2: 0.999 +eps: 1e-8 +rho: 0.01 \ No newline at end of file diff --git a/src/snraware/components/configs/scheduler/OneCycleLR.yaml b/src/snraware/components/configs/scheduler/OneCycleLR.yaml new file mode 100644 index 0000000..7f6f707 --- /dev/null +++ b/src/snraware/components/configs/scheduler/OneCycleLR.yaml @@ -0,0 +1,6 @@ + +name: "OneCycleLR" +#the percentage of the cycle spent increasing the learning rate +pct_start: 0.3 +anneal_strategy: "cos" # cos or linear +div_factor: 25 \ No newline at end of file diff --git a/src/snraware/components/heads/__init__.py b/src/snraware/components/heads/__init__.py new file mode 100644 index 0000000..5342578 --- /dev/null +++ b/src/snraware/components/heads/__init__.py @@ -0,0 +1,3 @@ +from .heads import PoolLinear as PoolLinear +from .heads import PreConv2D as PreConv2D +from .heads import SimpleConv2d as SimpleConv2d diff --git a/src/snraware/components/heads/heads.py b/src/snraware/components/heads/heads.py new file mode 100644 index 0000000..f18f68d --- /dev/null +++ b/src/snraware/components/heads/heads.py @@ -0,0 +1,89 @@ +""" +Post components. + +All modules take in a 5D tensor [B, C, T, H, W] and output a 2D logits [B, num_classes] +""" + +import torch +import torch.nn as nn + +from snraware.components.model import Conv2DExt + +# ------------------------------------------------------------------------------------------------- + + +class PreConv2D(nn.Module): + """A conv2d head to convert C channels to Cout channels.""" + + def __init__(self, C, C_out, bias=True): + super().__init__() + + self.layer = Conv2DExt( + in_channels=C, + out_channels=C_out, + kernel_size=[3, 3], + stride=[1, 1], + padding=[1, 1], + padding_mode="reflect", + bias=bias, + channel_first=True, + ) + + def forward(self, x): + return self.layer(x) + + +# ------------------------------------------------------------------------------------------------- + + +class PoolLinear(nn.Module): + def __init__(self, config, C, num_classes, add_tanh=False): + """ + For an input [B, C, D, H, W] tensor, first avgpool over [D, H, W], then convert [B, C, 1] tensor to [B, num_classes] logits. + + add_tanh (bool): whether to add tanh after linear layer. + """ + super().__init__() + + self.avgpool = nn.AdaptiveAvgPool3d(1) + self.head = nn.Sequential(nn.Linear(C, num_classes)) + if add_tanh: + self.head.append(nn.Tanh()) + + def forward(self, x): + if isinstance(x, list): + x = x[-1] + x = self.avgpool(x) + x = torch.flatten(x, start_dim=1) + x = self.head(x) + return x + + +# ------------------------------------------------------------------------------------------------- + + +class SimpleConv2d(nn.Module): + def __init__(self, config, C, num_classes): + """ + Takes in features [B, C, D, H, W] from backbone model and produces an output of [B, num_classes, D, H, W] + num_classes: the number of segmentation class (e.g. 1 for binary segmentation, N for N-target segmentation). + """ + super().__init__() + self.conv2d = Conv2DExt( + in_channels=C, + out_channels=num_classes, + kernel_size=[3, 3], + padding=[1, 1], + stride=[1, 1], + bias=True, + channel_first=True, + ) + + def forward(self, x): + if isinstance(x, list): + x = x[-1] + x = self.conv2d(x) + return x + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/__init__.py b/src/snraware/components/model/__init__.py new file mode 100644 index 0000000..a2cdbdc --- /dev/null +++ b/src/snraware/components/model/__init__.py @@ -0,0 +1,3 @@ +from .attention import * +from .backbone import * +from .config import * diff --git a/src/snraware/components/model/attention/__init__.py b/src/snraware/components/model/attention/__init__.py new file mode 100644 index 0000000..586b293 --- /dev/null +++ b/src/snraware/components/model/attention/__init__.py @@ -0,0 +1,10 @@ +from .attention_modules import * +from .convolution_modules import * +from .global_3d_attention_modules import * +from .local_3d_attention_modules import * +from .spatial_global_attention_modules import * +from .spatial_local_attention_modules import * +from .spatial_vit_attention_modules import * +from .swin_3d_attention_modules import * +from .temporal_attention_modules import * +from .vit_3d_attention_modules import * diff --git a/src/snraware/components/model/attention/attention_modules.py b/src/snraware/components/model/attention/attention_modules.py new file mode 100644 index 0000000..0e3e698 --- /dev/null +++ b/src/snraware/components/model/attention/attention_modules.py @@ -0,0 +1,755 @@ +""" +Attention modules for the Imaging-FM project. + +This module contains base classes and utility functions for implementing various attention mechanisms. + +To find more information, please visit the https://health-futures.github.io/resys, imaging fm model section. +""" + +import torch.nn as nn +from torch.nn import functional as F + +from snraware.components.setup.status import * + +# ------------------------------------------------------------------------------------------------- + + +def compute_conv_output_shape(h_w, kernel_size, stride, pad, dilation): + """ + Utility function for computing output of convolutions given the setup + @args: + - h_w (int, int): 2-tuple of height, width of input + - kernel_size, stride, pad (int, int): 2-tuple of conv parameters + - dilation (int): dilation conv parameter + @rets: + - h, w (int, int): 2-tuple of height, width of image returned by the conv. + """ + h_0 = h_w[0] + (2 * pad[0]) - (dilation * (kernel_size[0] - 1)) - 1 + w_0 = h_w[1] + (2 * pad[1]) - (dilation * (kernel_size[1] - 1)) - 1 + + h = torch.div(h_0, stride[0], rounding_mode="floor") + 1 + w = torch.div(w_0, stride[1], rounding_mode="floor") + 1 + + return h, w + + +# ------------------------------------------------------------------------------------------------- +# Create activation functions + + +def create_activation_func(name="gelu"): + if name == "elu": + return nn.modules.ELU(alpha=1, inplace=False) + elif name == "relu": + return nn.modules.ReLU(inplace=False) + elif name == "leakyrelu": + return nn.modules.LeakyReLU(negative_slope=0.1, inplace=False) + elif name == "prelu": + return nn.modules.PReLU(num_parameters=1, init=0.25) + elif name == "relu6": + return nn.modules.ReLU6(inplace=False) + elif name == "selu": + return nn.modules.SELU(inplace=False) + elif name == "celu": + return nn.modules.CELU(alpha=1, inplace=False) + elif name == "gelu": + return nn.modules.GELU(approximate="tanh") + else: + return nn.Identity() + + +# ------------------------------------------------------------------------------------------------- + + +def permute_to_B_T_C_H_W(x): + """ + X : [B, T/D/Z, C, ...] + res: [B, C, T, ...]. + """ + return torch.transpose(x, 1, 2) + + +def permute_to_B_C_T_H_W(x): + """ + X : [B, C, T/D/Z, ...] + res: [B, T, C, ...]. + """ + return torch.transpose(x, 1, 2) + + +# ------------------------------------------------------------------------------------------------- +class Conv2DExt(nn.Module): + # Extends torch 2D conv to support 5D inputs + # if channel_first is True, input x is [B, C, T, H, W] + # if channel_first is False, input x is [B, T, C, H, W] + def __init__( + self, + in_channels, + out_channels, + kernel_size=None, + stride=None, + padding=None, + padding_mode="reflect", + bias=False, + channel_first=False, + ): + if padding is None: + padding = [1, 1] + if stride is None: + stride = [1, 1] + if kernel_size is None: + kernel_size = [3, 3] + super().__init__() + self.channel_first = channel_first + if not isinstance(kernel_size, int): + kernel_size = kernel_size[:2] + if not isinstance(stride, int): + stride = stride[:2] + if not isinstance(padding, int): + padding = padding[:2] + + self.conv = nn.Conv2d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=bias, + padding_mode=padding_mode, + ) + + def forward(self, input): + # requires input to have 5 dimensions + if self.channel_first: + B, C, T, H, W = input.shape + x = torch.permute(input, [0, 2, 1, 3, 4]) + else: + B, T, C, H, W = input.shape + x = input + + y = self.conv(x.reshape((B * T, C, H, W))) + y = y.reshape([B, T, *y.shape[1:]]) + + if self.channel_first: + y = torch.permute(y, [0, 2, 1, 3, 4]) + + return y + + +# ------------------------------------------------------------------------------------------------- +class Conv3DExt(nn.Module): + # Extends torch 3D conv to support 5D inputs + + def __init__( + self, + in_channels, + out_channels, + kernel_size=None, + stride=None, + padding=None, + bias=False, + padding_mode="reflect", + channel_first=False, + ): + if padding is None: + padding = [1, 1, 1] + if stride is None: + stride = [1, 1, 1] + if kernel_size is None: + kernel_size = [3, 3, 3] + super().__init__() + self.channel_first = channel_first + self.conv = nn.Conv3d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=bias, + padding_mode=padding_mode, + ) + + def forward(self, input): + # requires input to have 5 dimensions + if self.channel_first: + y = self.conv(input) + return y + else: + _B, _T, _C, _H, _W = input.shape + x = torch.permute(input, (0, 2, 1, 3, 4)) + y = self.conv(x) + return torch.permute(y, (0, 2, 1, 3, 4)) + + +# ------------------------------------------------------------------------------------------------- +class LinearGridExt(nn.Module): + # Helper module for linear head in imaging attentions. + + def __init__(self, *args, **kwargs): + super().__init__() + self.linear = nn.Linear(*args, **kwargs) + + def forward(self, input): + # requires input to have 7 dimensions + *S, Gh, Gw, C = input.shape + y = self.linear(input.reshape((-1, C * Gh * Gw))) + y = y.reshape((*S, Gh, Gw, -1)) + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class LinearGrid3DExt(nn.Module): + def __init__(self, *args, **kwargs): + super().__init__() + self.linear = nn.Linear(*args, **kwargs) + + def forward(self, input): + *S, Gd, Gh, Gw, C = input.shape + y = self.linear(input.reshape((-1, C * Gd * Gh * Gw))) + y = y.reshape((*S, Gd, Gh, Gw, -1)) + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class PixelShuffle2DExt(nn.Module): + # Extends torch 2D pixel shuffle + + def __init__(self, *args, **kwargs): + super().__init__() + self.ps = nn.PixelShuffle(*args, **kwargs) + + def forward(self, input): + input = permute_to_B_T_C_H_W(input) + B, T, C, H, W = input.shape + y = self.ps(input.reshape((B * T, C, H, W))) + return permute_to_B_C_T_H_W(y.reshape([B, T, *y.shape[1:]])) + + +# ------------------------------------------------------------------------------------------------- + + +class LayerNorm2DExt(nn.Module): + def __init__(self, *args, **kwargs): + super().__init__() + self.ln = nn.LayerNorm(*args, **kwargs) + + def forward(self, input): + input = permute_to_B_T_C_H_W(input) + _B, _T, _C, _H, _W = input.shape + norm_input = self.ln(input) + return permute_to_B_C_T_H_W(norm_input) + + +# ------------------------------------------------------------------------------------------------- +class LayerNorm3DExt(nn.Module): + def __init__(self, *args, **kwargs): + super().__init__() + self.ln = nn.LayerNorm(*args, **kwargs) + + def forward(self, input): + norm_input = self.ln(input) + return norm_input + + +# ------------------------------------------------------------------------------------------------- +class BatchNorm2DExt(nn.Module): + # Extends BatchNorm2D to 5D inputs + + def __init__(self, *args, **kwargs): + super().__init__() + self.bn = nn.BatchNorm2d(*args, **kwargs) + + def forward(self, input): + # requires input to have 5 dimensions + input = permute_to_B_T_C_H_W(input) + B, T, C, H, W = input.shape + norm_input = self.bn(input.reshape(B * T, C, H, W)) + return permute_to_B_C_T_H_W(norm_input.reshape(input.shape)) + + +# ------------------------------------------------------------------------------------------------- +class BatchNorm3DExt(nn.Module): + # Corrects BatchNorm3D, switching first and second dimension + + def __init__(self, *args, **kwargs): + super().__init__() + self.bn = nn.BatchNorm3d(*args, **kwargs) + + def forward(self, input): + norm_input = self.bn(input) + return norm_input + + +# ------------------------------------------------------------------------------------------------- +class InstanceNorm2DExt(nn.Module): + # Extends InstanceNorm2D to 5D inputs + + def __init__(self, *args, **kwargs): + super().__init__() + self.inst = nn.InstanceNorm2d(*args, **kwargs) + + def forward(self, input): + input = permute_to_B_T_C_H_W(input) + B, T, C, H, W = input.shape + norm_input = self.inst(input.reshape(B * T, C, H, W)) + return permute_to_B_C_T_H_W(norm_input.reshape(input.shape)) + + +# ------------------------------------------------------------------------------------------------- +class InstanceNorm3DExt(nn.Module): + # Corrects InstanceNorm3D, switching first and second dimension + + def __init__(self, *args, **kwargs): + super().__init__() + self.inst = nn.InstanceNorm3d(*args, **kwargs) + + def forward(self, input): + norm_input = self.inst(input) + return norm_input + + +# ------------------------------------------------------------------------------------------------- +class AvgPool2DExt(nn.Module): + # Extends torch 2D averaging pooling to support 5D inputs + + def __init__(self, *args, **kwargs): + super().__init__() + self.avg_pool_2d = nn.AvgPool2d(*args, **kwargs) + + def forward(self, input): + input = permute_to_B_T_C_H_W(input) + B, T, C, H, W = input.shape + y = self.avg_pool_2d(input.reshape((B * T, C, H, W))) + return permute_to_B_C_T_H_W(torch.reshape(y, [B, T, *y.shape[1:]])) + + +# ------------------------------------------------------------------------------------------------- +def create_norm(norm_mode="instance2d", C=64, H=32, W=32, D=32): + if norm_mode == "layer": + n = LayerNorm2DExt([C, H, W]) + + elif norm_mode == "layer3d": + n = LayerNorm3DExt([C, D, H, W]) + + elif norm_mode == "batch2d": + n = BatchNorm2DExt(C) + + elif norm_mode == "batch3d": + n = BatchNorm3DExt(C) + + elif norm_mode == "instance2d": + n = InstanceNorm2DExt(C) + + else: + n = InstanceNorm3DExt(C) + + return n + + +# ------------------------------------------------------------------------------------------------- + + +def _get_relative_position_bias_tensor( + relative_position_bias_table: torch.Tensor, relative_position_index: torch.Tensor, N: int +) -> torch.Tensor: + """From pytorch source code.""" + relative_position_bias = relative_position_bias_table[ + relative_position_index[:N, :N].reshape(-1) + ] + relative_position_bias = torch.reshape(relative_position_bias, (N, N, -1)) + relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous() + return relative_position_bias.unsqueeze(0) + + +def _get_relative_position_bias( + relative_position_bias_table: torch.Tensor, + relative_position_index: torch.Tensor, + window_size: tuple[int], +) -> torch.Tensor: + """From pytorch source code.""" + N = window_size[0] * window_size[1] + return _get_relative_position_bias_tensor( + relative_position_bias_table, relative_position_index, N + ) + + +def _get_relative_position_bias_3D( + relative_position_bias_table: torch.Tensor, + relative_position_index: torch.Tensor, + window_size: tuple[int], +) -> torch.Tensor: + """From pytorch source code.""" + N = window_size[0] * window_size[1] * window_size[2] + return _get_relative_position_bias_tensor( + relative_position_bias_table, relative_position_index, N + ) + + +# ------------------------------------------------------------------------------------------------- + + +def cosine_attention(q, k): + """Computes cosine attention between query and key tensors.""" + att = F.normalize(q, dim=-1) @ F.normalize(k, dim=-1).transpose(-2, -1) + return att + + +# ------------------------------------------------------------------------------------------------- + + +def normalize_qk(q, k): + """ + Normalizes query and key tensors. + If cosine attention is used, normalization is not applied. + """ + eps = torch.finfo(k.dtype).eps + k = (k - torch.mean(k, dim=-1, keepdim=True)) / ( + torch.sqrt(torch.var(k, dim=-1, keepdim=True) + eps) + ) + q = (q - torch.mean(q, dim=-1, keepdim=True)) / ( + torch.sqrt(torch.var(q, dim=-1, keepdim=True) + eps) + ) + return q, k + + +# ------------------------------------------------------------------------------------------------- +class CnnAttentionBase(nn.Module): + """Base class for attention layers.""" + + def __init__( + self, + C_in, + C_out=16, + H=128, + W=128, + D=1, + n_head=8, + kernel_size=(3, 3, 3), + stride=(1, 1, 1), + padding=(1, 1, 1), + stride_qk=(1, 1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + flash_att=False, + with_timer=False, + ): + """ + Base class for the cnn attentions. + + Input to the attention layer has the size [B, C, T, H, W] + Output has the size [B, output_channels, T, H', W'] + + @args: + - C_in (int): number of input channels + - C_out (int): number of output channels + - H, W (int): image height and width + - D (int): number of frames in the input, 1 for 2D attention + - n_head (int): number of heads in self attention + - kernel_size, stride, padding (int, int, int): convolution parameters, in the order of (H, W, D), not (D, H, W)! + - stride_qk (int, int, int): stride to compute q and k + - att_dropout_p (float): probability of dropout for the attention matrix + - cosine_att (bool): whether to perform cosine attention; if True, normalize_Q_K will be ignored, as Q and K are already normalized; https://arxiv.org/pdf/2409.18747 + - normalize_Q_K (bool): whether to add normalization for Q and K matrix + - att_with_relative_position_bias (bool): whether to add relative positional bias + - att_with_output_proj (bool): whether to add output projection + """ + super().__init__() + + self.C_in = C_in + self.C_out = C_out + self.H = H + self.W = W + self.D = D + self.n_head = n_head + self.kernel_size = (kernel_size,) + self.stride = stride + self.stride_qk = stride_qk + self.padding = padding + self.att_dropout_p = att_dropout_p + self.cosine_att = cosine_att + self.normalize_Q_K = normalize_Q_K + self.att_with_relative_position_bias = att_with_relative_position_bias + self.att_with_output_proj = att_with_output_proj + self.with_timer = with_timer + + if att_with_output_proj: + if self.D > 1: + self.output_proj = Conv3DExt( + C_out, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + else: + self.output_proj = Conv2DExt( + C_out, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + else: + self.output_proj = nn.Identity() + + if att_dropout_p > 0: + self.attn_drop = nn.Dropout(p=att_dropout_p) + else: + self.attn_drop = nn.Identity() + + self.has_flash_attention = flash_att + + def perform_flash_atten(self, k, q, v): + softmax_scale = None + if self.cosine_att: + q = F.normalize(q, dim=-1) + k = F.normalize(k, dim=-1) + softmax_scale = 1 + elif self.normalize_Q_K: + eps = torch.finfo(k.dtype).eps + k = (k - torch.mean(k, dim=-1, keepdim=True)) / ( + torch.sqrt(torch.var(k, dim=-1, keepdim=True) + eps) + ) + q = (q - torch.mean(q, dim=-1, keepdim=True)) / ( + torch.sqrt(torch.var(q, dim=-1, keepdim=True) + eps) + ) + + if self.training: + y = F.scaled_dot_product_attention( + q, + k, + v, + attn_mask=None, + dropout_p=self.att_dropout_p, + is_causal=self.is_causal, + scale=softmax_scale, + ) + else: + y = F.scaled_dot_product_attention( + q, + k, + v, + attn_mask=None, + dropout_p=0.0, + is_causal=self.is_causal, + scale=softmax_scale, + ) + + return y + + def get_dimension_for_linear_mixer(self): + """If linear mixer is to be used, return the dimension of attention computation, as the input dimension for linear mixer.""" + return self.C_out * self.patch_size[0] * self.patch_size[1] + + def define_relative_position_bias_table_3D(self, num_win_h=100, num_win_w=100, num_win_d=100): + self.relative_position_bias_table = nn.Parameter( + torch.zeros( + (2 * num_win_h - 1) * (2 * num_win_w - 1) * (2 * num_win_d - 1), self.n_head + ) + ) + nn.init.trunc_normal_(self.relative_position_bias_table, std=0.02) + + def define_relative_position_index_3D(self, num_win_h=100, num_win_w=100, num_win_d=100): + # get pair-wise relative position index for each token inside the window + coords_h = torch.arange(num_win_h) + coords_w = torch.arange(num_win_w) + coords_d = torch.arange(num_win_d) + coords = torch.stack( + torch.meshgrid(coords_h, coords_w, coords_d, indexing="ij") + ) # 3, Wh, Ww, Wd + coords_flatten = torch.flatten(coords, 1) # 3, Wh*Ww*Wd + relative_coords = ( + coords_flatten[:, :, None] - coords_flatten[:, None, :] + ) # 3, Wh*Ww*Wd, Wh*Ww*Wd + relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww*Wd, Wh*Ww*Wd, 3 + relative_coords[:, :, 0] += num_win_h - 1 # shift to start from 0 + relative_coords[:, :, 1] += num_win_w - 1 + relative_coords[:, :, 2] += num_win_d - 1 + relative_coords[:, :, 0] *= (2 * num_win_w - 1) * (2 * num_win_d - 1) + relative_coords[:, :, 1] *= 2 * num_win_d - 1 + relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww + self.register_buffer("relative_position_index", relative_position_index) + + def get_relative_position_bias_3D(self, num_win_h, num_win_w, num_win_d) -> torch.Tensor: + return _get_relative_position_bias_3D( + self.relative_position_bias_table, + self.relative_position_index, + (num_win_h, num_win_w, num_win_d), + ) + + def define_relative_position_bias_table(self, num_win_h=100, num_win_w=100): + # define a parameter table of relative position bias + self.relative_position_bias_table = nn.Parameter( + torch.zeros((2 * num_win_h - 1) * (2 * num_win_w - 1), self.n_head) + ) # 2*Wh-1 * 2*Ww-1, nH + nn.init.trunc_normal_(self.relative_position_bias_table, std=0.02) + + def define_relative_position_index(self, num_win_h=100, num_win_w=100): + # get pair-wise relative position index for each token inside the window + coords_h = torch.arange(num_win_h) + coords_w = torch.arange(num_win_w) + coords = torch.stack(torch.meshgrid(coords_h, coords_w, indexing="ij")) # 2, Wh, Ww + coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww + relative_coords = ( + coords_flatten[:, :, None] - coords_flatten[:, None, :] + ) # 2, Wh*Ww, Wh*Ww + relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2 + relative_coords[:, :, 0] += num_win_h - 1 # shift to start from 0 + relative_coords[:, :, 1] += num_win_w - 1 + relative_coords[:, :, 0] *= 2 * num_win_w - 1 + relative_position_index = relative_coords.sum(-1) + self.register_buffer("relative_position_index", relative_position_index) + + def get_relative_position_bias(self, num_win_h, num_win_w) -> torch.Tensor: + return _get_relative_position_bias( + self.relative_position_bias_table, + self.relative_position_index, + (num_win_h, num_win_w), + ) + + @property + def device(self): + return next(self.parameters()).device + + def set_and_check_wind(self): + if self.D > 1: + if self.num_wind is not None: + self.window_size = [ + self.H // self.num_wind[0], + self.W // self.num_wind[1], + self.D // self.num_wind[2], + ] + self.window_size = [max(w, 1) for w in self.window_size] + self.num_wind = [ + self.H // self.window_size[0], + self.W // self.window_size[1], + self.D // self.window_size[2], + ] + else: + self.num_wind = [ + self.H // self.window_size[0], + self.W // self.window_size[1], + self.D // self.window_size[2], + ] + self.num_wind = [max(n, 1) for n in self.num_wind] + self.window_size = [ + self.H // self.num_wind[0], + self.W // self.num_wind[1], + self.D // self.num_wind[2], + ] + + assert self.num_wind[2] * self.window_size[2] == self.D, ( + f"self.num_wind[2]*self.window_size[2] == self.D, num_wind {self.num_wind}, window_size {self.window_size}, D {self.D}" + ) + else: + if self.num_wind is not None: + self.window_size = [self.H // self.num_wind[0], self.W // self.num_wind[1]] + self.window_size = [max(w, 1) for w in self.window_size] + self.num_wind = [self.H // self.window_size[0], self.W // self.window_size[1]] + else: + self.num_wind = [self.H // self.window_size[0], self.W // self.window_size[1]] + self.num_wind = [max(n, 1) for n in self.num_wind] + self.window_size = [self.H // self.num_wind[0], self.W // self.num_wind[1]] + + assert self.num_wind[0] * self.window_size[0] == self.H, ( + f"self.num_wind[0]*self.window_size[0] == self.H, num_wind {self.num_wind}, window_size {self.window_size}, H {self.H}" + ) + + assert self.num_wind[1] * self.window_size[1] == self.W, ( + f"self.num_wind[1]*self.window_size[1] == self.W, num_wind {self.num_wind}, window_size {self.window_size}, W {self.W}" + ) + + def set_and_check_patch(self): + if self.D > 1: + if self.num_patch is not None: + self.patch_size = [ + self.window_size[0] // self.num_patch[0], + self.window_size[1] // self.num_patch[1], + self.window_size[2] // self.num_patch[2], + ] + self.patch_size = [max(v, 1) for v in self.patch_size] + self.num_patch = [ + w // p for w, p in zip(self.window_size, self.patch_size, strict=False) + ] + else: + self.num_patch = [ + self.window_size[0] // self.patch_size[0], + self.window_size[1] // self.patch_size[1], + self.window_size[2] // self.patch_size[2], + ] + self.num_patch = [max(v, 1) for v in self.num_patch] + self.patch_size = [ + w // n for w, n in zip(self.window_size, self.num_patch, strict=False) + ] + + assert ( + (self.patch_size[0] * self.num_patch[0] == self.window_size[0]) + and (self.patch_size[1] * self.num_patch[1] == self.window_size[1]) + and (self.patch_size[2] * self.num_patch[2] == self.window_size[2]) + ), ( + f"self.patch_size*self.num_patch == self.window_size, patch_size {self.patch_size}, num_patch {self.num_patch}, window_size {self.window_size}" + ) + else: + if self.num_patch is not None: + self.patch_size = [ + self.window_size[0] // self.num_patch[0], + self.window_size[1] // self.num_patch[1], + ] + self.patch_size = [max(v, 1) for v in self.patch_size] + self.num_patch = [ + w // p for w, p in zip(self.window_size, self.patch_size, strict=False) + ] + else: + self.num_patch = [ + self.window_size[0] // self.patch_size[0], + self.window_size[1] // self.patch_size[1], + ] + self.num_patch = [max(v, 1) for v in self.num_patch] + self.patch_size = [ + w // n for w, n in zip(self.window_size, self.num_patch, strict=False) + ] + + assert (self.patch_size[0] * self.num_patch[0] == self.window_size[0]) and ( + self.patch_size[1] * self.num_patch[1] == self.window_size[1] + ), ( + f"self.patch_size*self.num_patch == self.window_size, patch_size {self.patch_size}, num_patch {self.num_patch}, window_size {self.window_size}" + ) + + def validate_window_patch(self): + assert self.window_size[0] * self.num_wind[0] == self.H, ( + "self.window_size[0]*self.num_wind[0] == self.H" + ) + assert self.window_size[1] * self.num_wind[1] == self.W, ( + "self.window_size[1]*self.num_wind[1] == self.W" + ) + assert self.patch_size[0] * self.num_patch[0] == self.window_size[0], ( + "self.patch_size[0]*self.num_patch[0] == self.window_size[0]" + ) + assert self.patch_size[0] * self.num_patch[1] == self.window_size[1], ( + "self.patch_size[1]*self.num_patch[1] == self.window_size[1]" + ) + + if self.D > 1: + assert self.window_size[2] * self.num_wind[2] == self.D, ( + "self.window_size[2]*self.num_wind[2] == self.D" + ) + assert self.patch_size[2] * self.num_patch[2] == self.window_size[2], ( + "self.patch_size[2]*self.num_patch[2] == self.window_size[2]" + ) + + def __str__(self): + res = create_generic_class_str(self) + return res diff --git a/src/snraware/components/model/attention/convolution_modules.py b/src/snraware/components/model/attention/convolution_modules.py new file mode 100644 index 0000000..cf9ae01 --- /dev/null +++ b/src/snraware/components/model/attention/convolution_modules.py @@ -0,0 +1,92 @@ +"""Convolution module to process 5D tensors.""" + +from .attention_modules import * + + +class ConvolutionModule(nn.Module): + """ + Either 2d or 3d depending on the argument. + Add residual connection if C_in == C_out. + """ + + def __init__( + self, + conv_type, + C_in, + C_out, + H=128, + W=128, + D=32, + kernel_size=(3, 3, 3), + stride=(1, 1, 1), + padding=(1, 1, 1), + norm_mode="instance2d", + activation_func="prelu", + ): + """ + @args: + - conv_type ("conv2d" or "conv3d"): the type of conv. + """ + super().__init__() + + assert conv_type == "conv2d" or conv_type == "conv3d", ( + f"Conv type not implemented: {conv_type}" + ) + + self.C_in = C_in + self.C_out = C_out + + if conv_type == "conv2d": + if len(kernel_size) == 3: + kernel_size = (kernel_size[0], kernel_size[1]) + if len(stride) == 3: + stride = (stride[0], stride[1]) + if len(padding) == 3: + padding = (padding[0], padding[1]) + + self.conv = Conv2DExt( + in_channels=C_in, + out_channels=C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + channel_first=True, + ) + elif conv_type == "conv3d": + if len(kernel_size) == 2: + kernel_size = (*kernel_size, kernel_size[0]) + if len(stride) == 2: + stride = (*stride, stride[0]) + if len(padding) == 2: + padding = (*padding, padding[0]) + + self.conv = Conv3DExt( + in_channels=C_in, + out_channels=C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + channel_first=True, + ) + else: + raise NotImplementedError(f"Conv type not implemented: {conv_type}") + + self.n1 = create_norm(norm_mode=norm_mode, C=C_out, H=H, W=W, D=D) + self.act_func = create_activation_func(name=activation_func) + + def forward(self, x): + """ + @args: + x ([B, C_in, T, H, W]): Input of a batch of time series. + + @rets: + y ([B, C_out, T, H', W']): Output of the batch + """ + res = self.act_func(self.n1(self.conv(x))) + if self.C_in == self.C_out: + res += x + + return res + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/attention/global_3d_attention_modules.py b/src/snraware/components/model/attention/global_3d_attention_modules.py new file mode 100644 index 0000000..f05fbfb --- /dev/null +++ b/src/snraware/components/model/attention/global_3d_attention_modules.py @@ -0,0 +1,455 @@ +""" +Global 3D attention modules +The [H, W, D] volume is split into windows. Every window is split into patches. +The attention is computed among all patches in all windows. +""" + +from einops import rearrange + +from .attention_modules import * + +# ------------------------------------------------------------------------------------------------- + + +class Global3DAttention(CnnAttentionBase): + """ + Multi-head attention among the global patches. Number of pixels in a window are [window_size, window_size, window_size]. + Number of pixels in a patch are [patch_size, patch_size, patch_size]. + + shuffle_in_window: if True, randomly permute patches within a window before computing attention. + """ + + def __init__( + self, + C_in, + C_out=16, + H=128, + W=128, + D=16, + window_size=None, + patch_size=None, + num_wind=None, + num_patch=None, + attention_type="conv", + n_head=8, + kernel_size=(3, 3, 3), + stride=(1, 1, 1), + padding=(1, 1, 1), + stride_qk=(1, 1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + shuffle_in_window=False, + with_timer=False, + ): + if num_patch is None: + num_patch = [4, 4, 2] + if num_wind is None: + num_wind = [4, 4, 4] + if patch_size is None: + patch_size = [8, 8, 2] + if window_size is None: + window_size = [32, 32, 4] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + D=D, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=with_timer, + ) + + self.attention_type = attention_type + self.window_size = window_size + self.patch_size = patch_size + self.num_wind = num_wind + self.num_patch = num_patch + self.shuffle_in_window = shuffle_in_window + + self.set_and_check_wind() + self.set_and_check_patch() + + self.validate_window_patch() + + if len(self.window_size) == 2: + self.window_size.append(1) + if len(self.num_wind) == 2: + self.num_wind.append(1) + + if len(self.patch_size) == 2: + self.patch_size.append(1) + if len(self.num_patch) == 2: + self.num_patch.append(1) + + num_pixel_patch = self.patch_size[0] * self.patch_size[1] * self.patch_size[2] + assert self.C_out * num_pixel_patch % self.n_head == 0, ( + f"Number of pixels in a window {self.C_out * num_pixel_patch} should be divisible by number of heads {self.n_head}" + ) + + print( + f"{Fore.GREEN}--> Spatial, global, H {H}, W {W}, D {D}, win size {self.window_size}, patch size {self.patch_size}{Style.RESET_ALL}, num_wind {self.num_wind}, num_patch {self.num_patch}{Style.RESET_ALL}" + ) + + if attention_type == "conv": + # key, query, value projections convolution + # Wk, Wq, Wv + if self.D > 1: + self.key = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.query = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.value = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + else: + self.key = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.query = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.value = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + elif attention_type == "lin": + self.key = LinearGrid3DExt(C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True) + self.query = LinearGrid3DExt( + C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True + ) + self.value = LinearGrid3DExt( + C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True + ) + else: + raise NotImplementedError(f"Attention type not implemented: {attention_type}") + + if self.att_with_relative_position_bias: + self.define_relative_position_bias_table_3D( + num_win_h=self.num_wind[0], num_win_w=self.num_wind[1], num_win_d=self.num_wind[2] + ) + self.define_relative_position_index_3D( + num_win_h=self.num_wind[0], num_win_w=self.num_wind[1], num_win_d=self.num_wind[2] + ) + + def attention(self, k, q, v): + ( + B, + num_patch_d_per_win, + num_patch_h_per_win, + num_patch_w_per_win, + num_win_d, + num_win_h, + num_win_w, + pd, + ph, + pw, + C, + ) = k.shape + pd_v, ph_v, pw_v, _ = v.shape[-4:] + + # format the window + hc = (C * pd * ph * pw) // self.n_head + hc_v = (C * pd_v * ph_v * pw_v) // self.n_head + + tm = start_timer(enable=self.with_timer) + # k, q, v will be [B, num_patch_d_per_win*num_patch_h_per_win*num_patch_w_per_win, self.n_head, num_win_d*num_win_h*num_win_w, hc] + k = k.reshape( + ( + B, + num_patch_d_per_win * num_patch_h_per_win * num_patch_w_per_win, + num_win_d * num_win_h * num_win_w, + self.n_head, + hc, + ) + ).transpose(2, 3) + q = q.reshape( + ( + B, + num_patch_d_per_win * num_patch_h_per_win * num_patch_w_per_win, + num_win_d * num_win_h * num_win_w, + self.n_head, + hc, + ) + ).transpose(2, 3) + v = v.reshape( + ( + B, + num_patch_d_per_win * num_patch_h_per_win * num_patch_w_per_win, + num_win_d * num_win_h * num_win_w, + self.n_head, + hc_v, + ) + ).transpose(2, 3) + end_timer(enable=self.with_timer, t=tm, msg="k, q, v - reshape") + + if self.shuffle_in_window: + tm = start_timer(enable=self.with_timer) + + num_win = num_win_d * num_win_h * num_win_w + num_patches_per_win = num_patch_d_per_win * num_patch_h_per_win * num_patch_w_per_win + + # random permute within a window + patch_indexes = torch.zeros([num_win, num_patches_per_win], dtype=torch.long) + for w in range(num_win): + patch_indexes[w, :] = torch.randperm(num_patches_per_win) + + reverse_patch_indexes = num_patches_per_win - 1 - patch_indexes + reverse_patch_indexes = torch.flip(reverse_patch_indexes, dims=(1,)) + + k_shuffled = torch.clone(k) + q_shuffled = torch.clone(q) + v_shuffled = torch.clone(v) + + for w in range(num_win): + k_shuffled[:, :, :, w] = k[:, patch_indexes[w, :], :, w] + q_shuffled[:, :, :, w] = q[:, patch_indexes[w, :], :, w] + v_shuffled[:, :, :, w] = v[:, patch_indexes[w, :], :, w] + + k = k_shuffled + q = q_shuffled + v = v_shuffled + + end_timer(enable=self.with_timer, t=tm, msg="shuffle_in_window") + + tm = start_timer(enable=self.with_timer) + # [B, T, num_patches, num_heads, num_windows, hc] x [B, T, num_patches, num_heads, hc, num_windows] -> (B, T, num_patches, num_heads, num_windows, num_windows) + if self.cosine_att: + att = cosine_attention(q, k) + else: + if self.normalize_Q_K: + q, k = normalize_qk(q, k) + + att = q @ k.transpose(-2, -1) * torch.sqrt(torch.tensor(1.0 / hc)) + end_timer(enable=self.with_timer, t=tm, msg="att") + + tm = start_timer(enable=self.with_timer) + att = F.softmax(att, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + if self.att_with_relative_position_bias: + relative_position_bias = self.get_relative_position_bias_3D( + num_win_h, num_win_w, num_win_d + ) + att = att + relative_position_bias + end_timer(enable=self.with_timer, t=tm, msg="relative_position_bias") + + tm = start_timer(enable=self.with_timer) + att = self.attn_drop(att) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + # (B, num_patches, num_heads, num_windows, num_windows) * (B, num_patches, num_heads, num_windows, hc_v) + y = att @ v # (B, num_patches, num_heads, num_windows, hc_v) + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + tm = start_timer(enable=self.with_timer) + y = y.transpose(3, 4) # (B, num_patches, num_windows, num_heads, hc_v) + end_timer(enable=self.with_timer, t=tm, msg="y.transpose") + + tm = start_timer(enable=self.with_timer) + if self.shuffle_in_window: + y_restored = torch.clone(y) + for w in range(num_win_d * num_win_h * num_win_w): + y_restored[:, :, w] = y[:, reverse_patch_indexes[w, :], w] + + y = torch.reshape( + y_restored, + ( + B, + num_patch_d_per_win, + num_patch_h_per_win, + num_patch_w_per_win, + num_win_d, + num_win_h, + num_win_w, + pd_v, + ph_v, + pw_v, + C, + ), + ) + else: + y = torch.reshape( + y, + ( + B, + num_patch_d_per_win, + num_patch_h_per_win, + num_patch_w_per_win, + num_win_d, + num_win_h, + num_win_w, + pd_v, + ph_v, + pw_v, + C, + ), + ) + end_timer(enable=self.with_timer, t=tm, msg="y reshape") + + tm = start_timer(enable=self.with_timer) + y = self.grid2im(y) + end_timer(enable=self.with_timer, t=tm, msg="grid2im") + + return y + + def forward(self, x): + _B, C, _D, _H, _W = x.size() + + assert C == self.C_in, ( + f"Input channel {C} does not match expected input channel {self.C_in}" + ) + + if self.attention_type == "conv": + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + tm = start_timer(enable=self.with_timer) + k = self.im2grid(k) + q = self.im2grid(q) + v = self.im2grid(v) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + else: + tm = start_timer(enable=self.with_timer) + x = self.im2grid(x) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + y = self.attention(k, q, v) + + tm = start_timer(enable=self.with_timer) + y = self.output_proj(y) + end_timer(enable=self.with_timer, t=tm, msg="output_proj") + + return y + + def im2grid(self, x): + """Reshape the input into windows of local areas.""" + _B, _C, D, H, W = x.shape + + if self.attention_type == "conv" and self.stride_qk[0] > 1: + wind_view = rearrange( + x, + "b c (num_win_d num_patch_d patch_size_d) (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w) -> b num_patch_d num_patch_h num_patch_w num_win_d num_win_h num_win_w patch_size_d patch_size_h patch_size_w c", + num_win_h=self.num_wind[0], + num_patch_h=self.num_patch[0], + patch_size_h=H // (self.num_wind[0] * self.num_patch[0]), + num_win_w=self.num_wind[1], + num_patch_w=self.num_patch[1], + patch_size_w=W // (self.num_wind[1] * self.num_patch[1]), + num_win_d=self.num_wind[2], + num_patch_d=self.num_patch[2], + patch_size_d=D // (self.num_wind[2] * self.num_patch[2]), + ) + else: + wind_view = rearrange( + x, + "b c (num_win_d num_patch_d patch_size_d) (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w) -> b num_patch_d num_patch_h num_patch_w num_win_d num_win_h num_win_w patch_size_d patch_size_h patch_size_w c", + num_win_h=self.num_wind[0], + num_patch_h=H // (self.num_wind[0] * self.patch_size[0]), + patch_size_h=self.patch_size[0], + num_win_w=self.num_wind[1], + num_patch_w=W // (self.num_wind[1] * self.patch_size[1]), + patch_size_w=self.patch_size[1], + num_win_d=self.num_wind[2], + num_patch_d=D // (self.num_wind[2] * self.patch_size[2]), + patch_size_d=self.patch_size[2], + ) + + return wind_view + + def grid2im(self, x): + """Reshape the windows back into the complete image.""" + ( + _b, + num_patch_d, + num_patch_h, + num_patch_w, + num_win_d, + num_win_h, + num_win_w, + pd, + ph, + pw, + _c, + ) = x.shape + + im_view = rearrange( + x, + "b num_patch_d num_patch_h num_patch_w num_win_d num_win_h num_win_w patch_size_d patch_size_h patch_size_w c -> b c (num_win_d num_patch_d patch_size_d) (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w)", + num_win_h=num_win_h, + num_patch_h=num_patch_h, + patch_size_h=ph, + num_win_w=num_win_w, + num_patch_w=num_patch_w, + patch_size_w=pw, + num_win_d=num_win_d, + num_patch_d=num_patch_d, + patch_size_d=pd, + ) + + return im_view + + def get_dimension_for_linear_mixer(self): + return self.C_out * self.patch_size[0] * self.patch_size[1] * self.patch_size[2] + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/attention/local_3d_attention_modules.py b/src/snraware/components/model/attention/local_3d_attention_modules.py new file mode 100644 index 0000000..0125759 --- /dev/null +++ b/src/snraware/components/model/attention/local_3d_attention_modules.py @@ -0,0 +1,401 @@ +"""Local 3D attention modules computes attentions among all patches within a windows.""" + +from einops import rearrange + +from .attention_modules import * + + +# ------------------------------------------------------------------------------------------------- +class Local3DAttention(CnnAttentionBase): + """Multi-head attention among all patches in a window.""" + + def __init__( + self, + C_in, + C_out=16, + H=256, + W=256, + D=128, + window_size=None, + patch_size=None, + num_wind=None, + num_patch=None, + attention_type="conv", + n_head=8, + kernel_size=(3, 3, 3), + stride=(1, 1, 1), + padding=(1, 1, 1), + stride_qk=(1, 1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + with_timer=False, + ): + """ + @args: + - window_size (int): number of pixels in a window + - patch_size(int): number of pixels in a patch. + """ + if num_patch is None: + num_patch = [4, 4, 4] + if num_wind is None: + num_wind = [8, 8, 8] + if patch_size is None: + patch_size = [4, 4, 4] + if window_size is None: + window_size = [32, 32, 8] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + D=D, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=with_timer, + ) + + self.attention_type = attention_type + self.window_size = window_size + self.patch_size = patch_size + self.num_wind = num_wind + self.num_patch = num_patch + + self.set_and_check_wind() + self.set_and_check_patch() + + if len(self.window_size) == 2: + self.window_size.append(1) + if len(self.num_wind) == 2: + self.num_wind.append(1) + + if len(self.patch_size) == 2: + self.patch_size.append(1) + if len(self.num_patch) == 2: + self.num_patch.append(1) + + self.validate_window_patch() + + num_pixel_patch = self.patch_size[0] * self.patch_size[1] * self.patch_size[2] + assert self.C_out * num_pixel_patch % self.n_head == 0, ( + f"Number of pixels in a patch {self.C_out * num_pixel_patch} should be divisible by number of heads {self.n_head}" + ) + + print( + f"{Fore.YELLOW}--> Spatial, local, H {H}, W {W}, D {D}, win size {self.window_size}, patch size {self.patch_size}{Style.RESET_ALL}, num_wind {self.num_wind}, num_patch {self.num_patch}" + ) + + if attention_type == "conv": + # key, query, value projections convolution + # Wk, Wq, Wv + if self.D > 1: + self.key = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.query = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.value = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + else: + self.key = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.query = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.value = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + elif attention_type == "lin": + # linear projections + + self.key = LinearGrid3DExt(C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True) + self.query = LinearGrid3DExt( + C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True + ) + self.value = LinearGrid3DExt( + C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True + ) + else: + raise NotImplementedError(f"Attention type not implemented: {attention_type}") + + if self.att_with_relative_position_bias: + self.define_relative_position_bias_table_3D( + num_win_h=self.num_patch[0], + num_win_w=self.num_patch[1], + num_win_d=self.num_patch[2], + ) + self.define_relative_position_index_3D( + num_win_h=self.num_patch[0], + num_win_w=self.num_patch[1], + num_win_d=self.num_patch[2], + ) + + def attention(self, k, q, v): + ( + B, + num_win_d, + num_win_h, + num_win_w, + num_patch_d_per_win, + num_patch_h_per_win, + num_patch_w_per_win, + pd, + ph, + pw, + C, + ) = k.shape + *_, pd_v, ph_v, pw_v, _ = v.shape + + assert self.num_patch[0] == num_patch_h_per_win + assert self.num_patch[1] == num_patch_w_per_win + assert self.num_patch[2] == num_patch_d_per_win + + # format the window + # use torch.sym_... to support onnx conversion + hc = (C * pd * ph * pw) // self.n_head + hc_v = (C * pd_v * ph_v * pw_v) // self.n_head + + # k, q, v will be [B, T, num_win_h*num_win_w, self.n_head, num_patch_h_per_win*num_patch_w_per_win, hc] + tm = start_timer(enable=self.with_timer) + k = k.reshape( + ( + B, + num_win_d * num_win_h * num_win_w, + num_patch_d_per_win * num_patch_h_per_win * num_patch_w_per_win, + self.n_head, + hc, + ) + ).transpose(2, 3) + q = q.reshape( + ( + B, + num_win_d * num_win_h * num_win_w, + num_patch_d_per_win * num_patch_h_per_win * num_patch_w_per_win, + self.n_head, + hc, + ) + ).transpose(2, 3) + v = v.reshape( + ( + B, + num_win_d * num_win_h * num_win_w, + num_patch_d_per_win * num_patch_h_per_win * num_patch_w_per_win, + self.n_head, + hc_v, + ) + ).transpose(2, 3) + end_timer(enable=self.with_timer, t=tm, msg="k, q, v - reshape") + + tm = start_timer(enable=self.with_timer) + # [B, num_windows, num_heads, num_patches, hc] x [B, num_windows, num_heads, hc, num_patches] -> (B, num_windows, num_heads, num_patches, num_patches) + if self.cosine_att: + att = cosine_attention(q, k) + else: + if self.normalize_Q_K: + q, k = normalize_qk(q, k) + + att = q @ k.transpose(-2, -1) * torch.sqrt(torch.tensor(1.0 / hc)) + end_timer(enable=self.with_timer, t=tm, msg="att") + + tm = start_timer(enable=self.with_timer) + att = F.softmax(att, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + if self.att_with_relative_position_bias: + relative_position_bias = self.get_relative_position_bias_3D( + num_patch_h_per_win, num_patch_w_per_win, num_patch_d_per_win + ) + att = att + relative_position_bias + end_timer(enable=self.with_timer, t=tm, msg="att_with_relative_position_bias") + + tm = start_timer(enable=self.with_timer) + att = self.attn_drop(att) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + # (B, num_windows, num_heads, num_patches, num_patches) * (B, num_windows, num_heads, num_patches, hc) + y = att @ v # (B, num_windows, num_heads, num_patches, hc) + y = y.transpose(2, 3) # (B, num_windows, num_patches, num_heads, hc) + y = torch.reshape( + y, + ( + B, + num_win_d, + num_win_h, + num_win_w, + num_patch_d_per_win, + num_patch_h_per_win, + num_patch_w_per_win, + pd_v, + ph_v, + pw_v, + C, + ), + ) + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + tm = start_timer(enable=self.with_timer) + y = self.grid2im(y) + end_timer(enable=self.with_timer, t=tm, msg="grid2im") + + return y + + def forward(self, x): + _B, C, _D, _H, _W = x.size() + + assert C == self.C_in, ( + f"Input channel {C} does not match expected input channel {self.C_in}" + ) + + if self.attention_type == "conv": + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + tm = start_timer(enable=self.with_timer) + k = self.im2grid(k) + q = self.im2grid(q) + v = self.im2grid(v) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + else: + tm = start_timer(enable=self.with_timer) + x = self.im2grid(x) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + y = self.attention(k, q, v) + + tm = start_timer(enable=self.with_timer) + y = self.output_proj(y) + end_timer(enable=self.with_timer, t=tm, msg="output_proj") + + return y + + def im2grid(self, x): + """Reshape the input into windows of local areas.""" + _B, _C, D, H, W = x.shape + + if self.attention_type == "conv" and self.stride_qk[0] > 1: + wind_view = rearrange( + x, + "b c (num_win_d num_patch_d patch_size_d) (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w) -> b num_win_d num_win_h num_win_w num_patch_d num_patch_h num_patch_w patch_size_d patch_size_h patch_size_w c", + num_win_h=self.num_wind[0], + num_patch_h=self.num_patch[0], + patch_size_h=H // (self.num_patch[0] * self.num_wind[0]), + num_win_w=self.num_wind[1], + num_patch_w=self.num_patch[1], + patch_size_w=W // (self.num_patch[1] * self.num_wind[1]), + num_win_d=self.num_wind[2], + num_patch_d=self.num_patch[2], + patch_size_d=D // (self.num_patch[2] * self.num_wind[2]), + ) + else: + wind_view = rearrange( + x, + "b c (num_win_d num_patch_d patch_size_d) (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w) -> b num_win_d num_win_h num_win_w num_patch_d num_patch_h num_patch_w patch_size_d patch_size_h patch_size_w c", + num_win_h=H // (self.num_patch[0] * self.patch_size[0]), + num_patch_h=self.num_patch[0], + patch_size_h=self.patch_size[0], + num_win_w=W // (self.num_patch[1] * self.patch_size[1]), + num_patch_w=self.num_patch[1], + patch_size_w=self.patch_size[1], + num_win_d=D // (self.num_patch[2] * self.patch_size[2]), + num_patch_d=self.num_patch[2], + patch_size_d=self.patch_size[2], + ) + + return wind_view + + def grid2im(self, x): + """Reshape the windows back into the complete image.""" + ( + _B, + num_win_d, + num_win_h, + num_win_w, + num_patch_d, + num_patch_h, + num_patch_w, + pd, + ph, + pw, + _c, + ) = x.shape + + im_view = rearrange( + x, + "b num_win_d num_win_h num_win_w num_patch_d num_patch_h num_patch_w patch_size_d patch_size_h patch_size_w c -> b c (num_win_d num_patch_d patch_size_d) (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w)", + num_win_h=num_win_h, + num_patch_h=num_patch_h, + patch_size_h=ph, + num_win_w=num_win_w, + num_patch_w=num_patch_w, + patch_size_w=pw, + num_win_d=num_win_d, + num_patch_d=num_patch_d, + patch_size_d=pd, + ) + return im_view + + def get_dimension_for_linear_mixer(self): + return self.C_out * self.patch_size[0] * self.patch_size[1] * self.patch_size[2] + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/attention/spatial_global_attention_modules.py b/src/snraware/components/model/attention/spatial_global_attention_modules.py new file mode 100644 index 0000000..87d776a --- /dev/null +++ b/src/snraware/components/model/attention/spatial_global_attention_modules.py @@ -0,0 +1,397 @@ +"""Spatial global attention modules.""" + +from einops import rearrange + +from .attention_modules import * + + +# ------------------------------------------------------------------------------------------------- +class SpatialGlobalAttention(CnnAttentionBase): + """ + Multi-head attention model for the global patching. + Number of pixels in a window are [window_size, window_size]. + Number of pixels in a patch are [patch_size, patch_size]. + The tensor is split into windows of size [window_size, window_size] for every [H, W] frame. + Every window is split into patches of size [patch_size, patch_size]. + Attention is computed between all corresponding patches among all windows. + """ + + def __init__( + self, + C_in, + C_out=16, + H=128, + W=128, + window_size=None, + patch_size=None, + num_wind=None, + num_patch=None, + attention_type="conv", + n_head=8, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + stride_qk=(1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + shuffle_in_window=False, + with_timer=False, + ): + """ + @args: + - shuffle_in_window (bool): If True, shuffle the order of patches in all windows; this will avoid the same set of patches are always inputted into the attention, but introducing randomness. + """ + if num_patch is None: + num_patch = [4, 4] + if num_wind is None: + num_wind = [4, 4] + if patch_size is None: + patch_size = [8, 8] + if window_size is None: + window_size = [32, 32] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=with_timer, + ) + + self.attention_type = attention_type + self.window_size = window_size + self.patch_size = patch_size + self.num_wind = num_wind + self.num_patch = num_patch + self.shuffle_in_window = shuffle_in_window + + self.set_and_check_wind() + self.set_and_check_patch() + + self.validate_window_patch() + + assert self.C_out * self.patch_size[0] * self.patch_size[1] % self.n_head == 0, ( + f"Number of pixels in a window {self.C_out * self.patch_size[0] * self.patch_size[1]} should be divisible by number of heads {self.n_head}" + ) + + print( + f"{Fore.GREEN}--> Spatial, global, win size {self.window_size}, patch size {self.patch_size}{Style.RESET_ALL}" + ) + + if attention_type == "conv": + # key, query, value projections convolution + # Wk, Wq, Wv + self.key = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=False, + ) + self.query = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=False, + ) + self.value = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=False, + ) + elif attention_type == "lin": + # linear projections + num_pixel_patch = self.patch_size[0] * self.patch_size[1] + self.key = LinearGridExt(C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True) + self.query = LinearGridExt(C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True) + self.value = LinearGridExt(C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True) + else: + raise NotImplementedError(f"Attention type not implemented: {attention_type}") + + if self.att_with_relative_position_bias: + self.define_relative_position_bias_table( + num_win_h=self.num_wind[0], num_win_w=self.num_wind[1] + ) + self.define_relative_position_index( + num_win_h=self.num_wind[0], num_win_w=self.num_wind[1] + ) + + def attention(self, k, q, v): + B, T, num_patch_h_per_win, num_patch_w_per_win, num_win_h, num_win_w, ph, pw, C = k.shape + ph_v, pw_v, _ = v.shape[-3:] + + # format the window + hc = (C * ph * pw) // self.n_head + hc_v = (C * ph_v * pw_v) // self.n_head + + tm = start_timer(enable=self.with_timer) + # k, q, v will be [B, T, num_patch_h_per_win*num_patch_w_per_win, self.n_head, num_win_h*num_win_w, hc] + k = k.reshape( + ( + B, + T, + num_patch_h_per_win * num_patch_w_per_win, + num_win_h * num_win_w, + self.n_head, + hc, + ) + ).transpose(3, 4) + q = q.reshape( + ( + B, + T, + num_patch_h_per_win * num_patch_w_per_win, + num_win_h * num_win_w, + self.n_head, + hc, + ) + ).transpose(3, 4) + v = v.reshape( + ( + B, + T, + num_patch_h_per_win * num_patch_w_per_win, + num_win_h * num_win_w, + self.n_head, + hc_v, + ) + ).transpose(3, 4) + end_timer(enable=self.with_timer, t=tm, msg="k, q, v - reshape") + + if self.shuffle_in_window: + tm = start_timer(enable=self.with_timer) + + # random permute within a window + patch_indexes = torch.zeros( + [num_win_h * num_win_w, num_patch_h_per_win * num_patch_w_per_win], + dtype=torch.long, + ) + for w in range(num_win_h * num_win_w): + patch_indexes[w, :] = torch.randperm(num_patch_h_per_win * num_patch_w_per_win) + + reverse_patch_indexes = num_patch_h_per_win * num_patch_w_per_win - 1 - patch_indexes + reverse_patch_indexes = torch.flip(reverse_patch_indexes, dims=(1,)) + + k_shuffled = torch.clone(k) + q_shuffled = torch.clone(q) + v_shuffled = torch.clone(v) + + for w in range(num_win_h * num_win_w): + k_shuffled[:, :, :, :, w] = k[:, :, patch_indexes[w, :], :, w] + q_shuffled[:, :, :, :, w] = q[:, :, patch_indexes[w, :], :, w] + v_shuffled[:, :, :, :, w] = v[:, :, patch_indexes[w, :], :, w] + + k = k_shuffled + q = q_shuffled + v = v_shuffled + + end_timer(enable=self.with_timer, t=tm, msg="shuffle_in_window") + + tm = start_timer(enable=self.with_timer) + # [B, T, num_patches, num_heads, num_windows, hc] x [B, T, num_patches, num_heads, hc, num_windows] -> (B, T, num_patches, num_heads, num_windows, num_windows) + if self.cosine_att: + att = cosine_attention(q, k) + else: + if self.normalize_Q_K: + q, k = normalize_qk(q, k) + + att = q @ k.transpose(-2, -1) * torch.sqrt(torch.tensor(1.0 / hc)) + end_timer(enable=self.with_timer, t=tm, msg="att") + + tm = start_timer(enable=self.with_timer) + att = F.softmax(att, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + if self.att_with_relative_position_bias: + relative_position_bias = self.get_relative_position_bias(num_win_h, num_win_w) + att = att + relative_position_bias + end_timer(enable=self.with_timer, t=tm, msg="relative_position_bias") + + tm = start_timer(enable=self.with_timer) + att = self.attn_drop(att) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + # (B, T, num_patches, num_heads, num_windows, num_windows) * (B, T, num_patches, num_heads, num_windows, hc_v) + y = att @ v # (B, T, num_patches, num_heads, num_windows, hc_v) + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + tm = start_timer(enable=self.with_timer) + y = y.transpose(3, 4) # (B, T, num_patches, num_windows, num_heads, hc_v) + end_timer(enable=self.with_timer, t=tm, msg="y.transpose") + + tm = start_timer(enable=self.with_timer) + if self.shuffle_in_window: + y_restored = torch.clone(y) + for w in range(num_win_h * num_win_w): + y_restored[:, :, :, w] = y[:, :, reverse_patch_indexes[w, :], w] + + y = torch.reshape( + y_restored, + ( + B, + T, + num_patch_h_per_win, + num_patch_w_per_win, + num_win_h, + num_win_w, + ph_v, + pw_v, + C, + ), + ) + else: + y = torch.reshape( + y, + ( + B, + T, + num_patch_h_per_win, + num_patch_w_per_win, + num_win_h, + num_win_w, + ph_v, + pw_v, + C, + ), + ) + end_timer(enable=self.with_timer, t=tm, msg="y reshape") + + tm = start_timer(enable=self.with_timer) + y = self.grid2im(y) + end_timer(enable=self.with_timer, t=tm, msg="grid2im") + + return y + + def forward(self, x): + _B, C, _T, _H, _W = x.size() + + assert C == self.C_in, ( + f"Input channel {C} does not match expected input channel {self.C_in}" + ) + + x = torch.permute(x, [0, 2, 1, 3, 4]) + + if self.attention_type == "conv": + tm = start_timer(enable=self.with_timer) + k = self.key(x) # (B, T, C, H_prime, W_prime) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + tm = start_timer(enable=self.with_timer) + k = self.im2grid( + k + ) # (B, T, num_patch_per_win, num_patch_per_win, num_win_h, num_win_w, Ps, Ps, C) + q = self.im2grid(q) + v = self.im2grid(v) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + else: + tm = start_timer(enable=self.with_timer) + x = self.im2grid( + x + ) # (B, T, num_patch_per_win, num_patch_per_win, num_win_h, num_win_w, Ps, Ps, C_in) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + + tm = start_timer(enable=self.with_timer) + k = self.key( + x + ) # (B, T, num_patch_per_win, num_patch_per_win, num_win_h, num_win_w, Ps, Ps, C) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + y = self.attention(k, q, v) + + y = torch.permute(y, [0, 2, 1, 3, 4]) + + tm = start_timer(enable=self.with_timer) + y = self.output_proj(y) + end_timer(enable=self.with_timer, t=tm, msg="output_proj") + + return y + + def im2grid(self, x): + """Reshape the input into windows of local areas.""" + _b, _t, _c, h, w = x.shape + + if self.attention_type == "conv" and self.stride_qk[0] > 1: + wind_view = rearrange( + x, + "b t c (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w) -> b t num_patch_h num_patch_w num_win_h num_win_w patch_size_h patch_size_w c", + num_win_h=self.num_wind[0], + num_patch_h=self.num_patch[0], + patch_size_h=h // (self.num_wind[0] * self.num_patch[0]), + num_win_w=self.num_wind[1], + num_patch_w=self.num_patch[1], + patch_size_w=w // (self.num_wind[0] * self.num_patch[1]), + ) + else: + wind_view = rearrange( + x, + "b t c (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w) -> b t num_patch_h num_patch_w num_win_h num_win_w patch_size_h patch_size_w c", + num_win_h=self.num_wind[0], + num_patch_h=h // (self.num_wind[0] * self.patch_size[0]), + patch_size_h=self.patch_size[0], + num_win_w=self.num_wind[1], + num_patch_w=w // (self.num_wind[0] * self.patch_size[1]), + patch_size_w=self.patch_size[1], + ) + + return wind_view + + def grid2im(self, x): + """Reshape the windows back into the complete image.""" + _b, _t, num_patch_h, num_patch_w, num_win_h, num_win_w, ph, pw, _c = x.shape + + # im_view = torch.permute(x, (0, 1, 8, 4, 2, 6, 5, 3, 7)) + + # im_view = rearrange(im_view, 'b t c num_win_h num_patch_h patch_size_h num_win_w num_patch_w patch_size_w -> b t c (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w)', + # num_win_h=num_win_h, num_patch_h=num_patch_h, patch_size_h=ph, + # num_win_w=num_win_w, num_patch_w=num_patch_w, patch_size_w=pw) + + im_view = rearrange( + x, + "b t num_patch_h num_patch_w num_win_h num_win_w patch_size_h patch_size_w c -> b t c (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w)", + num_win_h=num_win_h, + num_patch_h=num_patch_h, + patch_size_h=ph, + num_win_w=num_win_w, + num_patch_w=num_patch_w, + patch_size_w=pw, + ) + + # im_view = torch.permute(x, (0, 1, 4, 5, 2, 3, 6, 7, 8)) + + # im_view = rearrange(x, 'b t num_patch_h num_patch_w num_win_h num_win_w patch_size_h patch_size_w c -> b t c (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w)', + # num_win_h=num_win_h, num_patch_h=num_patch_h, patch_size_h=ph, + # num_win_w=num_win_w, num_patch_w=num_patch_w, patch_size_w=pw) + return im_view + + def get_dimension_for_linear_mixer(self): + return self.C_out * self.patch_size[0] * self.patch_size[1] + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/attention/spatial_local_attention_modules.py b/src/snraware/components/model/attention/spatial_local_attention_modules.py new file mode 100644 index 0000000..8537fc0 --- /dev/null +++ b/src/snraware/components/model/attention/spatial_local_attention_modules.py @@ -0,0 +1,317 @@ +"""Spatial local attention module.""" + +from einops import rearrange + +from .attention_modules import * + + +# ------------------------------------------------------------------------------------------------- +class SpatialLocalAttention(CnnAttentionBase): + """ + Multi-head attention model for the local patches. Every [H, W] frame is split into windows. + Every window is split into patches. Attention is computed between all patches in a window. + """ + + def __init__( + self, + C_in, + C_out=16, + H=256, + W=256, + window_size=None, + patch_size=None, + num_wind=None, + num_patch=None, + attention_type="conv", + n_head=8, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + stride_qk=(1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + with_timer=False, + ): + if num_patch is None: + num_patch = [4, 4] + if num_wind is None: + num_wind = [8, 8] + if patch_size is None: + patch_size = [8, 8] + if window_size is None: + window_size = [64, 64] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + D=1, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=with_timer, + ) + + self.attention_type = attention_type + self.window_size = window_size + self.patch_size = patch_size + self.num_wind = num_wind + self.num_patch = num_patch + + self.set_and_check_wind() + self.set_and_check_patch() + + self.validate_window_patch() + + assert self.C_out * self.patch_size[0] * self.patch_size[1] % self.n_head == 0, ( + f"Number of pixels in a patch {self.C_out * self.patch_size[0] * self.patch_size[1]} should be divisible by number of heads {self.n_head}" + ) + + print( + f"{Fore.GREEN}--> Spatial, local, win size {self.window_size}, patch size {self.patch_size}{Style.RESET_ALL}" + ) + + if attention_type == "conv": + # key, query, value projections convolution + # Wk, Wq, Wv + self.key = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=False, + ) + self.query = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=False, + ) + self.value = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=False, + ) + elif attention_type == "lin": + # linear projections + num_pixel_patch = self.patch_size[0] * self.patch_size[1] + self.key = LinearGridExt(C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True) + self.query = LinearGridExt(C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True) + self.value = LinearGridExt(C_in * num_pixel_patch, C_out * num_pixel_patch, bias=True) + else: + raise NotImplementedError(f"Attention type not implemented: {attention_type}") + + if self.att_with_relative_position_bias: + self.define_relative_position_bias_table( + num_win_h=self.num_patch[0], num_win_w=self.num_patch[1] + ) + self.define_relative_position_index( + num_win_h=self.num_patch[0], num_win_w=self.num_patch[1] + ) + + def attention(self, k, q, v): + B, T, num_win_h, num_win_w, num_patch_h_per_win, num_patch_w_per_win, ph, pw, C = k.shape + _, _, _, _, _, _, ph_v, pw_v, _ = v.shape + + assert self.num_patch[0] == num_patch_h_per_win + assert self.num_patch[1] == num_patch_w_per_win + + # format the window + # use torch.sym_... to support onnx conversion + hc = (C * ph * pw) // self.n_head + hc_v = (C * ph_v * pw_v) // self.n_head + + # k, q, v will be [B, T, num_win_h*num_win_w, self.n_head, num_patch_h_per_win*num_patch_w_per_win, hc] + tm = start_timer(enable=self.with_timer) + k = k.reshape( + ( + B, + T, + num_win_h * num_win_w, + num_patch_h_per_win * num_patch_w_per_win, + self.n_head, + hc, + ) + ).transpose(3, 4) + q = q.reshape( + ( + B, + T, + num_win_h * num_win_w, + num_patch_h_per_win * num_patch_w_per_win, + self.n_head, + hc, + ) + ).transpose(3, 4) + v = v.reshape( + ( + B, + T, + num_win_h * num_win_w, + num_patch_h_per_win * num_patch_w_per_win, + self.n_head, + hc_v, + ) + ).transpose(3, 4) + end_timer(enable=self.with_timer, t=tm, msg="k, q, v - reshape") + + tm = start_timer(enable=self.with_timer) + # [B, T, num_windows, num_heads, num_patches, hc] x [B, T, num_windows, num_heads, hc, num_patches] -> (B, T, num_windows, num_heads, num_patches, num_patches) + if self.cosine_att: + att = cosine_attention(q, k) + else: + if self.normalize_Q_K: + q, k = normalize_qk(q, k) + + att = q @ k.transpose(-2, -1) * torch.sqrt(torch.tensor(1.0 / hc)) + end_timer(enable=self.with_timer, t=tm, msg="att") + + tm = start_timer(enable=self.with_timer) + att = F.softmax(att, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + if self.att_with_relative_position_bias: + relative_position_bias = self.get_relative_position_bias( + num_patch_h_per_win, num_patch_w_per_win + ) + att = att + relative_position_bias + end_timer(enable=self.with_timer, t=tm, msg="att_with_relative_position_bias") + + tm = start_timer(enable=self.with_timer) + att = self.attn_drop(att) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + # (B, T, num_windows, num_heads, num_patches, num_patches) * (B, T, num_windows, num_heads, num_patches, hc) + y = att @ v # (B, T, num_windows, num_heads, num_patches, hc) + y = y.transpose(3, 4) # (B, T, num_windows, num_patches, num_heads, hc) + y = torch.reshape( + y, + (B, T, num_win_h, num_win_w, num_patch_h_per_win, num_patch_w_per_win, ph_v, pw_v, C), + ) + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + tm = start_timer(enable=self.with_timer) + y = self.grid2im(y) + end_timer(enable=self.with_timer, t=tm, msg="grid2im") + + return y + + def forward(self, x): + _B, C, _T, _H, _W = x.size() + + assert C == self.C_in, ( + f"Input channel {C} does not match expected input channel {self.C_in}" + ) + + x = torch.permute(x, [0, 2, 1, 3, 4]) + + if self.attention_type == "conv": + tm = start_timer(enable=self.with_timer) + k = self.key(x) # (B, T, C, H_prime, W_prime) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + tm = start_timer(enable=self.with_timer) + k = self.im2grid( + k + ) # (B, T, num_win_h, num_win_w, num_patch_per_win, num_patch_per_win, Ps, Ps, C) + q = self.im2grid(q) + v = self.im2grid(v) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + else: + tm = start_timer(enable=self.with_timer) + x = self.im2grid( + x + ) # (B, T, num_win_h, num_win_w, num_patch_per_win, num_patch_per_win, Ps, Ps, C_in) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + + tm = start_timer(enable=self.with_timer) + k = self.key( + x + ) # (B, T, num_win_h, num_win_w, num_patch_per_win, num_patch_per_win, Ps, Ps, C) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + y = self.attention(k, q, v) + + y = torch.permute(y, [0, 2, 1, 3, 4]) + + tm = start_timer(enable=self.with_timer) + y = self.output_proj(y) + end_timer(enable=self.with_timer, t=tm, msg="output_proj") + + return y + + def im2grid(self, x): + """Reshape the input into windows of local areas.""" + _, _, _, H, W = x.shape + + if self.attention_type == "conv" and self.stride_qk[0] > 1: + wind_view = rearrange( + x, + "b t c (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w) -> b t num_win_h num_win_w num_patch_h num_patch_w patch_size_h patch_size_w c", + num_win_h=self.num_wind[0], + num_patch_h=self.num_patch[0], + patch_size_h=H // (self.num_patch[0] * self.num_wind[0]), + num_win_w=self.num_wind[1], + num_patch_w=self.num_patch[1], + patch_size_w=W // (self.num_patch[1] * self.num_wind[1]), + ) + else: + wind_view = rearrange( + x, + "b t c (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w) -> b t num_win_h num_win_w num_patch_h num_patch_w patch_size_h patch_size_w c", + num_win_h=H // (self.num_patch[0] * self.patch_size[0]), + num_patch_h=self.num_patch[0], + patch_size_h=self.patch_size[0], + num_win_w=W // (self.num_patch[1] * self.patch_size[1]), + num_patch_w=self.num_patch[1], + patch_size_w=self.patch_size[1], + ) + + return wind_view + + def grid2im(self, x): + """Reshape the windows back into the complete image.""" + _b, _t, num_win_h, num_win_w, num_patch_h, num_patch_w, ph, pw, _c = x.shape + + im_view = rearrange( + x, + "b t num_win_h num_win_w num_patch_h num_patch_w patch_size_h patch_size_w c -> b t c (num_win_h num_patch_h patch_size_h) (num_win_w num_patch_w patch_size_w)", + num_win_h=num_win_h, + num_patch_h=num_patch_h, + patch_size_h=ph, + num_win_w=num_win_w, + num_patch_w=num_patch_w, + patch_size_w=pw, + ) + return im_view + + def get_dimension_for_linear_mixer(self): + return self.C_out * self.patch_size[0] * self.patch_size[1] + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/attention/spatial_vit_attention_modules.py b/src/snraware/components/model/attention/spatial_vit_attention_modules.py new file mode 100644 index 0000000..ac29a5e --- /dev/null +++ b/src/snraware/components/model/attention/spatial_vit_attention_modules.py @@ -0,0 +1,257 @@ +"""Implement the ViT style spatial attention on every [H, W] frame.""" + +from einops import rearrange + +from .attention_modules import * + + +# ------------------------------------------------------------------------------------------------- +class SpatialViTAttention(CnnAttentionBase): + """ + Multi-head attention model for ViT style. An image is spatially splitted into windows. + Attention matrix is computed between all windows. Number of pixels in a window are [window_size, window_size]. + """ + + def __init__( + self, + C_in, + C_out=16, + H=128, + W=128, + window_size=None, + num_wind=None, + attention_type="conv", + n_head=8, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + stride_qk=(1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + use_einsum=False, + with_timer=False, + ): + """ + Input tensor is split into windows of size [window_size, window_size] for every [H, W] frame. + Attention is computed between all windows. + """ + if window_size is None: + window_size = [32, 32] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=with_timer, + ) + + self.C_out = C_out + self.attention_type = attention_type + self.window_size = window_size + self.num_wind = num_wind + self.use_einsum = use_einsum + + self.set_and_check_wind() + + if attention_type == "conv": + # key, query, value projections convolution + # Wk, Wq, Wv + self.key = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=False, + ) + self.query = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=False, + ) + self.value = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=False, + ) + elif attention_type == "lin": + # linear projections + num_pixel_win = self.window_size[0] * self.window_size[1] + self.key = LinearGridExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + self.query = LinearGridExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + self.value = LinearGridExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + else: + raise NotImplementedError(f"Attention type not implemented: {attention_type}") + + if self.att_with_relative_position_bias: + self.define_relative_position_bias_table( + num_win_h=self.num_wind[0], num_win_w=self.num_wind[1] + ) + self.define_relative_position_index( + num_win_h=self.num_wind[0], num_win_w=self.num_wind[1] + ) + + def attention(self, k, q, v): + B, T, num_win_h, num_win_w, wh, ww, C = k.shape + _, _, _, _, wh_v, ww_v, _ = v.shape + + assert self.num_wind[0] == num_win_h + assert self.num_wind[1] == num_win_w + + # format the window + hc = torch.div(C * wh * ww, self.n_head, rounding_mode="floor") + hc_v = torch.div(C * wh_v * ww_v, self.n_head, rounding_mode="floor") + + tm = start_timer(enable=self.with_timer) + q = q.reshape((B, T, num_win_h * num_win_w, self.n_head, hc)).transpose(2, 3) + k = k.reshape((B, T, num_win_h * num_win_w, self.n_head, hc)).transpose(2, 3) + v = v.reshape((B, T, num_win_h * num_win_w, self.n_head, hc_v)).transpose(2, 3) + end_timer(enable=self.with_timer, t=tm, msg="k, q, v - reshape") + + tm = start_timer(enable=self.with_timer) + # [B, T, num_heads, num_windows, hc] x [B, T, num_heads, hc, num_windows] -> (B, T, num_heads, num_windows, num_windows) + if self.cosine_att: + att = cosine_attention(q, k) + else: + if self.normalize_Q_K: + q, k = normalize_qk(q, k) + + att = q @ k.transpose(-2, -1) * torch.sqrt(1.0 / hc) + + end_timer(enable=self.with_timer, t=tm, msg="att") + + tm = start_timer(enable=self.with_timer) + att = F.softmax(att, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + # add the relative positional bias + if self.att_with_relative_position_bias: + relative_position_bias = self.get_relative_position_bias(num_win_h, num_win_w) + att = att + relative_position_bias + end_timer(enable=self.with_timer, t=tm, msg="att_with_relative_position_bias") + + tm = start_timer(enable=self.with_timer) + att = self.attn_drop(att) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + # (B, T, num_heads, num_windows, num_windows) * (B, T, num_heads, num_windows, hc) + y = att @ v + y = y.transpose(2, 3) # (B, T, num_windows, num_heads, hc) + y = torch.reshape(y, (B, T, num_win_h, num_win_w, wh_v, ww_v, C)) + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + tm = start_timer(enable=self.with_timer) + y = self.grid2im(y) + end_timer(enable=self.with_timer, t=tm, msg="grid2im") + + return y + + def forward(self, x): + _B, C, _T, H, W = x.size() + + if self.attention_type == "lin" and self.att_with_relative_position_bias: + assert H == self.H and W == self.W, ( + "For lin attention_type with relative position bias, input H and W have to be the same as the class declaration." + ) + + assert C == self.C_in, ( + f"Input channel {C} does not match expected input channel {self.C_in}" + ) + assert H % self.num_wind[0] == 0, ( + f"Height {H} should be divisible by window num {self.num_wind[0]}" + ) + assert W % self.num_wind[1] == 0, ( + f"Width {W} should be divisible by window num {self.num_wind[1]}" + ) + + x = torch.permute(x, [0, 2, 1, 3, 4]) + + if self.attention_type == "conv": + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + tm = start_timer(enable=self.with_timer) + k = self.im2grid(k) + q = self.im2grid(q) + v = self.im2grid(v) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + else: + x = self.im2grid(x) + k = self.key(x) + q = self.query(x) + v = self.value(x) + + y = self.attention(k, q, v) + + y = torch.permute(y, [0, 2, 1, 3, 4]) + + tm = start_timer(enable=self.with_timer) + y = self.output_proj(y) + end_timer(enable=self.with_timer, t=tm, msg="output_proj") + + return y + + def im2grid(self, x): + """Reshape the input into windows of local areas.""" + _b, _t, _c, h, w = x.shape + + wind_view = rearrange( + x, + "b t c (num_win_h win_size_h) (num_win_w win_size_w) -> b t num_win_h num_win_w win_size_h win_size_w c", + num_win_h=self.num_wind[0], + win_size_h=h // self.num_wind[0], + num_win_w=self.num_wind[1], + win_size_w=w // self.num_wind[1], + ) + + return wind_view + + def grid2im(self, x): + """Reshape the windows back into the complete image.""" + # b, t, num_win_h, num_win_w, c, wh, ww = x.shape + + _b, _t, num_win_h, num_win_w, wh, ww, _c = x.shape + + im_view = rearrange( + x, + "b t num_win_h num_win_w win_size_h win_size_w c -> b t c (num_win_h win_size_h) (num_win_w win_size_w)", + num_win_h=num_win_h, + win_size_h=wh, + num_win_w=num_win_w, + win_size_w=ww, + ) + + return im_view + + def get_dimension_for_linear_mixer(self): + return self.C_out * self.window_size[0] * self.window_size[1] + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/attention/swin_3d_attention_modules.py b/src/snraware/components/model/attention/swin_3d_attention_modules.py new file mode 100644 index 0000000..1667e58 --- /dev/null +++ b/src/snraware/components/model/attention/swin_3d_attention_modules.py @@ -0,0 +1,504 @@ +"""Implement the SWIN 3D attention.""" + +from functools import reduce +from operator import mul + +import numpy as np +from einops import rearrange + +from .attention_modules import * + +# ------------------------------------------------------------------------------------------------- +# SWIN attention includes two steps, one is the normal ViT type attention and another is the shifted window attention + + +def window_partition(x, window_size): + """ + Args: + x: (B, D, H, W, C) + window_size (tuple[int]): window size. + + Returns: + windows: (B*num_windows, window_size*window_size, C) + """ + B, D, H, W, C = x.shape + x = x.view( + B, + D // window_size[0], + window_size[0], + H // window_size[1], + window_size[1], + W // window_size[2], + window_size[2], + C, + ) + windows = x.permute(0, 1, 3, 5, 2, 4, 6, 7).contiguous().view(-1, reduce(mul, window_size), C) + return windows + + +def window_partition_image(x, window_size): + """ + Args: + x: (B, H, W, C) + window_size (int): window size. + + Returns: + windows: (num_windows*B, window_size, window_size, C) + """ + B, H, W, C = x.shape + x = x.view(B, H // window_size[1], window_size[1], W // window_size[2], window_size[2], C) + windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size[1], window_size[2], C) + return windows + + +def window_reverse(windows, window_size, B, D, H, W): + """ + Args: + windows: (B*num_windows, window_size, window_size, C) + window_size (tuple[int]): Window size + H (int): Height of image + W (int): Width of image. + + Returns: + x: (B, D, H, W, C) + """ + x = windows.view( + B, + D // window_size[0], + H // window_size[1], + W // window_size[2], + window_size[0], + window_size[1], + window_size[2], + -1, + ) + x = x.permute(0, 1, 4, 2, 5, 3, 6, 7).contiguous().view(B, D, H, W, -1) + return x + + +def get_window_size(x_size, window_size, shift_size=None): + use_window_size = list(window_size) + if shift_size is not None: + use_shift_size = list(shift_size) + for i in range(len(x_size)): + if x_size[i] <= window_size[i]: + use_window_size[i] = x_size[i] + if shift_size is not None: + use_shift_size[i] = 0 + + if shift_size is None: + return tuple(use_window_size) + else: + return tuple(use_window_size), tuple(use_shift_size) + + +# ------------------------------------------------------------------------------------------------- + + +def compute_mask(D, H, W, window_size, shift_size, device): + img_mask = torch.zeros((1, D, H, W, 1), device=device) # 1 Dp Hp Wp 1 + cnt = 0 + for d in ( + slice(-window_size[0]), + slice(-window_size[0], -shift_size[0]), + slice(-shift_size[0], None), + ): + for h in ( + slice(-window_size[1]), + slice(-window_size[1], -shift_size[1]), + slice(-shift_size[1], None), + ): + for w in ( + slice(-window_size[2]), + slice(-window_size[2], -shift_size[2]), + slice(-shift_size[2], None), + ): + img_mask[:, d, h, w, :] = cnt + cnt += 1 + mask_windows = window_partition(img_mask, window_size) # nW, ws[0]*ws[1]*ws[2], 1 + mask_windows = mask_windows.squeeze(-1) # nW, ws[0]*ws[1]*ws[2] + attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) + attn_mask = attn_mask.masked_fill(attn_mask != 0, (-100.0)).masked_fill(attn_mask == 0, 0.0) + return attn_mask + + +# ------------------------------------------------------------------------------------------------- + + +class Swin3DAttention(CnnAttentionBase): + """Multi-head attention model for the swin. Number of pixels in a window are [window_size, window_size, window_size].""" + + def __init__( + self, + C_in, + C_out=16, + H=256, + W=256, + D=128, + window_size=None, + num_wind=None, + attention_type="conv", + n_head=8, + kernel_size=(3, 3, 3), + stride=(1, 1, 1), + padding=(1, 1, 1), + stride_qk=(1, 1, 1), + shifted=False, + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + with_timer=False, + ): + """shifted: if Ture, run the shifted window swin; if False, run the normal window swin.""" + if num_wind is None: + num_wind = [8, 8, 8] + if window_size is None: + window_size = [32, 32, 8] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + D=D, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=with_timer, + ) + + self.attention_type = attention_type + self.window_size = window_size + self.num_wind = num_wind + + self.shifted = shifted + + self.set_and_check_wind() + + if len(self.window_size) == 2: + self.window_size.append(1) + if len(self.num_wind) == 2: + self.num_wind.append(1) + + num_pixel_win = self.window_size[0] * self.window_size[1] * self.window_size[2] + assert self.C_out * num_pixel_win % self.n_head == 0, ( + f"Number of pixels in a patch {self.C_out * num_pixel_win} should be divisible by number of heads {self.n_head}" + ) + + self.win_size_h = self.window_size[0] + self.win_size_w = self.window_size[1] + self.win_size_d = self.window_size[2] + + self.shift_size_h = 0 + self.shift_size_w = 0 + self.shift_size_d = 0 + if self.shifted: + self.shift_size_h = max(self.win_size_h // 2, 1) + self.shift_size_w = max(self.win_size_w // 2, 1) + self.shift_size_d = max(self.win_size_d // 2, 1) + + print( + f"{Fore.YELLOW}--> Swin, H {H}, W {W}, D {D}, win size {self.window_size}, num_wind {self.num_wind}" + ) + + if attention_type == "conv": + # key, query, value projections convolution + # Wk, Wq, Wv + if self.D > 1: + self.key = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.query = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.value = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + else: + self.key = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.query = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.value = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + elif attention_type == "lin": + # linear projections + self.key = LinearGrid3DExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + self.query = LinearGrid3DExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + self.value = LinearGrid3DExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + else: + raise NotImplementedError(f"Attention type not implemented: {attention_type}") + + if self.att_with_relative_position_bias: + self.define_relative_position_bias_table_3D( + num_win_h=self.win_size_h, num_win_w=self.win_size_w, num_win_d=self.win_size_d + ) + self.define_relative_position_index_3D( + num_win_h=self.win_size_h, num_win_w=self.win_size_w, num_win_d=self.win_size_d + ) + + def attention(self, k, q, v, mask_matrix): + B, C, D, H, W = k.shape + _, _, _D_v, _H_v, _W_v = v.shape + + k = torch.permute(k, [0, 2, 3, 4, 1]) # [B, D, H, W, C] + q = torch.permute(q, [0, 2, 3, 4, 1]) + v = torch.permute(v, [0, 2, 3, 4, 1]) + + window_size, shift_size = get_window_size( + (D, H, W), + [self.win_size_d, self.win_size_h, self.win_size_w], + [self.shift_size_d, self.shift_size_h, self.shift_size_w], + ) + + # pad feature maps to multiples of window sizelspci | grep VGA + tm = start_timer(enable=self.with_timer) + pad_l = pad_t = pad_d0 = 0 + pad_d1 = (window_size[0] - D % window_size[0]) % window_size[0] + pad_b = (window_size[1] - H % window_size[1]) % window_size[1] + pad_r = (window_size[2] - W % window_size[2]) % window_size[2] + if pad_d1 > 0 or pad_b > 0 or pad_r > 0: + k = F.pad(k, (0, 0, pad_l, pad_r, pad_t, pad_b, pad_d0, pad_d1)) + q = F.pad(q, (0, 0, pad_l, pad_r, pad_t, pad_b, pad_d0, pad_d1)) + v = F.pad(v, (0, 0, pad_l, pad_r, pad_t, pad_b, pad_d0, pad_d1)) + _, Dp, Hp, Wp, _ = k.shape + # cyclic shift + if any(i > 0 for i in shift_size): + shifted_k = torch.roll( + k, shifts=(-shift_size[0], -shift_size[1], -shift_size[2]), dims=(1, 2, 3) + ) + shifted_q = torch.roll( + q, shifts=(-shift_size[0], -shift_size[1], -shift_size[2]), dims=(1, 2, 3) + ) + shifted_v = torch.roll( + v, shifts=(-shift_size[0], -shift_size[1], -shift_size[2]), dims=(1, 2, 3) + ) + attn_mask = mask_matrix + else: + shifted_k = k + shifted_q = q + shifted_v = v + attn_mask = None + end_timer(enable=self.with_timer, t=tm, msg="shifted, k, q, v") + + tm = start_timer(enable=self.with_timer) + k = window_partition(shifted_k, window_size) # B*num_win, wd*wh*ww, C + q = window_partition(shifted_q, window_size) + v = window_partition(shifted_v, window_size) + end_timer(enable=self.with_timer, t=tm, msg="window_partition") + + # --------------------------------------------------- + # compute attention matrix + # attn_windows = self.attn(k_windows, q_windows, v_windows, mask=attn_mask) + + B_ = k.shape[0] + N = k.shape[1] + hc = C // self.n_head + + N_v = v.shape[1] + + k = torch.reshape(k, [B_, N, self.n_head, hc]).permute(0, 2, 1, 3) + q = torch.reshape(q, [B_, N, self.n_head, hc]).permute(0, 2, 1, 3) + v = torch.reshape(k, [B_, N_v, self.n_head, hc]).permute(0, 2, 1, 3) + + tm = start_timer(enable=self.with_timer) + if self.cosine_att: + attn = cosine_attention(q, k) + else: + if self.normalize_Q_K and k.shape[-1] > 0: + q, k = normalize_qk(q, k) + + attn = q @ k.transpose(-2, -1) * torch.sqrt(torch.tensor(1.0 / hc)) + end_timer(enable=self.with_timer, t=tm, msg="attn") + + N = window_size[0] * window_size[1] * window_size[2] + + tm = start_timer(enable=self.with_timer) + if attn_mask is not None: + nW = attn_mask.shape[0] + attn = attn.view(B_ // nW, nW, self.n_head, N, N) + attn_mask.unsqueeze(1).unsqueeze(0) + attn = attn.view(-1, self.n_head, N, N) + attn = F.softmax(attn, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="attn_mask and softmax") + else: + attn = F.softmax(attn, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + if self.att_with_relative_position_bias: + relative_position_bias = self.get_relative_position_bias_3D( + self.win_size_h, self.win_size_w, self.win_size_d + ) + attn = attn + relative_position_bias + end_timer(enable=self.with_timer, t=tm, msg="att_with_relative_position_bias") + + tm = start_timer(enable=self.with_timer) + attn = self.attn_drop(attn) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + y = attn @ v + y = y.transpose(1, 2).reshape(B_, N, C) + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + # --------------------------------------------------- + + shifted_y = window_reverse(y, window_size, B, Dp, Hp, Wp) # B D' H' W' C + # reverse cyclic shift + if any(i > 0 for i in shift_size): + y = torch.roll( + shifted_y, + shifts=(shift_size[0], shift_size[1], shift_size[2]), + dims=(1, 2, 3), + ) + else: + y = shifted_y + + if pad_d1 > 0 or pad_r > 0 or pad_b > 0: + y = y[:, :D, :H, :W, :].contiguous() + + y = torch.permute(y, [0, 4, 1, 2, 3]) # from [B, D', H', W', C] to [B, C, D', H', W'] + + return y + + def forward(self, x): + _B, C, D, H, W = x.size() + + assert C == self.C_in, ( + f"Input channel {C} does not match expected input channel {self.C_in}" + ) + + if self.attention_type == "conv": + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + else: + tm = start_timer(enable=self.with_timer) + x = self.im2grid(x) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + tm = start_timer(enable=self.with_timer) + k = self.grid2im(k) + q = self.grid2im(q) + v = self.grid2im(v) + end_timer(enable=self.with_timer, t=tm, msg="grid2im") + + tm = start_timer(enable=self.with_timer) + if self.shifted: + _, _, D, H, W = k.shape + # compute mask_matrix + window_size = [self.win_size_d, self.win_size_h, self.win_size_w] + shift_size = [self.shift_size_d, self.shift_size_h, self.shift_size_w] + window_size, shift_size = get_window_size((D, H, W), window_size, shift_size) + Dp = int(np.ceil(D / window_size[0])) * window_size[0] + Hp = int(np.ceil(H / window_size[1])) * window_size[1] + Wp = int(np.ceil(W / window_size[2])) * window_size[2] + mask_matrix = compute_mask(Dp, Hp, Wp, window_size, shift_size, x.device) + else: + mask_matrix = None + end_timer(enable=self.with_timer, t=tm, msg="compute_mask") + + # k, q, v are [B, C, D, H, W] + tm = start_timer(enable=self.with_timer) + y = self.attention(k, q, v, mask_matrix) + end_timer(enable=self.with_timer, t=tm, msg="self.attention") + + tm = start_timer(enable=self.with_timer) + y = self.output_proj(y) + end_timer(enable=self.with_timer, t=tm, msg="output_proj") + + return y + + def im2grid(self, x): + """Reshape the input into windows of local areas.""" + _B, _C, D, H, W = x.shape + + wind_view = rearrange( + x, + "b c (num_win_d win_size_d) (num_win_h win_size_h) (num_win_w win_size_w) -> b num_win_d num_win_h num_win_w win_size_d win_size_h win_size_w c", + num_win_h=self.num_wind[0], + win_size_h=H // self.num_wind[0], + num_win_w=self.num_wind[1], + win_size_w=W // self.num_wind[1], + num_win_d=self.num_wind[2], + win_size_d=D // self.num_wind[2], + ) + + return wind_view + + def grid2im(self, x): + """Reshape the windows back into the complete image.""" + _b, num_win_d, num_win_h, num_win_w, win_size_d, win_size_h, win_size_w, _c = x.shape + + im_view = rearrange( + x, + "b num_win_d num_win_h num_win_w win_size_d win_size_h win_size_w c -> b c (num_win_d win_size_d) (num_win_h win_size_h) (num_win_w win_size_w)", + num_win_h=num_win_h, + win_size_h=win_size_h, + num_win_w=num_win_w, + win_size_w=win_size_w, + num_win_d=num_win_d, + win_size_d=win_size_d, + ) + return im_view + + def get_dimension_for_linear_mixer(self): + return self.C_out * self.window_size[0] * self.window_size[1] * self.window_size[2] + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/attention/temporal_attention_modules.py b/src/snraware/components/model/attention/temporal_attention_modules.py new file mode 100644 index 0000000..e3e5ec8 --- /dev/null +++ b/src/snraware/components/model/attention/temporal_attention_modules.py @@ -0,0 +1,356 @@ +""" +Implement the temporal/frame cnn attention. +For a [B, C, T, H, W] input, the output is [B, C_out, T, H', W'] where usually H'=H and W'=W. +All frames along T dimension are used to calculate attention, i.e. all T frames are used to calculate attention for each frame. +""" + +from .attention_modules import * + +# ------------------------------------------------------------------------------------------------- + + +class TemporalCnnAttentionBase(CnnAttentionBase): + def __init__( + self, + C_in, + C_out=16, + H=128, + W=128, + is_causal=False, + n_head=8, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + stride_qk=(1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_output_proj=True, + flash_att=False, + with_timer=False, + ): + """ + @args: + - is_causal (bool): whether to mask attention to imply causality. + """ + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_output_proj=att_with_output_proj, + flash_att=flash_att, + ) + + self.is_causal = is_causal + self.stride_f = stride_qk[0] + self.with_timer = with_timer + + # key, query, value projections convolution + # Wk, Wq, Wv + self.key = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=False, + ) + self.query = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=False, + ) + self.value = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=False, + ) + + self.register_buffer( + "mask", torch.tril(torch.ones(1000, 1000, dtype=torch.bool)).view(1, 1, 1000, 1000) + ) + + def attention(self, k, q, v): + raise NotImplementedError("This function is to be implemented by the subclass") + + def forward(self, x): + """ + @args: + x ([B, C, T, H, W]): Input of a batch of time series. + + @rets: + y ([B, C_out, T, H', W']): logits + """ + _B, C, _T, _H, _W = x.size() + + assert C == self.C_in, ( + f"Input channel {C} does not match expected input channel {self.C_in}" + ) + + x = torch.permute(x, [0, 2, 1, 3, 4]) + + # apply the key, query and value matrix + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + y = self.attention(k, q, v) + + y = torch.permute(y, [0, 2, 1, 3, 4]) + + tm = start_timer(enable=self.with_timer) + y = self.output_proj(y) + end_timer(enable=self.with_timer, t=tm, msg="output_proj") + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class TemporalChannelCnnAttention(TemporalCnnAttentionBase): + """Multi-head attention is to split the channel dimension.""" + + def __init__( + self, + C_in, + C_out=16, + H=128, + W=128, + is_causal=False, + n_head=8, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + stride_qk=(1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_output_proj=True, + flash_att=False, + with_timer=False, + ): + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + is_causal=is_causal, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_output_proj=att_with_output_proj, + flash_att=flash_att, + with_timer=with_timer, + ) + + assert self.C_out % self.n_head == 0, ( + f"Number of output channel {self.C_out} should be divisible by number of heads {self.n_head}" + ) + + print( + f"{Fore.CYAN}--> Temporal, attention on channels only, C_in {C_in}, C_out {C_out}{Style.RESET_ALL}" + ) + + def attention(self, k, q, v): + B, T, C_prime, H_prime, W_prime = k.shape + + hc = torch.div(C_prime, self.n_head, rounding_mode="floor") + Hv, Wv = v.shape[-2:] + + tm = start_timer(enable=self.with_timer) + k = k.view(B, T, self.n_head, hc, H_prime, W_prime).transpose(1, 2) + q = q.view(B, T, self.n_head, hc, H_prime, W_prime).transpose(1, 2) + v = v.view(B, T, self.n_head, hc, Hv, Wv).transpose(1, 2) + end_timer(enable=self.with_timer, t=tm, msg="k, q, v - reshape") + + B, nh, T, hc, H_prime, W_prime = k.shape + + if ( + self.has_flash_attention + and (not self.att_with_relative_position_bias) + and (Hv == H_prime) + and (Wv == W_prime) + ): + tm = start_timer(enable=self.with_timer) + q = q.view(B, nh, T, hc * H_prime * W_prime) + k = k.view(B, nh, T, hc * H_prime * W_prime) + v = v.view(B, nh, T, hc * H_prime * W_prime) + y = self.perform_flash_atten(q, k, v) + end_timer(enable=self.with_timer, t=tm, msg="perform_flash_atten") + else: + q = q.view(B, nh, T, hc * H_prime * W_prime) + k = k.view(B, nh, T, hc * H_prime * W_prime) + + tm = start_timer(enable=self.with_timer) + # (B, nh, T, hc*H'*W') x (B, nh, hc*H'*W', T) -> (B, nh, T, T) + if self.cosine_att: + att = cosine_attention(q, k) + else: + if self.normalize_Q_K: + q, k = normalize_qk(q, k) + + att = ( + q + @ k.transpose(-2, -1) + / torch.sqrt(torch.tensor(1.0 * hc * H_prime * W_prime)) + ) + end_timer(enable=self.with_timer, t=tm, msg="att") + + if self.is_causal: + att = att.masked_fill(self.mask[:, :, :T, :T] == 0, float("-inf")) + + tm = start_timer(enable=self.with_timer) + att = F.softmax(att, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + att = self.attn_drop(att) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + y = att @ v.view(B, nh, T, hc * Hv * Wv) + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + tm = start_timer(enable=self.with_timer) + y = y.transpose(1, 2).contiguous().view(B, T, self.C_out, Hv, Wv) + end_timer(enable=self.with_timer, t=tm, msg="y.transpose") + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class TemporalCnnAttention(TemporalCnnAttentionBase): + """Attention is on both channel and H and W dimensions.""" + + def __init__( + self, + C_in, + C_out=16, + H=128, + W=128, + is_causal=False, + n_head=8, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + stride_qk=(1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_output_proj=True, + flash_att=False, + with_timer=False, + ): + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + is_causal=is_causal, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_output_proj=att_with_output_proj, + flash_att=flash_att, + with_timer=with_timer, + ) + + self.stride_f = stride_qk[0] + + assert self.C_out * H * W % self.n_head == 0, ( + f"Number of output {self.C_out * H * W} should be divisible by number of heads {self.n_head}" + ) + + print( + f"{Fore.GREEN}--> Temporal, attention beyond channels, H {H}, W {W}, C_in {C_in}, C_out {C_out}{Style.RESET_ALL}" + ) + + def attention(self, k, q, v): + B, T, C_prime, H_prime, W_prime = k.shape + + H = torch.div(C_prime * H_prime * W_prime, self.n_head, rounding_mode="floor") + Hv, Wv = v.shape[-2:] + + tm = start_timer(enable=self.with_timer) + k = k.view(B, T, self.n_head, H).transpose(1, 2) + q = q.view(B, T, self.n_head, H).transpose(1, 2) + v = v.view(B, T, self.n_head, H * self.stride_f * self.stride_f).transpose(1, 2) + end_timer(enable=self.with_timer, t=tm, msg="k, q, v - reshape") + + # (B, nh, T, hc, H', W') x (B, nh, hc, H', W', T) -> (B, nh, T, T) + if ( + self.has_flash_attention + and (not self.att_with_relative_position_bias) + and (Hv == H_prime) + and (Wv == W_prime) + ): + tm = start_timer(enable=self.with_timer) + y = self.perform_flash_atten(q, k, v) + end_timer(enable=self.with_timer, t=tm, msg="perform_flash_atten") + else: + tm = start_timer(enable=self.with_timer) + if self.cosine_att: + att = cosine_attention(q, k) + else: + if self.normalize_Q_K: + q, k = normalize_qk(q, k) + + att = (q @ k.transpose(-2, -1)) * torch.sqrt(1.0 / H) + end_timer(enable=self.with_timer, t=tm, msg="att") + + if self.is_causal: + att = att.masked_fill(self.mask[:, :, :T, :T] == 0, float("-inf")) + + tm = start_timer(enable=self.with_timer) + att = F.softmax(att, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + att = self.attn_drop(att) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + y = att @ v + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + tm = start_timer(enable=self.with_timer) + y = y.transpose(1, 2).contiguous().view(B, T, self.C_out, Hv, Wv) + end_timer(enable=self.with_timer, t=tm, msg="y.transpose") + + return y + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/attention/vit_3d_attention_modules.py b/src/snraware/components/model/attention/vit_3d_attention_modules.py new file mode 100644 index 0000000..71c93f1 --- /dev/null +++ b/src/snraware/components/model/attention/vit_3d_attention_modules.py @@ -0,0 +1,315 @@ +"""Implement the ViT style spatial attention, for 3D patches.""" + +from einops import rearrange + +from .attention_modules import * + +# ------------------------------------------------------------------------------------------------- +# CNN attention with the ViT style - an image is split into windows. Attention coefficients are computed among all windows. + + +class ViT3DAttention(CnnAttentionBase): + """ + Multi-head cnn attention with the ViT style window split. + Attention matrix is computed between all windows. + Number of pixels in a window are [window_size, window_size, window_size]. + """ + + def __init__( + self, + C_in, + C_out=16, + H=128, + W=128, + D=32, + window_size=None, + num_wind=None, + attention_type="conv", + n_head=8, + kernel_size=(3, 3, 3), + stride=(1, 1, 1), + padding=(1, 1, 1), + stride_qk=(1, 1, 1), + att_dropout_p=0.0, + cosine_att=False, + normalize_Q_K=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + with_timer=False, + ): + """ + Input to the attention layer has the size [B, C, T, H, W] + Output has the size [B, output_channels, T, H, W]. + + Either window_size or num_wind should be supplied. + window_size is the number of pixels per window, along H and W and T. + num_wind is the number of windows along H and W. + if both are supplied, num_wind has the priority. + + @args: + - window_size (int): number of pixels in a window + """ + if num_wind is None: + num_wind = [8, 8, 4] + if window_size is None: + window_size = [16, 16, 8] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + D=D, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_qk, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=with_timer, + ) + + self.C_out = C_out + self.attention_type = attention_type + self.window_size = window_size + self.num_wind = num_wind + + self.set_and_check_wind() + + if len(self.window_size) == 2: + self.window_size.append(1) + if len(self.num_wind) == 2: + self.num_wind.append(1) + + print( + f"{Fore.GREEN}--> Spatial, ViT3D, H {H}, W {W}, D {D}, win size {self.window_size}{Style.RESET_ALL}" + ) + + if attention_type == "conv": + # key, query, value projections convolution + # Wk, Wq, Wv + if self.D > 1: + self.key = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.query = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.value = Conv3DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + else: + self.key = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.query = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride_qk, + padding=padding, + bias=True, + channel_first=True, + ) + self.value = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + elif attention_type == "lin": + # linear projections + num_pixel_win = self.window_size[0] * self.window_size[1] * self.window_size[2] + self.key = LinearGrid3DExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + self.query = LinearGrid3DExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + self.value = LinearGrid3DExt(C_in * num_pixel_win, C_out * num_pixel_win, bias=True) + else: + raise NotImplementedError(f"Attention type not implemented: {attention_type}") + + if self.att_with_relative_position_bias: + self.define_relative_position_bias_table_3D( + num_win_h=self.num_wind[0], num_win_w=self.num_wind[1], num_win_d=self.num_wind[2] + ) + self.define_relative_position_index_3D( + num_win_h=self.num_wind[0], num_win_w=self.num_wind[1], num_win_d=self.num_wind[2] + ) + + def attention(self, k, q, v): + B, num_win_d, num_win_h, num_win_w, wd, wh, ww, C = k.shape + _, _, _, _, wd_v, wh_v, ww_v, _ = v.shape + + # format the window + hc = torch.div(C * wd * wh * ww, self.n_head, rounding_mode="floor") + hc_v = torch.div(C * wd_v * wh_v * ww_v, self.n_head, rounding_mode="floor") + + tm = start_timer(enable=self.with_timer) + q = q.reshape((B, num_win_d * num_win_h * num_win_w, self.n_head, hc)).transpose(1, 2) + k = k.reshape((B, num_win_d * num_win_h * num_win_w, self.n_head, hc)).transpose(1, 2) + v = v.reshape((B, num_win_d * num_win_h * num_win_w, self.n_head, hc_v)).transpose(1, 2) + end_timer(enable=self.with_timer, t=tm, msg="k, q, v - reshape") + + tm = start_timer(enable=self.with_timer) + # [B, num_heads, num_windows, hc] x [B, num_heads, hc, num_windows] -> (B, num_heads, num_windows, num_windows) + if self.cosine_att: + att = cosine_attention(q, k) + else: + if self.normalize_Q_K: + q, k = normalize_qk(q, k) + + att = q @ k.transpose(-2, -1) * torch.sqrt(1.0 / hc.clone().detach()) + + end_timer(enable=self.with_timer, t=tm, msg="att") + + tm = start_timer(enable=self.with_timer) + att = F.softmax(att, dim=-1) + end_timer(enable=self.with_timer, t=tm, msg="softmax") + + tm = start_timer(enable=self.with_timer) + # add the relative positional bias + if self.att_with_relative_position_bias: + relative_position_bias = self.get_relative_position_bias_3D( + num_win_h, num_win_w, num_win_d + ) + att = att + relative_position_bias + end_timer(enable=self.with_timer, t=tm, msg="att_with_relative_position_bias_3D") + + tm = start_timer(enable=self.with_timer) + att = self.attn_drop(att) + end_timer(enable=self.with_timer, t=tm, msg="attn_drop") + + tm = start_timer(enable=self.with_timer) + # (B, num_heads, num_windows, num_windows) * (B, num_heads, num_windows, hc) + y = att @ v + y = y.transpose(1, 2) # (B, num_windows, num_heads, hc) + y = torch.reshape(y, (B, num_win_d, num_win_h, num_win_w, wd_v, wh_v, ww_v, C)) + end_timer(enable=self.with_timer, t=tm, msg="att @ v") + + tm = start_timer(enable=self.with_timer) + y = self.grid2im(y) + end_timer(enable=self.with_timer, t=tm, msg="grid2im") + + return y + + def forward(self, x): + """ + @args: + x ([B, C, D, H, W]): Input of a batch of time series. + + @rets: + y ([B, C_out, D, H', W']): output tensor + """ + _B, C, D, H, W = x.size() + + if self.attention_type == "lin" and self.att_with_relative_position_bias: + assert H == self.H and W == self.W and D == self.D, ( + "For lin attention_type with relative position bias, input H and W have to be the same as the class declaration." + ) + + assert C == self.C_in, ( + f"Input channel {C} does not match expected input channel {self.C_in}" + ) + assert H % self.num_wind[0] == 0, ( + f"Height {H} should be divisible by window num {self.num_wind[0]}" + ) + assert W % self.num_wind[1] == 0, ( + f"Width {W} should be divisible by window num {self.num_wind[1]}" + ) + assert D % self.num_wind[2] == 0, ( + f"Depth {D} should be divisible by window num {self.num_wind[2]}" + ) + + if self.attention_type == "conv": + tm = start_timer(enable=self.with_timer) + k = self.key(x) + q = self.query(x) + v = self.value(x) + end_timer(enable=self.with_timer, t=tm, msg="compute k, q, v") + + tm = start_timer(enable=self.with_timer) + k = self.im2grid(k) # (B, num_win_d, num_win_h, num_win_w, wd, wh, ww, C) + q = self.im2grid(q) + v = self.im2grid(v) + end_timer(enable=self.with_timer, t=tm, msg="im2grid") + else: + x = self.im2grid(x) # (B, num_win_d, num_win_h, num_win_w, wd, wh, ww, C) + k = self.key(x) + q = self.query(x) + v = self.value(x) + + y = self.attention(k, q, v) + + tm = start_timer(enable=self.with_timer) + y = self.output_proj(y) + end_timer(enable=self.with_timer, t=tm, msg="output_proj") + + return y + + def im2grid(self, x): + """Reshape the input into windows of local areas.""" + _b, _c, d, h, w = x.shape + + wind_view = rearrange( + x, + "b c (num_win_d win_size_d) (num_win_h win_size_h) (num_win_w win_size_w) -> b num_win_d num_win_h num_win_w win_size_d win_size_h win_size_w c", + num_win_d=self.num_wind[2], + win_size_d=d // self.num_wind[2], + num_win_h=self.num_wind[0], + win_size_h=h // self.num_wind[0], + num_win_w=self.num_wind[1], + win_size_w=w // self.num_wind[1], + ) + + return wind_view + + def grid2im(self, x): + """Reshape the windows back into the complete image.""" + _b, num_win_d, num_win_h, num_win_w, wd, wh, ww, _c = x.shape + + im_view = rearrange( + x, + "b num_win_d num_win_h num_win_w win_size_d win_size_h win_size_w c -> b c (num_win_d win_size_d) (num_win_h win_size_h) (num_win_w win_size_w)", + num_win_d=num_win_d, + win_size_d=wd, + num_win_h=num_win_h, + win_size_h=wh, + num_win_w=num_win_w, + win_size_w=ww, + ) + + return im_view + + def get_dimension_for_linear_mixer(self): + return self.C_out * self.window_size[0] * self.window_size[1] * self.window_size[2] + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/backbone/__init__.py b/src/snraware/components/model/backbone/__init__.py new file mode 100644 index 0000000..52ab912 --- /dev/null +++ b/src/snraware/components/model/backbone/__init__.py @@ -0,0 +1,6 @@ +from .backbone_base import * +from .backbone_hrnet import * +from .backbone_soanet import * +from .backbone_unet import * +from .blocks import * +from .cells import * diff --git a/src/snraware/components/model/backbone/backbone_base.py b/src/snraware/components/model/backbone/backbone_base.py new file mode 100644 index 0000000..3536b71 --- /dev/null +++ b/src/snraware/components/model/backbone/backbone_base.py @@ -0,0 +1,603 @@ +""" +Define the base class for the backbones. +Contain common methods and utilities for the backbone models. + +The output of a backbone is a list of tensors. If more than one tensor is returned, this list contains the outputs from all resolution levels. +The first tensor res[0] has the highest resolution. + +For the hrnet, the res[-1] is the aggregated tensor from all resolutoin levels. + +For more information, please refer to the backbone section in the documentation. +""" + +import copy +import inspect +from abc import ABC, abstractmethod +from collections import OrderedDict + +import interpol +import torch +import torch.nn as nn +from torch import Tensor + +from snraware.components.model.attention import * + +from .blocks import * + +__all__ = [ + "BackboneBase", + "DownSample", + "UpSample", + "set_window_patch_sizes_keep_num_window", + "set_window_patch_sizes_keep_window_size", +] + +# ------------------------------------------------------------------------------------------------- + + +def set_window_patch_sizes_keep_num_window(kwargs, HW, num_wind, num_patch, module_name=None): + """ + Set window and patch sizes based on the image size and number of windows and patches. + + Args: + kwargs (dict): dictonary containing the following keys: + window_size (list): list of two integers representing the window size [H, W]. + patch_size (list): list of two integers representing the patch size [H, W]. + num_wind (list): list of two integers representing the number of windows [num_wind_H, num_wind_W]. + num_patch (list): list of two integers representing the number of patches [num_patch_H, num_patch_W]. + num_wind (list): number of windows. + num_patch (list): number of patches. + + Returns: + kwargs: updated dictionary + """ + num_wind = [2 if v < 2 else v for v in num_wind] + num_patch = [2 if v < 2 else v for v in num_patch] + + kwargs["window_size"][0] = max(HW[0] // num_wind[0], 1) + kwargs["window_size"][1] = max(HW[1] // num_wind[1], 1) + + kwargs["patch_size"][0] = max(kwargs["window_size"][0] // num_patch[0], 1) + kwargs["patch_size"][1] = max(kwargs["window_size"][1] // num_patch[1], 1) + + kwargs["num_wind"] = num_wind + kwargs["num_patch"] = num_patch + + info_str = f" --> image size {HW} - windows size {kwargs['window_size']} - patch size {kwargs['patch_size']} - num windows {kwargs['num_wind']} - num patch {kwargs['num_patch']}" + + if module_name is not None: + info_str = module_name + info_str + + print(info_str) + + return kwargs + + +# ------------------------------------------------------------------------------------------------- + + +def set_window_patch_sizes_keep_window_size( + kwargs, HW, window_size_input, patch_size_input, module_name=None +): + """Set window and patch sizes according to image sizes and number of windows and patches.""" + window_size = copy.deepcopy(window_size_input) + patch_size = copy.deepcopy(patch_size_input) + + if HW[0] // window_size[0] < 2: + window_size[0] = max(HW[0] // 2, 1) + + if HW[1] // window_size[1] < 2: + window_size[1] = max(HW[1] // 2, 1) + + if window_size[0] // patch_size[0] < 2: + patch_size[0] = max(window_size[0] // 2, 1) + + if window_size[1] // patch_size[1] < 2: + patch_size[1] = max(window_size[1] // 2, 1) + + kwargs["window_size"] = window_size + kwargs["patch_size"] = patch_size + + kwargs["num_wind"][0] = max(HW[0] // window_size[0], 1) + kwargs["num_wind"][1] = max(HW[1] // window_size[1], 1) + + kwargs["num_patch"][0] = max(window_size[0] // patch_size[0], 1) + kwargs["num_patch"][1] = max(window_size[1] // patch_size[1], 1) + + info_str = f" --> image size {HW} - windows size {kwargs['window_size']} - patch size {kwargs['patch_size']} - num windows {kwargs['num_wind']} - num patch {kwargs['num_patch']}" + + if module_name is not None: + info_str = module_name + info_str + + print(info_str) + + return kwargs + + +# ------------------------------------------------------------------------------------------------- +# building blocks + + +class _D2(nn.Module): + """ + Downsample by 2 layer. + + This module takes in a [B, T, C, H, W] tensor and downsample it to [B, T, C, H//2, W//2] + + By default, the operation is performed with a bilinear interpolation. + If with_conv is True, a 1x1 convolution is added after interpolation. + If with_interpolation is False, the stride convolution is used. + """ + + def __init__(self, C_in=16, C_out=-1, use_interpolation=True, with_conv=True) -> None: + super().__init__() + + self.C_in = C_in + self.C_out = C_out if C_out > 0 else C_in + + self.use_interpolation = use_interpolation + self.with_conv = with_conv + + self.stride_conv = None + self.conv = None + + if not self.use_interpolation: + self.stride_conv = Conv2DExt( + in_channels=self.C_in, + out_channels=self.C_out, + kernel_size=[3, 3], + stride=[2, 2], + padding=[1, 1], + ) + elif self.with_conv or (self.C_in != self.C_out): + self.conv = Conv2DExt( + in_channels=self.C_in, + out_channels=self.C_out, + kernel_size=[1, 1], + stride=[1, 1], + padding=[0, 0], + ) + + def forward(self, x: Tensor) -> Tensor: + B, T, C, H, W = x.shape + if self.use_interpolation: + y = F.interpolate( + x.view((B * T, C, H, W)), + scale_factor=(0.5, 0.5), + mode="bilinear", + align_corners=False, + recompute_scale_factor=False, + ) + y = torch.reshape(y, (B, T, *y.shape[1:])) + if self.with_conv: + y = self.conv(y) + else: + y = self.stride_conv(x) + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class _D2_patch_merging(nn.Module): + """ + Downsample by 2 layer using patch merging. + + This module takes in a [B, T, C, H, W] tensor and first reformat it to [B, T, 4*C_in, H//2, W//2], + then a conv is used to get C_out channels. + """ + + def __init__(self, C_in=16, C_out=64) -> None: + super().__init__() + + self.C_in = C_in + self.C_out = C_out if C_out > 0 else C_in + + self.conv = Conv2DExt( + in_channels=4 * self.C_in, + out_channels=self.C_out, + kernel_size=[1, 1], + stride=[1, 1], + padding=[0, 0], + ) + + def forward(self, x: Tensor) -> Tensor: + _B, _T, _C, _H, _W = x.shape + + x0 = x[:, :, :, 0::2, 0::2] # B T C, H/2 W/2 + x1 = x[:, :, :, 1::2, 0::2] + x2 = x[:, :, :, 0::2, 1::2] + x3 = x[:, :, :, 1::2, 1::2] + + y = torch.cat([x0, x1, x2, x3], dim=2) # B T 4*C H/2 W/2 + y = self.conv(y) + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class _D2_3D(nn.Module): + """ + Downsample by 2. + + This module takes in a [B, T, C, H, W] tensor and downsample it to [B, T//2, C, H//2, W//2] + + By default, the operation is performed with a trilinear interpolation. + If with_conv is True, a 1x1 convolution is added after interpolation. + If with_interpolation is False, the stride convolution is used. + """ + + def __init__(self, C_in=16, C_out=-1, use_interpolation=True, with_conv=True) -> None: + super().__init__() + + self.C_in = C_in + self.C_out = C_out if C_out > 0 else C_in + + self.use_interpolation = use_interpolation + self.with_conv = with_conv + + self.stride_conv = None + self.conv = None + + if not self.use_interpolation: + self.stride_conv = Conv3DExt( + in_channels=self.C_in, + out_channels=self.C_out, + kernel_size=[3, 3, 3], + stride=[2, 2, 2], + padding=[1, 1, 1], + ) + elif self.with_conv or (self.C_in != self.C_out): + self.conv = Conv3DExt( + in_channels=self.C_in, + out_channels=self.C_out, + kernel_size=[1, 1, 1], + stride=[1, 1, 1], + padding=[0, 0, 0], + ) + + def forward(self, x: Tensor) -> Tensor: + _B, _T, _C, _H, _W = x.shape + if self.use_interpolation: + y = F.interpolate( + torch.permute(x, (0, 2, 1, 3, 4)), + scale_factor=(0.5, 0.5, 0.5), + mode="trilinear", + align_corners=False, + recompute_scale_factor=False, + ) + y = torch.permute(y, (0, 2, 1, 3, 4)) + if self.with_conv: + y = self.conv(y) + else: + y = self.stride_conv(x) + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class _D2_patch_merging_3D(nn.Module): + """ + Downsample by 2 layer using patch merging. + + This module takes in a [B, T, C, H, W] tensor and first reformat it to [B, T//2, 8*C_in, H//2, W//2], + then a conv is used to get C_out channels. + """ + + def __init__(self, C_in=16, C_out=64) -> None: + super().__init__() + + self.C_in = C_in + self.C_out = C_out if C_out > 0 else C_in + + self.conv = Conv3DExt( + in_channels=8 * self.C_in, + out_channels=self.C_out, + kernel_size=[1, 1, 1], + stride=[1, 1, 1], + padding=[0, 0, 0], + ) + + def forward(self, x: Tensor) -> Tensor: + _B, _T, _C, _H, _W = x.shape + + x0 = x[:, 0::2, :, 0::2, 0::2] # B T C, H/2 W/2 + x1 = x[:, 0::2, :, 1::2, 0::2] + x2 = x[:, 0::2, :, 0::2, 1::2] + x3 = x[:, 0::2, :, 1::2, 1::2] + x4 = x[:, 1::2, :, 0::2, 0::2] + x5 = x[:, 1::2, :, 1::2, 0::2] + x6 = x[:, 1::2, :, 0::2, 1::2] + x7 = x[:, 1::2, :, 1::2, 1::2] + + y = torch.cat([x0, x1, x2, x3, x4, x5, x6, x7], dim=2) + y = self.conv(y) + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class DownSample(nn.Module): + """Downsample by x=2^N, by using N D2 layers.""" + + def __init__( + self, N=2, C_in=16, C_out=-1, use_interpolation=True, with_conv=True, is_3D=False + ) -> None: + super().__init__() + + C_out = C_out if C_out > 0 else C_in + + self.N = N + self.C_in = C_in + self.C_out = C_out + self.use_interpolation = use_interpolation + self.with_conv = with_conv + self.is_3D = is_3D + + DownSampleLayer = _D2_patch_merging + if is_3D: + DownSampleLayer = _D2_patch_merging_3D + + layers = [("D2_0", DownSampleLayer(C_in=C_in, C_out=C_out))] + + for n in range(1, N): + layers.append((f"D2_{n}", DownSampleLayer(C_in=C_out, C_out=C_out))) + + self.block = nn.Sequential(OrderedDict(layers)) + + def forward(self, x: Tensor) -> Tensor: + return permute_to_B_C_T_H_W(self.block(permute_to_B_T_C_H_W(x))) + + +# ------------------------------------------------------------------------------------------------- + + +class _U2(nn.Module): + """ + Upsample by 2. + + This module takes in a [B, T, Cin, H, W] tensor and upsample it to [B, T, Cout, 2*H, 2*W], if channel_first is False + This module takes in a [B, Cin, T, H, W] tensor and upsample it to [B, Cout, T, 2*H, 2*W], if channel_first is True + + By default, the operation is performed with a bilinear interpolation. + If with_conv is True, a 1x1 convolution is added after interpolation. + """ + + def __init__( + self, C_in=16, C_out=-1, method="linear", with_conv=True, channel_first=False + ) -> None: + super().__init__() + + self.C_in = C_in + self.C_out = C_out if C_out > 0 else C_in + + self.method = method + + self.with_conv = with_conv + self.channel_first = channel_first + + self.conv = None + if self.with_conv or (self.C_in != self.C_out): + self.conv = Conv2DExt( + in_channels=self.C_in, + out_channels=self.C_out, + kernel_size=[3, 3], + stride=[1, 1], + padding=[1, 1], + channel_first=self.channel_first, + ) + + def forward(self, x: Tensor) -> Tensor: + B, D1, D2, H, W = x.shape + + if self.method == "NN": + y = F.interpolate( + x.reshape((B * D1, D2, H, W)), + size=(2 * H, 2 * W), + mode="nearest", + recompute_scale_factor=False, + ) + elif self.method == "linear": + y = F.interpolate( + x.reshape((B * D1, D2, H, W)), + size=(2 * H, 2 * W), + mode="bilinear", + align_corners=False, + recompute_scale_factor=False, + ) + else: + opt = dict(shape=[2 * H, 2 * W], anchor="first", bound="replicate") + y = interpol.resize(x.reshape((B * D1, D2, H, W)), **opt, interpolation=5) + + y = torch.reshape(y, (B, D1, *y.shape[1:])) + if self.with_conv: + y = self.conv(y) + + return y + + +class _U2_3D(nn.Module): + """ + Upsample by 2 for 3D tensors. + + By default, the operation is performed with a trilinear interpolation. + If with_conv is True, a 1x1 convolution is added after interpolation. + """ + + def __init__( + self, C_in=16, C_out=-1, method="linear", with_conv=True, channel_first=False + ) -> None: + super().__init__() + + self.C_in = C_in + self.C_out = C_out if C_out > 0 else C_in + self.method = method + self.with_conv = with_conv + self.channel_first = channel_first + + self.conv = None + if self.with_conv or (self.C_in != self.C_out): + self.conv = Conv3DExt( + in_channels=self.C_in, + out_channels=self.C_out, + kernel_size=[3, 3, 3], + stride=[1, 1, 1], + padding=[1, 1, 1], + channel_first=self.channel_first, + ) + + def forward(self, x: Tensor) -> Tensor: + if self.channel_first: + x_channel_first = x + else: + x_channel_first = torch.permute(x, (0, 2, 1, 3, 4)) + + _B, _C, T, H, W = x_channel_first.shape + + if self.method == "NN": + y = F.interpolate( + x_channel_first, + size=(2 * T, 2 * H, 2 * W), + mode="nearest", + recompute_scale_factor=False, + ) + elif self.method == "linear": + y = F.interpolate( + x_channel_first, + size=(2 * T, 2 * H, 2 * W), + mode="trilinear", + align_corners=False, + recompute_scale_factor=False, + ) + else: + opt = dict(shape=[2 * T, 2 * H, 2 * W], anchor="first", bound="replicate") + y = interpol.resize(x_channel_first, **opt, interpolation=5) + + if not self.channel_first: + y = torch.permute(y, (0, 2, 1, 3, 4)) + + if self.with_conv: + y = self.conv(y) + + return y + + +# ------------------------------------------------------------------------------------------------- + + +class UpSample(nn.Module): + """Upsample by x(2^N), by using N U2 layers.""" + + def __init__( + self, + N=2, + C_in=16, + C_out=-1, + method="linear", + with_conv=True, + is_3D=False, + channel_first=False, + ) -> None: + super().__init__() + + C_out = C_out if C_out > 0 else C_in + + self.N = N + self.C_in = C_in + self.C_out = C_out + self.with_conv = with_conv + self.is_3D = is_3D + self.method = method + self.channel_first = channel_first + + UpSampleLayer = _U2 + if is_3D: + UpSampleLayer = _U2_3D + + layers = [ + ( + "U2_0", + UpSampleLayer( + C_in=C_in, + C_out=C_out, + method=method, + with_conv=with_conv, + channel_first=self.channel_first, + ), + ) + ] + for n in range(1, N): + layers.append( + ( + f"U2_{n}", + UpSampleLayer( + C_in=C_out, + C_out=C_out, + method=method, + with_conv=with_conv, + channel_first=self.channel_first, + ), + ) + ) + + self.block = nn.Sequential(OrderedDict(layers)) + + def forward(self, x: Tensor) -> Tensor: + return permute_to_B_C_T_H_W(self.block(permute_to_B_T_C_H_W(x))) + + +# ------------------------------------------------------------------------------------------------- +class BackboneBase(nn.Module, ABC): + """Base class for backbone models.""" + + def __init__(self, config) -> None: + """ + @args: + - config (Namespace): runtime namespace for setup. + """ + super().__init__() + self.config = config + + # preset some default parameters + self.use_interpolation = True # whether to use interpolation for up/downsampling + self.with_conv = True # whether to add convolution after up/downsampling + + @abstractmethod + def get_number_of_output_channels(self) -> int: + """Derived class needs to return number of output channels.""" + pass + + def permute(self, x): + return torch.permute(x, (0, 2, 1, 3, 4)) + + def get_block_parameters(self, model_config): + """ + Helper function for block instantiation. + This function extracts the parameters used to define a block and store them as a dictionary. + """ + sig = inspect.signature(Block.__init__) + params = dict() + for name in sig.parameters.keys(): + if name in model_config: + params[name] = model_config[name] + + block_config = copy.copy(self.config.block) + block_config.cell.window_size = model_config["window_size"] + block_config.cell.patch_size = model_config["patch_size"] + params["config"] = block_config + + return params + + @property + def device(self): + return next(self.parameters()).device + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/backbone/backbone_hrnet.py b/src/snraware/components/model/backbone/backbone_hrnet.py new file mode 100644 index 0000000..d917b3c --- /dev/null +++ b/src/snraware/components/model/backbone/backbone_hrnet.py @@ -0,0 +1,1056 @@ +""" +Backbone model - High-resolution, HRNet architecture. + +This file implements a HRNet design for the imaging backbone. +The input to the model is [B, C_in, T, H, W]. The output of the model is [B, C_out, D, H, W]. +For every resolution level, the image size will be reduced by x2, with the number of channels increasing by x2. + +Besides the aggregated output tensor, this backbone model also outputs the per-resolution-level feature maps as a list. + +Please refer to the backbone section in the documentation for more details on the model structure. +""" + +from collections import OrderedDict + +import torch +import torch.nn as nn +from colorama import Fore, Style + +from snraware.components.model.attention import * +from snraware.components.model.backbone.backbone_base import ( + BackboneBase, + DownSample, + UpSample, + set_window_patch_sizes_keep_num_window, + set_window_patch_sizes_keep_window_size, +) +from snraware.components.model.backbone.blocks import * +from snraware.components.model.backbone.cells import * + +__all__ = ["HRnet"] + + +# ------------------------------------------------------------------------------------- +class HRnet(BackboneBase): + """This class implemented the HRnet with maximal 5 resolution levels.""" + + def __init__(self, config, input_feature_channels, H, W, D): + """ + @args: + - config (Namespace): runtime namespace for setup + - input_feature_channels (List[int] or int): number of channels in each input tensor + - H, W, D (int): H, W, and depth of the input tensor [B, input_feature_channels, D, H, W]. + + @args (from config): + --------------------------------------------------------------- + model specific arguments + --------------------------------------------------------------- + + - C (int): number of operation channels; the input channel is first scaled up to C channels. The downsample will double the number of operation channels, + and upsample will halve the number of operation channels. + + - num_resolution_levels (int): number of resolution levels; each deeper level will reduce spatial size by x2 + + - block_str (str | list of strings): This string is the "Block string" to define the attention layers in a block. + If a list of string is given, each string defines the attention structure for a resolution level. For example, + ["T1L1G1", "T1L1G1T1L1G1"] means the blocks at the first resolution level has three attention layers, and the second resolution level has six attention layers. + + If one string is given, e.g. ["T1L1G1"], it means all resolution levels have the same attention structure, i.e. three attention layers. + + During the configuration process, the block_str is parsed and converted into a list of block configurations for each resolution level. + + --------------------------------------------------------------- + @outputs: + A list of tensors for each resolution level; the last tensor is the aggregation of all resolution levels. + """ + super().__init__(config) + + # set up the model specific parameters + self.C = config.num_of_channels + + self.num_resolution_levels = config.num_resolution_levels + assert self.num_resolution_levels <= 5 and self.num_resolution_levels >= 2, ( + "Maximal number of resolution levels is 5" + ) + + self.block_config = config.block_config + if len(self.block_config) < self.num_resolution_levels: + self.block_config.extend( + [ + config.block_config[-1] + for _i in range(self.num_resolution_levels - len(self.block_config)) + ] + ) + + if isinstance(input_feature_channels, list): + C_in = input_feature_channels[-1] + else: + C_in = input_feature_channels + + # compute number of windows and patches + self.num_wind = [ + H // config.block.cell.window_size[0], + W // config.block.cell.window_size[1], + ] + self.num_patch = [ + config.block.cell.window_size[0] // config.block.cell.patch_size[0], + config.block.cell.window_size[1] // config.block.cell.patch_size[1], + ] + + if len(config.block.cell.window_size) == 3: + self.num_wind.append(D // config.block.cell.window_size[2]) + self.num_patch.append( + config.block.cell.window_size[2] // config.block.cell.patch_size[2] + ) + + # dict to store the parameters used for making blocks + # these values can change from block to block + model_config = dict() + model_config["C_in"] = C_in + model_config["C_out"] = self.C + model_config["H"] = H + model_config["W"] = W + model_config["D"] = D + model_config["num_wind"] = self.num_wind + model_config["num_patch"] = self.num_patch + model_config["block_config"] = config.block_config[0] + model_config["window_size"] = self.config.block.cell.window_size + model_config["patch_size"] = self.config.block.cell.patch_size + + window_sizes = [] + patch_sizes = [] + + print( + f"{Fore.CYAN}=====> Create HRNET, {self.config.block.cell_type}, {self.config.block.cell.attention_type} - {H} - {W} - {D} - {self.config.block.cell.window_size} - {self.config.block.cell.patch_size} - {self.config.block.block_dense_connection} <====={Style.RESET_ALL}" + ) + + if self.num_resolution_levels >= 1: + # define B00 + self.B00, model_config = self.add_a_block( + level=0, + block_name="B00", + model_config=model_config, + c_in=C_in, + c_out=self.C, + h=H, + w=W, + window_sizes=model_config["window_size"], + patch_sizes=model_config["patch_size"], + block_config=self.block_config[0], + window_sizing_method=self.config.block.cell.window_sizing_method, + ) + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + # output stage 0 + self.output_B0, model_config = self.add_a_block( + level=0, + block_name="output_0", + model_config=model_config, + c_in=self.C, + c_out=self.C, + h=H, + w=W, + window_sizes=window_sizes[-1], + patch_sizes=patch_sizes[-1], + block_config=self.block_config[0], + window_sizing_method="keep_window_size", + ) + + if self.num_resolution_levels >= 2: + # define B01 + self.B01, model_config = self.add_a_block( + level=0, + block_name="B01", + model_config=model_config, + c_in=self.C, + c_out=self.C, + h=H, + w=W, + window_sizes=window_sizes[0], + patch_sizes=patch_sizes[0], + block_config=self.block_config[0], + window_sizing_method="keep_window_size", + ) + + # define B11 + self.B11, model_config = self.add_a_block( + level=1, + block_name="B11", + model_config=model_config, + c_in=2 * self.C, + c_out=2 * self.C, + h=H // 2, + w=W // 2, + window_sizes=window_sizes[0], + patch_sizes=patch_sizes[0], + block_config=self.block_config[1], + window_sizing_method=self.config.block.cell.window_sizing_method, + ) + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + # define down sample + self.down_00_11 = DownSample( + N=1, + C_in=self.C, + C_out=2 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + # define output B1 + self.output_B1, model_config = self.add_a_block( + level=1, + block_name="output_1", + model_config=model_config, + c_in=2 * self.C, + c_out=2 * self.C, + h=H // 2, + w=W // 2, + window_sizes=window_sizes[-1], + patch_sizes=patch_sizes[-1], + block_config=self.block_config[1], + window_sizing_method="keep_window_size", + ) + + if self.num_resolution_levels >= 3: + # define B02 + self.B02, model_config = self.add_a_block( + level=0, + block_name="B02", + model_config=model_config, + c_in=self.C, + c_out=self.C, + h=H, + w=W, + window_sizes=window_sizes[0], + patch_sizes=patch_sizes[0], + block_config=self.block_config[0], + window_sizing_method="keep_window_size", + ) + + # define B12 + self.B12, model_config = self.add_a_block( + level=1, + block_name="B12", + model_config=model_config, + c_in=2 * self.C, + c_out=2 * self.C, + h=H // 2, + w=W // 2, + window_sizes=window_sizes[1], + patch_sizes=patch_sizes[1], + block_config=self.block_config[1], + window_sizing_method="keep_window_size", + ) + + # define B22 + self.B22, model_config = self.add_a_block( + level=2, + block_name="B22", + model_config=model_config, + c_in=4 * self.C, + c_out=4 * self.C, + h=H // 4, + w=W // 4, + window_sizes=window_sizes[1], + patch_sizes=patch_sizes[1], + block_config=self.block_config[2], + window_sizing_method=self.config.block.cell.window_sizing_method, + ) + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + # define down sample + self.down_01_12 = DownSample( + N=1, + C_in=self.C, + C_out=2 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_01_22 = DownSample( + N=2, + C_in=self.C, + C_out=4 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_11_22 = DownSample( + N=1, + C_in=2 * self.C, + C_out=4 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + # define output B2 + self.output_B2, model_config = self.add_a_block( + level=2, + block_name="output_B2", + model_config=model_config, + c_in=4 * self.C, + c_out=4 * self.C, + h=H // 4, + w=W // 4, + window_sizes=window_sizes[-1], + patch_sizes=patch_sizes[-1], + block_config=self.block_config[2], + window_sizing_method="keep_window_size", + ) + + if self.num_resolution_levels >= 4: + # define B03 + self.B03, model_config = self.add_a_block( + level=0, + block_name="B03", + model_config=model_config, + c_in=self.C, + c_out=self.C, + h=H, + w=W, + window_sizes=window_sizes[0], + patch_sizes=patch_sizes[0], + block_config=self.block_config[0], + window_sizing_method="keep_window_size", + ) + + # define B13 + self.B13, model_config = self.add_a_block( + level=1, + block_name="B13", + model_config=model_config, + c_in=2 * self.C, + c_out=2 * self.C, + h=H // 2, + w=W // 2, + window_sizes=window_sizes[1], + patch_sizes=patch_sizes[1], + block_config=self.block_config[1], + window_sizing_method="keep_window_size", + ) + + # define B23 + self.B23, model_config = self.add_a_block( + level=2, + block_name="B23", + model_config=model_config, + c_in=4 * self.C, + c_out=4 * self.C, + h=H // 4, + w=W // 4, + window_sizes=window_sizes[2], + patch_sizes=patch_sizes[2], + block_config=self.block_config[2], + window_sizing_method="keep_window_size", + ) + + # define B33 + self.B33, model_config = self.add_a_block( + level=3, + block_name="B33", + model_config=model_config, + c_in=8 * self.C, + c_out=8 * self.C, + h=H // 8, + w=W // 8, + window_sizes=window_sizes[2], + patch_sizes=patch_sizes[2], + block_config=self.block_config[3], + window_sizing_method=self.config.block.cell.window_sizing_method, + ) + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + # define down sample + self.down_02_13 = DownSample( + N=1, + C_in=self.C, + C_out=2 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_02_23 = DownSample( + N=2, + C_in=self.C, + C_out=4 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_02_33 = DownSample( + N=3, + C_in=self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_12_23 = DownSample( + N=1, + C_in=2 * self.C, + C_out=4 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_12_33 = DownSample( + N=2, + C_in=2 * self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_22_33 = DownSample( + N=1, + C_in=4 * self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + # define output B3 + self.output_B3, model_config = self.add_a_block( + level=3, + block_name="output_B3", + model_config=model_config, + c_in=8 * self.C, + c_out=8 * self.C, + h=H // 8, + w=W // 8, + window_sizes=window_sizes[-1], + patch_sizes=patch_sizes[-1], + block_config=self.block_config[3], + window_sizing_method="keep_window_size", + ) + + if self.num_resolution_levels >= 5: + # define B04 + self.B04, model_config = self.add_a_block( + level=0, + block_name="B04", + model_config=model_config, + c_in=self.C, + c_out=self.C, + h=H, + w=W, + window_sizes=window_sizes[0], + patch_sizes=patch_sizes[0], + block_config=self.block_config[0], + window_sizing_method="keep_window_size", + ) + + # define B14 + self.B14, model_config = self.add_a_block( + level=1, + block_name="B14", + model_config=model_config, + c_in=2 * self.C, + c_out=2 * self.C, + h=H // 2, + w=W // 2, + window_sizes=window_sizes[1], + patch_sizes=patch_sizes[1], + block_config=self.block_config[1], + window_sizing_method="keep_window_size", + ) + + # define B24 + self.B24, model_config = self.add_a_block( + level=2, + block_name="B24", + model_config=model_config, + c_in=4 * self.C, + c_out=4 * self.C, + h=H // 4, + w=W // 4, + window_sizes=window_sizes[2], + patch_sizes=patch_sizes[2], + block_config=self.block_config[2], + window_sizing_method="keep_window_size", + ) + + # define B34 + self.B34, model_config = self.add_a_block( + level=3, + block_name="B34", + model_config=model_config, + c_in=8 * self.C, + c_out=8 * self.C, + h=H // 8, + w=W // 8, + window_sizes=window_sizes[3], + patch_sizes=patch_sizes[3], + block_config=self.block_config[3], + window_sizing_method="keep_window_size", + ) + + # define B44 + self.B44, model_config = self.add_a_block( + level=4, + block_name="B44", + model_config=model_config, + c_in=16 * self.C, + c_out=16 * self.C, + h=H // 16, + w=W // 16, + window_sizes=window_sizes[2], + patch_sizes=patch_sizes[2], + block_config=self.block_config[4], + window_sizing_method=self.config.block.cell.window_sizing_method, + ) + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + # define down sample + self.down_03_14 = DownSample( + N=1, + C_in=self.C, + C_out=2 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_03_24 = DownSample( + N=2, + C_in=self.C, + C_out=4 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_03_34 = DownSample( + N=3, + C_in=self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_03_44 = DownSample( + N=4, + C_in=self.C, + C_out=16 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + self.down_13_24 = DownSample( + N=1, + C_in=2 * self.C, + C_out=4 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_13_34 = DownSample( + N=2, + C_in=2 * self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_13_44 = DownSample( + N=3, + C_in=2 * self.C, + C_out=16 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + self.down_23_34 = DownSample( + N=1, + C_in=4 * self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.down_23_44 = DownSample( + N=2, + C_in=4 * self.C, + C_out=16 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + self.down_33_44 = DownSample( + N=1, + C_in=8 * self.C, + C_out=16 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + # define output B4 + self.output_B4, model_config = self.add_a_block( + level=4, + block_name="output_B4", + model_config=model_config, + c_in=16 * self.C, + c_out=16 * self.C, + h=H // 16, + w=W // 16, + window_sizes=window_sizes[-1], + patch_sizes=patch_sizes[-1], + block_config=self.block_config[4], + window_sizing_method="keep_window_size", + ) + + # fusion stage + if self.num_resolution_levels >= 2: + self.down_0_1 = DownSample( + N=1, + C_in=self.C, + C_out=2 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 3: + self.down_0_2 = DownSample( + N=2, + C_in=self.C, + C_out=4 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 4: + self.down_0_3 = DownSample( + N=3, + C_in=self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 5: + self.down_0_4 = DownSample( + N=4, + C_in=self.C, + C_out=16 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 3: + self.down_1_2 = DownSample( + N=1, + C_in=2 * self.C, + C_out=4 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 4: + self.down_1_3 = DownSample( + N=2, + C_in=2 * self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 5: + self.down_1_4 = DownSample( + N=3, + C_in=2 * self.C, + C_out=16 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 4: + self.down_2_3 = DownSample( + N=1, + C_in=4 * self.C, + C_out=8 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 5: + self.down_2_4 = DownSample( + N=2, + C_in=4 * self.C, + C_out=16 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 5: + self.down_3_4 = DownSample( + N=1, + C_in=8 * self.C, + C_out=16 * self.C, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 2: + self.up_1_0 = UpSample( + N=1, + C_in=2 * self.C, + C_out=self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 3: + self.up_2_0 = UpSample( + N=2, + C_in=4 * self.C, + C_out=self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 4: + self.up_3_0 = UpSample( + N=3, + C_in=8 * self.C, + C_out=self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 5: + self.up_4_0 = UpSample( + N=4, + C_in=16 * self.C, + C_out=self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 3: + self.up_2_1 = UpSample( + N=1, + C_in=4 * self.C, + C_out=2 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 4: + self.up_3_1 = UpSample( + N=2, + C_in=8 * self.C, + C_out=2 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 5: + self.up_4_1 = UpSample( + N=3, + C_in=16 * self.C, + C_out=2 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 4: + self.up_3_2 = UpSample( + N=1, + C_in=8 * self.C, + C_out=4 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 5: + self.up_4_2 = UpSample( + N=2, + C_in=16 * self.C, + C_out=4 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 5: + self.up_4_3 = UpSample( + N=1, + C_in=16 * self.C, + C_out=8 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 2: + self.up_1 = UpSample( + N=1, + C_in=2 * self.C, + C_out=2 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 3: + self.up_2 = UpSample( + N=2, + C_in=4 * self.C, + C_out=4 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 4: + self.up_3 = UpSample( + N=3, + C_in=8 * self.C, + C_out=8 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.num_resolution_levels >= 5: + self.up_4 = UpSample( + N=4, + C_in=16 * self.C, + C_out=16 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + + print(f"{Fore.CYAN}=====> Completed, create HRNET <====={Style.RESET_ALL}") + + def add_a_block( + self, + level, + block_name, + model_config, + c_in, + c_out, + h, + w, + window_sizes, + patch_sizes, + block_config, + window_sizing_method, + ): + model_config["C_in"] = c_in + model_config["C_out"] = c_out + model_config["H"] = h + model_config["W"] = w + + if window_sizing_method == "keep_num_window": + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name=block_name, + ) + elif window_sizing_method == "keep_window_size": + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes, + patch_sizes, + module_name=block_name, + ) + else: # mixed + if level == 0: + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name=block_name, + ) + elif level == 1: + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes, + patch_sizes, + module_name=block_name, + ) + elif level == 2: + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + [v // 2 for v in self.num_wind], + self.num_patch, + module_name=block_name, + ) + elif level == 3: + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes, + patch_sizes, + module_name=block_name, + ) + else: # level == 4 + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + [v // 4 for v in self.num_wind], + self.num_patch, + module_name=block_name, + ) + + model_config["block_config"] = block_config + + print( + f"{Fore.BLUE}{block_name} -- {model_config['C_in']} to {model_config['C_out']} --- {[type(m).__name__ for m in model_config['block_config']]}{Style.RESET_ALL}" + ) + + params = self.get_block_parameters(model_config) + m = Block(**params) + + return m, model_config + + def forward(self, x): + """ + @args: + - x (list of 5D torch.Tensor): the input image, [B, Cin, D, H, W]. + + @rets: + - y_hat (5D torch.Tensor): aggregated output tensor + - y_level_outputs (Tuple): tuple of tensor for every resolution level + """ + if isinstance(x, list): + x = x[-1] + + _B, _D, _Cin, _H, _W = x.shape + + y_hat = None + y_level_outputs = None + + # compute the block outputs + if self.num_resolution_levels >= 1: + x_00 = self.B00(x) + + if self.num_resolution_levels >= 2: + x_01 = self.B01(x_00) + x_11 = self.B11(self.down_00_11(x_00)) + + if self.num_resolution_levels >= 3: + x_02 = self.B02(x_01 + x_00) + + x_12 = self.B12(x_11 + self.down_01_12(x_01)) + + x_22 = self.B22(self.down_11_22(x_11) + self.down_01_22(x_01)) + + if self.num_resolution_levels >= 4: + x_03 = self.B03(x_02 + x_00 + x_01) + + x_13 = self.B13(x_12 + self.down_02_13(x_02) + x_11) + + x_23 = self.B23(x_22 + self.down_12_23(x_12) + self.down_02_23(x_02)) + + x_33 = self.B33(self.down_22_33(x_22) + self.down_12_33(x_12) + self.down_02_33(x_02)) + + if self.num_resolution_levels >= 5: + x_04 = self.B04(x_03 + x_02 + x_01 + x_00) + + x_14 = self.B14(x_13 + self.down_03_14(x_03) + x_12 + x_11) + + x_24 = self.B24(x_23 + self.down_13_24(x_13) + self.down_03_24(x_03) + x_22) + + x_34 = self.B34( + x_33 + self.down_23_34(x_23) + self.down_13_34(x_13) + self.down_03_34(x_03) + ) + + x_44 = self.B44( + self.down_33_44(x_33) + + self.down_23_44(x_23) + + self.down_13_44(x_13) + + self.down_03_44(x_03) + ) + + if self.num_resolution_levels == 1: + y_hat_0 = self.output_B0(x_00) + y_hat = y_hat_0 + y_level_outputs = [y_hat_0] + + if self.num_resolution_levels == 2: + y_hat_0 = x_01 + self.up_1_0(x_11) + y_hat_1 = x_11 + self.down_0_1(x_01) + + y_hat_0 = self.output_B0(y_hat_0) + y_hat_1 = self.output_B1(y_hat_1) + + y_hat = torch.cat((y_hat_0, self.up_1(y_hat_1)), dim=1) + + y_level_outputs = [y_hat_0, y_hat_1] + + if self.num_resolution_levels == 3: + y_hat_0 = x_02 + self.up_1_0(x_12) + self.up_2_0(x_22) + y_hat_1 = self.down_0_1(x_02) + x_12 + self.up_2_1(x_22) + y_hat_2 = self.down_0_2(x_02) + self.down_1_2(x_12) + x_22 + + y_hat_0 = self.output_B0(y_hat_0) + y_hat_1 = self.output_B1(y_hat_1) + y_hat_2 = self.output_B2(y_hat_2) + + y_hat = torch.cat((y_hat_0, self.up_1(y_hat_1), self.up_2(y_hat_2)), dim=1) + y_level_outputs = [y_hat_0, y_hat_1, y_hat_2] + + if self.num_resolution_levels == 4: + y_hat_0 = x_03 + self.up_1_0(x_13) + self.up_2_0(x_23) + self.up_3_0(x_33) + y_hat_1 = self.down_0_1(x_03) + x_13 + self.up_2_1(x_23) + self.up_3_1(x_33) + y_hat_2 = self.down_0_2(x_03) + self.down_1_2(x_13) + x_23 + self.up_3_2(x_33) + y_hat_3 = self.down_0_3(x_03) + self.down_1_3(x_13) + self.down_2_3(x_23) + x_33 + + y_hat_0 = self.output_B0(y_hat_0) + y_hat_1 = self.output_B1(y_hat_1) + y_hat_2 = self.output_B2(y_hat_2) + y_hat_3 = self.output_B3(y_hat_3) + + y_hat = torch.cat( + (y_hat_0, self.up_1(y_hat_1), self.up_2(y_hat_2), self.up_3(y_hat_3)), dim=1 + ) + + y_level_outputs = [y_hat_0, y_hat_1, y_hat_2, y_hat_3] + + if self.num_resolution_levels == 5: + y_hat_0 = ( + x_04 + + self.up_1_0(x_14) + + self.up_2_0(x_24) + + self.up_3_0(x_34) + + self.up_4_0(x_44) + ) + y_hat_1 = ( + self.down_0_1(x_04) + + x_14 + + self.up_2_1(x_24) + + self.up_3_1(x_34) + + self.up_4_1(x_44) + ) + y_hat_2 = ( + self.down_0_2(x_04) + + self.down_1_2(x_14) + + x_24 + + self.up_3_2(x_34) + + self.up_4_2(x_44) + ) + y_hat_3 = ( + self.down_0_3(x_04) + + self.down_1_3(x_14) + + self.down_2_3(x_24) + + x_34 + + self.up_4_3(x_44) + ) + y_hat_4 = ( + self.down_0_4(x_04) + + self.down_1_4(x_14) + + self.down_2_4(x_24) + + self.down_3_4(x_34) + + x_44 + ) + + y_hat_0 = self.output_B0(y_hat_0) + y_hat_1 = self.output_B1(y_hat_1) + y_hat_2 = self.output_B2(y_hat_2) + y_hat_3 = self.output_B3(y_hat_3) + y_hat_4 = self.output_B4(y_hat_4) + + y_hat = torch.cat( + ( + y_hat_0, + self.up_1(y_hat_1), + self.up_2(y_hat_2), + self.up_3(y_hat_3), + self.up_4(y_hat_4), + ), + dim=1, + ) + + y_level_outputs = [y_hat_0, y_hat_1, y_hat_2, y_hat_3, y_hat_4] + + return [*y_level_outputs, y_hat] + + def __str__(self): + return create_generic_class_str( + obj=self, exclusion_list=[nn.Module, OrderedDict, Block, DownSample, UpSample] + ) + + def get_number_of_output_channels(self): + return int( + self.config.num_of_channels + * sum([np.power(2, k) for k in range(self.config.num_resolution_levels)]) + ) diff --git a/src/snraware/components/model/backbone/backbone_soanet.py b/src/snraware/components/model/backbone/backbone_soanet.py new file mode 100644 index 0000000..d6fd2e6 --- /dev/null +++ b/src/snraware/components/model/backbone/backbone_soanet.py @@ -0,0 +1,179 @@ +"""Backbone model - Stack of attention architecture.""" + +from collections import OrderedDict + +import numpy as np +import torch.nn as nn +from colorama import Fore, Style + +from snraware.components.model.attention import * +from snraware.components.model.backbone.backbone_base import ( + BackboneBase, + DownSample, + UpSample, + set_window_patch_sizes_keep_num_window, + set_window_patch_sizes_keep_window_size, +) +from snraware.components.model.backbone.blocks import * +from snraware.components.model.backbone.cells import * + +__all__ = ["SOAnet"] + + +# ------------------------------------------------------------------------------------- +class SOAnet(BackboneBase): + """This class implemented a multi-stage attention network, which is a stack of attention blocks. Every stage only has one block.""" + + def __init__(self, config, input_feature_channels, H, W, D): + """ + @args: + - config (Namespace): runtime namespace for setup + - input_feature_channels (List[int]): list of ints indicating the number of channels in each input tensor. + + @args (from config): + + --------------------------------------------------------------- + model specific arguments + --------------------------------------------------------------- + + - C (int): number of operation channels, when resolution is reduced by x2, number of channels will increase by x2 + - self.num_stages (int): number of stages; each deeper stage will reduce spatial size by x2 and increase number of channels by x2. + """ + super().__init__(config) + + if isinstance(input_feature_channels, list): + C_in = input_feature_channels[-1] + else: + C_in = input_feature_channels + + self.C = config.num_of_channels + self.num_stages = config.num_stages + self.downsample = config.downsample + + self.block_config = config.block_config + if len(self.block_config) < self.num_stages: + self.block_config.extend( + [config.block_config[-1] for _i in range(self.num_stages - len(self.block_config))] + ) + + # compute number of windows and patches + self.num_wind = [ + H // self.config.block.cell.window_size[0], + W // self.config.block.cell.window_size[1], + ] + self.num_patch = [ + self.config.block.cell.window_size[0] // self.config.block.cell.patch_size[0], + self.config.block.cell.window_size[1] // self.config.block.cell.patch_size[1], + ] + + if len(self.config.block.cell.window_size) == 3: + self.num_wind.append(D // self.config.block.cell.window_size[2]) + self.num_patch.append( + self.config.block.cell.window_size[2] // self.config.block.cell.patch_size[2] + ) + + model_config = dict() + model_config["C_in"] = C_in + model_config["C_out"] = self.C + model_config["H"] = H + model_config["W"] = W + model_config["D"] = D + model_config["num_wind"] = self.num_wind + model_config["num_patch"] = self.num_patch + model_config["block_config"] = config.block_config[0] + model_config["window_size"] = self.config.block.cell.window_size + model_config["patch_size"] = self.config.block.cell.patch_size + + print( + f"{Fore.CYAN}=====> Create SOANET, {self.config.block.cell_type}, {self.config.block.cell.attention_type} - {H} - {W} - {D} - {self.config.block.cell.window_size} - {self.config.block.cell.patch_size} - {self.config.block.cell_type} - {self.config.block.block_dense_connection} <====={Style.RESET_ALL}" + ) + + self.layers = nn.ModuleList() + + stage_C_in = C_in + stage_C_out = self.C + + for stage in range(self.num_stages): + module_name = f"Stage{stage}" + + model_config["C_in"] = stage_C_in + model_config["C_out"] = stage_C_out + model_config["H"] = H if not self.downsample else H // int(np.power(2, stage)) + model_config["W"] = W if not self.downsample else W // int(np.power(2, stage)) + + if self.config.block.cell.window_sizing_method == "keep_num_window": + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name=module_name, + ) + elif self.config.block.cell.window_sizing_method == "keep_window_size": + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + model_config["window_size"], + model_config["patch_size"], + module_name=module_name, + ) + else: # mixed + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + model_config["window_size"], + model_config["patch_size"], + module_name=module_name, + ) + + model_config["block_config"] = self.block_config[stage] + self.layers.append(Block(**self.get_block_parameters(model_config))) + + print( + f"{Fore.BLUE}{module_name} -- {model_config['C_in']} to {model_config['C_out']} --- {[type(m).__name__ for m in model_config['block_config']]}{Style.RESET_ALL}" + ) + + if self.downsample: + if stage < self.num_stages - 1: + self.downsample = DownSample( + N=1, + C_in=stage_C_out, + C_out=2 * stage_C_out, + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + self.layers.append(self.downsample) + + print( + f"{Fore.BLUE}{module_name}, self.downsample -- {stage_C_out} to {2 * stage_C_out}{Style.RESET_ALL}" + ) + + # update the stage input and output channels + stage_C_in = 2 * stage_C_out + stage_C_out = 2 * stage_C_out + else: + stage_C_in = stage_C_out + stage_C_out = 2 * stage_C_out + + print(f"{Fore.CYAN}=====> Completed, create SOANET <====={Style.RESET_ALL}") + + def forward(self, x): + if isinstance(x, list): + x = x[-1] + + y_level_outputs = [] + + y_hat = x + for a_layer in self.layers: + y_hat = a_layer(y_hat) + y_level_outputs.append(y_hat) + + return y_level_outputs + + def __str__(self): + return create_generic_class_str( + obj=self, exclusion_list=[nn.Module, OrderedDict, Block, DownSample, UpSample] + ) + + def get_number_of_output_channels(self): + return int(self.config.num_of_channels * np.power(2, self.config.num_stages - 1)) diff --git a/src/snraware/components/model/backbone/backbone_unet.py b/src/snraware/components/model/backbone/backbone_unet.py new file mode 100644 index 0000000..13bb468 --- /dev/null +++ b/src/snraware/components/model/backbone/backbone_unet.py @@ -0,0 +1,651 @@ +"""Backbone model - UNet architecture, with attention.""" + +from collections import OrderedDict + +import torch +import torch.nn as nn +from colorama import Fore, Style + +from snraware.components.model.attention import * +from snraware.components.model.backbone.backbone_base import ( + BackboneBase, + DownSample, + UpSample, + set_window_patch_sizes_keep_num_window, + set_window_patch_sizes_keep_window_size, +) +from snraware.components.model.backbone.blocks import * +from snraware.components.model.backbone.cells import * + +__all__ = ["Unet"] + + +# ------------------------------------------------------------------------------------------------- +class _unet_attention(nn.Module): + """ + Unet attention scheme. + + The query q is from the lower resolution level [B, C_q, T, H, W]; + The value x is from the higher resolution level [B, C, T, H, W] + + Output is a gated value tensor [B, C, T, H, W] + """ + + def __init__(self, C_q=32, C=16) -> None: + super().__init__() + + self.C_q = C_q + self.C = C + + self.conv_query = Conv2DExt( + in_channels=self.C_q, + out_channels=self.C, + kernel_size=[1, 1], + stride=[1, 1], + padding=[0, 0], + channel_first=False, + ) + self.conv_x = Conv2DExt( + in_channels=self.C, + out_channels=self.C, + kernel_size=[1, 1], + stride=[1, 1], + padding=[0, 0], + channel_first=False, + ) + + self.conv_gate = Conv2DExt( + in_channels=self.C, + out_channels=1, + kernel_size=[1, 1], + stride=[1, 1], + padding=[0, 0], + channel_first=False, + ) + + def forward(self, q, x): + q = permute_to_B_T_C_H_W(q) + x = permute_to_B_T_C_H_W(x) + + v = F.relu(self.conv_query(q) + self.conv_x(x), inplace=False) + g = torch.sigmoid(self.conv_gate(v)) # [B, T, 1, H, W] + + y = x * g + + y = permute_to_B_C_T_H_W(y) + + return y + + +# ------------------------------------------------------------------------------------------------- +# stcnnt unet + + +class Unet(BackboneBase): + """ + This class implemented an Unet with maximal 5 down/upsample levels. + + The attention window_size and patch_size are in the unit of pixels and set for the top level resolution. For every downsample level, they are reduced by x2 to keep the number of windows roughly the same. + """ + + def __init__(self, config, input_feature_channels, H, W, D): + """ + @args: + - config (Namespace): runtime namespace for setup + - input_feature_channels (List[int]): list of ints indicating the number of channels in each input tensor. + + @args (from config): + + --------------------------------------------------------------- + model specific arguments + --------------------------------------------------------------- + + - C (int): number of operation channels, when resolution is reduced by x2, number of channels will increase by x2 + - num_resolution_levels (int): number of resolution levels; each deeper level will reduce spatial size by x2 + + - block_str (str | list of strings): This string is the "Block string" to define the attention layers in a block. If a list of string is given, each string defines the attention structure for a resolution level. The last string is the bridge structure. + + During the configuration process, the block_str is parsed and converted into a list of block configurations for each resolution level. + + - use_unet_attention (bool): whether to use unet attention from lower resolution to higher resolution + """ + super().__init__(config) + + if isinstance(input_feature_channels, list): + C_in = input_feature_channels[-1] + else: + C_in = input_feature_channels + + assert config.num_resolution_levels <= 5 and config.num_resolution_levels >= 1, ( + "Maximal number of resolution levels is 5" + ) + + self.C = config.num_of_channels + self.num_resolution_levels = config.num_resolution_levels + self.use_unet_attention = config.use_unet_attention + + self.block_config = config.block_config + if ( + len(self.block_config) < self.num_resolution_levels + 1 + ): # num_resolution_levels+1 includes the bridge + self.block_config.extend( + [ + config.block_config[-1] + for _i in range(self.num_resolution_levels + 1 - len(self.block_config)) + ] + ) + + # compute number of windows and patches + self.num_wind = [ + H // config.block.cell.window_size[0], + W // config.block.cell.window_size[1], + ] + self.num_patch = [ + config.block.cell.window_size[0] // config.block.cell.patch_size[0], + config.block.cell.window_size[1] // config.block.cell.patch_size[1], + ] + + if len(config.block.cell.window_size) == 3: + self.num_wind.append(D // config.block.cell.window_size[2]) + self.num_patch.append( + config.block.cell.window_size[2] // config.block.cell.patch_size[2] + ) + + model_config = dict() + model_config["C_in"] = C_in + model_config["C_out"] = self.C + model_config["H"] = H + model_config["W"] = W + model_config["D"] = D + model_config["num_wind"] = self.num_wind + model_config["num_patch"] = self.num_patch + model_config["block_config"] = config.block_config[0] + model_config["window_size"] = self.config.block.cell.window_size + model_config["patch_size"] = self.config.block.cell.patch_size + + print( + f"{Fore.CYAN}=====> Create UNET, {self.config.block.cell_type}, {self.config.block.cell.attention_type} - {H} - {W} - {D} - {self.config.block.cell.window_size} - {self.config.block.cell.patch_size} - {self.config.block.block_dense_connection} <====={Style.RESET_ALL}" + ) + + window_sizes = [] + patch_sizes = [] + + if self.num_resolution_levels >= 1: + # define D0 + model_config["C_in"] = C_in + model_config["C_out"] = self.C + model_config["H"] = H + model_config["W"] = W + + if self.config.block.cell.window_sizing_method == "keep_num_window": + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name="D0", + ) + elif self.config.block.cell.window_sizing_method == "keep_window_size": + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + model_config["window_size"], + model_config["patch_size"], + module_name="D0", + ) + else: # mixed + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name="D0", + ) + + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + model_config["block_config"] = self.block_config[0] + self.D0 = Block(**self.get_block_parameters(model_config)) + + self.down_0 = DownSample( + N=1, + C_in=model_config["C_out"], + C_out=model_config["C_out"], + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 2: + # define D1 + model_config["C_in"] = self.C + model_config["C_out"] = 2 * self.C + model_config["H"] = H // 2 + model_config["W"] = W // 2 + + if self.config.block.cell.window_sizing_method == "keep_num_window": + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name="D1", + ) + elif self.config.block.cell.window_sizing_method == "keep_window_size": + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[0], + patch_sizes[0], + module_name="D1", + ) + else: # mixed + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[0], + patch_sizes[0], + module_name="D1", + ) + + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + model_config["block_config"] = self.block_config[1] + self.D1 = Block(**self.get_block_parameters(model_config)) + + self.down_1 = DownSample( + N=1, + C_in=model_config["C_out"], + C_out=model_config["C_out"], + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 3: + # define D2 + model_config["C_in"] = 2 * self.C + model_config["C_out"] = 4 * self.C + model_config["H"] = H // 4 + model_config["W"] = W // 4 + + if self.config.block.cell.window_sizing_method == "keep_num_window": + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name="D2", + ) + elif self.config.block.cell.window_sizing_method == "keep_window_size": + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[1], + patch_sizes[1], + module_name="D2", + ) + else: # mixed + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + [v // 2 for v in self.num_wind], + self.num_patch, + module_name="D2", + ) + + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + model_config["block_config"] = self.block_config[2] + self.D2 = Block(**self.get_block_parameters(model_config)) + + self.down_2 = DownSample( + N=1, + C_in=model_config["C_out"], + C_out=model_config["C_out"], + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 4: + # define D3 + model_config["C_in"] = 4 * self.C + model_config["C_out"] = 8 * self.C + model_config["H"] = H // 8 + model_config["W"] = W // 8 + + if self.config.block.cell.window_sizing_method == "keep_num_window": + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name="D3", + ) + elif self.config.block.cell.window_sizing_method == "keep_window_size": + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[2], + patch_sizes[2], + module_name="D3", + ) + else: # mixed + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[2], + patch_sizes[2], + module_name="D3", + ) + + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + model_config["block_config"] = self.block_config[3] + self.D3 = Block(**self.get_block_parameters(model_config)) + + self.down_3 = DownSample( + N=1, + C_in=model_config["C_out"], + C_out=model_config["C_out"], + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + if self.num_resolution_levels >= 5: + # define D4 + model_config["C_in"] = 8 * self.C + model_config["C_out"] = 16 * self.C + model_config["H"] = H // 16 + model_config["W"] = W // 16 + + if self.config.block.cell.window_sizing_method == "keep_num_window": + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + self.num_wind, + self.num_patch, + module_name="D4", + ) + elif self.config.block.cell.window_sizing_method == "keep_window_size": + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[3], + patch_sizes[3], + module_name="D4", + ) + else: # mixed + model_config = set_window_patch_sizes_keep_num_window( + model_config, + [model_config["H"], model_config["W"]], + [v // 4 for v in self.num_wind], + self.num_patch, + module_name="D4", + ) + + window_sizes.append(model_config["window_size"]) + patch_sizes.append(model_config["patch_size"]) + + model_config["block_config"] = self.block_config[4] + self.D4 = Block(**self.get_block_parameters(model_config)) + + self.down_4 = DownSample( + N=1, + C_in=model_config["C_out"], + C_out=model_config["C_out"], + use_interpolation=self.use_interpolation, + with_conv=self.with_conv, + ) + + # define the bridge + model_config["C_in"] = model_config["C_out"] + model_config["block_config"] = self.block_config[-1] + model_config["H"] //= 2 + model_config["W"] //= 2 + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[-1], + patch_sizes[-1], + module_name="bridge", + ) + self.bridge = Block(**self.get_block_parameters(model_config)) + + if self.num_resolution_levels >= 5: + self.up_4 = UpSample( + N=1, + C_in=16 * self.C, + C_out=16 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.use_unet_attention: + self.attention_4 = _unet_attention(C_q=16 * self.C, C=16 * self.C) + + model_config["C_in"] = 32 * self.C + model_config["C_out"] = 8 * self.C + model_config["H"] = H // 16 + model_config["W"] = W // 16 + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[3], + patch_sizes[3], + module_name="U4", + ) + model_config["block_config"] = self.block_config[4] + self.U4 = Block(**self.get_block_parameters(model_config)) + + if self.num_resolution_levels >= 4: + self.up_3 = UpSample( + N=1, + C_in=8 * self.C, + C_out=8 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.use_unet_attention: + self.attention_3 = _unet_attention(C_q=8 * self.C, C=8 * self.C) + + model_config["C_in"] = 16 * self.C + model_config["C_out"] = 4 * self.C + model_config["H"] = H // 8 + model_config["W"] = W // 8 + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[2], + patch_sizes[2], + module_name="U3", + ) + model_config["block_config"] = self.block_config[3] + self.U3 = Block(**self.get_block_parameters(model_config)) + + if self.num_resolution_levels >= 3: + self.up_2 = UpSample( + N=1, + C_in=4 * self.C, + C_out=4 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.use_unet_attention: + self.attention_2 = _unet_attention(C_q=4 * self.C, C=4 * self.C) + + model_config["C_in"] = 8 * self.C + model_config["C_out"] = 2 * self.C + model_config["H"] = H // 4 + model_config["W"] = W // 4 + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[1], + patch_sizes[1], + module_name="U2", + ) + model_config["block_config"] = self.block_config[2] + self.U2 = Block(**self.get_block_parameters(model_config)) + + if self.num_resolution_levels >= 2: + self.up_1 = UpSample( + N=1, + C_in=2 * self.C, + C_out=2 * self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.use_unet_attention: + self.attention_1 = _unet_attention(C_q=2 * self.C, C=2 * self.C) + + model_config["C_in"] = 4 * self.C + model_config["C_out"] = self.C + model_config["H"] = H // 2 + model_config["W"] = W // 2 + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[0], + patch_sizes[0], + module_name="U1", + ) + model_config["block_config"] = self.block_config[1] + self.U1 = Block(**self.get_block_parameters(model_config)) + + if self.num_resolution_levels >= 1: + self.up_0 = UpSample( + N=1, + C_in=self.C, + C_out=self.C, + method=self.config.block.cell.upsample_method, + with_conv=self.with_conv, + ) + if self.use_unet_attention: + self.attention_0 = _unet_attention(C_q=self.C, C=self.C) + + model_config["C_in"] = 2 * self.C + model_config["C_out"] = self.C + model_config["H"] = H + model_config["W"] = W + model_config = set_window_patch_sizes_keep_window_size( + model_config, + [model_config["H"], model_config["W"]], + window_sizes[0], + patch_sizes[0], + module_name="U0", + ) + model_config["block_config"] = self.block_config[0] + self.U0 = Block(**self.get_block_parameters(model_config)) + + print(f"{Fore.CYAN}=====> Completed, create UNET <====={Style.RESET_ALL}") + + def forward(self, x): + if isinstance(x, list): + x = x[-1] + + _B, _D, _Cin, _H, _W = x.shape + + # first we go down the resolution ... + if self.num_resolution_levels >= 1: + x_0 = self.D0(x) + x_d_0 = self.down_0(x_0) + + if self.num_resolution_levels >= 2: + x_1 = self.D1(x_d_0) + x_d_1 = self.down_1(x_1) + + if self.num_resolution_levels >= 3: + x_2 = self.D2(x_d_1) + x_d_2 = self.down_2(x_2) + + if self.num_resolution_levels >= 4: + x_3 = self.D3(x_d_2) + x_d_3 = self.down_3(x_3) + + if self.num_resolution_levels >= 5: + x_4 = self.D4(x_d_3) + x_d_4 = self.down_4(x_4) + + # now we go up the resolution ... + if self.num_resolution_levels == 1: + y_d_0 = self.bridge(x_d_0) + y_0 = self.up_0(y_d_0) + x_gated_0 = self.attention_0(q=y_0, x=x_0) if self.use_unet_attention else x_0 + y_hat = self.U0(torch.cat((x_gated_0, y_0), dim=1)) + + if self.num_resolution_levels == 2: + y_d_1 = self.bridge(x_d_1) + y_1 = self.up_1(y_d_1) + x_gated_1 = self.attention_1(q=y_1, x=x_1) if self.use_unet_attention else x_1 + y_d_0 = self.U1(torch.cat((x_gated_1, y_1), dim=1)) + + y_0 = self.up_0(y_d_0) + x_gated_0 = self.attention_0(q=y_0, x=x_0) if self.use_unet_attention else x_0 + y_hat = self.U0(torch.cat((x_gated_0, y_0), dim=1)) + + if self.num_resolution_levels == 3: + y_d_2 = self.bridge(x_d_2) + y_2 = self.up_2(y_d_2) + x_gated_2 = self.attention_2(q=y_2, x=x_2) if self.use_unet_attention else x_2 + y_d_1 = self.U2(torch.cat((x_gated_2, y_2), dim=1)) + + y_1 = self.up_1(y_d_1) + x_gated_1 = self.attention_1(q=y_1, x=x_1) if self.use_unet_attention else x_1 + y_d_0 = self.U1(torch.cat((x_gated_1, y_1), dim=1)) + + y_0 = self.up_0(y_d_0) + x_gated_0 = self.attention_0(q=y_0, x=x_0) if self.use_unet_attention else x_0 + y_hat = self.U0(torch.cat((x_gated_0, y_0), dim=1)) + + if self.num_resolution_levels == 4: + y_d_3 = self.bridge(x_d_3) + y_3 = self.up_3(y_d_3) + x_gated_3 = self.attention_3(q=y_3, x=x_3) if self.use_unet_attention else x_3 + y_d_2 = self.U3(torch.cat((x_gated_3, y_3), dim=1)) + + y_2 = self.up_2(y_d_2) + x_gated_2 = self.attention_2(q=y_2, x=x_2) if self.use_unet_attention else x_2 + y_d_1 = self.U2(torch.cat((x_gated_2, y_2), dim=1)) + + y_1 = self.up_1(y_d_1) + x_gated_1 = self.attention_1(q=y_1, x=x_1) if self.use_unet_attention else x_1 + y_d_0 = self.U1(torch.cat((x_gated_1, y_1), dim=1)) + + y_0 = self.up_0(y_d_0) + x_gated_0 = self.attention_0(q=y_0, x=x_0) if self.use_unet_attention else x_0 + y_hat = self.U0(torch.cat((x_gated_0, y_0), dim=1)) + + if self.num_resolution_levels == 5: + y_d_4 = self.bridge(x_d_4) + y_4 = self.up_4(y_d_4) + x_gated_4 = self.attention_4(q=y_4, x=x_4) if self.use_unet_attention else x_4 + y_d_3 = self.U4(torch.cat((x_gated_4, y_4), dim=1)) + + y_3 = self.up_3(y_d_3) + x_gated_3 = self.attention_3(q=y_3, x=x_3) if self.use_unet_attention else x_3 + y_d_2 = self.U3(torch.cat((x_gated_3, y_3), dim=1)) + + y_2 = self.up_2(y_d_2) + x_gated_2 = self.attention_2(q=y_2, x=x_2) if self.use_unet_attention else x_2 + y_d_1 = self.U2(torch.cat((x_gated_2, y_2), dim=1)) + + y_1 = self.up_1(y_d_1) + x_gated_1 = self.attention_1(q=y_1, x=x_1) if self.use_unet_attention else x_1 + y_d_0 = self.U1(torch.cat((x_gated_1, y_1), dim=1)) + + y_0 = self.up_0(y_d_0) + x_gated_0 = self.attention_0(q=y_0, x=x_0) if self.use_unet_attention else x_0 + y_hat = self.U0(torch.cat((x_gated_0, y_0), dim=1)) + + return [y_hat] + + def __str__(self): + return create_generic_class_str( + obj=self, + exclusion_list=[nn.Module, OrderedDict, Block, DownSample, UpSample, _unet_attention], + ) + + def get_number_of_output_channels(self): + return int(self.config.num_of_channels) diff --git a/src/snraware/components/model/backbone/blocks.py b/src/snraware/components/model/backbone/blocks.py new file mode 100644 index 0000000..d7bab56 --- /dev/null +++ b/src/snraware/components/model/backbone/blocks.py @@ -0,0 +1,248 @@ +""" +A block contains a set of cells. Block structure is configurable by the 'block string'. +For example, 'L1T1G1' means to configure with a local attention (L1) with mixer (1 after 'L'), followed by a temporal attention with mixer (T1) +and a global attention with mixer (G1). + +For more details on the configuration, please refer to the Cell and Block section of the documentation. + +""" + +from snraware.components.model.attention import * +from snraware.components.model.config import ( + ConvolutionConfig, + SpatialGlobal3DConfig, + SpatialGlobalConfig, + SpatialLocal3DConfig, + SpatialLocalConfig, + SpatialViTConfig, + Swin3DConfig, + TemporalAttentionConfig, + ViT3DConfig, +) +from snraware.components.setup.status import create_generic_class_str + +from .cells import * + +__all__ = ["Block"] + +# ------------------------------------------------------------------------------------------------- + + +class Block(nn.Module): + def __init__(self, block_config, C_in, C_out, H, W, D, config): + """ + Block of multiple transformer cells stacked on top of each other. + + @args: + - block_config (list[CellTypeVariant]): block configuration defines the structure of the block. + every element of block config is for an attention types defined in the snraware.components.model.config. + + - C_in (int): number of input channels. + - C_out (int): number of output channels. + - H (int): height of the input feature map. + - W (int): width of the input feature map. + - D (int): depth of the input feature map, default is 1 for 2D data. + - config: configuration object containing additional parameters + """ + super().__init__() + + self.block_config = block_config + self.C_in = C_in + self.C_out = C_out + self.H = H + self.W = W + self.D = D + self.config = config + + window_size = self.config.cell.window_size + patch_size = self.config.cell.patch_size + + self.window_size = window_size + self.patch_size = patch_size + + num_wind = [ + max(H // window_size[0], 1), + max(W // window_size[1], 1), + max(D // window_size[2], 1), + ] + num_patch = [max(a // b, 1) for a, b in zip(window_size, patch_size, strict=False)] + + self.num_wind = num_wind + self.num_patch = num_patch + + self.cell_type = self.config.cell_type + self.block_dense_connection = self.config.block_dense_connection + + # fixed parameters + kernel_size = [3, 3, 3] + stride = [1, 1, 1] + padding = [1, 1, 1] + stride_s = [1, 1, 1] + mixer_kernel_size = [3, 3, 3] + mixer_stride = [1, 1, 1] + mixer_padding = [1, 1, 1] + is_causal = False + shuffle_in_window = False + use_flash_attention = False + with_mixer = True + + self.cells = [] + + for i, atten_config in enumerate(block_config): + if isinstance(atten_config, TemporalAttentionConfig): + att_type = "temporal" + elif isinstance(atten_config, SpatialLocalConfig): + att_type = "local" + elif isinstance(atten_config, SpatialGlobalConfig): + att_type = "global" + shuffle_in_window = atten_config.shuffle_in_window + elif isinstance(atten_config, ConvolutionConfig): + if atten_config.conv_type == "conv2d": + att_type = "conv2d" + elif atten_config.conv_type == "conv3d": + att_type = "conv3d" + else: + raise ValueError(f"Incorrect conv_type: {atten_config.conv_type}") + elif isinstance(atten_config, SpatialLocal3DConfig): + att_type = "local_3d" + elif isinstance(atten_config, SpatialGlobal3DConfig): + att_type = "global_3d" + shuffle_in_window = atten_config.shuffle_in_window + elif isinstance(atten_config, SpatialViTConfig): + att_type = "vit_2d" + elif isinstance(atten_config, ViT3DConfig): + att_type = "vit_3d" + elif isinstance(atten_config, Swin3DConfig): + if atten_config.shifted_window_attention: + att_type = "swin_3d_shifted" + else: + att_type = "swin_3d" + else: + raise ValueError(f"Incorrect atten_config: {atten_config}") + + C = C_in if i == 0 else C_out + + if self.cell_type.lower() == "sequential": + self.cells.append( + ( + f"cell_{i}", + Cell( + C_in=C, + C_out=C_out, + H=H, + W=W, + D=D, + att_mode=att_type, + attention_type=self.config.cell.attention_type, + mixer_type=self.config.cell.mixer_type, + window_size=window_size, + patch_size=patch_size, + num_wind=num_wind, + num_patch=num_patch, + is_causal=is_causal, + n_head=self.config.cell.n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_s=stride_s, + stride_t=self.config.cell.temporal.stride_qk, + activation_func=self.config.cell.activation_func, + mixer_kernel_size=mixer_kernel_size, + mixer_stride=mixer_stride, + mixer_padding=mixer_padding, + normalize_Q_K=self.config.cell.normalize_Q_K, + att_dropout_p=self.config.cell.att_dropout_p, + dropout_p=self.config.cell.dropout_p, + cosine_att=self.config.cell.cosine_att, + temporal_multi_head_att_on_C_H_W=self.config.cell.temporal.temporal_multi_head_att_on_C_H_W, + att_with_relative_position_bias=self.config.cell.att_with_relative_position_bias, + att_with_output_proj=self.config.cell.att_with_output_proj, + scale_ratio_in_mixer=self.config.cell.scale_ratio_in_mixer, + with_mixer=with_mixer, + norm_mode=self.config.cell.norm_mode, + shuffle_in_window=shuffle_in_window, + use_flash_attention=use_flash_attention, + ), + ) + ) + else: + self.cells.append( + ( + f"cell_{i}", + Parallel_Cell( + C_in=C, + C_out=C_out, + H=H, + W=W, + D=D, + att_mode=att_type, + attention_type=self.config.cell.attention_type, + mixer_type=self.config.cell.mixer_type, + window_size=self.config.cell.window_size, + patch_size=self.config.cell.patch_size, + num_wind=self.config.cell.num_wind, + num_patch=self.config.cell.num_patch, + is_causal=is_causal, + n_head=self.config.cell.n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_s=stride_s, + stride_t=self.config.cell.temporal.stride_qk, + activation_func=self.config.cell.activation_func, + mixer_kernel_size=mixer_kernel_size, + mixer_stride=mixer_stride, + mixer_padding=mixer_padding, + normalize_Q_K=self.config.cell.normalize_Q_K, + att_dropout_p=self.config.cell.att_dropout_p, + dropout_p=self.config.cell.dropout_p, + cosine_att=self.config.cell.cosine_att, + temporal_multi_head_att_on_C_H_W=self.config.cell.temporal.temporal_multi_head_att_on_C_H_W, + att_with_relative_position_bias=self.config.cell.att_with_relative_position_bias, + att_with_output_proj=self.config.cell.att_with_output_proj, + scale_ratio_in_mixer=self.config.cell.scale_ratio_in_mixer, + with_mixer=with_mixer, + norm_mode=self.config.cell.norm_mode, + shuffle_in_window=shuffle_in_window, + use_flash_attention=use_flash_attention, + ), + ) + ) + + self.make_block() + + @property + def device(self): + return next(self.parameters()).device + + def make_block(self): + self.block = nn.ModuleDict(OrderedDict(self.cells)) + + def forward(self, x): + num_cells = len(self.block) + + if self.block_dense_connection: + block_res = [] + + for c in range(num_cells): + if c == 0: + block_res.append(self.block[f"cell_{c}"](x)) + else: + input = 0 + for k in block_res: + input = input + k + block_res.append(self.block[f"cell_{c}"](input)) + + x = block_res[-1] + else: + for c in range(num_cells): + x = self.block[f"cell_{c}"](x) + + return x + + def __str__(self): + res = create_generic_class_str(self) + return res + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/backbone/cells.py b/src/snraware/components/model/backbone/cells.py new file mode 100644 index 0000000..c0890c3 --- /dev/null +++ b/src/snraware/components/model/backbone/cells.py @@ -0,0 +1,744 @@ +""" +This file implements the cell structure in the model architecture. A cell is a 'transformer module' consisting +of attention layers, normalization layers and mixers with non-linearities. + +Two type of cells are implemented here: + +- sequential norm first, transformer model +- Parallel cell, proposed in https://arxiv.org/abs/2302.05442 + +""" + +import torchvision + +from snraware.components.model.attention import * +from snraware.components.setup.status import create_generic_class_str + +__all__ = ["Cell", "Parallel_Cell"] + +# ------------------------------------------------------------------------------------------------- + + +class Cell_Base(nn.Module): + """Base class for the cell.""" + + def __init__( + self, + C_in, + C_out, + H, + W, + D, + att_mode, + attention_type="conv", + mixer_type="conv", + window_size=None, + patch_size=None, + num_wind=None, + num_patch=None, + is_causal=False, + n_head=8, + kernel_size=(3, 3, 3), + stride=(1, 1, 1), + padding=(1, 1, 1), + activation_func="prelu", + stride_s=(1, 1, 1), + stride_t=(1, 1, 1), + mixer_kernel_size=(5, 5, 5), + mixer_stride=(1, 1, 1), + mixer_padding=(2, 2, 2), + normalize_Q_K=False, + att_dropout_p=0.0, + dropout_p=0.1, + cosine_att=True, + temporal_multi_head_att_on_C_H_W=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + scale_ratio_in_mixer=4.0, + with_mixer=True, + norm_mode="layer", + shuffle_in_window=False, + use_flash_attention=False, + ): + """ + @args: + - C_in (int): number of input channels + - C_out (int): number of output channels + - H, W, D (int): expected height, width and depth of the input, [B, C, D, H, W] + - att_mode ("local", "global", "temporal", "local_3d", "global_3d", "vit_2d", "vit_3d", "swin_3d", "swin_3d_shifted", "conv2d", "conv3d"): different attention modules + - attention_type ("conv", "lin"): what to use in attention modules to compute Q/K/V + - mixer_type ("conv", "lin"): type of mixers; for temporal attention, only conv mixer is possible + - window_size (int): size of window in the order of [H, W, D] + - patch_size (int): size of patches + - is_causal (bool): whether to mask attention to imply causality + - n_head (int): number of heads in self attention + - kernel_size, stride, padding (int, int): convolution parameters + - stride_s (int, int): stride for spatial attention k,q matrices + - stride_t (int, int): stride for temporal/frame attention k,q matrices + - normalize_Q_K (bool): whether to use layernorm to normalize Q and K, as in 22B ViT paper + - att_dropout_p (float): probability of dropout for attention coefficients + - dropout_p (float): probability of dropout for attention output + - cosine_att(bool): whether to use cosine attention + - temporal_multi_head_att_on_C_H_W (bool): whether to use temporal multi-head attention on C,H,W dimensions; if false, with channel dimension for the multi-head attention + - att_with_relative_position_bias (bool): whether to use relative position bias in attention + - with_mixer (bool): whether to add a conv2D mixer after attention + - att_with_output_proj (bool): whether to add output projection in the attention layer + - scale_ratio_in_mixer (float): channel scaling ratio in the mixer + - norm_mode ("layer", "batch2d", "instance2d", "batch3d", "instance3d"): + - layer: each C,H,W + - batch2d: along B*T + - instance2d: each H,W + - batch3d: along B + - instance3d: each T,H,W + - shuffle_in_window (bool): whether to shuffle the input in the window for global attention + - use_flash_attention (bool): whether to use flash attention for the attention module. + """ + if num_patch is None: + num_patch = [4, 4, 1] + if num_wind is None: + num_wind = [8, 8, 1] + super().__init__() + + self.C_in = C_in + self.C_out = C_out + self.H = H + self.W = W + self.D = D + self.att_mode = att_mode + self.attention_type = attention_type + self.mixer_type = mixer_type + self.window_size = window_size + self.patch_size = patch_size + self.num_wind = num_wind + self.num_patch = num_patch + self.is_causal = is_causal + self.n_head = n_head + + self.kernel_size = kernel_size + self.stride = stride + self.padding = padding + + self.activation_func = activation_func + + self.stride_s = stride_s + self.stride_t = stride_t + + self.mixer_kernel_size = mixer_kernel_size + self.mixer_stride = mixer_stride + self.mixer_padding = mixer_padding + + self.normalize_Q_K = normalize_Q_K + self.cosine_att = cosine_att + self.att_with_relative_position_bias = att_with_relative_position_bias + self.att_dropout_p = att_dropout_p + self.dropout_p = dropout_p + self.att_with_output_proj = att_with_output_proj + self.with_mixer = with_mixer + self.scale_ratio_in_mixer = scale_ratio_in_mixer + self.norm_mode = norm_mode + self.shuffle_in_window = shuffle_in_window + + self.use_flash_attention = use_flash_attention + + self.temporal_multi_head_att_on_C_H_W = temporal_multi_head_att_on_C_H_W + + self.n1 = create_norm(norm_mode=norm_mode, C=C_in, H=H, W=W, D=D) + self.n2 = create_norm(norm_mode=norm_mode, C=C_out, H=H, W=W, D=D) + + if C_in != C_out: + self.input_proj = Conv2DExt( + C_in, + C_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=True, + channel_first=True, + ) + else: + self.input_proj = nn.Identity() + + if att_mode == "temporal": + if self.temporal_multi_head_att_on_C_H_W: + self.attn = TemporalCnnAttention( + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + is_causal=is_causal, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_t, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_dropout_p=att_dropout_p, + att_with_output_proj=att_with_output_proj, + flash_att=self.use_flash_attention, + ) + else: + self.attn = TemporalChannelCnnAttention( + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + is_causal=is_causal, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=stride_t, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_dropout_p=att_dropout_p, + att_with_output_proj=att_with_output_proj, + flash_att=self.use_flash_attention, + ) + elif att_mode == "local": + self.attn = SpatialLocalAttention( + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + window_size=window_size, + patch_size=patch_size, + num_wind=num_wind, + num_patch=num_patch, + attention_type=attention_type, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=self.stride_s, + normalize_Q_K=normalize_Q_K, + cosine_att=cosine_att, + att_with_relative_position_bias=att_with_relative_position_bias, + att_dropout_p=att_dropout_p, + att_with_output_proj=att_with_output_proj, + ) + elif att_mode == "local_3d": + self.attn = Local3DAttention( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + D=D, + window_size=window_size, + patch_size=patch_size, + num_wind=num_wind, + num_patch=num_patch, + attention_type=attention_type, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=self.stride_s, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + elif att_mode == "global": + self.attn = SpatialGlobalAttention( + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + window_size=[k // 2 for k in window_size], + patch_size=patch_size, + num_wind=[2 * n for n in num_wind], + num_patch=[p // 2 for p in num_patch], + attention_type=attention_type, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=self.stride_s, + normalize_Q_K=normalize_Q_K, + cosine_att=cosine_att, + att_with_relative_position_bias=att_with_relative_position_bias, + att_dropout_p=att_dropout_p, + att_with_output_proj=att_with_output_proj, + shuffle_in_window=shuffle_in_window, + ) + elif att_mode == "global_3d": + self.attn = Global3DAttention( + C_in=C_in, + C_out=C_out, + H=H, + W=H, + D=D, + window_size=window_size, + patch_size=patch_size, + num_wind=num_wind, + num_patch=num_patch, + attention_type=attention_type, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=self.stride_s, + att_dropout_p=att_dropout_p, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + shuffle_in_window=shuffle_in_window, + ) + elif att_mode == "vit_2d": + self.attn = SpatialViTAttention( + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + window_size=window_size, + num_wind=num_wind, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=self.stride_s, + normalize_Q_K=normalize_Q_K, + cosine_att=cosine_att, + att_with_relative_position_bias=att_with_relative_position_bias, + att_dropout_p=att_dropout_p, + att_with_output_proj=att_with_output_proj, + ) + elif att_mode == "vit_3d": + self.attn = ViT3DAttention( + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + D=self.D, + window_size=window_size, + num_wind=num_wind, + kernel_size=kernel_size, + stride=stride, + padding=padding, + stride_qk=self.stride_s, + normalize_Q_K=normalize_Q_K, + cosine_att=cosine_att, + att_with_relative_position_bias=att_with_relative_position_bias, + att_dropout_p=att_dropout_p, + att_with_output_proj=att_with_output_proj, + ) + elif att_mode == "swin_3d": + self.attn = Swin3DAttention( + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + D=self.D, + window_size=window_size, + num_wind=num_wind, + kernel_size=kernel_size, + stride=stride, + padding=padding, + shifted=False, + stride_qk=self.stride_s, + normalize_Q_K=normalize_Q_K, + cosine_att=cosine_att, + att_with_relative_position_bias=att_with_relative_position_bias, + att_dropout_p=att_dropout_p, + att_with_output_proj=att_with_output_proj, + ) + elif att_mode == "swin_3d_shifted": + self.attn = Swin3DAttention( + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + D=self.D, + window_size=window_size, + num_wind=num_wind, + kernel_size=kernel_size, + stride=stride, + padding=padding, + shifted=True, + stride_qk=self.stride_s, + normalize_Q_K=normalize_Q_K, + cosine_att=cosine_att, + att_with_relative_position_bias=att_with_relative_position_bias, + att_dropout_p=att_dropout_p, + att_with_output_proj=att_with_output_proj, + ) + elif att_mode == "conv2d" or att_mode == "conv3d": + self.attn = ConvolutionModule( + conv_type=att_mode, + C_in=C_in, + C_out=C_out, + H=self.H, + W=self.W, + D=self.D, + kernel_size=kernel_size, + stride=stride, + padding=padding, + norm_mode=norm_mode, + activation_func=self.activation_func, + ) + else: + raise NotImplementedError(f"Attention mode not implemented: {att_mode}") + + self.stochastic_depth = torchvision.ops.StochasticDepth(p=self.dropout_p, mode="row") + + self.with_mixer = with_mixer + if self.with_mixer: + self.create_mixer() + + def create_mixer(self): + raise NotImplementedError + + @property + def device(self): + return next(self.parameters()).device + + def __str__(self): + res = create_generic_class_str(self) + return res + + +# ------------------------------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------------------------------- +class Cell(Cell_Base): + """ + The Pre-Norm implementation is used here. + + x-> Norm -> attention -> + -> Norm -> mixer -> + -> logits + |------------------------| |-----------------------| + """ + + def __init__( + self, + C_in, + C_out, + H, + W, + D, + att_mode, + attention_type="conv", + mixer_type="conv", + window_size=None, + patch_size=None, + num_wind=None, + num_patch=None, + is_causal=False, + n_head=8, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + activation_func="prelu", + stride_s=(1, 1), + stride_t=(1, 1), + mixer_kernel_size=(5, 5), + mixer_stride=(1, 1), + mixer_padding=(2, 2), + normalize_Q_K=False, + att_dropout_p=0.0, + dropout_p=0.1, + cosine_att=True, + temporal_multi_head_att_on_C_H_W=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + scale_ratio_in_mixer=4.0, + with_mixer=True, + norm_mode="layer", + shuffle_in_window=False, + use_flash_attention=False, + ): + if num_patch is None: + num_patch = [4, 4] + if num_wind is None: + num_wind = [8, 8] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + D=D, + att_mode=att_mode, + attention_type=attention_type, + mixer_type=mixer_type, + window_size=window_size, + patch_size=patch_size, + num_wind=num_wind, + num_patch=num_patch, + is_causal=is_causal, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + activation_func=activation_func, + stride_s=stride_s, + stride_t=stride_t, + mixer_kernel_size=mixer_kernel_size, + mixer_stride=mixer_stride, + mixer_padding=mixer_padding, + normalize_Q_K=normalize_Q_K, + att_dropout_p=att_dropout_p, + dropout_p=dropout_p, + cosine_att=cosine_att, + temporal_multi_head_att_on_C_H_W=temporal_multi_head_att_on_C_H_W, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + scale_ratio_in_mixer=scale_ratio_in_mixer, + with_mixer=with_mixer, + norm_mode=norm_mode, + shuffle_in_window=shuffle_in_window, + use_flash_attention=use_flash_attention, + ) + + def create_mixer(self): + act_func = create_activation_func(name=self.activation_func) + + if ( + self.mixer_type == "conv" + or self.att_mode == "temporal" + or self.att_mode == "conv2d" + or self.att_mode == "conv3d" + ): + mixer_cha = int(self.scale_ratio_in_mixer * self.C_out) + + self.mlp = nn.Sequential( + Conv2DExt( + self.C_out, + mixer_cha, + kernel_size=self.mixer_kernel_size, + stride=self.mixer_stride, + padding=self.mixer_padding, + bias=True, + channel_first=True, + ), + act_func, + Conv2DExt( + mixer_cha, + self.C_out, + kernel_size=self.mixer_kernel_size, + stride=self.mixer_stride, + padding=self.mixer_padding, + bias=True, + channel_first=True, + ), + ) + elif self.mixer_type == "lin": + # apply mixer on every patch + D = self.attn.get_dimension_for_linear_mixer() + D_prime = int(self.scale_ratio_in_mixer * D) + + self.mlp = nn.Sequential( + nn.Linear(D, D_prime, bias=True), act_func, nn.Linear(D_prime, D, bias=True) + ) + else: + raise NotImplementedError(f"Mixer mode not implemented: {self.mixer_type}") + + def forward(self, x): + x = self.input_proj(x) + self.stochastic_depth(self.attn(self.n1(x))) + + if self.with_mixer: + if ( + self.mixer_type == "conv" + or self.att_mode == "temporal" + or self.att_mode == "conv2d" + or self.att_mode == "conv3d" + ): + x = x + self.stochastic_depth(self.mlp(self.n2(x))) + elif self.mixer_type == "lin": + x = self.n2(x) + if "3d" in self.att_mode: + x = self.attn.im2grid(x) + *Dim, a, b, c, d = x.shape + x = self.mlp(x.reshape((*Dim, -1))) + x = torch.reshape(x, (*Dim, a, b, c, d)) + x = self.attn.grid2im(x) + x = self.stochastic_depth(x) + else: + x = permute_to_B_T_C_H_W(x) + x = self.attn.im2grid(x) + *Dim, C, wh, ww = x.shape + x = self.mlp(x.reshape((*Dim, -1))) + x = self.attn.grid2im(x.reshape((*Dim, C, wh, ww))) + x = permute_to_B_C_T_H_W(x) + x = self.stochastic_depth(x) + + return x + + +# ------------------------------------------------------------------------------------------------- + + +class Parallel_Cell(Cell_Base): + """ + Parallel transformer cell. + + x -> Norm ----> attention --> + -> + -> logits + | |--> CNN mixer-----| | + |----------> input_proj ----> ---| + + """ + + def __init__( + self, + C_in, + C_out, + H, + W, + D, + att_mode, + attention_type="conv", + mixer_type="conv", + window_size=None, + patch_size=None, + num_wind=None, + num_patch=None, + is_causal=False, + n_head=8, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + stride_s=(1, 1), + stride_t=(1, 1), + activation_func="prelu", + mixer_kernel_size=(5, 5), + mixer_stride=(1, 1), + mixer_padding=(2, 2), + normalize_Q_K=False, + att_dropout_p=0.0, + dropout_p=0.1, + cosine_att=True, + temporal_multi_head_att_on_C_H_W=False, + att_with_relative_position_bias=True, + att_with_output_proj=True, + scale_ratio_in_mixer=4.0, + with_mixer=True, + norm_mode="layer", + shuffle_in_window=False, + use_flash_attention=False, + ): + """Complete transformer parallel cell.""" + if num_patch is None: + num_patch = [4, 4] + if num_wind is None: + num_wind = [8, 8] + super().__init__( + C_in=C_in, + C_out=C_out, + H=H, + W=W, + D=D, + att_mode=att_mode, + attention_type=attention_type, + mixer_type=mixer_type, + window_size=window_size, + patch_size=patch_size, + num_wind=num_wind, + num_patch=num_patch, + is_causal=is_causal, + n_head=n_head, + kernel_size=kernel_size, + stride=stride, + padding=padding, + activation_func=activation_func, + stride_s=stride_s, + stride_t=stride_t, + mixer_kernel_size=mixer_kernel_size, + mixer_stride=mixer_stride, + mixer_padding=mixer_padding, + normalize_Q_K=normalize_Q_K, + att_dropout_p=att_dropout_p, + dropout_p=dropout_p, + cosine_att=cosine_att, + temporal_multi_head_att_on_C_H_W=temporal_multi_head_att_on_C_H_W, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + scale_ratio_in_mixer=scale_ratio_in_mixer, + with_mixer=with_mixer, + norm_mode=norm_mode, + shuffle_in_window=shuffle_in_window, + use_flash_attention=use_flash_attention, + ) + + def create_mixer(self): + act_func = create_activation_func(name=self.activation_func) + + if ( + self.mixer_type == "conv" + or self.att_mode == "temporal" + or self.att_mode == "conv2d" + or self.att_mode == "conv3d" + ): + mixer_cha = int(self.scale_ratio_in_mixer * self.C_out) + + self.mlp = nn.Sequential( + Conv2DExt( + self.C_in, + mixer_cha, + kernel_size=self.mixer_kernel_size, + stride=self.mixer_stride, + padding=self.mixer_padding, + bias=True, + channel_first=True, + ), + act_func, + Conv2DExt( + mixer_cha, + self.C_out, + kernel_size=self.mixer_kernel_size, + stride=self.mixer_stride, + padding=self.mixer_padding, + bias=True, + channel_first=True, + ), + ) + elif self.mixer_type == "lin": + D = self.attn.get_dimension_for_linear_mixer() + D = D // self.C_out + D *= self.C_in + + D_out = int(D // self.C_in * self.C_out) + D_prime = int(self.scale_ratio_in_mixer * D_out) + + self.mlp = nn.Sequential( + nn.Linear(D, D_prime, bias=True), act_func, nn.Linear(D_prime, D_out, bias=True) + ) + else: + raise NotImplementedError(f"Mixer mode not implemented: {self.mixer_type}") + + def forward(self, x): + x_normed = self.n1(x) + y = self.stochastic_depth(self.attn(x_normed)) + + if self.with_mixer: + if ( + self.mixer_type == "conv" + or self.att_mode == "temporal" + or self.att_mode == "conv2d" + or self.att_mode == "conv3d" + ): + res_mixer = self.stochastic_depth(self.mlp(x_normed)) + else: + if "3d" in self.att_mode: + t = self.attn.im2grid(x_normed) + *Dim, a, b, c, _d = t.shape + t = self.mlp(t.reshape((*Dim, -1))) + t = torch.reshape(t, (*Dim, a, b, c, -1)) + t = self.attn.grid2im(t) + res_mixer = self.stochastic_depth(t) + else: + res_mixer = self.attn.im2grid(permute_to_B_T_C_H_W(x_normed)) + *Dim, wh, ww, _C = res_mixer.shape + res_mixer = self.mlp(res_mixer.reshape((*Dim, -1))) + res_mixer = self.attn.grid2im(res_mixer.reshape((*Dim, wh, ww, self.C_out))) + res_mixer = permute_to_B_C_T_H_W(res_mixer) + res_mixer = self.stochastic_depth(res_mixer) + + y += res_mixer + + y += self.input_proj(x) + + return y + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/model/config.py b/src/snraware/components/model/config.py new file mode 100644 index 0000000..31cb158 --- /dev/null +++ b/src/snraware/components/model/config.py @@ -0,0 +1,320 @@ +"""Configuration classes for model parameters.""" + +from dataclasses import dataclass, field + +import omegaconf + +__all__ = [ + "BackboneConfig", + "BlockConfig", + "CellConfig", + "ConvolutionConfig", + "HRNetConfig", + "SOANetConfig", + "SpatialGlobal3DConfig", + "SpatialGlobalConfig", + "SpatialLocal3DConfig", + "SpatialLocalConfig", + "SpatialViTConfig", + "Swin3DConfig", + "TemporalAttentionConfig", + "UNetConfig", + "ViT3DConfig", + "create_block_config", +] + + +# ------------------------------------------------------------------------------------------------- +@dataclass +class CellConfig: + # parameters for the cell + + # conv or lin, type of attention in the spatial attention modules + attention_type: str = "conv" + + # conv or lin, type of mixer in the spatial attention modules; only conv is possible for the temporal attention + mixer_type: str = "conv" + + # size of window for spatial attention. This is the number of pixels in a window. Given image height and weight H and W, number of windows is H/windows_size[0] * W/windows_size[1]; for 3D usecase, window_size order is [H, W, D] + window_size: list[int] = field(default_factory=lambda: [16, 16, 1]) + + # size of patch for spatial attention. This is the number of pixels in a patch. An image is first split into windows. Every window is further split into patches. + patch_size: list[int] = field(default_factory=lambda: [2, 2, 1]) + + # method to adjust window_size between resolution levels, "keep_window_size", "keep_num_window", "mixed". + # "keep_window_size" means number of pixels in a window is kept after down/upsample the image; "keep_num_window" means the number of windows is kept after down/upsample the image; "mixed" means interleave both methods. + window_sizing_method: str = "mixed" + + # number of transformer heads + n_head: int = 16 + + # query, key and value conv setting for the cells + kernel_size: list[int] = field(default_factory=lambda: [3, 3, 3]) + stride: list[int] = field(default_factory=lambda: [1, 1, 1]) + padding: list[int] = field(default_factory=lambda: [1, 1, 1]) + + # stride for spatial dimensions of query and key tensors + stride_s: list[int] = field(default_factory=lambda: [1, 1, 1]) + + # conv setting for the mixers in the cells + mixer_kernel_size: list[int] = field(default_factory=lambda: [3, 3, 3]) + mixer_stride: list[int] = field(default_factory=lambda: [1, 1, 1]) + mixer_padding: list[int] = field(default_factory=lambda: [1, 1, 1]) + + # he scaling ratio to increase/decrease dimensions in the mixer of an attention layer + scale_ratio_in_mixer: float = 4.0 + + # whether to normalize Q and K before computing attention matrix + normalize_Q_K: bool = True + # whether to use cosine attention for computing attention matrix + cosine_att: bool = True + # whether to add relative position bias to the attention matrix + att_with_relative_position_bias: bool = True + # dropout probability for attention matrix + att_dropout_p: float = 0.0 + # dropout probability after applying the attention matrix; aka stochastic residual connections + dropout_p: float = 0.1 + # whether to add output projection in attention layer + att_with_output_proj: bool = True + + # normalization mode + norm_mode: str = "instance2d" # "layer", "batch2d", "instance2d", "batch3d", "instance3d" + + # whether to treat timed data as causal and mask future entries + is_causal: bool = False + + # nonlinear activation function, "elu", "relu", "leakyrelu", "prelu", "relu6", "selu", "celu", "gelu" + activation_func: str = "prelu" + + # upsampling method in backbone, "NN", "linear", "bspline" + upsample_method: str = "linear" + + +# configuration for cells with specific attention types +# the name string of attention modules are defined in the documentation + + +@dataclass +class TemporalAttentionConfig(CellConfig): + # If True, multi-head attention is applied to the C, H, W dimensions for the temporal attention. + # If False, multi-head attention is applied to the C dimension only. + temporal_multi_head_att_on_C_H_W: bool = False + + # whether to use flash attention for the temporal attention layers + flash_att: bool = False + + # stride for temporal dimension of query and key tensors + stride_qk: list[int] = field(default_factory=lambda: [1, 1, 1]) + + +@dataclass +class SpatialLocalConfig(CellConfig): + pass + + +@dataclass +class SpatialGlobalConfig(CellConfig): + # whether to shuffle patches in a window for the global attention + shuffle_in_window: bool = False + + +@dataclass +class ConvolutionConfig(CellConfig): + conv_type: str = "conv2d" # conv2d or conv3d + + +@dataclass +class SpatialLocal3DConfig(CellConfig): + pass + + +@dataclass +class SpatialGlobal3DConfig(CellConfig): + # whether to shuffle patches in a window for the global attention + shuffle_in_window: bool = False + + +@dataclass +class SpatialViTConfig(CellConfig): + pass + + +@dataclass +class ViT3DConfig(CellConfig): + pass + + +@dataclass +class Swin3DConfig(CellConfig): + shifted_window_attention: bool = False + + +CellTypeVariant = ( + TemporalAttentionConfig + | SpatialLocalConfig + | SpatialGlobalConfig + | ConvolutionConfig + | SpatialLocal3DConfig + | SpatialGlobal3DConfig + | SpatialViTConfig + | ViT3DConfig + | Swin3DConfig +) + +# --------------------------------------------------------------------------------------------- + + +@dataclass +class BlockConfig: + # block contains cell parameters + cell: CellConfig = field(default_factory=CellConfig) + + # sequential or parallel cells + cell_type: str = "sequential" + + # whether to add dense connections between cells in a block + block_dense_connection: bool = False + + +# --------------------------------------------------------------------------------------------- +def create_block_config(block_str: list[str], block: BlockConfig) -> list[list[CellTypeVariant]]: + block_config = [] + for att_types in block_str: + assert len(att_types) >= 1, "At least one attention module is required to build the model" + assert not (len(att_types) % 2), "require attention and mixer info for each cell" + + a_block_config = [] + + for i in range(0, len(att_types), 2): + att_type = att_types[i] + mixer = att_types[i + 1] + + if att_type == "L": + if mixer == "3": + a_block_config.append(SpatialLocal3DConfig()) + else: + a_block_config.append(SpatialLocalConfig()) + elif att_type == "G": + if mixer == "3": + a_block_config.append( + SpatialGlobal3DConfig( + shuffle_in_window=block.cell.spatial_global_3d.shuffle_in_window + ) + ) + else: + a_block_config.append( + SpatialGlobalConfig( + shuffle_in_window=block.cell.spatial_global.shuffle_in_window + ) + ) + elif att_type == "T": + a_block_config.append( + TemporalAttentionConfig( + temporal_multi_head_att_on_C_H_W=block.cell.temporal.temporal_multi_head_att_on_C_H_W, + flash_att=block.cell.temporal.flash_att, + stride_qk=block.cell.temporal.stride_qk, + ) + ) + elif att_type == "V" and mixer == "2": + a_block_config.append(SpatialViTConfig()) + elif att_type == "V" and mixer == "3": + a_block_config.append(ViT3DConfig()) + elif att_type == "s" and mixer == "h": + a_block_config.append(Swin3DConfig()) + elif att_type == "S" and mixer == "3": + a_block_config.append(Swin3DConfig()) + elif att_type == "S" and mixer == "h": + a_block_config.append(Swin3DConfig(shifted_window_attention=True)) + elif att_type == "C" and mixer == "2": + a_block_config.append(ConvolutionConfig(conv_type="conv2d")) + elif att_type == "C" and mixer == "3": + a_block_config.append(ConvolutionConfig(conv_type="conv3d")) + else: + raise ValueError(f"Incorrect att_type: {att_type}, mixer: {mixer}") + + block_config.append(a_block_config) + + return block_config + + +@dataclass +class BackboneConfig: + block: BlockConfig = field(default_factory=BlockConfig) + + # name of the backbone + name: str = "HRnet" + + # number of channels in main body of backbone + num_of_channels: int = 32 + + # number of resolution levels; image size reduce by x2 for every level + num_resolution_levels: int = 2 + + # block string to define the attention layers in blocks; if multiple strings are given, each is for a resolution level + block_str: list[str] = field(default_factory=lambda: ["T1L1G1"]) + + # block config to store the configuration objects for all cells + # this field is created from the block_str field in the __post_init__ + block_config: list[list[CellTypeVariant]] = field( + default_factory=lambda: [ + [TemporalAttentionConfig(), SpatialLocalConfig(), SpatialGlobalConfig()] + ] + ) + + # whether to use interpolation in downsample layer; if False, use stride convolution + use_interpolation: bool = True + + # whether to add conv in downsample layers; if False, only interpolation is performed + with_conv: bool = True + + # whether to add unet channel attention on the skip connections between resolution levels + use_unet_attention: bool = True + + # number of attention stages in the soanet backbone + # optionally, tensor size can be reduced by x2 for every stage + num_stages: int = 2 + + # whether to downsample between stages + downsample: bool = True + + def __post_init__(self): + assert isinstance(self.block_str, list) or isinstance( + self.block_str, omegaconf.listconfig.ListConfig + ), "block_str must be a list of strings" + self.block_config = create_block_config(self.block_str, self.block) + + +# --------------------------------------------------------------------------------------------- + + +@dataclass +class HRNetConfig(BackboneConfig): + num_resolution_levels: int = 2 + + +# --------------------------------------------------------------------------------------------- + + +@dataclass +class UNetConfig(BackboneConfig): + # number of resolution levels; image size reduce by x2 for every level + num_resolution_levels: int = 2 + + # whether to add unet channel attention on the skip connections between resolution levels + use_unet_attention: bool = True + + +# --------------------------------------------------------------------------------------------- + + +@dataclass +class SOANetConfig(BackboneConfig): + # number of attention stages in the soanet backbone + # optionally, tensor size can be reduced by x2 for every stage + num_stages: int = 2 + + # whether to downsample between stages + downsample: bool = True + + +# --------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/optim/__init__.py b/src/snraware/components/optim/__init__.py new file mode 100644 index 0000000..4d5aa72 --- /dev/null +++ b/src/snraware/components/optim/__init__.py @@ -0,0 +1 @@ +from .optim import OptimScheduler as OptimScheduler diff --git a/src/snraware/components/optim/optim.py b/src/snraware/components/optim/optim.py new file mode 100755 index 0000000..1c8d0fb --- /dev/null +++ b/src/snraware/components/optim/optim.py @@ -0,0 +1,165 @@ +"""Configuration for the optimizer and scheduler.""" + +import torch.nn as nn +import torch.optim as optim + +from snraware.components.sophia import SophiaG + +__all__ = ["OptimScheduler"] + +# ------------------------------------------------------------------------------------------------- +# the transformer type models will be better trained if the weight decay is not performed +# on some parameters, such as bias, normalization etc. +# This class will split the parameters into those that will and won't use weight decay, as two parameter groups + + +class OptimScheduler: + """ + Create optimizer and scheduler. + self.optim holds the optimizer. + self.sched holds the learning rate scheduler. + """ + + def __init__(self, config, model, total_num_steps): + """ + @args: + - model (torch.nn.module): model containing pre/backbone/post module we aim to optimize + - total_num_steps (int): total number of steps in training, used for OneCycleLR. + """ + super().__init__() + + # Save vars + self.config = config + self.model = model + self.total_num_steps = total_num_steps + + self.optim = None + self.sched = None + self.curr_epoch = 0 + + self.set_up_optim_and_scheduling(total_updates=self.total_num_steps) + + def split_decay_optim_groups(self, module, lr=-1, wd=0.0): + """ + This function splits up parameter groups into those that will and those that won"t experience weight decay + Adapted from mingpt: https://github.com/karpathy/minGPT + @args: + - module (torch module): module to create weight decay parameter groups for + - lr (float): learning rate for the module m + - weight_decay (float, from config): weight decay coefficient for regularization. + """ + # separate out all parameters to those that will and won"t experience regularizing weight decay + decay = set() + no_decay = set() + whitelist_weight_modules = (nn.Linear, nn.Conv2d, nn.Conv3d) + blacklist_weight_modules = ( + nn.LayerNorm, + nn.BatchNorm2d, + nn.BatchNorm3d, + nn.InstanceNorm2d, + nn.InstanceNorm3d, + nn.parameter.Parameter, + ) + for mn, m in module.named_modules(): + for pn, _p in m.named_parameters(): + fpn = f"{mn}.{pn}" if mn else pn # full param name + + # random note: because named_modules and named_parameters are recursive + # we will see the same tensors p many many times. but doing it this way + # allows us to know which parent module any tensor p belongs to... + if len([sub_m for sub_mn, sub_m in m.named_modules()]) == 1 or pn.endswith( + "relative_position_bias_table" + ): + if pn.endswith("bias"): + # all biases will not be decayed + no_decay.add(fpn) + elif pn.endswith("weight") and isinstance(m, whitelist_weight_modules): + # weights of whitelist modules will be weight decayed + decay.add(fpn) + elif pn.endswith("weight") and isinstance(m, blacklist_weight_modules): + # weights of blacklist modules will NOT be weight decayed + no_decay.add(fpn) + elif pn.endswith("relative_position_bias_table"): + no_decay.add(fpn) + else: + no_decay.add(fpn) + + # validate that we considered every parameter + param_dict = {pn: p for pn, p in module.named_parameters()} + inter_params = decay & no_decay + union_params = decay | no_decay + assert len(inter_params) == 0, ( + f"parameters {inter_params!s} made it into both decay/no_decay sets!" + ) + assert len(param_dict.keys() - union_params) == 0, ( + f"parameters {param_dict.keys() - union_params!s} were not separated into either decay/no_decay set!" + ) + + # create the pytorch optimizer object + optim_groups = [ + { + "params": [param_dict[pn] for pn in sorted(list(decay))], + "weight_decay": wd, + }, # With weight decay group + { + "params": [param_dict[pn] for pn in sorted(list(no_decay))], + "weight_decay": 0.0, + }, # Without weight decay group + ] + + if lr >= 0: + optim_groups[0]["lr"] = lr + optim_groups[1]["lr"] = lr + + return optim_groups + + def configure_optim_groups(self): + """This function splits up pre, backbone, and post parameters into different parameter groups.""" + optim_groups = self.split_decay_optim_groups( + self.model, lr=self.config.optim.lr, wd=self.config.optim.weight_decay + ) + + return optim_groups + + def set_up_optim_and_scheduling(self, total_updates=1): + """ + Sets up the optimizer and the learning rate scheduler using the config + @args: + - total_updates (int): total number of updates in training (used for OneCycleLR) + @outputs: + - self.optim: optimizer + - self.sched: scheduler. + """ + c = self.config + + optim_groups = self.configure_optim_groups() + + self.optim = SophiaG( + optim_groups, + betas=(c.optim.beta1, c.optim.beta2), + rho=c.optim.rho, + weight_decay=c.optim.weight_decay, + ) + + # get the list of max_lr + max_lrs = [t["lr"] for t in optim_groups] + self.sched = optim.lr_scheduler.OneCycleLR( + self.optim, + max_lr=max_lrs, + total_steps=total_updates, + pct_start=c.scheduler.pct_start, + anneal_strategy=c.scheduler.anneal_strategy, + div_factor=c.scheduler.div_factor, + ) + + def report_lr(self): + lr_record = [] + for ind, g in enumerate(self.optim.param_groups): + lr_record.append({ind: g["lr"]}) + return lr_record + + +# ------------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + pass diff --git a/src/snraware/components/setup/__init__.py b/src/snraware/components/setup/__init__.py new file mode 100644 index 0000000..e5c8023 --- /dev/null +++ b/src/snraware/components/setup/__init__.py @@ -0,0 +1 @@ +from .status import * diff --git a/src/snraware/components/setup/status.py b/src/snraware/components/setup/status.py new file mode 100644 index 0000000..b8b4ebf --- /dev/null +++ b/src/snraware/components/setup/status.py @@ -0,0 +1,438 @@ +"""Utility functions to measure system status.""" + +import logging +import os +import random +import time +from collections import OrderedDict + +import numpy as np +import torch +from colorama import Back, Fore, Style +from prettytable import PrettyTable +from torchinfo import summary + +# ------------------------------------------------------------------------------------------------- + + +def set_seed(seed: int = 42) -> None: + """Take care of all the random seeds to run deterministically.""" + np.random.seed(seed) + random.seed(seed) + torch.manual_seed(seed) + + # When running on the CuDNN backend, two further options must be set + if torch.cuda.is_available(): + torch.cuda.manual_seed(seed) + gpu_name = torch.cuda.get_device_name() + if "amd" not in gpu_name.lower(): + print(f"{Fore.YELLOW}Set the cudnn.deterministic to be True.{Style.RESET_ALL}") + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + else: + print(f"{Fore.YELLOW}Do not set the cudnn.deterministic.{Style.RESET_ALL}") + # Set a fixed value for the hash seed + os.environ["PYTHONHASHSEED"] = str(seed) + print(f"Random seed set as {seed}") + + +# ------------------------------------------------------------------------------------------------- + + +def start_timer(enable=False): + if enable: + if torch.cuda.is_available(): + start = torch.cuda.Event(enable_timing=True) + end = torch.cuda.Event(enable_timing=True) + start.record() + return (start, end) + else: + start = time.perf_counter() + return (start,) + else: + return None + + +def end_timer(enable=False, t=None, msg="", verbose=True): + if enable: + if torch.cuda.is_available(): + t[1].record() + torch.cuda.synchronize() + duration = t[0].elapsed_time(t[1]) + if verbose: + print(f"{Fore.LIGHTBLUE_EX}{msg} {duration} ms ...{Style.RESET_ALL}", flush=True) + return duration + else: + duration = time.perf_counter() - t[0] + if verbose: + print( + f"{Fore.LIGHTBLUE_EX}{msg} {duration * 1e3} ms ...{Style.RESET_ALL}", + flush=True, + ) + return duration + else: + return 0.0 + + +# ------------------------------------------------------------------------------------------------- + + +def get_cuda_info(device): + if torch.cuda.is_available(): + return { + "PyTorch_version": torch.__version__, + "CUDA_version": torch.version.cuda, + "cuDNN_version": torch.backends.cudnn.version(), + "Arch_version": torch._C._cuda_getArchFlags(), + "device_count": torch.cuda.device_count(), + "device_name": torch.cuda.get_device_name(device=device), + "device_id": torch.cuda.current_device(), + "cuda_capability": torch.cuda.get_device_capability(device=device), + "device_properties": torch.cuda.get_device_properties(device=device), + "reserved_memory": torch.cuda.memory_reserved(device=device) / 1024**3, + "allocated_memory": torch.cuda.memory_allocated(device=device) / 1024**3, + "max_allocated_memory": torch.cuda.max_memory_allocated(device=device) / 1024**3, + "gpu_name": torch.cuda.get_device_name(), + } + else: + return { + "PyTorch_version": torch.__version__, + "CUDA_version": -1, + "cuDNN_version": -1, + "Arch_version": -1, + "device_count": 0, + "device_name": "cpu", + "device_id": 0, + "cuda_capability": 0, + "device_properties": 0, + "reserved_memory": 0, + "allocated_memory": 0, + "max_allocated_memory": 0, + "gpu_name": "none", + } + + +def support_bfloat16(device): + if torch.cuda.is_available(): + DISABLE_FLOAT16_INFERENCE = os.environ.get("DISABLE_FLOAT16_INFERENCE", "False") + if DISABLE_FLOAT16_INFERENCE == "True": + return False + + info = get_cuda_info(device) + if ( + info["gpu_name"].find("A100") >= 0 + or info["gpu_name"].find("H100") + or info["gpu_name"].find("B100") + or info["gpu_name"].find("B200") >= 0 + ): + return True + else: + return False + else: + return False + + +# ------------------------------------------------------------------------------------------------- +def get_gpu_ram_usage(device="cuda:0"): + """ + Get info regarding memory usage of a device + @args: + - device (torch.device): the device to get info about + @rets: + - result_string (str): a string containing the info. + """ + result_string = ( + f"torch.cuda.memory_allocated: {torch.cuda.memory_allocated(device=device) / 1024 / 1024 / 1024:.3}GB\n" + + f"torch.cuda.memory_reserved: {torch.cuda.memory_reserved(device=device) / 1024 / 1024 / 1024:.3f}GB\n" + + f"torch.cuda.max_memory_reserved: {torch.cuda.max_memory_reserved(device=device) / 1024 / 1024 / 1024:.3f}GB" + ) + + return result_string + + +# ------------------------------------------------------------------------------------------------- +# Model info + + +def model_info(model, config, H, W, D): + """ + Prints model info and sets total and trainable parameters in the config + @args: + - model (torch model): the model to check parameters of + - config (Namespace): runtime namespace for setup + @rets: + - model_summary (ModelStatistics object): the model summary + see torchinfo/model_statistics.py for more information. + """ + c = config + col_names = ( + "num_params", + "params_percent", + "mult_adds", + "input_size", + "output_size", + "trainable", + ) + row_settings = ["var_names", "depth"] + dtypes = [torch.float32] + model = model.module if config.trainer.ddp else model + model.train() + + for task_ind, task in enumerate(model.tasks.values()): + C_out = task.C_out + + a_sample = task.train_set[0][0] # Get a sample from the training set + _C, D, H, W = a_sample.shape[:4] + + mod_batch_size = config.trainer.batch_size[task_ind] + + example_pre_input = a_sample.unsqueeze(0).repeat(mod_batch_size, 1, 1, 1, 1).to(c.device) + + example_pre_output = task.pre_component(example_pre_input) + if not isinstance(example_pre_output, list): + example_pre_output = [example_pre_output] + + pre_model_summary = summary( + task.pre_component, + verbose=0, + mode="train", + depth=c.trainer.summary_depth, + input_data=[example_pre_input], + col_names=col_names, + row_settings=row_settings, + dtypes=dtypes, + device=config.device, + ) + logging.info( + f"{Fore.MAGENTA}{'-' * 40}Summary of pre component for task {task.task_name}{'-' * 40}{Style.RESET_ALL}" + ) + logging.info(f"\n{pre_model_summary!s}") + + # ----------------------------------------------------------------------------------------- + example_backbone_output = model.backbone_component(example_pre_output) + if not isinstance(example_backbone_output, list): + example_backbone_output = [example_backbone_output] + + torch.cuda.empty_cache() + + # ----------------------------------------------------------------------------------------- + example_backbone_output.insert(0, example_pre_input) + post_model_summary = summary( + task.post_component, + verbose=0, + mode="train", + depth=c.trainer.summary_depth, + input_data=[example_backbone_output], + col_names=col_names, + row_settings=row_settings, + dtypes=dtypes, + device=config.device, + ) + logging.info( + f"{Fore.MAGENTA}{'-' * 40}Summary of post component for task {task.task_name}{'-' * 40}{Style.RESET_ALL}" + ) + logging.info(f"\n{post_model_summary!s}") + + torch.cuda.empty_cache() + + # ------------------------------------------------------------------------------------------------------------------ + task_ind = 0 + torch.ones((mod_batch_size, C_out, D, H, W)).to(c.device) + backbone_model_summary = summary( + model.backbone_component, + verbose=0, + mode="train", + depth=c.trainer.summary_depth, + input_data=[example_pre_output], + col_names=col_names, + row_settings=row_settings, + dtypes=dtypes, + device=config.device, + ) + + logging.info( + f"{Fore.MAGENTA}{'-' * 40}Summary of backbone component{'-' * 40}{Style.RESET_ALL}" + ) + logging.info(f"\n{backbone_model_summary!s}") + + torch.cuda.empty_cache() + + return backbone_model_summary + + +# ------------------------------------------------------------------------------------------------- + + +def count_parameters(model): + table = PrettyTable(["Modules", "Parameters"]) + total_params = 0 + for name, parameter in model.named_parameters(): + if not parameter.requires_grad: + continue + params = parameter.numel() + table.add_row([name, params]) + total_params += params + print(table) + print(f"Total Trainable Params: {total_params}") + return total_params + + +# ------------------------------------------------------------------------------------------------- +def get_device(device=None): + """ + Wrapper around getting device + @args: + - device (torch.device): if not None this device will be returned + otherwise check if cuda is available + @rets: + - device (torch.device): the device to be used. + """ + return device if device is not None else "cuda" if torch.cuda.is_available() else "cpu" + + +# ------------------------------------------------------------------------------------------------- + + +def none_or_str(value): + """Convert arg from a string to None.""" + if value in ["None", "none", None]: + return None + return value + + +def str_to_bool(value): + """Convert arg from a string to bool.""" + if str(value) in ["1", "True", "true", "T", "t"]: + return True + else: + return False + + +def create_generic_class_str(obj: object, exclusion_list=None) -> str: + """ + Create a generic name of a class + @args: + - obj (object): the class to make string of + - exclusion_list (object list): the objects to exclude from the class string + @rets: + - class_str (str): the generic class string. + """ + if exclusion_list is None: + exclusion_list = [torch.nn.Module, OrderedDict] + name = type(obj).__name__ + + vars_list = [] + for key, value in vars(obj).items(): + valid = True + for type_e in exclusion_list: + if isinstance(value, type_e) or key.startswith("_"): + valid = False + break + + if valid: + vars_list.append(f"{key}={value!r}") + + vars_str = ",\n".join(vars_list) + return f"{name}({vars_str})" + + +# ------------------------------------------------------------------------------------------------- + + +def get_rank_str(rank, global_rank=-1): + if rank == 0: + if global_rank >= 0: + return ( + f"{Fore.BLUE}{Back.WHITE}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + ) + else: + return f"{Fore.BLUE}{Back.WHITE}rank {rank} {Style.RESET_ALL}" + if rank == 1: + if global_rank >= 0: + return f"{Fore.GREEN}{Back.WHITE}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + else: + return f"{Fore.GREEN}{Back.WHITE}rank {rank} {Style.RESET_ALL}" + if rank == 2: + if global_rank >= 0: + return f"{Fore.YELLOW}{Back.WHITE}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + else: + return f"{Fore.YELLOW}{Back.WHITE}rank {rank} {Style.RESET_ALL}" + if rank == 3: + if global_rank >= 0: + return f"{Fore.MAGENTA}{Back.WHITE}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + else: + return f"{Fore.MAGENTA}{Back.WHITE}rank {rank} {Style.RESET_ALL}" + if rank == 4: + if global_rank >= 0: + return f"{Fore.LIGHTYELLOW_EX}{Back.WHITE}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + else: + return f"{Fore.LIGHTYELLOW_EX}{Back.WHITE}rank {rank} {Style.RESET_ALL}" + if rank == 5: + if global_rank >= 0: + return f"{Fore.LIGHTBLUE_EX}{Back.WHITE}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + else: + return f"{Fore.LIGHTBLUE_EX}{Back.WHITE}rank {rank} {Style.RESET_ALL}" + if rank == 6: + if global_rank >= 0: + return f"{Fore.LIGHTRED_EX}{Back.WHITE}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + else: + return f"{Fore.LIGHTRED_EX}{Back.WHITE}rank {rank} {Style.RESET_ALL}" + if rank == 7: + if global_rank >= 0: + return f"{Fore.LIGHTCYAN_EX}{Back.WHITE}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + else: + return f"{Fore.LIGHTCYAN_EX}{Back.WHITE}rank {rank} {Style.RESET_ALL}" + + if global_rank >= 0: + return ( + f"{Fore.WHITE}{Style.BRIGHT}rank {rank}, global_rank {global_rank} {Style.RESET_ALL}" + ) + else: + return f"{Fore.WHITE}{Style.BRIGHT}rank {rank} {Style.RESET_ALL}" + + +# ------------------------------------------------------------------------------------------------- + + +class AverageMeter: + """Computes and stores the average and current value.""" + + def __init__(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + self.vals = [] + self.counts = [] + + def reset(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + self.vals = [] + self.counts = [] + + def update(self, val, n=1): + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / self.count + + self.vals.append(val) + self.counts.append(n) + + def update_array(self, vals): + self.val = np.mean(vals) + self.sum += np.sum(vals) + self.count += vals.size + self.avg = self.sum / self.count + + self.vals.append(list(vals)) + self.counts.append(list(np.ones_like(vals))) + + def status(self): + return np.array(self.vals), np.array(self.counts) + + +# ------------------------------------------------------------------------------------------------- diff --git a/src/snraware/components/sophia/__init__.py b/src/snraware/components/sophia/__init__.py new file mode 100644 index 0000000..e4eb99a --- /dev/null +++ b/src/snraware/components/sophia/__init__.py @@ -0,0 +1 @@ +from .sophia import SophiaG as SophiaG diff --git a/src/snraware/components/sophia/sophia.py b/src/snraware/components/sophia/sophia.py new file mode 100644 index 0000000..45dd7f1 --- /dev/null +++ b/src/snraware/components/sophia/sophia.py @@ -0,0 +1,262 @@ +# type: ignore[reportMissingTypeArgument, reportUntypedFunctionDecorator, reportUnnecessaryIsInstance] +""" +SophiaG optimizer implementation based on this paper. + +@article{liu2023sophia, + title={Sophia: A Scalable Stochastic Second-order Optimizer for Language Model Pre-training}, + author={Liu, Hong and Li, Zhiyuan and Hall, David and Liang, Percy and Ma, Tengyu}, + journal={arXiv preprint arXiv:2305.14342}, + year={2023} +} + +This file is from https://github.com/Liuhong99/Sophia/blob/a7e157229b71d58cf995d32854f1be15c265b350/sophia.py + +MIT License + +Copyright (c) 2023 Hong Liu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +""" + +import torch +from torch import Tensor +from torch.optim.optimizer import Optimizer + + +class SophiaG(Optimizer): + def __init__( + self, + params, + lr=1e-4, + betas=(0.965, 0.99), + rho=0.04, + weight_decay=1e-1, + *, + maximize: bool = False, + capturable: bool = False, + ): + if not 0.0 <= lr: + raise ValueError(f"Invalid learning rate: {lr}") + if not 0.0 <= betas[0] < 1.0: + raise ValueError(f"Invalid beta parameter at index 0: {betas[0]}") + if not 0.0 <= betas[1] < 1.0: + raise ValueError(f"Invalid beta parameter at index 1: {betas[1]}") + if not 0.0 <= rho: + raise ValueError(f"Invalid rho parameter at index 1: {rho}") + if not 0.0 <= weight_decay: + raise ValueError(f"Invalid weight_decay value: {weight_decay}") + defaults = dict( + lr=lr, + betas=betas, + rho=rho, + weight_decay=weight_decay, + maximize=maximize, + capturable=capturable, + ) + super().__init__(params, defaults) + + def __setstate__(self, state): + super().__setstate__(state) + for group in self.param_groups: + group.setdefault("maximize", False) + group.setdefault("capturable", False) + state_values = list(self.state.values()) + step_is_tensor = (len(state_values) != 0) and torch.is_tensor(state_values[0]["step"]) + if not step_is_tensor: + for s in state_values: + s["step"] = torch.tensor(float(s["step"])) + + @torch.no_grad() + def update_hessian(self): + for group in self.param_groups: + _beta1, beta2 = group["betas"] + for p in group["params"]: + if p.grad is None: + continue + state = self.state[p] + + if len(state) == 0: + state["step"] = ( + torch.zeros((1,), dtype=torch.float, device=p.device) + if self.defaults["capturable"] + else torch.tensor(0.0) + ) + state["exp_avg"] = torch.zeros_like(p, memory_format=torch.preserve_format) + state["hessian"] = torch.zeros_like(p, memory_format=torch.preserve_format) + + if "hessian" not in state.keys(): + state["hessian"] = torch.zeros_like(p, memory_format=torch.preserve_format) + + state["hessian"].mul_(beta2).addcmul_(p.grad, p.grad, value=1 - beta2) + + @torch.no_grad() + def step(self, closure=None, bs=5120): + loss = None + if closure is not None: + with torch.enable_grad(): + loss = closure() + + for group in self.param_groups: + params_with_grad = [] + grads = [] + exp_avgs = [] + state_steps = [] + hessian = [] + beta1, beta2 = group["betas"] + + for p in group["params"]: + if p.grad is None: + continue + params_with_grad.append(p) + + if p.grad.is_sparse: + raise RuntimeError("Hero does not support sparse gradients") + grads.append(p.grad) + state = self.state[p] + # State initialization + if len(state) == 0: + state["step"] = ( + torch.zeros((1,), dtype=torch.float, device=p.device) + if self.defaults["capturable"] + else torch.tensor(0.0) + ) + state["exp_avg"] = torch.zeros_like(p, memory_format=torch.preserve_format) + state["hessian"] = torch.zeros_like(p, memory_format=torch.preserve_format) + + if "hessian" not in state.keys(): + state["hessian"] = torch.zeros_like(p, memory_format=torch.preserve_format) + + exp_avgs.append(state["exp_avg"]) + state_steps.append(state["step"]) + hessian.append(state["hessian"]) + + if self.defaults["capturable"]: + bs = torch.ones((1,), dtype=torch.float, device=p.device) * bs + + sophiag( + params_with_grad, + grads, + exp_avgs, + hessian, + state_steps, + bs=bs, + beta1=beta1, + beta2=beta2, + rho=group["rho"], + lr=group["lr"], + weight_decay=group["weight_decay"], + maximize=group["maximize"], + capturable=group["capturable"], + ) + + return loss + + +def sophiag( + params: list[Tensor], + grads: list[Tensor], + exp_avgs: list[Tensor], + hessian: list[Tensor], + state_steps: list[Tensor], + capturable: bool = False, + *, + bs: int, + beta1: float, + beta2: float, + rho: float, + lr: float, + weight_decay: float, + maximize: bool, +): + if not all(isinstance(t, torch.Tensor) for t in state_steps): + raise RuntimeError( + "API has changed, `state_steps` argument must contain a list of singleton tensors" + ) + + func = _single_tensor_sophiag + + func( + params, + grads, + exp_avgs, + hessian, + state_steps, + bs=bs, + beta1=beta1, + beta2=beta2, + rho=rho, + lr=lr, + weight_decay=weight_decay, + maximize=maximize, + capturable=capturable, + ) + + +def _single_tensor_sophiag( + params: list[Tensor], + grads: list[Tensor], + exp_avgs: list[Tensor], + hessian: list[Tensor], + state_steps: list[Tensor], + *, + bs: int, + beta1: float, + beta2: float, + rho: float, + lr: float, + weight_decay: float, + maximize: bool, + capturable: bool, +): + for i, param in enumerate(params): + grad = grads[i] if not maximize else -grads[i] + exp_avg = exp_avgs[i] + hess = hessian[i] + step_t = state_steps[i] + + if capturable: + assert param.is_cuda and step_t.is_cuda and bs.is_cuda + + if torch.is_complex(param): + grad = torch.view_as_real(grad) + exp_avg = torch.view_as_real(exp_avg) + hess = torch.view_as_real(hess) + param = torch.view_as_real(param) + + # update step + step_t += 1 + + # Perform stepweight decay + param.mul_(1 - lr * weight_decay) + + # Decay the first and second moment running average coefficient + exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1) + + if capturable: + step_size = lr + step_size_neg = step_size.neg() + + ratio = (exp_avg.abs() / (rho * bs * hess + 1e-15)).clamp(None, 1) + param.addcmul_(exp_avg.sign(), ratio, value=step_size_neg) + else: + step_size_neg = -lr + + ratio = (exp_avg.abs() / (rho * bs * hess + 1e-15)).clamp(None, 1) + param.addcmul_(exp_avg.sign(), ratio, value=step_size_neg) diff --git a/src/snraware/projects/loss/__init__.py b/src/snraware/projects/loss/__init__.py new file mode 100644 index 0000000..5075789 --- /dev/null +++ b/src/snraware/projects/loss/__init__.py @@ -0,0 +1,2 @@ +from .gaussian import * +from .imaging_loss import * diff --git a/src/snraware/projects/loss/gaussian.py b/src/snraware/projects/loss/gaussian.py new file mode 100644 index 0000000..c3e6a6e --- /dev/null +++ b/src/snraware/projects/loss/gaussian.py @@ -0,0 +1,109 @@ +"""Gaussian kernel and its derivatives.""" + +import numpy as np + +__all__ = ["create_gaussian_window_1d", "create_gaussian_window_2d", "create_gaussian_window_3d"] + + +# ------------------------------------------------------------------------------------------------- + + +def gaussian_function(kernelSamplePoints, sigma): + """ + Compute gaussian and its derivatives. + + Args: + kernelSamplePoints (np array): sampled kernal points + sigma (float): guassian sigma + + Returns: + G, D, DD: guassian kernel, guassian 1st and 2nd order derivatives + """ + N = 1 / np.sqrt(2 * np.pi * sigma * sigma) + T = np.exp(-(kernelSamplePoints * kernelSamplePoints) / (2 * sigma * sigma)) + + G = N * T + G = G / np.sum(G) + + D = N * (-kernelSamplePoints / (sigma * sigma)) * T + D = D / np.sum(np.abs(D)) + + DD = N * ( + (-1 / (sigma * sigma) * T) + + ((-kernelSamplePoints / (sigma * sigma)) * (-kernelSamplePoints / (sigma * sigma)) * T) + ) + DD = DD / np.sum(np.abs(DD)) + + return G, D, DD + + +# ------------------------------------------------------------------------------------------------- + + +def compute_gaussian_and_derivatives_1D(sigma, halfwidth, voxelsize): + """ + Compute gaussian kernels and their derivatives. + + Args: + sigma (float): sigma in the unit of physical world + halfwidth (float): sampled halfwidth + voxelsize (float): voxel size, in the same unit of sigma + + Returns: + kernelSamplePoints, Gx, Dx, Dxx: sampled locations, gaussian and its derivatives + """ + s = np.arange(2 * round(halfwidth * sigma / voxelsize) + 1) + kernelSamplePoints = (s - round(halfwidth * sigma / voxelsize)) * voxelsize + Gx, Dx, Dxx = gaussian_function(kernelSamplePoints, sigma) + + return kernelSamplePoints, Gx, Dx, Dxx + + +# ------------------------------------------------------------------------------------------------- + + +def create_gaussian_window_1d(sigma=1.25, halfwidth=3, voxelsize=1.0, order=1): + """Creates a 1D gaussian kernel.""" + k_0 = compute_gaussian_and_derivatives_1D(sigma, halfwidth, voxelsize) + window = k_0[order + 1] + window /= np.sum(np.abs(window)) + + return window + + +# ------------------------------------------------------------------------------------------------- + + +def create_gaussian_window_2d( + sigma=(1.25, 1.25), halfwidth=(3, 3), voxelsize=(1.0, 1.0), order=(1, 1) +): + """Creates a 2D gaussian kernel.""" + k_0 = compute_gaussian_and_derivatives_1D(sigma[0], halfwidth[0], voxelsize[0]) + k_1 = compute_gaussian_and_derivatives_1D(sigma[1], halfwidth[1], voxelsize[1]) + window = k_0[order[0] + 1][:, np.newaxis] * k_1[order[1] + 1][:, np.newaxis].T + window /= np.sum(np.abs(window)) + + return window + + +# ------------------------------------------------------------------------------------------------- + + +def create_gaussian_window_3d( + sigma=(1.25, 1.25, 1.25), halfwidth=(3, 3, 3), voxelsize=(1.0, 1.0, 1.0), order=(1, 1, 1) +): + """Creates a 3D gaussian kernel.""" + k_0 = compute_gaussian_and_derivatives_1D(sigma[0], halfwidth[0], voxelsize[0]) + k_1 = compute_gaussian_and_derivatives_1D(sigma[1], halfwidth[1], voxelsize[1]) + k_2 = compute_gaussian_and_derivatives_1D(sigma[2], halfwidth[2], voxelsize[2]) + window = k_0[order[0] + 1][:, np.newaxis] * k_1[order[1] + 1][:, np.newaxis].T + window = window[:, :, np.newaxis] * np.expand_dims(k_2[order[2] + 1], axis=(0, 1)) + window /= np.sum(np.abs(window)) + + return window + + +# ------------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + pass diff --git a/src/snraware/projects/loss/imaging_loss.py b/src/snraware/projects/loss/imaging_loss.py new file mode 100644 index 0000000..3eba344 --- /dev/null +++ b/src/snraware/projects/loss/imaging_loss.py @@ -0,0 +1,1178 @@ +""" +Losses for imaging applications. + +Provides implmentation of the following types of losses: + - FSIM: Feature Similarity Index Measure + - SSIM: Structural Similarity Index Measure for 2D + - SSIM3D: Structural Similarity Index Measure for 3D + - MSSSIM: multi-scale SSIM for 2D + - L1: Mean Absolute Error + - MSE: Mean Squared Error + - Perpendicular: perp loss for complex images + - Combined: Any weighed combination of the above + +Allows custom weights for each indvidual loss calculation as well. + +The input tensor has the shape [B, C, T/F, H, W]. It can be complex or real in data types. + +A weights tensor can be supplied to provide different weighting for each sample. weights can be [B], or [B, T] in shape. +In the former case, every batch can have different weighting. In the latter, each batch and frame step can have different weighting. +""" + +import numpy as np +import piq +import torch +import torch.nn.functional as F +import torchmetrics +import torchvision +from pytorch_wavelets import DWTForward, DWTInverse +from torchmetrics.functional.image import structural_similarity_index_measure as ssim_functional + +from snraware.projects.loss.gaussian import ( + create_gaussian_window_1d, + create_gaussian_window_2d, + create_gaussian_window_3d, +) + +__all__ = [ + "PSNR", + "Charbonnier_Loss", + "Combined_Loss", + "FSIM_Loss", + "GaussianDeriv1D_Loss", + "GaussianDeriv3D_Loss", + "GaussianDeriv_Loss", + "L1_Loss", + "MSE_Loss", + "MSSSIM_Loss", + "PSNR_Loss", + "Perpendicular_Loss", + "SSIM3D_Loss", + "SSIM_Loss", + "VGGPerceptualLoss", +] + + +# ------------------------------------------------------------------------------------------------- +def _get_magnitude_tensor(outputs, targets, is_complex=True): + if is_complex: + assert targets.shape[1] == 2, ( + f"Complex type requires image to have C=2, given C={targets.shape[1]}" + ) + outputs_mag = torch.sqrt(outputs[:, :1] * outputs[:, :1] + outputs[:, 1:] * outputs[:, 1:]) + targets_mag = torch.sqrt(targets[:, :1] * targets[:, :1] + targets[:, 1:] * targets[:, 1:]) + else: + outputs_mag = outputs + targets_mag = targets + + return outputs_mag, targets_mag + + +# ------------------------------------------------------------------------------------------------- +# Feature Similarity Index Measure (FSIM) loss + + +class FSIM_Loss: + """Weighted FSIM loss.""" + + def __init__(self, chromatic=False, data_range=None, complex_i=False, device="cpu"): + """ + @args: + - chromatic (bool) : flag to compute FSIMc, which also takes into account chromatic components + - data_range (float): max data value in the training; if none, determine from data + - complex_i (bool): whether images are 2 channelled for complex data + - device (torch.device): device to run the loss on. + """ + self.complex_i = complex_i + self.chromatic = chromatic + self.data_range = data_range + + def __call__(self, outputs, targets, weights=None): + outputs_im, targets_im = _get_magnitude_tensor(outputs, targets, is_complex=self.complex_i) + + B, C, T, H, W = targets_im.shape + outputs_im = torch.reshape(torch.permute(outputs_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + targets_im = torch.reshape(torch.permute(targets_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + + data_range = self.data_range + if self.data_range is None: + data_range = torch.max(torch.cat((targets_im, outputs_im), dim=0)) + + loss = piq.fsim( + outputs_im, + targets_im, + reduction="none", + data_range=data_range, + chromatic=self.chromatic, + ) + + if weights is not None: + if weights.ndim == 1: + weights_used = weights.expand(T, B).permute(1, 0).reshape(B * T) + elif weights.ndim == 2: + weights_used = weights.reshape(B * T) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for FSIM_Loss" + ) + + v = torch.sum(weights_used * loss) / ( + torch.sum(weights_used) + torch.finfo(torch.float32).eps + ) + else: + v = torch.mean(loss) + + if torch.any(torch.isnan(v)): + v = torch.tensor(1.0, requires_grad=True) + + return 1.0 - v + + +# ------------------------------------------------------------------------------------------------- +# SSIM loss + + +class SSIM_Loss: + """Weighted SSIM loss.""" + + def __init__(self, window_size=11, complex_i=False, device="cpu"): + """ + @args: + - window_size (int): size of the window to use for loss computation + - complex_i (bool): whether images are 2 channelled for complex data + - device (torch.device): device to run the loss on. + """ + self.complex_i = complex_i + self.window_size = window_size + + def __call__(self, outputs, targets, weights=None): + outputs_im, targets_im = _get_magnitude_tensor(outputs, targets, is_complex=self.complex_i) + + B, C, T, H, W = targets_im.shape + outputs_im = torch.reshape(torch.permute(outputs_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + targets_im = torch.reshape(torch.permute(targets_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + + if weights is not None: + if weights.ndim == 1: + weights_used = weights.expand(T, B).permute(1, 0).reshape(B * T) + elif weights.ndim == 2: + weights_used = weights.reshape(B * T) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for SSIM_Loss" + ) + + v_ssim = torch.sum( + weights_used + * ssim_functional( + outputs_im, + targets_im, + gaussian_kernel=False, + kernel_size=self.window_size, + reduction=None, + ) + ) / (torch.sum(weights_used) + torch.finfo(torch.float32).eps) + else: + v_ssim = torch.mean( + ssim_functional( + outputs_im, + targets_im, + gaussian_kernel=False, + kernel_size=self.window_size, + reduction=None, + ) + ) + + if torch.any(torch.isnan(v_ssim)): + v_ssim = torch.tensor(1.0, requires_grad=True) + + return 1.0 - v_ssim + + +# ------------------------------------------------------------------------------------------------- +# SSIM3D loss + + +class SSIM3D_Loss(SSIM_Loss): + """Weighted SSIM3D loss.""" + + def __init__(self, window_size=11, complex_i=False, device="cpu"): + super().__init__(window_size=window_size, complex_i=complex_i, device=device) + + def __call__(self, outputs, targets, weights=None): + outputs_im, targets_im = _get_magnitude_tensor(outputs, targets, is_complex=self.complex_i) + + _B, _C, _T, _H, _W = targets_im.shape + + if weights is not None: + if not weights.ndim == 1: + raise NotImplementedError("Only support 1D(Batch) weights for SSIM3D_Loss") + v_ssim = torch.sum( + weights + * ssim_functional( + outputs_im, + targets_im, + gaussian_kernel=False, + kernel_size=self.window_size, + reduction=None, + ) + ) / (torch.sum(weights) + torch.finfo(torch.float32).eps) + else: + v_ssim = torch.mean( + ssim_functional( + outputs_im, + targets_im, + gaussian_kernel=False, + kernel_size=self.window_size, + reduction=None, + ) + ) + + if torch.any(torch.isnan(v_ssim)): + v_ssim = torch.tensor(1.0, requires_grad=True) + + return 1.0 - v_ssim + + +# ------------------------------------------------------------------------------------------------- +# MSSSIM loss + + +class MSSSIM_Loss: + """Weighted MSSSIM loss.""" + + def __init__(self, window_size=5, complex_i=False, data_range=256.0, device="cpu"): + """ + @args: + - window_size (int): size of the window to use for loss computation + - complex_i (bool): whether images are 2 channelled for complex data + - device (torch.device): device to run the loss on. + """ + self.complex_i = complex_i + self.data_range = data_range + self.msssim_loss = torchmetrics.image.MultiScaleStructuralSimilarityIndexMeasure( + sigma=window_size, reduction=None + ) + + def __call__(self, outputs, targets, weights=None): + outputs_im, targets_im = _get_magnitude_tensor(outputs, targets, is_complex=self.complex_i) + + B, C, T, H, W = targets_im.shape + outputs_im = torch.reshape(torch.permute(outputs_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + targets_im = torch.reshape(torch.permute(targets_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + + v = self.msssim_loss(outputs_im, targets_im) + + if weights is not None: + if weights.ndim == 1: + weights_used = weights.expand(T, B).permute(1, 0).reshape(B * T) + elif weights.ndim == 2: + weights_used = weights.reshape(B * T) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for MSSSIM_Loss" + ) + + v_ssim = torch.sum(weights_used * v) / ( + torch.sum(weights_used) + torch.finfo(torch.float32).eps + ) + else: + v_ssim = torch.mean(v) + + v_ssim = torch.clamp(v_ssim, 0.0, 1.0) + + self.msssim_loss.reset() + return 1.0 - v_ssim + + +# ------------------------------------------------------------------------------------------------- +# L1/mae loss + + +class L1_Loss: + """Weighted L1 loss.""" + + def __init__(self, complex_i=False): + """ + @args: + - complex_i (bool): whether images are 2 channelled for complex data. + """ + self.complex_i = complex_i + + def __call__(self, outputs, targets, weights=None): + B, C, T, _H, _W = targets.shape + if self.complex_i: + assert C == 2, f"Complex type requires image to have C=2, given C={C}" + diff_L1 = torch.abs(outputs[:, 0] - targets[:, 0]) + torch.abs( + outputs[:, 1] - targets[:, 1] + ) + else: + diff_L1 = torch.abs(outputs - targets) + + if weights is not None: + if weights.ndim == 1: + weights = weights.reshape(B, 1, 1, 1, 1) + elif weights.ndim == 2: + weights = weights.reshape(B, T, 1, 1, 1) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for L1_Loss" + ) + + v_l1 = torch.sum(weights * diff_L1) / ( + torch.sum(weights) + torch.finfo(torch.float32).eps + ) + else: + v_l1 = torch.sum(diff_L1) + + if torch.any(torch.isnan(v_l1)): + v_l1 = torch.mean(0.0 * outputs) + + return v_l1 / diff_L1.numel() + + +# ------------------------------------------------------------------------------------------------- +# MSE loss + + +class MSE_Loss: + """Weighted MSE loss.""" + + def __init__(self, rmse_mode=False, complex_i=False): + """ + @args: + - rmse_mode (bool): whether to turn root mean sequare error + - complex_i (bool): whether images are 2 channelled for complex data. + """ + self.rmse_mode = rmse_mode + self.complex_i = complex_i + + def __call__(self, outputs, targets, weights=None): + outputs = outputs.to(torch.float32) + targets = targets.to(torch.float32) + + B, C, T, _H, _W = targets.shape + if self.complex_i: + assert C == 2, f"Complex type requires image to have C=2, given C={C}" + diff_mag_square = torch.square(outputs[:, 0] - targets[:, 0]) + torch.square( + outputs[:, 1] - targets[:, 1] + ) + else: + diff_mag_square = torch.square(outputs - targets) + + if self.rmse_mode: + diff_mag_square = torch.sqrt(diff_mag_square) + + if weights is not None: + if weights.ndim == 1: + weights = weights.reshape(B, 1, 1, 1, 1) + elif weights.ndim == 2: + weights = weights.reshape(B, T, 1, 1, 1) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for MSE_Loss" + ) + + v_l2 = torch.sum(weights * diff_mag_square) / ( + torch.sum(weights) + torch.finfo(torch.float32).eps + ) + else: + return torch.mean(diff_mag_square) + + if torch.any(torch.isnan(v_l2)): + v_l2 = torch.mean(0.0 * outputs) + + return v_l2 / diff_mag_square.numel() + + +# ------------------------------------------------------------------------------------------------- +# PSNR + + +class PSNR: + """PSNR as a comparison metric.""" + + def __init__(self, range=1.0): + """ + @args: + - range (float): max range of the values in the images. + """ + self.range = range + + def __call__(self, outputs, targets): + num = self.range * self.range + den = torch.mean(torch.square(targets - outputs)) + torch.finfo(torch.float32).eps + + return 10 * torch.log10(num / den) + + +class PSNR_Loss: + """PSNR as a comparison metric.""" + + def __init__(self, range=1.0): + """ + @args: + - range (float): max range of the values in the images. + """ + self.range = range + + def __call__(self, outputs, targets, weights=None): + B, _C, T, _H, _W = targets.shape + + num = self.range * self.range + den = torch.square(targets - outputs) + torch.finfo(torch.float32).eps + + if weights is not None: + if weights.ndim == 1: + weights = weights.reshape(B, 1, 1, 1, 1) + elif weights.ndim == 2: + weights = weights.reshape(B, T, 1, 1, 1) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for PSNR_Loss" + ) + + v_l2 = torch.sum(weights * torch.log10(num / den)) / ( + torch.sum(weights) + torch.finfo(torch.float32).eps + ) + else: + v_l2 = torch.sum(torch.log10(num / den)) + + if torch.any(torch.isnan(v_l2)): + v_l2 = torch.mean(0.0 * outputs) + + return 10 - v_l2 / den.numel() + + +# ------------------------------------------------------------------------------------------------- + + +def perpendicular_loss_complex(X, Y): + """ + Perpendicular loss for complex MR images. + + from https://gitlab.com/computational-imaging-lab/perp_loss/-/blob/main/PerpLoss_-_Image_reconstruction.ipynb + + Args: + X (complex images): torch complex images + Y (complex images): torch complex images + + Outputs: + final_term: the loss in the same size as X and Y + """ + assert X.is_complex() + assert Y.is_complex() + + mag_input = torch.abs(X) + mag_target = torch.abs(Y) + cross = torch.abs(X.real * Y.imag - X.imag * Y.real) + + eps_float32 = torch.finfo(torch.float32).eps + + angle = torch.atan2(X.imag, X.real + eps_float32) - torch.atan2(Y.imag, Y.real + eps_float32) + ploss = torch.abs(cross) / (mag_input + eps_float32) + + aligned_mask = (torch.cos(angle) < 0).bool() + + final_term = torch.zeros_like(ploss) + final_term[aligned_mask] = mag_target[aligned_mask] + ( + mag_target[aligned_mask] - ploss[aligned_mask] + ) + final_term[~aligned_mask] = ploss[~aligned_mask] + + return final_term + + +class Perpendicular_Loss: + """Perpendicular loss.""" + + def __init__(self): + pass + + def __call__(self, outputs, targets, weights=None): + B, C, T, _H, _W = targets.shape + + if C == 1: + loss = perpendicular_loss_complex( + outputs[:, 0] + 1j * outputs[:, 0], targets[:, 0] + 1j * targets[:, 0] + ) + else: + loss = perpendicular_loss_complex( + outputs[:, 0] + 1j * outputs[:, 1], targets[:, 0] + 1j * targets[:, 1] + ) + + if weights is not None: + if weights.ndim == 1: + weights = weights.reshape(B, 1, 1, 1, 1) + elif weights.ndim == 2: + weights = weights.reshape(B, T, 1, 1, 1) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for Perpendicular_Loss" + ) + + v = torch.sum(weights * loss) / (torch.sum(weights) + torch.finfo(torch.float32).eps) + else: + return torch.mean(loss) + + if torch.any(torch.isnan(v)): + v = torch.mean(0.0 * outputs) + + return v / targets.numel() + + +# ------------------------------------------------------------------------------------------------- +# Charbonnier Loss + + +class Charbonnier_Loss: + """Charbonnier Loss (L1).""" + + def __init__(self, complex_i=False, eps=1e-3): + """ + @args: + - complex_i (bool): whether images are 2 channelled for complex data + - eps (float): epsilon, different values can be tried here. + """ + self.complex_i = complex_i + self.eps = eps + + def __call__(self, outputs, targets, weights=None): + B, C, T, _H, _W = targets.shape + if self.complex_i: + assert C == 2, f"Complex type requires image to have C=2, given C={C}" + diff_L1_real = torch.abs(outputs[:, 0] - targets[:, 0]) + diff_L1_imag = torch.abs(outputs[:, 1] - targets[:, 1]) + loss = torch.sqrt( + diff_L1_real * diff_L1_real + diff_L1_imag * diff_L1_imag + self.eps * self.eps + ) + else: + diff_L1 = torch.abs(outputs - targets) + loss = torch.sqrt(diff_L1 * diff_L1 + self.eps * self.eps) + + if weights is not None: + if weights.ndim == 1: + weights = weights.reshape(B, 1, 1, 1, 1) + elif weights.ndim == 2: + weights = weights.reshape(B, T, 1, 1, 1) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for L1_Loss" + ) + + v_l1 = torch.sum(weights * loss) / torch.sum(weights) + else: + v_l1 = torch.sum(loss) + + return v_l1 / loss.numel() + + +# ------------------------------------------------------------------------------------------------- +# Perceptual Loss + + +class VGGPerceptualLoss(torch.nn.Module): + """Perceptual Loss (VGG Loss).""" + + def __init__(self, complex_i=False, resize=False, interpolate_mode="bilinear"): + super().__init__() + blocks = [] + blocks.append(torchvision.models.vgg16(weights="DEFAULT").features[:4].eval()) + blocks.append(torchvision.models.vgg16(weights="DEFAULT").features[4:9].eval()) + blocks.append(torchvision.models.vgg16(weights="DEFAULT").features[9:16].eval()) + blocks.append(torchvision.models.vgg16(weights="DEFAULT").features[16:23].eval()) + for bl in blocks: + for p in bl.parameters(): + p.requires_grad = False + self.blocks = torch.nn.ModuleList(blocks) + self.transform = torch.nn.functional.interpolate + self.resize = resize + self.interpolate_mode = interpolate_mode + self.complex_i = complex_i + self.register_buffer("mean", torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1)) + self.register_buffer("std", torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1)) + + # can also try feature_layers=[2], style_layers=[0, 1, 2, 3] + def __call__(self, outputs, targets, feature_layers=None, style_layers=None, weights=None): + if style_layers is None: + style_layers = [] + if feature_layers is None: + feature_layers = [0, 1, 2, 3] + B, C, T, H, W = targets.shape + if self.complex_i: + assert C == 2, f"Complex type requires image to have C=2, given C={C}" + outputs_im = torch.sqrt(outputs[:, 0] * outputs[:, 0] + outputs[:, 1] * outputs[:, 1]) + targets_im = torch.sqrt(targets[:, 0] * targets[:, 0] + targets[:, 1] * targets[:, 1]) + outputs_im = torch.unsqueeze(outputs_im, dim=1) + targets_im = torch.unsqueeze(targets_im, dim=1) + else: + outputs_im = outputs + targets_im = targets + + B, C, T, H, W = targets_im.shape + outputs_im = torch.reshape(torch.permute(outputs_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + targets_im = torch.reshape(torch.permute(targets_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + + if outputs_im.shape[1] != 3: + outputs_im = outputs_im.repeat(1, 3, 1, 1) + targets_im = targets_im.repeat(1, 3, 1, 1) + + if self.resize: + outputs_im = self.transform( + outputs_im, mode=self.interpolate_mode, size=(224, 224), align_corners=False + ) + targets_im = self.transform( + targets_im, mode=self.interpolate_mode, size=(224, 224), align_corners=False + ) + + outputs_im = outputs_im.to(dtype=torch.float32) + targets_im = targets_im.to(dtype=torch.float32) + + loss = 0.0 + x = outputs_im + y = targets_im + for i, block in enumerate(self.blocks): + x = block(x) + y = block(y) + if i in feature_layers: + loss += torch.nn.functional.l1_loss(x, y) + if i in style_layers: + act_x = x.reshape(x.shape[0], x.shape[1], -1) + act_y = y.reshape(y.shape[0], y.shape[1], -1) + gram_x = act_x @ act_x.permute(0, 2, 1) + gram_y = act_y @ act_y.permute(0, 2, 1) + loss += torch.nn.functional.l1_loss(gram_x, gram_y) + + if weights is not None: + if weights.ndim == 1: + weights_used = weights.expand(T, B).permute(1, 0).reshape(B * T) + elif weights.ndim == 2: + weights_used = weights.reshape(B * T) + else: + raise NotImplementedError("Only support 1D(Batch) or 2D(Batch+Time) weights") + v_vgg = torch.sum(weights_used * loss) / ( + torch.sum(weights_used) + torch.finfo(torch.float16).eps + ) + else: + v_vgg = torch.mean(loss) + + return v_vgg + + +# ------------------------------------------------------------------------------------------------- + + +class GaussianDeriv1D_Loss: + """Weighted gaussian derivative loss for 1D.""" + + def __init__(self, sigmas=None, complex_i=False, device="cpu"): + """ + @args: + - sigmas (list): sigma for every scale + - complex_i (bool): whether images are 2 channelled for complex data + - device (torch.device): device to run the loss on. + """ + if sigmas is None: + sigmas = [0.5, 1.0, 1.25] + self.complex_i = complex_i + self.sigmas = sigmas + + # compute kernels + self.kernels = [] + for sigma in sigmas: + k_1d = create_gaussian_window_1d(sigma=sigma, halfwidth=3, voxelsize=1.0, order=1) + kx = k_1d.shape + k_1d = torch.from_numpy(np.reshape(k_1d, (1, 1, kx))).to(torch.float32) + self.kernels.append(k_1d.to(device=device)) + + def __call__(self, outputs, targets, weights=None): + outputs_im, targets_im = _get_magnitude_tensor(outputs, targets, is_complex=self.complex_i) + + B, _T, C = targets_im.shape + outputs_im = torch.permute(outputs_im, (0, 2, 1)) + targets_im = torch.permute(targets_im, (0, 2, 1)) + + loss = 0 + for k_1d in self.kernels: + grad_outputs_im = F.conv1d( + outputs_im, k_1d, bias=None, stride=1, padding="same", groups=C + ) + grad_targets_im = F.conv1d( + targets_im, k_1d, bias=None, stride=1, padding="same", groups=C + ) + loss += torch.mean( + torch.abs(grad_outputs_im - grad_targets_im), dim=(1, 2), keepdim=True + ) + + loss /= len(self.kernels) + + if weights is not None: + if weights.ndim == 1: + weights_used = weights.reshape((B, 1)) + else: + raise NotImplementedError( + "Only support 1D(Batch) weights for GaussianDeriv1D_Loss" + ) + + v = torch.sum(weights_used * loss) / ( + torch.sum(weights_used) + torch.finfo(torch.float32).eps + ) + else: + v = torch.mean(loss) + + if torch.any(torch.isnan(v)): + v = torch.mean(0.0 * outputs) + + return v + + +# ------------------------------------------------------------------------------------------------- + + +class GaussianDeriv_Loss: + """ + Weighted gaussian derivative loss for 2D + For every sigma, the gaussian derivatives are computed for outputs and targets along the magnitude of H and W + The l1 loss are computed to measure the agreement of gaussian derivatives. + + If sigmas have more than one value, every sigma in sigmas are used to compute a guassian derivative tensor + The mean l1 is returned + """ + + def __init__(self, sigmas=None, complex_i=False, device="cpu"): + """ + @args: + - sigmas (list): sigma for every scale + - complex_i (bool): whether images are 2 channelled for complex data + - device (torch.device): device to run the loss on. + """ + if sigmas is None: + sigmas = [0.5, 1.0, 1.25] + self.complex_i = complex_i + self.sigmas = sigmas + + # compute kernels + self.kernels = [] + for sigma in sigmas: + k_2d = create_gaussian_window_2d( + sigma=(sigma, sigma), halfwidth=(3, 3), voxelsize=(1.0, 1.0), order=(1, 1) + ) + kx, ky = k_2d.shape + k_2d = torch.from_numpy(np.reshape(k_2d, (1, 1, kx, ky))).to(torch.float32) + self.kernels.append(k_2d.to(device=device)) + + def __call__(self, outputs, targets, weights=None): + outputs_im, targets_im = _get_magnitude_tensor(outputs, targets, is_complex=self.complex_i) + + B, C, T, H, W = targets_im.shape + outputs_im = torch.reshape(torch.permute(outputs_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + targets_im = torch.reshape(torch.permute(targets_im, (0, 2, 1, 3, 4)), (B * T, C, H, W)) + + loss = 0 + for k_2d in self.kernels: + grad_outputs_im = F.conv2d( + outputs_im, k_2d, bias=None, stride=1, padding="same", groups=C + ) + grad_targets_im = F.conv2d( + targets_im, k_2d, bias=None, stride=1, padding="same", groups=C + ) + loss += torch.mean( + torch.abs(grad_outputs_im - grad_targets_im), dim=(1, 2, 3), keepdim=True + ) + + loss /= len(self.kernels) + + if weights is not None: + if weights.ndim == 1: + weights_used = weights.expand(T, B).permute(1, 0).reshape(B * T) + elif weights.ndim == 2: + weights_used = weights.reshape(B * T) + else: + raise NotImplementedError( + "Only support 1D(Batch) or 2D(Batch+Time) weights for GaussianDeriv_Loss" + ) + + v = torch.sum(weights_used * loss) / ( + torch.sum(weights_used) + torch.finfo(torch.float32).eps + ) + else: + v = torch.mean(loss) + + if torch.any(torch.isnan(v)): + v = torch.mean(0.0 * outputs) + + return v + + +# ------------------------------------------------------------------------------------------------- + + +class GaussianDeriv3D_Loss: + """ + Weighted gaussian derivative loss for 3D + For every sigma, the gaussian derivatives are computed for outputs and targets along the magnitude of T, H, W + The l1 loss are computed to measure the agreement of gaussian derivatives. + + If sigmas have more than one value, every sigma in sigmas are used to compute a guassian derivative tensor + The mean l1 is returned + """ + + def __init__(self, sigmas=None, sigmas_T=None, complex_i=False, device="cpu"): + """ + @args: + - sigmas (list): sigma for every scale along H and W + - sigmas_T (list): sigma for every scale along T + - complex_i (bool): whether images are 2 channelled for complex data + - device (torch.device): device to run the loss on. + """ + if sigmas_T is None: + sigmas_T = [0.5, 1.0, 1.25] + if sigmas is None: + sigmas = [0.5, 1.0, 1.25] + self.complex_i = complex_i + self.sigmas = sigmas + self.sigmas_T = sigmas_T + + assert len(self.sigmas_T) == len(self.sigmas) + + # compute kernels + self.kernels = [] + for sigma, sigma_T in zip(sigmas, sigmas_T, strict=True): + k_3d = create_gaussian_window_3d( + sigma=(sigma, sigma, sigma_T), + halfwidth=(3, 3, 3), + voxelsize=(1.0, 1.0, 1.0), + order=(1, 1, 1), + ) + kx, ky, kz = k_3d.shape + k_3d = torch.from_numpy(np.reshape(k_3d, (1, 1, kx, ky, kz))).to(torch.float32) + k_3d = torch.permute(k_3d, [0, 1, 4, 2, 3]) + self.kernels.append(k_3d.to(device=device)) + + def __call__(self, outputs, targets, weights=None): + outputs_im, targets_im = _get_magnitude_tensor(outputs, targets, is_complex=self.complex_i) + + _B, C, _T, _H, _W = targets_im.shape + + loss = 0 + for k_3d in self.kernels: + grad_outputs_im = F.conv3d( + outputs_im, k_3d, bias=None, stride=1, padding="same", groups=C + ) + grad_targets_im = F.conv3d( + targets_im, k_3d, bias=None, stride=1, padding="same", groups=C + ) + loss += torch.mean( + torch.abs(grad_outputs_im - grad_targets_im), dim=(1, 2, 3, 4), keepdim=True + ) + + loss /= len(self.kernels) + + if weights is not None: + if not weights.ndim == 1: + raise NotImplementedError( + "Only support 1D(Batch) weights for GaussianDeriv3D_Loss" + ) + v = torch.sum(weights * loss) / (torch.sum(weights) + torch.finfo(torch.float32).eps) + else: + v = torch.mean(loss) + + if torch.any(torch.isnan(v)): + v = torch.mean(0.0 * outputs) + + return v + + +# ------------------------------------------------------------------------------------------------- + + +class Spectral_Loss: + """ + Test Spectral loss based on fft. + + Mask out center and edges of the shifted square fft and + use different methods to computer the loss + """ + + def __init__(self, dim=None, min_bound=5, max_bound=95, complex_i=False, device="cpu"): + """ + @args: + - dim (int list): the dimension to take fft on + - min_bound (int): the percentage of middle mask + - max_bound (int): the percentage of edge mask + - complex_i (bool): whether images are 2 channelled for complex data + - device (torch.device): device to run the loss on. + """ + if dim is None: + dim = [-2, -1] + self.dim = dim + self.eps = 1e-7 + + self.min_bound = min_bound + self.max_bound = max_bound + + self.complex_i = complex_i + self.device = device + + def create_mask(self, shape): + _B, _C, _T, H, W = shape + + mask = torch.zeros(*shape) + + # keep edges at 0 make everything inside 1 + upper_bound_H = int(self.max_bound * H / 100) + upper_bound_W = int(self.max_bound * W / 100) + mask[:, :, :, (H - upper_bound_H) : upper_bound_H, (W - upper_bound_W) : upper_bound_W] = 1 + + lower_bound_H = int(self.min_bound * H / 100) + lower_bound_W = int(self.min_bound * W / 100) + ch, cw = H // 2, W // 2 + + # make the inner square 0 + # with the edge case to keep the middle single pixel + if self.min_bound != -1: + mask[ + :, + :, + :, + (ch - lower_bound_H) : (ch + lower_bound_H + 1), + (cw - lower_bound_W) : (cw + lower_bound_W + 1), + ] = 0 + + return mask + + def __call__(self, outputs, targets, weights=None): + _B, C, _T, _H, _W = targets.shape + if self.complex_i: + assert C == 2, f"Complex type requires image to have C=2, given C={C}" + outputs_im = outputs[:, 0] + 1j * outputs[:, 1] + targets_im = targets[:, 0] + 1j * targets[:, 1] + outputs_im = torch.unsqueeze(outputs_im, dim=1) + targets_im = torch.unsqueeze(targets_im, dim=1) + else: + outputs_im = outputs + targets_im = targets + + outputs_im = outputs_im.to(device=self.device) + targets_im = targets_im.to(device=self.device) + + outputs_fft = torch.fft.fftshift( + torch.fft.fftn(outputs_im, dim=self.dim, norm="backward"), dim=self.dim + ) + targets_fft = torch.fft.fftshift( + torch.fft.fftn(targets_im, dim=self.dim, norm="backward"), dim=self.dim + ) + + outputs_fft[outputs_fft == 0] = self.eps + targets_fft[targets_fft == 0] = self.eps + + outputs_fft_log = torch.log(outputs_fft) + targets_fft_log = torch.log(targets_fft) + + mask = self.create_mask(outputs_fft.shape).to(self.device) + + outputs_fft_log_masked = outputs_fft_log * mask + targets_fft_log_masked = targets_fft_log * mask + + # loss = perpendicular_loss_complex(outputs_fft_masked, targets_fft_masked) + + loss = torch.abs(targets_fft_log_masked - outputs_fft_log_masked) + + if weights is not None: + if not weights.ndim == 1: + raise NotImplementedError("Only support 1D(Batch) weights for spectral loss") + v = torch.sum(weights * loss) / (torch.sum(weights) + torch.finfo(torch.float16).eps) + else: + v = torch.sum(loss) + + if torch.any(torch.isnan(v)): + v = torch.mean(0.0 * outputs) + + return v / targets.numel() + + +# ------------------------------------------------------------------------------------------------- + + +class Wavelet_Loss: + """ + Test wavelet loss based on pytorch_wavelets. + This version performs 2D wavelet transformation on the last two dimensions [-2, -1]. + """ + + def __init__( + self, + J=1, + wave="db3", + mode="reflect", + separable=False, + only_h=True, + complex_i=False, + device="cpu", + ): + """ + @args: + - J (int): number of wavelet levels + - wave (str): wavelet type, 'haar', 'db', 'sym', 'coif', 'bior', 'rbio', 'dmey', 'gaus', 'mexh', 'morl', 'cgau', 'shan', 'fbsp', 'cmor' + - mode (str): 'zero', 'symmetric', 'reflect' or 'periodization' + - only_h (bool): if True, only count high frequency bands for loss + - complex_i (bool): whether images are 2 channelled for complex data. + """ + self.J = J + self.wave = wave + self.mode = mode + self.separable = separable + self.only_h = only_h + self.complex_i = complex_i + self.device = device + + self.wt = DWTForward(J=J, wave=wave, mode=mode).to(device=self.device) + self.iwt = DWTInverse(wave=wave, mode=mode).to(device=self.device) + + def __call__(self, outputs, targets, weights=None): + B, C, T, H, W = targets.shape + if self.complex_i: + assert C == 2, f"Complex type requires image to have C=2, given C={C}" + + outputs_im = outputs + targets_im = targets + + outputs_im = outputs_im.to(device=self.device) + targets_im = targets_im.to(device=self.device) + + ol, oh = self.wt(torch.reshape(outputs_im, (B * C, T, H, W))) + tl, th = self.wt(torch.reshape(targets_im, (B * C, T, H, W))) + + if weights is not None: + if not weights.ndim == 1: + raise NotImplementedError("Only support 1D(Batch) weights for spectral loss") + + loss = 0 + for a, b in zip(oh, th, strict=False): + v = torch.abs(a - b) + loss += torch.sum(weights * v) / ( + torch.sum(weights) + torch.finfo(torch.float16).eps + ) + + if not self.only_h: + v = torch.mean(torch.abs(ol - tl)) + loss += torch.sum(weights * v) / ( + torch.sum(weights) + torch.finfo(torch.float16).eps + ) + + loss /= targets.numel() + else: + loss = 0 + for a, b in zip(oh, th, strict=False): + loss += torch.mean(torch.abs(a - b)) + + if not self.only_h: + loss += torch.mean(torch.abs(ol - tl)) + + if torch.any(torch.isnan(loss)): + loss = torch.mean(0.0 * outputs) + + return loss + + +# ------------------------------------------------------------------------------------------------- +# Combined loss class + + +class Combined_Loss: + """ + Combined loss for image enhancement + Sums multiple loss with their respective weights. + """ + + def __init__(self, losses, loss_weights, complex_i=False, device="cpu") -> None: + """ + @args: + - losses (list of "ssim", "ssim3D", "l1", "mse"): + list of losses to be combined + - loss_weights (list of floats) + weights of the losses in the combined loss + - complex_i (bool): whether images are 2 channelled for complex data + - device (torch.device): device to run the loss on. + """ + assert len(losses) > 0, "At least one loss is required to setup" + assert len(losses) <= len(loss_weights), "Each loss should have its weight" + + self.complex_i = complex_i + self.device = device + + losses = [self.str_to_loss(loss) for loss in losses] + self.losses = list(zip(losses, loss_weights, strict=False)) + + def str_to_loss(self, loss_name): + if loss_name.lower() == "mse": + loss_f = MSE_Loss(rmse_mode=False, complex_i=self.complex_i) + elif loss_name.lower() == "rmse": + loss_f = MSE_Loss(rmse_mode=True, complex_i=self.complex_i) + elif loss_name.lower() == "l1": + loss_f = L1_Loss(complex_i=self.complex_i) + elif loss_name.lower() == "charbonnier": + loss_f = Charbonnier_Loss(complex_i=self.complex_i) + elif loss_name.lower() == "perceptual": + loss_f = VGGPerceptualLoss(complex_i=self.complex_i) + loss_f.to(self.device) + elif loss_name.lower() == "ssim": + loss_f = SSIM_Loss(window_size=7, complex_i=self.complex_i, device=self.device) + elif loss_name.lower() == "ssim3d": + loss_f = SSIM3D_Loss(window_size=5, complex_i=self.complex_i, device=self.device) + elif loss_name == "psnr": + loss_f = PSNR_Loss(range=2048.0) + elif loss_name.lower() == "perpendicular": + loss_f = Perpendicular_Loss() + elif loss_name.lower() == "msssim": + loss_f = MSSSIM_Loss( + window_size=3, complex_i=self.complex_i, data_range=256, device=self.device + ) + elif loss_name.lower() == "gaussian": + loss_f = GaussianDeriv_Loss( + sigmas=[0.25, 0.5, 1.0, 1.5], complex_i=self.complex_i, device=self.device + ) + elif loss_name.lower() == "gaussian3d": + loss_f = GaussianDeriv3D_Loss( + sigmas=[0.25, 0.5, 1.0], + sigmas_T=[0.25, 0.5, 0.5], + complex_i=self.complex_i, + device=self.device, + ) + elif loss_name.lower() == "spec": + loss_f = Spectral_Loss( + dim=[-2, -1], + min_bound=5, + max_bound=95, + complex_i=self.complex_i, + device=self.device, + ) + elif loss_name.lower() == "dwt": + loss_f = Wavelet_Loss( + J=2, + wave="db3", + mode="reflect", + separable=False, + only_h=True, + complex_i=self.complex_i, + device=self.device, + ) + else: + raise NotImplementedError(f"Loss type not implemented: {loss_name}") + + return loss_f + + def __call__(self, outputs, targets, weights=None): + outputs = outputs.to(torch.float32) + targets = targets.to(torch.float32) + + combined_loss = 0 + for loss_f, weight in self.losses: + v = weight * loss_f(outputs=outputs, targets=targets, weights=weights) + if not torch.any(torch.isnan(v)): + combined_loss += v + + return combined_loss + + +# -------------------------------------------------------------------- diff --git a/src/snraware/projects/mri/__init__.py b/src/snraware/projects/mri/__init__.py new file mode 100644 index 0000000..32020b8 --- /dev/null +++ b/src/snraware/projects/mri/__init__.py @@ -0,0 +1,2 @@ +from .denoising import * +from .snr import * diff --git a/src/snraware/projects/mri/denoising/__init__.py b/src/snraware/projects/mri/denoising/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/snraware/projects/mri/denoising/configs/config.yaml b/src/snraware/projects/mri/denoising/configs/config.yaml new file mode 100644 index 0000000..3326abc --- /dev/null +++ b/src/snraware/projects/mri/denoising/configs/config.yaml @@ -0,0 +1,61 @@ +# Configuration for the MR denoising project + +hydra: + run: + dir: ./.run/${now:%Y-%m-%d}--${now:%H-%M-%S} + + searchpath: + - file://./src/snraware/components/configs + +defaults: + - backbone: hrnet # hrnet, unet or soanet + - optim: sophia + - scheduler: OneCycleLR + - dataset: dataset_base + - trainer: lightning + - logging: logging_base + - heads: base_heads + - _self_ + - override hydra/job_logging: colorlog + - override hydra/hydra_logging: colorlog + +# override for denoising +backbone: + num_of_channels: 64 + block_str: ["T1L1G1", "T1L1G1"] + block: + cell: + window_size: [8, 8, 16] + patch_size: [4, 4, 2] + norm_mode: layer + n_head: 64 + +optim: + lr: 1e-5 + weight_decay: 0.0 + +step_mode: + total_steps: 0 # 0 means using trainer.max_epochs to compute the total steps + steps_to_report: 500 + steps_to_timing: 3000 + steps_to_validate: 30000 + steps_to_save_ckpts: 30000 + +train_data_dir: "./test/data/mri/denoising/tra" +test_data_dir: "./test/data/mri/denoising/test" + +seed: null + +batch_size: 1 +num_workers: 8 +prefetch_factor: 8 + +loss: ["mse", "perpendicular", "perceptual", "charbonnier", "gaussian3d"] +loss_weights: [1.0, 1.0, 1.0, 1.0, 1.0] + +scale_model_output_with_noise_level: false + +val_data_portion: 0.04 +overlap_for_inference: [16, 16, 8] + +device_mesh: [2, 2] \ No newline at end of file diff --git a/src/snraware/projects/mri/denoising/configs/dataset/dataset_base.yaml b/src/snraware/projects/mri/denoising/configs/dataset/dataset_base.yaml new file mode 100644 index 0000000..0c38d99 --- /dev/null +++ b/src/snraware/projects/mri/denoising/configs/dataset/dataset_base.yaml @@ -0,0 +1,29 @@ +_target_: snraware.projects.mri.denoising.data.MRIDenoisingDataset + +cutout_shape: [64, 64, 16] +repetition: 1 +min_noise_level: 0.1 +max_noise_level: 128.0 +kspace_filter_sigma: [0.8, 1.0, 1.5, 2.0, 2.25] +kspace_filter_sigma_T: [0.25, 0.5, 0.65, 0.85, 1.0, 1.5, 2.0, 2.25] +prob_apply_filter_T: 0.2 +pf_filter_ratio: [1.0, 0.875, 0.75, 0.625] +phase_resolution_ratio: [1.0, 0.75, 0.65, 0.55] +readout_resolution_ratio: [1.0, 0.75, 0.65, 0.55] +only_white_noise: false +ignore_gmap: false +add_noise: true +add_salt_pepper: true +salt_pepper_amount: 0.4 +salt_pepper_prob: 0.4 +add_poisson: true +poisson_prob: 0.4 +shuffle_along_3rd_dim: true +shuffle_along_3rd_dim_prob: 0.1 +matrix_size_adjust_ratio: [0.5, 0.75, 1.0, 1.25, 1.5] +matrix_size_adjust_prob: 0.5 +resolution_reduction_prob: 0.0 +partial_fourier_prob: 0.0 +single_frame_mode: true +single_frame_mode_prob: 0.1 +dicom_mode: false \ No newline at end of file diff --git a/src/snraware/projects/mri/denoising/configs/logging/logging_base.yaml b/src/snraware/projects/mri/denoising/configs/logging/logging_base.yaml new file mode 100644 index 0000000..f5b133f --- /dev/null +++ b/src/snraware/projects/mri/denoising/configs/logging/logging_base.yaml @@ -0,0 +1,17 @@ +project: "ifm-mri-denoising" +run_name: "denoising_training" + +output_dir: ${hydra:run.dir}/${logging.run_name} + +wandb_entity: "biomed-signal-processing" +wandb_dir: ${logging.output_dir}/wandb +use_wandb: false + +save_ckpt_every_n_epochs: 2 + +save_batches_to_output_dir: false + +# if wandb is enabled, batches are logged to wandb as videos +log_train_batches: 16 +log_val_batches: 16 +log_test_batches: 32 \ No newline at end of file diff --git a/src/snraware/projects/mri/denoising/configs/trainer/lightning.yaml b/src/snraware/projects/mri/denoising/configs/trainer/lightning.yaml new file mode 100644 index 0000000..ae33d60 --- /dev/null +++ b/src/snraware/projects/mri/denoising/configs/trainer/lightning.yaml @@ -0,0 +1,31 @@ +_target_: lightning.Trainer + +max_epochs: 120 + +precision: 32 + +devices: "auto" +accelerator: "auto" +strategy: "auto" # auto or fsdp +num_nodes: 1 + +gradient_clip_val: 1.0 +accumulate_grad_batches: 1 + +log_every_n_steps: 5 +enable_progress_bar: true +enable_model_summary: true +enable_checkpointing: true + +check_val_every_n_epoch: ${logging.save_ckpt_every_n_epochs} +val_check_interval: 1.0 + +fast_dev_run: false +overfit_batches: 0.0 +limit_train_batches: 1.0 +limit_val_batches: 1.0 +limit_test_batches: 1.0 + +deterministic: false +benchmark: true +profiler: null \ No newline at end of file diff --git a/src/snraware/projects/mri/denoising/data.py b/src/snraware/projects/mri/denoising/data.py new file mode 100644 index 0000000..7b5f133 --- /dev/null +++ b/src/snraware/projects/mri/denoising/data.py @@ -0,0 +1,712 @@ +""" +Dataset class for MR denoising training. Data is loaded from the file system. + +- All data files are loaded from `data_dir`. + +- Each sample is organized as: + // containing both "image" and "gmap". + + - `im` has shape [H, W, T/F] + - `gmap` has shape [H, W, 1 or T/F, N] + - N is the number of gmaps; for 2D+T data, gmap.shape[2] is usually 1; for 3D datasets, gmap.shape[2] equals the number of frames F. + +- Noise is generated and added to the clean image on the fly. + +- The returned sample tensor has shape [repetition, 3, cx, cy, ct]: + - `repetition` is the number of repetitions + - `cx`, `cy`, `ct` are the cutout patch sizes + - 3 channels correspond to real, imaginary, and gmap. If `dicom_mode` is enabled, the imaginary part is zero. Repetitions are created by sampling different noise levels and augmentations from the same input data. + +""" + +import os +import time + +import cv2 +import numpy as np +import torch +from colorama import Fore, Style +from skimage.util import view_as_blocks + +from snraware.projects.mri.denoising.utils import ( + deserialize_from_stream, + find_files_with_extension, +) +from snraware.projects.mri.snr.fftc import fft2c, ifft2c +from snraware.projects.mri.snr.imaging import adjust_matrix_size, apply_resolution_reduction_2D +from snraware.projects.mri.snr.noise import NoiseGenerator + +__all__ = ["MRIDenoisingDataset", "MRIDenoisingDatasetTest"] +# ------------------------------------------------------------------------------------------------- +# train dataset class + + +class MRIDenoisingDataset(torch.utils.data.Dataset): + def __init__( + self, + data_dir, + cutout_shape=(64, 64, 16), + repetition=1, + min_noise_level=1.0, + max_noise_level=32.0, + kspace_filter_sigma=(0.8, 1.0, 1.5, 2.0, 2.25), + kspace_filter_sigma_T=(0.25, 0.5, 0.65, 0.85, 1.0, 1.5, 2.0, 2.25), + prob_apply_filter_T=0.2, + pf_filter_ratio=(1.0, 0.875, 0.75, 0.625), + phase_resolution_ratio=(1.0, 0.75, 0.65, 0.55), + readout_resolution_ratio=(1.0, 0.75, 0.65, 0.55), + only_white_noise=False, + ignore_gmap=False, + add_noise=True, + add_salt_pepper=False, + salt_pepper_amount=0.4, + salt_pepper_prob=0.4, + add_poisson=False, + poisson_prob=0.4, + shuffle_along_3rd_dim=True, + shuffle_along_3rd_dim_prob=0.5, + matrix_size_adjust_ratio=(0.5, 0.75, 1.0, 1.25, 1.5), + matrix_size_adjust_prob=0.25, + resolution_reduction_prob=0.0, + partial_fourier_prob=0.0, + single_frame_mode=True, + single_frame_mode_prob=0.05, + dicom_mode=False, + rng=None, + ): + """ + Initialize the denoising dataset. + + Args: + data_dir (str): Path to the data directory. + cutout_shape (list): Shape of the image cutouts, for [H, W, T/F] + repetition (int): Number of repetitions for each sample. If set to K, every sampling + will create K different noisy versions of the same input. It is useful to improve efficiency. + + Parameters for the NoiseGenerator: + min_noise_level (float): Minimum noise level. + max_noise_level (float): Maximum noise level. + kspace_filter_sigma (list): List of k-space filter sigma values. + kspace_filter_sigma_T (list): List of k-space filter sigma values for T dimension. + prob_apply_filter_T (float): Probability of applying T dimension filter. + pf_filter_ratio (list): List of phase filter ratios. + phase_resolution_ratio (list): List of phase resolution ratios. + readout_resolution_ratio (list): List of readout resolution ratios. + only_white_noise (bool): Whether to use only white noise; if true, not create colored noise. + + ignore_gmap (bool): If ture, force to ignore the input gmap and supply an uniform R=1 array. + add_noise (bool): Whether to add noise to the images; if false, no noise will be added. + + Parameters for more augmentation: + add_salt_pepper (bool): Whether to add salt and pepper noise. + salt_pepper_amount (float): Amount of salt and pepper noise. + salt_pepper_prob (float): Probability of adding salt and pepper noise. + add_poisson (bool): Whether to add Poisson noise. + poisson_prob (float): Probability of adding Poisson noise. + shuffle_along_3rd_dim (bool): Whether to shuffle cutout frames along the 3rd dimension, to introduce temporal jitter and break signal consistency. + shuffle_along_3rd_dim_prob (float): Probability of shuffling along the 3rd dimension. + matrix_size_adjust_ratio (list): List of adjustment ratios. Before adding noise, image is randomly resized according to these ratios. + matrix_size_adjust_prob (float): Probability of applying matrix size adjustment. + resolution_reduction_prob (float): Probability of applying resolution reduction on sample images. + partial_fourier_prob (float): Probability of applying partial fourier on sample images. + single_frame_mode (bool): To help model for a single 2D image denoising, a frame can be randomly picked from the 2D+T or 3D data and used as input to cut multiple patches. For the case where T/F is 1, single frame model is always applied. + single_frame_mode_prob (float): Probability of applying single frame model. + dicom_mode (bool): If true, the image will be scaled by the signal level and converted to integer, to mimic the dicom images. + """ + assert os.path.exists(data_dir), f"data_dir {data_dir} does not exist." + assert cutout_shape[0] >= 8 and cutout_shape[1] >= 8 and cutout_shape[2] >= 8, ( + f"cutout_shape {cutout_shape} is not valid." + ) + assert repetition >= 1, f"repetition {repetition} is not valid." + + self.data_dir = data_dir + + self.cutout_shape = cutout_shape + self.repetition = repetition + + self.min_noise_level = min_noise_level + self.max_noise_level = max_noise_level + self.kspace_filter_sigma = kspace_filter_sigma + self.kspace_filter_sigma_T = kspace_filter_sigma_T + self.prob_apply_filter_T = prob_apply_filter_T + self.pf_filter_ratio = pf_filter_ratio + self.phase_resolution_ratio = phase_resolution_ratio + self.readout_resolution_ratio = readout_resolution_ratio + + self.ignore_gmap = ignore_gmap + self.only_white_noise = only_white_noise + self.add_noise = add_noise + self.add_salt_pepper = add_salt_pepper + self.salt_pepper_amount = salt_pepper_amount + self.salt_pepper_prob = salt_pepper_prob + self.add_poisson = add_poisson + self.poisson_prob = poisson_prob + + self.shuffle_along_3rd_dim = shuffle_along_3rd_dim + self.shuffle_along_3rd_dim_prob = shuffle_along_3rd_dim_prob + self.matrix_size_adjust_ratio = matrix_size_adjust_ratio + self.matrix_size_adjust_prob = matrix_size_adjust_prob + self.resolution_reduction_prob = resolution_reduction_prob + self.partial_fourier_prob = partial_fourier_prob + self.single_frame_mode = single_frame_mode + self.single_frame_mode_prob = single_frame_mode_prob + self.dicom_mode = dicom_mode + + self.samples = find_files_with_extension(self.data_dir, ".dat") + print( + f"{Fore.YELLOW}Found {len(self.samples)} samples from data directory{Style.RESET_ALL}" + ) + + if rng is None: + self.rng = np.random.Generator(np.random.PCG64(int(time.time()))) + else: + self.rng = rng + + self.noise_gen = NoiseGenerator( + min_noise_level=self.min_noise_level, + max_noise_level=self.max_noise_level, + kspace_filter_sigma=self.kspace_filter_sigma, + kspace_filter_sigma_T=self.kspace_filter_sigma_T, + pf_filter_ratio=self.pf_filter_ratio, + prob_apply_filter_T=self.prob_apply_filter_T, + phase_resolution_ratio=self.phase_resolution_ratio, + readout_resolution_ratio=self.readout_resolution_ratio, + only_white_noise=self.only_white_noise, + ) + + # ----------------------------------------------------------------------- + + def _load_one_sample_from_file(self, fname): + with open(fname, "rb") as f: + data = deserialize_from_stream(f) + return data + + def load_one_sample(self, i): + """ + Load one sample and create noisy pairs. + @args: + - i (int): index of the sample to load + @rets: + - noisy_im (5D torch.Tensor): noisy data, in the shape of [repetition, 3, H, W] for real, imag and gmap + - clean_im (5D torch.Tensor) : clean data, [repetition, 2, H, W] for real, imag + - noise_sigma (1D torch.Tensor): noise levels sampled, [repetition]. + """ + # get the sample + deserialized = self._load_one_sample_from_file(self.samples[i]) + + data = deserialized[1] + gmaps = deserialized[2] + + # load data and gmap + RO, E1, T = data.shape + + # pick a gmap and check its consistency to the data + num_gmaps = 1 + if self.ignore_gmap: + gmaps = np.ones((RO, E1, T, num_gmaps), dtype=np.float32) + else: + assert gmaps.shape[0] == RO and gmaps.shape[1] == E1 + + if gmaps.ndim == 2: + gmaps = np.expand_dims(gmaps, axis=(2, 3)) + + if gmaps.shape[2] == 1: + gmaps = np.repeat(gmaps, T, axis=2) + + data = data.astype(np.complex64) + gmaps = gmaps.astype(np.float32) + + # augmentation with the random flip + data, gmaps = self._random_flip(data, gmaps) + + # augmentation with the matrix size adjustment + if self.matrix_size_adjust_prob > 0 and self.rng.uniform() < self.matrix_size_adjust_prob: + data, gmaps = self._random_adjust_matrix_size(data, gmaps) + RO, E1, T = data.shape + + # --------------------------------------------------------- + # for every repetition, create a pair of samples + clean_data = None + noisy_data = None + noise_sigma = np.zeros((self.repetition,), dtype=np.float32) + noise_sigma_generated = np.zeros((self.repetition,), dtype=np.float32) + gmap_data = None + single_frame_model = (T == 1) or ( + self.single_frame_mode and self.rng.uniform() < self.single_frame_mode_prob + ) + for rep in range(self.repetition): + # we can sample a different gmap every repetition + gmap, _picked_ind_gmap = self._load_gmap(gmaps, index_picked=-1) + + if single_frame_model: + gmap_rep = self._pad_data_2D_for_cutout(gmap) + if rep == 0: # only need to get the 2D frame once for all repetitions + data_rep, gmap_rep = self._process_single_frame_mode( + self._pad_data_2D_for_cutout(data), gmap_rep + ) + else: + _, gmap_rep = self._process_single_frame_mode( + self._pad_data_2D_for_cutout(data), gmap_rep + ) + + else: + # ensure sample size is still valid for cutout + data_rep = self._pad_data_for_cutout(data) + gmap_rep = self._pad_data_for_cutout(gmap) + + if rep == 0: + clean_data = np.zeros((self.repetition, *data_rep.shape), dtype=np.complex64) + noisy_data = np.zeros((self.repetition, *data_rep.shape), dtype=np.complex64) + gmap_data = np.zeros((self.repetition, *gmap_rep.shape), dtype=np.float32) + + # create and add noise; different noise is sampled for every repetition + clean_data[rep], noisy_data[rep], noise_sigma[rep], noise_sigma_generated[rep] = ( + self._generate_corrupted_data(data_rep, gmap_rep) + ) + gmap_data[rep] = gmap_rep + + # --------------------------------------------------------- + # augment with other noise types if needed + if self.add_salt_pepper and self.rng.uniform() < self.salt_pepper_prob: + noisy_data = self._add_salt_and_pepper_noise(noisy_data) + + if self.add_poisson and self.rng.uniform() < self.poisson_prob: + noisy_data = self._add_poisson_noise(clean_data, noisy_data) + + if self.dicom_mode: + clean_data, noisy_data, gmap_data = self._process_dicom_mode( + clean_data, noisy_data, gmap_data + ) + + # --------------------------------------------------------- + # cut out the patch + s_x, s_y, s_t = self._get_cutout_range(clean_data[0]) + + clean_data_patch = self._get_cutout(clean_data, s_x, s_y, s_t) + noisy_data_patch = self._get_cutout(noisy_data, s_x, s_y, s_t) + gmap_patch = self._get_cutout(gmap_data, s_x, s_y, s_t) + + # augment to introduce some inconsistency + if (self.shuffle_along_3rd_dim) and (self.rng.uniform() < self.shuffle_along_3rd_dim_prob): + # perform shuffle along the 3rd dimension + t_indexes = np.arange(clean_data_patch.shape[-1]) + np.random.shuffle(t_indexes) + + clean_data_patch = clean_data_patch[:, :, :, t_indexes] + noisy_data_patch = noisy_data_patch[:, :, :, t_indexes] + + # --------------------------------------------------------- + # concatenate the real and imag parts + clean_data_patch = np.expand_dims(clean_data_patch, axis=0) + noisy_data_patch = np.expand_dims(noisy_data_patch, axis=0) + gmap_patch = np.expand_dims(gmap_patch, axis=0) + + noisy_data = np.concatenate( + [np.real(noisy_data_patch), np.imag(noisy_data_patch), gmap_patch], axis=0 + ) + clean_data = np.concatenate([np.real(clean_data_patch), np.imag(clean_data_patch)], axis=0) + + # --------------------------------------------------------- + # from [C, REP, H, W, T] to [REP, C, T, H, W] + noisy_data = np.transpose(noisy_data, [1, 0, 4, 2, 3]) + clean_data = np.transpose(clean_data, [1, 0, 4, 2, 3]) + + return ( + torch.from_numpy(noisy_data.astype(np.float32)), + torch.from_numpy(clean_data.astype(np.float32)), + torch.from_numpy(noise_sigma.astype(np.float32)), + torch.from_numpy(noise_sigma_generated.astype(np.float32)), + ) + + # ---------------------------------------------------------------- + + def _pad_data_2D_for_cutout(self, data): + if data.shape[0] < self.cutout_shape[0]: + data = np.pad( + data, ((0, self.cutout_shape[0] - data.shape[0]), (0, 0), (0, 0)), "symmetric" + ) + if data.shape[1] < self.cutout_shape[1]: + data = np.pad( + data, ((0, 0), (0, self.cutout_shape[1] - data.shape[1]), (0, 0)), "symmetric" + ) + return data + + def _pad_data_for_cutout(self, data): + data = self._pad_data_2D_for_cutout(data) + if data.shape[2] < self.cutout_shape[2]: + data = np.pad( + data, ((0, 0), (0, 0), (0, self.cutout_shape[2] - data.shape[2])), "symmetric" + ) + return data + + # ---------------------------------------------------------------- + + def _get_cutout_range(self, data): + x, y, t = data.shape + cx, cy, ct = self.cutout_shape + + s_x = self.rng.integers(0, x - cx + 1) + s_y = self.rng.integers(0, y - cy + 1) + s_t = self.rng.integers(0, t - ct + 1) + + return s_x, s_y, s_t + + # ----------------------------------------------------------- + + def _get_cutout(self, data, s_x, s_y, s_t): + """Given the start points, cut out a patch.""" + cx, cy, ct = self.cutout_shape + if data.ndim == 4: + return data[:, s_x : s_x + cx, s_y : s_y + cy, s_t : s_t + ct] + else: + return data[s_x : s_x + cx, s_y : s_y + cy, s_t : s_t + ct] + + # ---------------------------------------------------------------- + + def _load_gmap(self, gmaps, index_picked=-1): + """ + Loads a random gmap. Note it is a chance to get the R=1 gmap (aka 1.0 everywhere), no acceleration. + + Args: + gmaps (np.array): gmap [H, W, 1 or T/F, N] + index_picked (int, optional): if set, return gmap[:,:,:,index_picked]. Defaults to -1 for random picking. + + Returns: + a_gmap (np.array): picked gmap [H, W, 1 or T/F] + """ + N = gmaps.shape[-1] + if index_picked >= 0 and index_picked < N: + return gmaps[:, :, :, index_picked] + + random_factor = self.rng.integers(0, N + 1) + if random_factor < N: + return gmaps[:, :, :, random_factor], random_factor + else: + return np.ones(gmaps.shape[:3]), N + + # ---------------------------------------------------------------- + + def _random_flip(self, data, gmap): + """Randomly flips the input image and gmap.""" + flip1 = self.rng.integers(0, 2) > 0 + flip2 = self.rng.integers(0, 2) > 0 + + def flip(image): + if image.ndim == 4: + if flip1: + image = image[::-1, :, :, :].copy() + if flip2: + image = image[:, ::-1, :, :].copy() + elif image.ndim == 2: + if flip1: + image = image[::-1, :].copy() + if flip2: + image = image[:, ::-1].copy() + else: + if flip1: + image = image[::-1, :, :].copy() + if flip2: + image = image[:, ::-1, :].copy() + return image + + return flip(data), flip(gmap) + + # ---------------------------------------------------------------- + + def _random_adjust_matrix_size(self, data, gmaps): + matrix_size_adjust_ratio = self.matrix_size_adjust_ratio[ + self.rng.integers(0, len(self.matrix_size_adjust_ratio)).item() + ] + data_adjusted = adjust_matrix_size(data, matrix_size_adjust_ratio) + + gmaps_adjusted = np.zeros([*data_adjusted.shape, gmaps.shape[-1]]) + for rep in range(gmaps.shape[-1]): + for k in range(gmaps.shape[2]): + gmaps_adjusted[:, :, k, rep] = cv2.resize( + gmaps[:, :, k, rep], + dsize=(data_adjusted.shape[1], data_adjusted.shape[0]), + interpolation=cv2.INTER_LINEAR, + ) + + return data_adjusted, gmaps_adjusted + + # ---------------------------------------------------------------- + + def _random_reduce_resolution(self, data): + ratio_RO = self.readout_resolution_ratio[ + self.rng.integers(0, len(self.readout_resolution_ratio)).item() + ] + ratio_E1 = self.phase_resolution_ratio[ + self.rng.integers(0, len(self.phase_resolution_ratio)).item() + ] + + data_reduced_resolution, _, _ = apply_resolution_reduction_2D( + im=data, ratio_RO=ratio_RO, ratio_E1=ratio_E1, snr_scaling=False, norm="backward" + ) + + return data_reduced_resolution + + # ro_filter_sigma = self.kspace_filter_sigma[self.rng.integers(0, len(self.kspace_filter_sigma)).item()] + # e1_filter_sigma = self.kspace_filter_sigma[self.rng.integers(0, len(self.kspace_filter_sigma)).item()] + # data_reduced_resolution_filtered, _, _ = apply_image_filter_2D( + # data_reduced_resolution, sigma_RO=ro_filter_sigma, sigma_E1=e1_filter_sigma + # ) + + # if self.rng.uniform() < self.prob_apply_filter_T: + # T_filter_sigma = self.kspace_filter_sigma_T[self.rng.integers(0, len(self.kspace_filter_sigma_T)).item()] + # data_degraded, _ = apply_image_filter_T(data_reduced_resolution_filtered, sigma_T=T_filter_sigma) + # return data_degraded + # else: + # return data_reduced_resolution_filtered + + # ---------------------------------------------------------------- + + def _random_partial_fourier(self, data): + RO, E1 = data.shape[0], data.shape[1] + pf_lottery = self.rng.integers( + 0, 3 + ).item() # 0, only 1st dim; 1, only 2nd dim; 2, both dim + pf_ratio_RO = self.pf_filter_ratio[self.rng.integers(0, len(self.pf_filter_ratio)).item()] + pf_ratio_E1 = self.pf_filter_ratio[self.rng.integers(0, len(self.pf_filter_ratio)).item()] + + if self.rng.random() < 0.5: # picke pre or post-zero PF mode + start_RO = 0 + end_RO = int(pf_ratio_RO * RO) + else: + start_RO = RO - int(pf_ratio_RO * RO) + end_RO = RO - 1 + + if self.rng.random() < 0.5: + start_E1 = 0 + end_E1 = int(pf_ratio_E1 * E1) + else: + start_E1 = E1 - int(pf_ratio_E1 * E1) + end_E1 = E1 - 1 + + if pf_lottery == 0: # only apply PF on RO, not E1 + start_E1 = 0 + end_E1 = E1 - 1 + + if pf_lottery == 1: # only apply PF on E1, not RO + start_RO = 0 + end_RO = RO - 1 + + kspace = fft2c(data) + kspace[:start_RO, :, :] = 0 + kspace[end_RO + 1 :, :, :] = 0 + kspace[:, :start_E1, :] = 0 + kspace[:, end_E1 + 1 :, :] = 0 + + data_pf = ifft2c(kspace) + return data_pf + + # ---------------------------------------------------------------- + + def _process_single_frame_mode(self, data, gmap): + """Pick a 2D frame and extract patches to add the 3rd dimension.""" + T = data.shape[2] + + picked_t = self.rng.integers(0, T) + data_frame = data[:, :, picked_t] + gmap_frame = gmap[:, :, picked_t] + + def prepare_patches(frame): + pad_H = (-1 * frame.shape[0]) % self.cutout_shape[0] + pad_W = (-1 * frame.shape[1]) % self.cutout_shape[1] + frame = np.pad(frame, ((0, pad_H), (0, pad_W)), "symmetric") + frame = view_as_blocks(frame, (self.cutout_shape[0], self.cutout_shape[1])) + frame = np.reshape(frame, [-1, *frame.shape[-2:]]) + num_patches = frame.shape[0] + if num_patches > self.cutout_shape[2]: + start_t = self.rng.integers(0, max(num_patches - self.cutout_shape[2], 1)) + frame = frame[start_t : start_t + self.cutout_shape[2]] + elif num_patches < self.cutout_shape[2]: + pad_T = (self.cutout_shape[2] - num_patches) // 2 + pad_T_end = self.cutout_shape[2] - (num_patches + pad_T) + frame = np.pad(frame, ((pad_T, pad_T_end), (0, 0), (0, 0)), "symmetric") + + return frame + + data_patches_2D = prepare_patches(data_frame) + gmap_patches_2D = prepare_patches(gmap_frame) + + return np.transpose(data_patches_2D, [1, 2, 0]), np.transpose(gmap_patches_2D, [1, 2, 0]) + + # ---------------------------------------------------------------- + + def _apply_dicom_converion(self, im, scaling=1000.0): + mag = np.abs(im) + out = np.copy(im) + out.real = np.round(scaling * mag) + out.imag = 0 + return out + + def _process_dicom_mode(self, clean_data, noisy_data, gmap): + signal_scaling = np.percentile(np.abs(clean_data), 95) + clean_res = self._apply_dicom_converion(clean_data, scaling=1000.0 / signal_scaling) + noisy_res = self._apply_dicom_converion(noisy_data, scaling=1000.0 / signal_scaling) + return clean_res, noisy_res, np.full(gmap.shape, 1.0) + + # ---------------------------------------------------------------- + + def _generate_corrupted_data(self, data, gmap): + res = np.copy(data) + + if self.rng.uniform() < self.resolution_reduction_prob: + res = self._random_reduce_resolution(res) + + if self.rng.uniform() < self.partial_fourier_prob: + res = self._random_partial_fourier(res) + + if self.add_noise: + # create noise + nn, noise_sigma = self.noise_gen.generate( + RO=data.shape[0], E1=data.shape[1], T=data.shape[2], REP=1 + ) + noise_sigma_generated = np.std(nn) + + # apply gmap + nn_rep = np.squeeze(nn) * gmap + + # add noise to complex image and scale + res += nn_rep + + # scale the data + noise_scaling_ratio = 1.0 + if noise_sigma > 0: + noise_scaling_ratio = np.sqrt(noise_sigma * noise_sigma + 1).item() + + clean_data = data / noise_scaling_ratio + noisy_data = res / noise_scaling_ratio + noise_sigmas = noise_sigma + else: + clean_data = data + noisy_data = res + noise_sigmas = 0.0 + noise_sigma_generated = 0.0 + + return clean_data, noisy_data, noise_sigmas, noise_sigma_generated + + # ---------------------------------------------------------------- + + def _add_salt_and_pepper_noise(self, noisy_data): + s_vs_p = self.rng.uniform() # a random number to decide how much salt and how much pepper + amount = self.rng.uniform(0, self.salt_pepper_amount) + out = np.copy(noisy_data) + + # Salt mode + num_salt = np.ceil(amount * noisy_data.size * s_vs_p) + coords = self.rng.integers(0, noisy_data.size, int(num_salt)) + cc = np.unravel_index(coords, noisy_data.shape) + out[cc] *= self.rng.uniform(1.0, 10.0) + + # Pepper mode + num_pepper = np.ceil(amount * noisy_data.size * (1.0 - s_vs_p)) + coords = self.rng.integers(0, noisy_data.size, int(num_pepper)) + cc = np.unravel_index(coords, noisy_data.shape) + out[cc] *= self.rng.uniform(0, 1.0) + + return out + + # ---------------------------------------------------------------- + + def _add_poisson_noise(self, clean_data, noisy_data): + lam_ratio = self.rng.integers(1, 10) + mag = np.abs(clean_data) / lam_ratio + pn = np.random.poisson(mag, clean_data.shape) - mag + return noisy_data + pn + + # ---------------------------------------------------------------- + + def __len__(self): + return len(self.samples) + + # ---------------------------------------------------------------- + + def __getitem__(self, index): + return self.load_one_sample(index) + + +# ------------------------------------------------------------------------------------------------- +# test dataset class + + +class MRIDenoisingDatasetTest(MRIDenoisingDataset): + """ + Dataset for MRI denoising testing. + Returns full images. No cutouts. + + The test dataset has the entry for noisy_images as the input to the network. + + // --> "image" for ground-truth + // --> "noisy" for test input + // --> "noise_sigma" for the noise level of this test case + + """ + + def __init__(self, data_dir, ignore_gmap=False, dicom_mode=False): + super().__init__( + data_dir=data_dir, + ignore_gmap=ignore_gmap, + dicom_mode=dicom_mode, + ) + + def load_one_sample(self, i): + """ + Loads one sample from the saved images + @args: + - i (int): index of retreive + @rets: + - noisy (5D torch.Tensor): noisy data, in the shape of [3, T, H, W] + - clean (5D torch.Tensor) : clean data, [1, RO, E1] for magnitude and [2, T, H, W] for complex + - noise_sigma (0D torch.Tensor): noise sigma added to the image. + """ + deserialized = self._load_one_sample_from_file(self.samples[i]) + + noisy_data = deserialized[0] + clean_data = deserialized[1] + gmap = deserialized[2] + noise_sigma = deserialized[3] + + # load data and gmap + RO, E1, T = clean_data.shape + + # pick a gmap and check its consistency to the data + if self.ignore_gmap: + gmap = np.ones((RO, E1, T), dtype=np.float32) + + if gmap.ndim == 2 or gmap.shape[2] == 1: + gmap = np.repeat(gmap[:, :, np.newaxis], T, axis=2) + + clean_data = clean_data.astype(np.complex64) + noisy_data = noisy_data.astype(np.complex64) + gmap = gmap.astype(np.float32) + + if self.dicom_mode: + clean_data, noisy_data, gmap = self._process_dicom_mode(clean_data, noisy_data, gmap) + + clean_data = np.expand_dims(clean_data, axis=0) + noisy_data = np.expand_dims(noisy_data, axis=0) + gmap = np.expand_dims(gmap, axis=0) + + noisy_data = np.concatenate([np.real(noisy_data), np.imag(noisy_data), gmap], axis=0) + clean_data = np.concatenate([np.real(clean_data), np.imag(clean_data)], axis=0) + + # from [C, H, W, T] to [C, T, H, W] + noisy_data = np.transpose(noisy_data, [0, 3, 1, 2]) + clean_data = np.transpose(clean_data, [0, 3, 1, 2]) + + return ( + torch.from_numpy(noisy_data.astype(np.float32)), + torch.from_numpy(clean_data.astype(np.float32)), + torch.from_numpy(noise_sigma.astype(np.float32)), + torch.from_numpy(noise_sigma.astype(np.float32)), + ) + + +# ------------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + pass + +# ----------------------------------------------------------------- diff --git a/src/snraware/projects/mri/denoising/inference.py b/src/snraware/projects/mri/denoising/inference.py new file mode 100644 index 0000000..09eadcd --- /dev/null +++ b/src/snraware/projects/mri/denoising/inference.py @@ -0,0 +1,359 @@ +"""Run the denoising model inference.""" + +import numpy as np +import torch +from skimage.util.shape import view_as_windows + +__all__ = [ + "apply_model", + "image_to_patches", + "patches_to_image", + "running_inference", +] + +# ----------------------------------------------------------------- + + +def image_to_patches(image, cutout=(64, 64, 16), overlap=(16, 16, 8)): + """ + Extract patches from an image. + + Args: + image (np.ndarray): The input image to extract patches from. + cutout (tuple, optional): The size of the patches to extract. Defaults to (64,64,16). + overlap (tuple, optional): The overlap between patches. Defaults to (16,16,8). + + Returns: + np.ndarray: The extracted patches. + """ + CO, TO, HO, WO = image.shape # original + Hc, Wc, Tc = cutout # cutout + Ho, Wo, To = overlap # overlap + Ts, Hs, Ws = Tc - To, Hc - Ho, Wc - Wo # sliding window shape + + # padding the image so we have a complete coverup + # in each dim we pad the left side by overlap + # and then cover the right side by what remains from the sliding window + image_pad = np.pad( + image, ((0, 0), (To, -TO % Ts), (Ho, -HO % Hs), (Wo, -WO % Ws)), "symmetric" + ) + + # breaking the image down into patches + # and remembering the length in each dimension + image_patches = view_as_windows(image_pad, (CO, Tc, Hc, Wc), (1, Ts, Hs, Ws)) + _K, _Ntme, Nrow, Ncol, _, _, _, _ = image_patches.shape + image_patches_shape = image_patches.shape + + is_2d_mode = False + if Tc == 1: + image_patches = np.transpose(image_patches, (4, 0, 1, 2, 3, 5, 6, 7)) + Tc = Nrow * Ncol + is_2d_mode = True + + image_batch = image_patches.reshape(-1, CO, Tc, Hc, Wc) # shape:(num_patches,C,T,H,W) + + return ( + image_batch, + is_2d_mode, + (CO, TO, HO, WO), + image_patches_shape, + image_pad.shape, + (Ts, Hs, Ws), + ) + + +# ------------------------------------------------------------------------------------------------- + + +def patches_to_image( + image_batch_pred, + d_type, + is_2d_mode, + image_shape, + image_patches_shape, + image_pad_shape, + sliding_win_shape, + ratio_H, + ratio_W, + cutout=(64, 64, 16), + overlap=(16, 16, 8), +): + """ + Reconstruct an image from its patches. + + Averaged values are computed in the overlapped region. + + Args: + image_batch_pred (np.ndarray): The predicted image patches. + d_type (np.dtype): The data type of the output image. + is_2d_mode (bool): Whether the model is 2D or 3D. + image_shape (tuple): The shape of the original image. + image_patches_shape (tuple): The shape of the image patches. + image_pad_shape (tuple): The shape of the padded image. + sliding_win_shape (tuple): The shape of the sliding window. + ratio_H (int): The upsampling ratio for height. + ratio_W (int): The upsampling ratio for width. + cutout (tuple, optional): The size of the patches to extract. Defaults to (64,64,16). + overlap (tuple, optional): The overlap between patches. Defaults to (16,16,8). + + Returns: + np.ndarray: The reconstructed image. + """ + _CO, TO, HO, WO = image_shape + Hc, Wc, Tc = cutout + Ho, Wo, To = overlap + K, Ntme, Nrow, Ncol, _, _, _, _ = image_patches_shape + Ts, Hs, Ws = sliding_win_shape + + H_o, W_o = image_batch_pred.shape[-2], image_batch_pred.shape[-1] + C_out = image_batch_pred.shape[1] + + if is_2d_mode: + image_batch_pred = np.reshape( + image_batch_pred, (Ntme * K * Nrow * Ncol, C_out, 1, H_o, W_o) + ) + Tc = 1 + + # set the output image shape, consider the upsampling ratio + image_patches_ot_shape = (K, Ntme, Nrow, Ncol, C_out, Tc, H_o, W_o) + image_pad_ot_shape = ( + C_out, + image_pad_shape[1], + image_pad_shape[-2] * ratio_H, + image_pad_shape[-1] * ratio_W, + ) + + # --------------------------------------------------------------------------------------------- + # setting up the weight matrix + # matrix_weight defines how much a patch contributes to a pixel + # image_wgt is the sum of all weights. easier calculation for result + + cutout_output = list(cutout) + cutout_output[1] *= ratio_H + cutout_output[2] *= ratio_W + + matrix_weight = np.ones((cutout_output), dtype=d_type) + + Ho *= ratio_H + Wo *= ratio_W + + for t in range(To): + matrix_weight[:, :, t] *= (t + 1) / To + matrix_weight[:, :, -t - 1] *= (t + 1) / To + + for h in range(Ho): + matrix_weight[h, :, :] *= (h + 1) / Ho + matrix_weight[-h - 1, :, :] *= (h + 1) / Ho + + for w in range(Wo): + matrix_weight[:, w, :] *= (w + 1) / Wo + matrix_weight[:, -w - 1, :] *= (w + 1) / Wo + + matrix_weight = np.transpose(matrix_weight, (2, 0, 1)) + + image_wgt = np.zeros(image_pad_ot_shape, dtype=d_type) # filled in the loop below + matrix_weight = np.repeat(matrix_weight[np.newaxis, :], C_out, axis=0) + matrix_rep = np.repeat(matrix_weight[np.newaxis], Ntme * Nrow * Ncol, axis=0) + matrix_rep = matrix_rep.reshape(image_patches_ot_shape) + + # --------------------------------------------------------------------------------------------- + # Putting the patches back together + image_batch_pred = image_batch_pred.reshape(image_patches_ot_shape) + image_prd = np.zeros(image_pad_ot_shape, dtype=d_type) + + Hc *= ratio_H + Wc *= ratio_W + + Hs *= ratio_H + Ws *= ratio_W + + HO *= ratio_H + WO *= ratio_W + + for nt in range(Ntme): + for nr in range(Nrow): + for nc in range(Ncol): + image_wgt[ + :, Ts * nt : Ts * nt + Tc, Hs * nr : Hs * nr + Hc, Ws * nc : Ws * nc + Wc + ] += matrix_rep[0, nt, nr, nc] + image_prd[ + :, Ts * nt : Ts * nt + Tc, Hs * nr : Hs * nr + Hc, Ws * nc : Ws * nc + Wc + ] += matrix_weight * image_batch_pred[0, nt, nr, nc] + + image_prd /= image_wgt + + # remove the extra padding + image_fin = image_prd[:, To : To + TO, Ho : Ho + HO, Wo : Wo + WO] + + return image_fin + + +# ------------------------------------------------------------------------------------------------- + + +def running_inference( + model, + image, + cutout=(64, 64, 16), + overlap=(16, 16, 4), + batch_size=1, + device="cpu", + verbose=False, +): + """ + Runs inference by breaking image into overlapping patches + Runs the patches through the model and then stiches them back + @args: + - model (torch or onnx model): the model to run inference with + - image (numpy.ndarray): the image to run inference on + - requires the image to have [C,T,H,W] + - cutout (int 3-tuple): the patch shape for each cutout [H,W,T] + - overlap (int 3-tuple): the number of pixels to overlap [H,W,T] + - required to be smaller than cutout + - batch_size (int): number of patches per model call + - device (torch.device): the device to run inference on + @rets: + - output (4D numpy.ndarray): result as numpy array [C,T,H,W]. + """ + assert cutout > overlap, "cutout should be greater than overlap" + + # split input image to patches + ( + image_batch, + is_2d_mode, + image_shape, + image_patches_shape, + image_pad_shape, + sliding_win_shape, + ) = image_to_patches(image, cutout=cutout, overlap=overlap) + + d_type = image.dtype + Tc = image_batch.shape[2] + + model.to(device=device) + model.eval() + + image_batch_pred = None + with torch.inference_mode(): + # process every patch, respects the batch setting + for i in range(0, image_batch.shape[0], batch_size): + x_in = torch.from_numpy(image_batch[i : i + batch_size]).to( + device=device, dtype=torch.float32 + ) + res = model(x_in) + res = res.to(torch.float32).cpu().numpy() + + if image_batch_pred is None: + image_batch_pred = np.empty( + (image_batch.shape[0], res.shape[1], Tc, res.shape[-2], res.shape[-1]), + dtype=d_type, + ) + ratio_H = int(res.shape[-2] // x_in.shape[-2]) + ratio_W = int(res.shape[-1] // x_in.shape[-1]) + + image_batch_pred[i : i + batch_size] = res + + # put processed patches together + output = patches_to_image( + image_batch_pred, + d_type, + is_2d_mode, + image_shape, + image_patches_shape, + image_pad_shape, + sliding_win_shape, + ratio_H, + ratio_W, + cutout=cutout, + overlap=overlap, + ) + + return output + + +# ------------------------------------------------------------------------------------------------- + + +def apply_model( + model, + data, + gmap, + scaling_factor=1.0, + cutout=(64, 64, 16), + overlap=(16, 16, 8), + batch_size=1, + device="cuda", + verbose=False, +): + """ + Apply the inference model to the data x with gmap g + Input + data : [H, W, T or 1] + gmap : [H, W, T or 1] + scaling_factor : scaling factor to adjust denoising strength, smaller value is for higher strength (e.g. 0.5 is more smoothing than 1.0) + Output + res: [H, W, T]. + """ + H, W, T = data.shape + if gmap.ndim == 2: + gmap = np.expand_dims(gmap, axis=2) + + if gmap.shape[2] == 1 and T > 1: + gmap = np.repeat(gmap, T, axis=2) + + assert gmap.shape[0] == H and gmap.shape[1] == W + + if verbose: + print(f"---> apply_model, data array {data.shape}", flush=True) + print(f"---> apply_model, gmap array {gmap.shape}, median {np.median(gmap)}", flush=True) + print(f"---> apply_model, scaling_factor {scaling_factor}", flush=True) + print(f"---> apply_model, cutout {cutout}", flush=True) + print(f"---> apply_model, overlap {overlap}", flush=True) + print(f"---> apply_model, batch_size {batch_size}", flush=True) + print(f"---> apply_model, device {device}", flush=True) + + res = np.copy(data) + + try: + x = np.transpose(data, [2, 0, 1]) * scaling_factor + g = np.transpose(gmap, [2, 0, 1]) + + if np.iscomplexobj(x): + input = np.concatenate( + (x[np.newaxis, :].real, x[np.newaxis, :].imag, g[np.newaxis, :]), axis=0 + ) + else: + input = np.concatenate((x[np.newaxis, :], g[np.newaxis, :]), axis=0) + + if verbose: + print( + f"---> apply_model, for input array {input.shape}, gmap {g.shape}, cutout {cutout}, overlap {overlap}, batch_size {batch_size}", + flush=True, + ) + + res = running_inference( + model=model, + image=input, + cutout=cutout, + overlap=overlap, + batch_size=batch_size, + device=device, + verbose=verbose, + ) + + if np.iscomplexobj(x): + res = res[0] + 1j * res[1] + + res = np.transpose(res, [1, 2, 0]) / scaling_factor + + except Exception as e: + print(f"Error happened in apply_model: {e}") + + return res + + +# --------------------------------------------------------------------------------------- + + +if __name__ == "__main__": + pass diff --git a/src/snraware/projects/mri/denoising/inference_model.py b/src/snraware/projects/mri/denoising/inference_model.py new file mode 100644 index 0000000..1ece2bf --- /dev/null +++ b/src/snraware/projects/mri/denoising/inference_model.py @@ -0,0 +1,103 @@ +"""Run the denoising model inference.""" + +import torch +from omegaconf import OmegaConf + +from snraware.projects.mri.denoising.lightning_denoising import LitDenoising +from snraware.projects.mri.denoising.model import DenoisingModel + +__all__ = [ + "load_lit_model", + "load_model", + "load_scripted_model", +] + +# ------------------------------------------------------------------------------------------------- + + +def load_scripted_model(saved_model_path): + """ + Load a saved torch scripted ".pts" model + @rets: + - model (torch scripted model): the model ready for inference. + """ + model = None + try: + model = torch.jit.load(saved_model_path) + except Exception as e: + print(f"Error happened in load_scripted_model for {saved_model_path}: {e}") + + return model + + +# ------------------------------------------------------------------------------------------------- + + +def load_model(saved_model_path, saved_config_path): + """ + Load a saved torch ".pth" model + @rets: + - model (torch model): the model ready for inference + - config (omegaconf): the config used to create the model. + """ + model = None + config = None + try: + # load config + config = OmegaConf.load(saved_config_path) + + # instantiate a model with this config + model = DenoisingModel( + config=config, + D=config.dataset.cutout_shape[2], + H=config.dataset.cutout_shape[0], + W=config.dataset.cutout_shape[1], + ) + + # load the model weights + status = torch.load(saved_model_path) + if "model_state_dict" in status: + model.load_state_dict(status["model_state_dict"]) + else: + model.load_state_dict(status) + except Exception as e: + print(f"Error happened in load_model for {saved_model_path}, {saved_config_path}: {e}") + + return model, config + + +# ------------------------------------------------------------------------------------------------- + + +def load_lit_model(saved_model_path, saved_config_path): + """ + Load a saved lightning model + @rets: + - model (torch model): the model ready for inference + - config (omegaconf): the config used to create the model. + """ + lit_model = None + config = None + try: + # load config + config = OmegaConf.load(saved_config_path) + + model = DenoisingModel( + config=config, + D=config.dataset.cutout_shape[2], + H=config.dataset.cutout_shape[0], + W=config.dataset.cutout_shape[1], + ) + + lit_model = LitDenoising.load_from_checkpoint(saved_model_path, model=model, config=config) + except Exception as e: + print(f"Error happened in load_lit_model for {saved_model_path}, {saved_config_path}: {e}") + + return lit_model, config + + +# --------------------------------------------------------------------------------------- + + +if __name__ == "__main__": + pass diff --git a/src/snraware/projects/mri/denoising/lightning_denoising.py b/src/snraware/projects/mri/denoising/lightning_denoising.py new file mode 100644 index 0000000..79d37d3 --- /dev/null +++ b/src/snraware/projects/mri/denoising/lightning_denoising.py @@ -0,0 +1,577 @@ +import os +import time + +import cv2 +import hydra +import lightning as L +import numpy as np +import torch +import wandb +from colorama import Fore, Style +from omegaconf import OmegaConf +from torch.utils.data import random_split + +from snraware.components.optim import OptimScheduler +from snraware.projects.loss.imaging_loss import PSNR, Combined_Loss +from snraware.projects.mri.denoising.data import MRIDenoisingDatasetTest +from snraware.projects.mri.denoising.inference import running_inference + +# ----------------------------------------------------------------- + + +class LitDenoising(L.LightningModule): + def __init__(self, model, config): + super().__init__() + self.config = config + self.model = model + self.batch_size = self.model.config.batch_size + self.scheduler_type = self.model.config.scheduler.name + self.optim_sched = None + self.loss_fn = None + self.metric_names = [ + "mse", + "l1", + "perpendicular", + "perceptual", + "psnr", + "spec", + "dwt", + "ssim", + "ssim3d", + ] + self.metrics = None + self.durations = [] + + self.train_saved_dir = os.path.join(self.config.logging.output_dir, "train_samples") + self.val_saved_dir = os.path.join(self.config.logging.output_dir, "val_samples") + self.test_saved_dir = os.path.join(self.config.logging.output_dir, "test_samples") + + if self.config.logging.save_batches_to_output_dir: + os.makedirs(self.train_saved_dir, exist_ok=True) + os.makedirs(self.val_saved_dir, exist_ok=True) + os.makedirs(self.test_saved_dir, exist_ok=True) + + self._epoch_start_time = 0.0 + + def forward(self, x): + return self.model(x) + + def setup(self, stage=None): + if stage == "fit" or stage is None: + # create loss function + if self.loss_fn is None: + self.loss_fn = Combined_Loss( + losses=self.config.loss, + loss_weights=self.config.loss_weights, + complex_i=True, + device=self.device, + ) + # create metrics + if self.metrics is None: + self.metrics = [ + Combined_Loss( + losses=[n], loss_weights=[1.0], complex_i=True, device=self.device + ) + for n in self.metric_names + ] + + # add PSNR + self.metric_names.insert(0, "PSNR") + self.metrics.insert(0, PSNR(range=2048.0)) + + def on_train_start(self): + if self.trainer.global_rank == 0 and self.config.logging.log_train_batches > 0: + # get some training samples + tra_loader = torch.utils.data.DataLoader( + self.trainer.datamodule.train_set, + batch_size=self.config.batch_size, + shuffle=True, + num_workers=self.config.num_workers, + drop_last=True, + ) + + loader_iter = iter(tra_loader) + + for batch_idx in range(self.config.logging.log_train_batches): + noisy, clean, noise_sigma = self._decompose_batch(next(loader_iter)) + self._save_and_log_batches( + stage="train", + noisy=noisy, + clean=clean, + noise_sigma=noise_sigma, + pred=None, + batch_idx=batch_idx, + ) + + def on_train_epoch_start(self): + # mark epoch start using a stable wall clock + self._epoch_start_time = time.perf_counter() + + def on_train_epoch_end(self): + # compute elapsed time safely without relying on internal timers + start = getattr(self, "_epoch_start_time", None) + if start is None: + return + duration_in_seconds = time.perf_counter() - start + self.durations.append(duration_in_seconds) + + # log as the max across ranks so the slowest worker defines the epoch time + self.log( + "time/epoch_seconds", + torch.tensor(duration_in_seconds, device=self.device), + prog_bar=False, + on_step=False, + on_epoch=True, + sync_dist=True, + reduce_fx="max", + ) + + def _decompose_batch(self, batch): + noisy, clean, noise_sigma, _ = batch + # flatten batch and repetition + if noisy.ndim == 6: + noisy = noisy.view(-1, *noisy.shape[2:]) + clean = clean.view(-1, *clean.shape[2:]) + noise_sigma = noise_sigma.view(-1, *noise_sigma.shape[2:]) + return noisy, clean, noise_sigma + + def training_step(self, batch, batch_idx): + try: + noisy, clean, _ = self._decompose_batch(batch) + pred = self.model(noisy) + if torch.isnan(pred).any(): + return None + loss = self.loss_fn(pred, clean) + + curr_lrs = self.optim_sched.report_lr() + self.log( + "lr", + curr_lrs[0][0], + prog_bar=True, + on_step=True, + on_epoch=False, + sync_dist=True, + batch_size=self.batch_size, + ) + + self.log( + "tra/loss", + loss, + prog_bar=True, + on_step=True, + on_epoch=True, + sync_dist=True, + batch_size=self.batch_size, + ) + + for i, m in enumerate(self.metrics): + v = m(pred, clean) + self.log( + f"tra/{self.metric_names[i]}", + v, + prog_bar=True, + on_step=False, + on_epoch=True, + sync_dist=True, + batch_size=self.batch_size, + ) + + if torch.isnan(loss).any(): + return None + + return loss + + except Exception as e: + print(f"Error computing loss: {e}") + return None + + def validation_step(self, batch, batch_idx): + noisy, clean, noise_sigma = self._decompose_batch(batch) + pred = self.model(noisy) + loss = self.loss_fn(pred, clean) + + if self.trainer.sanity_checking: + print(f"Running validation sanity check for batch {batch_idx}") + self.log( + "val/loss_sanity_check", + loss, + prog_bar=True, + on_step=False, + on_epoch=True, + sync_dist=True, + batch_size=self.batch_size, + ) + else: + self.log( + "val/loss", + loss, + prog_bar=True, + on_step=False, + on_epoch=True, + sync_dist=True, + batch_size=self.batch_size, + ) + + # for the validation, to compare different sample more fairly + B = noisy.shape[0] + noise_sigmas = torch.reshape(noise_sigma, [B, 1, 1, 1, 1]) + + noisy[:, 0] *= noise_sigmas[:, 0] + noisy[:, 1] *= noise_sigmas[:, 0] + clean *= noise_sigmas + pred *= noise_sigmas + + for i, m in enumerate(self.metrics): + v = m(pred, clean) + self.log( + f"val/{self.metric_names[i]}", + v, + prog_bar=True, + on_step=False, + on_epoch=True, + sync_dist=True, + batch_size=self.batch_size, + ) + + if self.trainer.global_rank == 0: + self._save_and_log_batches( + stage="val", + noisy=noisy, + clean=clean, + noise_sigma=noise_sigma, + pred=pred, + batch_idx=batch_idx, + ) + + def test_step(self, batch, batch_idx): + noisy, clean, noise_sigma = self._decompose_batch(batch) + B, C, T, H, W = noisy.shape + pred = torch.zeros((B, C - 1, T, H, W), device=noisy.device, dtype=noisy.dtype) + + # since the whole images are used in testing, call the running_inference + for b in range(B): + input = noisy[b] + output = running_inference( + model=self.model, + image=input.detach().cpu().numpy(), + cutout=tuple(self.config.dataset.cutout_shape), + overlap=tuple(self.config.overlap_for_inference), + batch_size=self.config.batch_size, + device=self.device, + ) + pred[b] = torch.from_numpy(output).to(device=self.device, dtype=pred.dtype) + + noise_sigmas = torch.reshape(noise_sigma, [B, 1, 1, 1, 1]) + + noisy[:, 0] *= noise_sigmas[:, 0] + noisy[:, 1] *= noise_sigmas[:, 0] + clean *= noise_sigmas + pred *= noise_sigmas + + for i, m in enumerate(self.metrics): + v = m(pred, clean) + self.log(f"test/{self.metric_names[i]}", v, batch_size=B, sync_dist=True) + + if self.trainer.global_rank == 0: + self._save_and_log_batches( + stage="test", + noisy=noisy, + clean=clean, + noise_sigma=noise_sigma, + pred=pred, + batch_idx=batch_idx, + ) + + def configure_optimizers(self): + total_num_steps = self.trainer.estimated_stepping_batches + print(f"Total number of steps: {total_num_steps}") + self.optim_sched = OptimScheduler(self.config, self.model, total_num_steps) + return [self.optim_sched.optim], [ + {"scheduler": self.optim_sched.sched, "interval": "step", "frequency": 1} + ] + + def configure_gradient_clipping( + self, + optimizer, + gradient_clip_val, + gradient_clip_algorithm, + ): + # Clip the gradient norm using the FSDP-compatible method + if gradient_clip_val is not None and gradient_clip_val > 0: + torch.nn.utils.clip_grad_norm_(self.parameters(), max_norm=gradient_clip_val) + + def _save_and_log_batches(self, stage, noisy, clean, noise_sigma, pred, batch_idx): + noisy_im, clean_im, pred_im, gmap, noise_sigma = _prepare_batch_samples( + noisy=noisy, clean=clean, output=pred, noise_sigma=noise_sigma + ) + + if stage == "train": + save_path = self.train_saved_dir + fname = "train_batch" + num_logged = self.config.logging.log_train_batches + + elif stage == "val": + save_path = self.val_saved_dir + fname = "val_batch" + num_logged = self.config.logging.log_val_batches + + elif stage == "test": + save_path = self.test_saved_dir + fname = "test_batch" + num_logged = self.config.logging.log_test_batches + else: + num_logged = 0 + + if batch_idx < num_logged: + if self.config.logging.save_batches_to_output_dir: + _save_batch_samples( + saved_path=save_path, + fname=f"{fname}_{batch_idx}", + noisy_im=noisy_im, + clean_im=clean_im, + pred_im=pred_im, + gmap=gmap, + noise_sigma=noise_sigma, + ) + + # compute the metrics for logged cases + B = noisy.shape[0] + pred_metrics = dict() + if pred is not None: + for i, m in enumerate(self.metrics): + metric_res = [] + for b in range(B): + v = m(pred[b].unsqueeze(0), clean[b].unsqueeze(0)) + metric_res.append(v.item()) + pred_metrics[self.metric_names[i]] = metric_res + + # log as videos + vid, caption = _save_batch_samples_as_video( + noisy_im=noisy_im, + clean_im=clean_im, + pred_im=pred_im, + gmap=gmap, + noise_sigma=noise_sigma, + pred_metrics=pred_metrics, + ) + if self.logger is not None and isinstance( + self.logger, L.pytorch.loggers.wandb.WandbLogger + ): + self.logger.experiment.log( + { + f"{fname}_{batch_idx}": wandb.Video( + vid, caption=caption, fps=1, format="gif" + ) + } + ) + + +# ----------------------------------------------------------------- + + +class DenoisingDataModule(L.LightningDataModule): + def __init__(self, config): + super().__init__() + self.config = config + self.rng = np.random.default_rng(self.config.seed) + self.generator = torch.Generator() + self.generator.manual_seed(self.config.seed) + + self.train_data = None + self.test_set = None + self.train_set = None + self.val_set = None + self.test_set = None + + def prepare_data(self): + pass + + def setup(self, stage: str): + if self.train_data is None: + self.train_data = hydra.utils.instantiate( + self.config.dataset, + data_dir=self.config.train_data_dir, + rng=self.rng, + ) + val_size = int(len(self.train_data) * self.config.val_data_portion) + train_size = len(self.train_data) - val_size + self.train_set, self.val_set = random_split( + self.train_data, [train_size, val_size], generator=self.generator + ) + print( + f"{Fore.GREEN}Train size: {train_size}, Validation size: {val_size}{Style.RESET_ALL}" + ) + + if self.test_set is None: + self.test_set = MRIDenoisingDatasetTest( + data_dir=self.config.test_data_dir, + ignore_gmap=self.config.dataset.ignore_gmap, + dicom_mode=self.config.dataset.dicom_mode, + ) + print(f"{Fore.CYAN}Test size: {len(self.test_set)}{Style.RESET_ALL}") + + def train_dataloader(self): + return torch.utils.data.DataLoader( + self.train_set, + batch_size=self.config.batch_size, + shuffle=True, + num_workers=self.config.num_workers, + prefetch_factor=self.config.prefetch_factor, + drop_last=True, + ) + + def val_dataloader(self): + return torch.utils.data.DataLoader( + self.val_set, + batch_size=self.config.batch_size, + shuffle=False, + num_workers=self.config.num_workers, + prefetch_factor=self.config.prefetch_factor, + ) + + def test_dataloader(self): + return torch.utils.data.DataLoader( + self.test_set, + batch_size=1, + shuffle=False, + num_workers=self.config.num_workers, + prefetch_factor=self.config.prefetch_factor, + ) + + +# ----------------------------------------------------------------- + + +def _prepare_batch_samples(noisy, clean, output, noise_sigma): + noisy_im = noisy.detach().cpu().numpy() + clean_im = clean.detach().cpu().numpy() + noise_sigma = noise_sigma.detach().cpu().numpy() + + noisy_im = np.transpose(noisy_im, [3, 4, 2, 1, 0]) + clean_im = np.transpose(clean_im, [3, 4, 2, 1, 0]) + + pred_im = None + if output is not None: + pred_im = output.detach().to(torch.float32).cpu().numpy() + pred_im = np.transpose(pred_im, [3, 4, 2, 1, 0]) + pred_im = pred_im[:, :, :, 0, :] + 1j * pred_im[:, :, :, 1, :] + + gmap = noisy_im[:, :, :, 2, :] + noisy_im = noisy_im[:, :, :, 0, :] + 1j * noisy_im[:, :, :, 1, :] + clean_im = clean_im[:, :, :, 0, :] + 1j * clean_im[:, :, :, 1, :] + + return noisy_im, clean_im, pred_im, gmap, noise_sigma + + +def _save_batch_samples(saved_path, fname, noisy_im, clean_im, pred_im, gmap, noise_sigma): + np.save(os.path.join(saved_path, f"{fname}_noisy_im_real.npy"), np.real(noisy_im)) + np.save(os.path.join(saved_path, f"{fname}_noisy_im_imag.npy"), np.imag(noisy_im)) + np.save(os.path.join(saved_path, f"{fname}_gmap.npy"), gmap) + np.save(os.path.join(saved_path, f"{fname}_clean_im_real.npy"), np.real(clean_im)) + np.save(os.path.join(saved_path, f"{fname}_clean_im_imag.npy"), np.imag(clean_im)) + np.save(os.path.join(saved_path, f"{fname}_noise_sigma.npy"), noise_sigma) + + if pred_im is not None: + np.save(os.path.join(saved_path, f"{fname}_pred_im_real.npy"), np.real(pred_im)) + np.save(os.path.join(saved_path, f"{fname}_pred_im_imag.npy"), np.imag(pred_im)) + + +def _save_batch_samples_as_video(noisy_im, clean_im, pred_im, gmap, noise_sigma, pred_metrics): + """Save a batch and log it as a video if using wandb.""" + new_line = "\n" + H, W, T, B = noisy_im.shape + + caption = f"{H}-{W}-{T}, " + vid = np.zeros((T, B * H, 3 * W)) # for noisy, clean, pred + a_vid = np.zeros((H, 3 * W, T)) + for b in range(B): + a_vid[:, 0:W, :] = np.abs(noisy_im[:, :, :, b]) + + a_clean_im = np.abs(clean_im[:, :, :, b]) + a_vid[:, W : 2 * W, :] = a_clean_im + if pred_im is not None: + a_vid[:, 2 * W : 3 * W, :] = np.abs(pred_im[:, :, :, b]) + else: + a_vid[:, 2 * W : 3 * W, :] = a_clean_im + + gmap_range = np.percentile(gmap[:, :, :, b], [10, 90]) + caption += ( + f"gmap {gmap_range[0]:.4f}-{gmap_range[1]:.4f}, noise {noise_sigma[b].item():.2f}" + ) + for k, v in pred_metrics.items(): + caption += f", {k} {v[b]:.2f}" + caption += new_line + + a_vid = np.clip( + a_vid, a_min=0.5 * np.median(a_clean_im), a_max=np.percentile(a_clean_im, 90) + ) + temp = np.zeros_like(a_vid) + vid[:, b * H : (b + 1) * H, :] = np.transpose( + cv2.normalize(a_vid, temp, 0, 255, norm_type=cv2.NORM_MINMAX), [2, 0, 1] + ) + + return np.repeat(vid[:, np.newaxis, :, :].astype("uint8"), 3, axis=1), caption + + +# ----------------------------------------------------------------- + + +def after_training(model, config): + """Save the model for inference and check it is valid.""" + model.eval() + model.to(device="cpu") + + model_input = torch.randn( + 1, + 3, + config.dataset.cutout_shape[2], + config.dataset.cutout_shape[0], + config.dataset.cutout_shape[1], + requires_grad=False, + dtype=torch.float32, + ) + with torch.inference_mode(): + y = model(model_input) + + model_scripted = torch.jit.trace(model, model_input, strict=False) + + model_scripted_fname = os.path.join( + config.logging.output_dir, f"model_{config.logging.project}_{config.logging.run_name}.pts" + ) + model_scripted.save(model_scripted_fname) + model_scripted_loaded = torch.jit.load(model_scripted_fname) + + with torch.inference_mode(): + y_scripted = model_scripted_loaded(model_input) + + diff = torch.linalg.norm(y - y_scripted) + if diff / torch.linalg.norm(y) > 1e-3: + print( + f"{Fore.YELLOW}Saved model gives different inference results - {model_scripted_fname}{Style.RESET_ALL}" + ) + else: + print( + f"{Fore.GREEN}Saved model is validated for inference - {model_scripted_fname}{Style.RESET_ALL}" + ) + + # further save model in pytorch format + model_fname = os.path.join( + config.logging.output_dir, f"model_{config.logging.project}_{config.logging.run_name}.pth" + ) + torch.save(model.state_dict(), model_fname) + + config_yaml = OmegaConf.to_yaml(config, resolve=True) + config_fname = os.path.join( + config.logging.output_dir, + f"config_{config.logging.project}_{config.logging.run_name}.yaml", + ) + with open(config_fname, "w") as f: + f.write(config_yaml) + + return model_scripted_fname, model_fname, config_fname + + +# ----------------------------------------------------------------- + +if __name__ == "__main__": + pass diff --git a/src/snraware/projects/mri/denoising/model.py b/src/snraware/projects/mri/denoising/model.py new file mode 100644 index 0000000..8039091 --- /dev/null +++ b/src/snraware/projects/mri/denoising/model.py @@ -0,0 +1,94 @@ +""" +Model for MR denoising. The input is [B, 3, T/F, H, W] tensor for real/imag and g-factor along the channel dimension. +Output is [B, 2, T/F, H, W] tensor with denoised results. +""" + +import hydra +import torch + +from snraware.components.model import Conv2DExt, HRnet, SOAnet, Unet + +# ----------------------------------------------------------------- + + +def create_backbone(config, component_type, C_in, H, W, D): + """ + Create model components from configuration. + Return value is the created component and C_out for number of output channels. + """ + if component_type.lower() == "hrnet": + model = HRnet(config=config, input_feature_channels=C_in, H=H, W=W, D=D) + C_out = model.get_number_of_output_channels() + elif component_type.lower() == "unet": + model = Unet(config=config, input_feature_channels=C_in, H=H, W=W, D=D) + C_out = model.get_number_of_output_channels() + elif component_type.lower() == "soanet": + model = SOAnet(config=config, input_feature_channels=C_in, H=H, W=W, D=D) + C_out = model.get_number_of_output_channels() + + else: + raise NotImplementedError(f"Not implemented: {component_type}") + + return model, C_out + + +# ----------------------------------------------------------------- + + +class DenoisingModel(torch.nn.Module): + def __init__(self, config, D, H, W, C_in=3, C_out=2): + super().__init__() + + self.config = config + self.D = D + self.H = H + self.W = W + self.C_in = C_in + self.C_out = C_out + + backbone_C = config.backbone.num_of_channels + + self.pre = Conv2DExt( + in_channels=C_in, + out_channels=backbone_C, + kernel_size=[3, 3], + stride=[1, 1], + padding=[1, 1], + padding_mode="reflect", + bias=True, + channel_first=True, + ) + + # create the backbone model + backbone_config = hydra.utils.instantiate(config.backbone) + self.bk, bk_C_out = create_backbone( + backbone_config, component_type=config.backbone.name, C_in=backbone_C, H=H, W=W, D=D + ) + + self.post = Conv2DExt( + in_channels=bk_C_out, + out_channels=C_out, + kernel_size=[3, 3], + stride=[1, 1], + padding=[1, 1], + padding_mode="reflect", + bias=True, + channel_first=True, + ) + + @property + def device(self): + return next(self.parameters()).device + + def forward(self, x): + res_pre = self.pre(x) + + res_backbone = self.bk(res_pre)[-1] + + C = res_pre.shape[1] + + # establish the residual connection + res_backbone[:, :C, :, :, :] = res_pre + res_backbone[:, :C, :, :, :] + + y_hat = self.post(res_backbone) + return y_hat diff --git a/src/snraware/projects/mri/denoising/run.py b/src/snraware/projects/mri/denoising/run.py new file mode 100644 index 0000000..312136f --- /dev/null +++ b/src/snraware/projects/mri/denoising/run.py @@ -0,0 +1,188 @@ +"""Main training script for the MRI denoising model.""" + +import os + +import hydra +import lightning as L +import torch +from colorama import Fore, Style +from lightning.fabric.utilities.throughput import measure_flops +from lightning.pytorch.callbacks import ModelCheckpoint, ModelSummary, TQDMProgressBar +from lightning.pytorch.loggers import WandbLogger +from lightning.pytorch.strategies import FSDPStrategy +from omegaconf import DictConfig, OmegaConf + +import snraware +from snraware.projects.mri.denoising.lightning_denoising import ( + DenoisingDataModule, + LitDenoising, + after_training, +) +from snraware.projects.mri.denoising.model import DenoisingModel + + +def _measure_flops(Lit_model, config): + dummy_input = torch.randn( + 1, + Lit_model.model.C_in, + config.dataset.cutout_shape[2], + config.dataset.cutout_shape[0], + config.dataset.cutout_shape[1], + device=Lit_model.device, + ) + + def model_fwd(): + return Lit_model(dummy_input) + + with torch.inference_mode(): + flops = measure_flops(Lit_model, model_fwd) + return flops + + +@hydra.main(version_base=None, config_path="./configs", config_name="config") +def run_training(config: DictConfig): + torch.set_float32_matmul_precision("highest") + + # lightning needs NODE_RANK env variable for multi-node training + if "NODE_RANK" not in os.environ and "RANK" in os.environ: + os.environ["NODE_RANK"] = os.environ["RANK"] + print( + f"{Fore.YELLOW}NODE_RANK set to {os.environ['NODE_RANK']} from RANK{Style.RESET_ALL}", + flush=True, + ) + print(f"{Fore.YELLOW}RANK is {os.environ['RANK']}{Style.RESET_ALL}", flush=True) + print(f"{Fore.YELLOW}NODE_RANK is {os.environ['NODE_RANK']}{Style.RESET_ALL}", flush=True) + if "MASTER_ADDR" in os.environ: + print( + f"{Fore.YELLOW}MASTER_ADDR is {os.environ['MASTER_ADDR']}{Style.RESET_ALL}", flush=True + ) + if "MASTER_PORT" in os.environ: + print( + f"{Fore.YELLOW}MASTER_PORT is {os.environ['MASTER_PORT']}{Style.RESET_ALL}", flush=True + ) + if "WORLD_SIZE" in os.environ: + print( + f"{Fore.YELLOW}WORLD_SIZE is {os.environ['WORLD_SIZE']}{Style.RESET_ALL}", flush=True + ) + + if config.seed is None: + config.seed = torch.randint(0, 2**32, (1,)).item() + L.seed_everything(config.seed) + + # Print configuration + print(f"{Fore.YELLOW}Training Configuration:{Style.RESET_ALL}") + OmegaConf.to_yaml(config, resolve=True) + print(OmegaConf.to_yaml(config, resolve=True)) + print(f"{Fore.YELLOW}{'---' * 30}{Style.RESET_ALL}") + print(f"Output directory: {config.logging.output_dir}") + print(f"{Fore.YELLOW}{'---' * 30}{Style.RESET_ALL}") + + # init wandb + wandb_logger = None + if config.logging.use_wandb: + wandb_logger = WandbLogger( + project=config.logging.project, + name=config.logging.run_name, + entity=config.logging.wandb_entity, + save_dir=config.logging.wandb_dir, + log_model=True, + config=OmegaConf.to_container(config, resolve=True, throw_on_missing=False), + ) + + # set up dataset + data_module = DenoisingDataModule(config=config) + + # callback to save the best model + checkpoint_callback = ModelCheckpoint( + save_top_k=-1, + monitor="val/loss", + mode="min", + save_last=True, + every_n_epochs=min(config.logging.save_ckpt_every_n_epochs, config.trainer.max_epochs), + dirpath=os.path.join(config.logging.output_dir, "checkpoints"), + filename="checkpoint-{epoch:02d}", + ) + + # set up the trainer + if config.trainer.strategy == "fsdp": + policy = { + snraware.components.model.backbone.Block, + snraware.components.model.backbone.DownSample, + snraware.components.model.backbone.UpSample, + } + strategy = FSDPStrategy( + auto_wrap_policy=policy, + sharding_strategy="HYBRID_SHARD", + device_mesh=tuple(config.device_mesh), + activation_checkpointing_policy={ + snraware.components.model.backbone.DownSample, + snraware.components.model.backbone.UpSample, + }, + ) + else: + strategy = config.trainer.strategy + + progress_bar = TQDMProgressBar(refresh_rate=config.trainer.log_every_n_steps, leave=True) + + trainer = hydra.utils.instantiate( + config.trainer, + callbacks=[ + checkpoint_callback, + progress_bar, + ModelSummary(max_depth=4), + ], + logger=wandb_logger, + strategy=strategy, + ) + + print( + f"Using {trainer.num_devices} devices for training on {trainer.num_nodes} nodes, strategy {config.trainer.strategy}." + ) + + # set up model + model = DenoisingModel( + config=config, + D=config.dataset.cutout_shape[2], + H=config.dataset.cutout_shape[0], + W=config.dataset.cutout_shape[1], + ) + + # set up lightning components + lit_model = LitDenoising(model=model, config=config) + + # report flops + if trainer.global_rank == 0: + flops = _measure_flops(lit_model, config) + num_params = sum(p.numel() for p in model.parameters()) + print(f"{Fore.YELLOW}GFLOPs: {flops / 1e9:.2f} GFLOPs{Style.RESET_ALL}", flush=True) + print( + f"{Fore.YELLOW}Num_Parameters: {num_params / 1e6:.2f} M{Style.RESET_ALL}", flush=True + ) + if wandb_logger is not None: + wandb_logger.experiment.summary["GFLOPS"] = flops / 1e9 + wandb_logger.experiment.summary["Num_Parameters_m"] = num_params / 1e6 + + # fit the model + trainer.fit(model=lit_model, datamodule=data_module) + trainer.print(torch.cuda.memory_summary()) + + # test the model + test_res = trainer.test(model=lit_model, datamodule=data_module) + + # save for use in production environment + if trainer.global_rank == 0: + model_scripted_fname, model_fname, config_fname = after_training(model, config) + print( + f"{Fore.GREEN}Trained model saved, scripted format : {model_scripted_fname}{Style.RESET_ALL}" + ) + print(f"{Fore.GREEN}Trained model saved, torch format : {model_fname}{Style.RESET_ALL}") + print( + f"{Fore.GREEN}Trained model saved, config file : {config_fname}{Style.RESET_ALL}" + ) + print(f"{Fore.YELLOW}{'---' * 30}{Style.RESET_ALL}") + + return test_res + + +if __name__ == "__main__": + run_training() diff --git a/src/snraware/projects/mri/denoising/run_inference.py b/src/snraware/projects/mri/denoising/run_inference.py new file mode 100644 index 0000000..53d4b06 --- /dev/null +++ b/src/snraware/projects/mri/denoising/run_inference.py @@ -0,0 +1,197 @@ +"""Run MRI inference for denoising.""" + +import argparse +import os + +import numpy as np +import torch +from colorama import Fore, Style +from omegaconf import OmegaConf + +from snraware.components.setup import end_timer, start_timer +from snraware.projects.mri.denoising.inference import apply_model +from snraware.projects.mri.denoising.inference_model import ( + load_lit_model, + load_model, + load_scripted_model, +) + +# ------------------------------------------------------------------------------------------------- + + +def run_inference( + saved_model_path, + saved_config_path, + input_dir, + input_fname, + gmap_fname, + no_gmap, + scaling_factor=1.0, + batch_size=1, + cutout=None, + overlap=(16, 16, 8), + device=None, +): + """Run inference on the input data using the specified model and configuration.""" + # load the model + if saved_model_path.endswith(".pts"): + model = load_scripted_model(saved_model_path) + config = OmegaConf.load(saved_config_path) + elif saved_model_path.endswith(".pth") or saved_model_path.endswith(".ckpt"): + model, config = load_model(saved_model_path, saved_config_path) + else: + model, config = load_lit_model(saved_model_path, saved_config_path) + print( + f"{Fore.YELLOW}Load in model file - {saved_model_path} - {saved_config_path}{Style.RESET_ALL}" + ) + + if saved_model_path.endswith(".ckpt"): + from snraware.projects.mri.denoising.lightning_denoising import after_training + + config.logging.output_dir = os.path.dirname(saved_model_path) + after_training(model, config) + + # ------------------------------------------- + # load the data + image = ( + np.load(os.path.join(input_dir, f"{input_fname}_real.npy")) + + np.load(os.path.join(input_dir, f"{input_fname}_imag.npy")) * 1j + ) + image = np.squeeze(image) + if image.ndim == 2: + image = image[:, :, np.newaxis] + + if no_gmap: + gmap = np.ones([image.shape[0], image.shape[1], image.shape[2]]) + else: + gmap_file = f"{input_dir}/{gmap_fname}.npy" + if os.path.exists(gmap_file): + gmap = np.load(f"{input_dir}/{gmap_fname}.npy") + + print(f"{Fore.YELLOW}Load in data - {image.shape} - gmap {gmap.shape}{Style.RESET_ALL}") + + batch_size = batch_size + cutout = tuple(config.dataset.cutout_shape) + if cutout is not None: + cutout = tuple(cutout) + + t0 = start_timer(enable=True) + output = apply_model( + model=model, + data=image, + gmap=gmap, + scaling_factor=scaling_factor, + cutout=cutout, + overlap=tuple(overlap), + batch_size=batch_size, + device=device, + verbose=True, + ) + print( + f"{Fore.YELLOW}Inference time {end_timer(t=t0, enable=True, verbose=False) / 1e3:.2f} sec{Style.RESET_ALL}" + ) + return output + + +# ------------------------------------------------------------------------------------------------- + + +def arg_parser(): + """ + @args: + - No args + @rets: + - config (Namespace): runtime namespace for setup. + """ + parser = argparse.ArgumentParser("Argument parser for STCNNT MRI test evaluation") + + parser.add_argument("--input_dir", default=None, help="folder to load the data") + parser.add_argument("--output_dir", default=None, help="folder to save the data") + parser.add_argument( + "--scaling_factor", + type=float, + default=1.0, + help="scaling factor to adjust model strength; higher scaling means lower strength", + ) + parser.add_argument( + "--saved_model_path", + type=str, + default=None, + help='model path. endswith ".pth" or ".pts" or ".ckpt"', + ) + parser.add_argument( + "--saved_config_path", type=str, default=None, help="model config yaml file." + ) + + parser.add_argument("--batch_size", type=int, default=1, help="batch size for inference") + parser.add_argument( + "--cutout", + nargs="+", + type=int, + default=None, + help="cutout size for (H, W, T), None means using the config setting", + ) + parser.add_argument( + "--overlap", + nargs="+", + type=int, + default=[16, 16, 8], + help="overlap for (H, W, T), (0, 0, 0) means no overlap", + ) + + parser.add_argument("--input_fname", type=str, default="input", help="input file name") + parser.add_argument("--gmap_fname", type=str, default="gmap", help="gmap input file name") + + parser.add_argument( + "--no_gmap", action="store_true", help="if set, do not load gmap, but set gmap as all ones" + ) + + return parser.parse_args() + + +# ------------------------------------------------------------------------------------------------- +# the main function for setup, eval call and saving results + + +def main(): + args = arg_parser() + + if torch.cuda.is_available(): + device = torch.device("cuda") + else: + device = torch.device("cpu") + + print(f"{Fore.YELLOW}Find devices - {device}{Style.RESET_ALL}") + + assert os.path.exists(args.input_dir), f"input_dir {args.input_dir} does not exist" + assert os.path.exists(args.saved_model_path), ( + f"saved_model_path {args.saved_model_path} does not exist" + ) + assert os.path.exists(args.saved_config_path), ( + f"saved_config_path {args.saved_config_path} does not exist" + ) + if not os.path.exists(args.output_dir): + os.makedirs(args.output_dir) + print(f"{Fore.YELLOW}Create output_dir {args.output_dir}{Style.RESET_ALL}") + + output = run_inference( + args.saved_model_path, + args.saved_config_path, + args.input_dir, + args.input_fname, + args.gmap_fname, + args.no_gmap, + scaling_factor=args.scaling_factor, + batch_size=args.batch_size, + cutout=args.cutout, + overlap=tuple(args.overlap), + device=device, + ) + + print(f"save results in {args.output_dir}, output - {output.shape}") + np.save(os.path.join(args.output_dir, "output_real.npy"), output.real) + np.save(os.path.join(args.output_dir, "output_imag.npy"), output.imag) + + +if __name__ == "__main__": + main() diff --git a/src/snraware/projects/mri/denoising/utils.py b/src/snraware/projects/mri/denoising/utils.py new file mode 100644 index 0000000..86ef45d --- /dev/null +++ b/src/snraware/projects/mri/denoising/utils.py @@ -0,0 +1,116 @@ +"""Utility functions for MR denoising.""" + +import io +import os +import pickle + +import numpy as np + +__all__ = [ + "deserialize_from_bytes", + "deserialize_from_stream", + "find_files_with_extension", + "find_samples_in_folder", + "serialize_to_bytes", + "serialize_to_stream", +] + +# ------------------------------------------------------------------------------------------------- + + +def serialize_to_stream(noisy, clean, gmap, noise_sigma, stream): + """Serialize the sample to a stream.""" + pickle.dump((noisy, clean, gmap, noise_sigma), stream) + + +def deserialize_from_stream(stream): + """Deserialize the sample from a stream.""" + return pickle.load(stream) + + +def serialize_to_bytes( + noisy: np.ndarray, + clean: np.ndarray, + gamp: np.ndarray, + noise_sigma: np.ndarray, +) -> bytes: + """ + Serialize arrays to a bytes object. + + Uses the same pickle-based format as serialize_to_stream to keep consistency. + """ + with io.BytesIO() as buf: + serialize_to_stream(noisy, clean, gamp, noise_sigma, buf) + return buf.getvalue() + + +def deserialize_from_bytes(data: bytes) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """Deserialize arrays from a bytes object.""" + with io.BytesIO(data) as buf: + return deserialize_from_stream(buf) + + +# ------------------------------------------------------------------------------------------------- + + +def find_files_with_extension(directory, extension): + """ + Finds all files with a specified extension in a given directory + and its subdirectories. + + Args: + directory (str): The path to the starting directory. + extension (str): The file extension to search for (e.g., ".txt", ".py"). + + Returns: + list: A list of full paths to the found files. + """ + found_files = [] + for root, _, files in os.walk(directory): + for file in files: + if file.endswith(extension): + found_files.append(os.path.join(root, file)) + return found_files + + +def find_samples_in_folder(folder): + """ + Find all samples in a folder and return a list of tuples. + + Args: + folder (str): The path to the folder containing the samples. + """ + samples = find_files_with_extension(folder, ".dat") + return samples + + +# ------------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + import h5py + + h5_file = h5py.File("/data/raw_data/denoising/test_max_noise_level_32.0_1000.h5") + keys = list(h5_file.keys()) + n = len(keys) + + dst_dir = "/data/raw_data/denoising/test/test_max_noise_level_32.0_1000" + os.makedirs(dst_dir, exist_ok=True) + + for i in range(n): + key = keys[i] + + case_dir = os.path.join(dst_dir, key) + os.makedirs(case_dir, exist_ok=True) + + cases = h5_file[key] + + for case_key in cases.keys(): + data = cases[case_key] + noisy = np.array(data["noisy"]) + clean = np.array(data["image"]) + gmap = np.array(data["gmap"]) + noise_sigma = np.array(data["noise_sigma"]) + + dst_file = os.path.join(case_dir, f"{case_key}.dat") + with open(dst_file, "wb") as f: + serialize_to_stream(noisy, clean, gmap, noise_sigma, f) diff --git a/src/snraware/projects/mri/snr/__init__.py b/src/snraware/projects/mri/snr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/snraware/projects/mri/snr/fftc.py b/src/snraware/projects/mri/snr/fftc.py new file mode 100644 index 0000000..ba32199 --- /dev/null +++ b/src/snraware/projects/mri/snr/fftc.py @@ -0,0 +1,105 @@ +"""Utility functions for fft and ifft, centered.""" + +from scipy.fft import fft, fft2, fftn, fftshift, ifft, ifft2, ifftn, ifftshift + +__all__ = ["fft1c", "fft2c", "fft3c", "ifft1c", "ifft2c", "ifft3c"] +# -------------------------------------------------------------- + + +def centered_fft(image, norm="ortho"): + return fftshift(fft2(ifftshift(image), norm=norm)) + + +def fft1c(image, norm="ortho"): + """ + Perform centered 1D fft. + + Args: + image ([RO, ...]): Perform fft2c on the first dimension + norm : 'ortho' or 'backward' + Returns: + res: fft1c results + """ + return fftshift(fft(ifftshift(image, axes=(0,)), axis=0, norm=norm), axes=(0,)) + + +def fft2c(image, norm="ortho"): + """ + Perform centered 2D fft. + + Args: + image ([RO, E1, ...]): Perform fft2c on the first two dimensions + norm : 'ortho' or 'backward' + Returns: + res: fft2c results + """ + return fftshift(fft2(ifftshift(image, axes=(0, 1)), axes=(0, 1), norm=norm), axes=(0, 1)) + + +def fft3c(image, norm="ortho"): + """ + Perform centered 3D fft. + + Args: + image ([RO, E1, E2, ...]): Perform fft3c on the first three dimensions + norm : 'ortho' or 'backward' + Returns: + res: fft3c results + """ + return fftshift( + fftn(ifftshift(image, axes=(0, 1, 2)), axes=(0, 1, 2), norm=norm), axes=(0, 1, 2) + ) + + +# -------------------------------------------------------------- + + +def centered_ifft(kspace, norm="ortho"): + return fftshift(ifft2(ifftshift(kspace), norm=norm)) + + +def ifft1c(kspace, norm="ortho"): + """ + Perform centered 1D ifft. + + Args: + image ([RO, ...]): Perform fft2c on the first dimension + norm : 'ortho' or 'backward' + Returns: + res: fft1c results + """ + return fftshift(ifft(ifftshift(kspace, axes=(0,)), axis=0, norm=norm), axes=(0,)) + + +def ifft2c(kspace, norm="ortho"): + """ + Perform centered 2D ifft. + + Args: + image ([RO, E1, ...]): Perform fft2c on the first two dimensions + norm : 'ortho' or 'backward' + Returns: + res: fft2c results + """ + return fftshift(ifft2(ifftshift(kspace, axes=(0, 1)), axes=(0, 1), norm=norm), axes=(0, 1)) + + +def ifft3c(kspace, norm="ortho"): + """ + Perform centered 2D ifft. + + Args: + image ([RO, E1, E2, ...]): Perform fft3c on the first two dimensions + norm : 'ortho' or 'backward' + Returns: + res: fft3c results + """ + return fftshift( + ifftn(ifftshift(kspace, axes=(0, 1, 2)), axes=(0, 1, 2), norm=norm), axes=(0, 1, 2) + ) + + +# -------------------------------------------------------------- + +if __name__ == "__main__": + pass diff --git a/src/snraware/projects/mri/snr/filter.py b/src/snraware/projects/mri/snr/filter.py new file mode 100644 index 0000000..a2c231d --- /dev/null +++ b/src/snraware/projects/mri/snr/filter.py @@ -0,0 +1,266 @@ +"""K-space fiter and partial Fourier filter.""" + +import math + +import numpy as np + +from .fftc import fft1c, fft2c, ifft1c, ifft2c + +__all__ = [ + "apply_image_filter_2D", + "apply_image_filter_T", + "apply_kspace_filter_1D", + "apply_kspace_filter_2D", + "apply_kspace_filter_3D", + "generate_asymmetric_filter", + "generate_symmetric_filter", +] + +# -------------------------------------------------------------- + + +def generate_symmetric_filter(len, filterType, sigma=1.5, snr_scaling=True): + """ + Compute the SNR unit symmetric filter. + + Args: + len (int): length of filter + filterType (str): Gaussian or None + sigma (float, optional): sigma for gaussian filter. Defaults to 1.5. + snr_scaling (bool): if True, keep the noise level; if False, keep the signal level + + Returns: + filter: len array + """ + filter = np.ones(len, dtype=np.float32) + + if (filterType == "Gaussian") and sigma > 0: + r = -1.0 * sigma * sigma / 2 + + if len % 2 == 0: + # to make sure the zero points match and boundary of filters are symmetric + stepSize = 2.0 / (len - 2) + x = -1 + np.arange(len - 1) * stepSize + + filter = np.zeros(len, dtype=np.float32) + filter[1:] = np.exp(r * (x * x)) + filter[0] = 0 + else: + stepSize = 2.0 / (len - 1) + x = -1 + np.arange(len) * stepSize + filter = np.exp(r * (x * x)) + + if snr_scaling: + sos = np.sum(filter * filter) + filter /= math.sqrt(sos / len) + else: + filter /= np.max(filter) + + return filter + + +# -------------------------------------------------------------- + + +def generate_asymmetric_filter(len, start, end, filterType="TapperedHanning", width=10): + """ + Create the asymmetric kspace filter. + + Args: + len (int): length of the filter + start (int): start of filter + end (int): end of the filter + filterType (str): None or TapperedHanning + width (int, optional): width of transition band. Defaults to 10. + """ + if start > len - 1: + start = 0 + + if end > len - 1: + end = len - 1 + + if start > end: + start = 0 + end = len - 1 + + filter = np.zeros(len, dtype=np.float32) + filter[start : end + 1] = 1.0 + + if width == 0 or width >= len: + width = 1 + + w = np.ones(width) + + if filterType == "TapperedHanning": + w = 0.5 * (1 - np.cos(2.0 * math.pi * np.arange(1, width + 1) / (2 * width + 1))) + + if start == 0 and end == len - 1: + filter[:width] = w + filter[-width:] = w[::-1] + + if start == 0 and end < len - 1: + filter[end - width + 1 : end + 1] = w[::-1] + + if start > 0 and end == len - 1: + filter[start : start + width] = w + + if start > 0 and end < len - 1: + filter[start : start + width] = w + filter[end - width + 1 : end + 1] = w[::-1] + + sos = np.sum(filter * filter) + filter /= math.sqrt(sos / (len)) + + return filter + + +# -------------------------------------------------------------- + + +def apply_kspace_filter_1D(kspace, fRO): + """ + Apply the 1D kspace filter. + + Args: + kspace ([RO, E1, CHA, PHS]): kspace, can be 1D, 2D or 3D or 4D + fRO ([RO]): kspace fitler along RO + + Returns: + kspace_filtered: filtered ksapce + """ + RO = kspace.shape[0] + assert fRO.shape[0] == RO + + if kspace.ndim == 1: + kspace_filtered = kspace * fRO + if kspace.ndim == 2: + kspace_filtered = kspace * fRO.reshape((RO, 1)) + if kspace.ndim == 3: + kspace_filtered = kspace * fRO.reshape((RO, 1, 1)) + if kspace.ndim == 4: + kspace_filtered = kspace * fRO.reshape((RO, 1, 1, 1)) + + return kspace_filtered + + +# -------------------------------------------------------------- + + +def apply_kspace_filter_2D(kspace, fRO, fE1): + """ + Apply the 2D kspace filter. + + Args: + kspace ([RO, E1, CHA, PHS]): kspace, can be 2D or 3D or 4D + fRO ([RO]): kspace fitler along RO + fE1 ([E1]): kspace filter along E1 + + Returns: + kspace_filtered: filtered ksapce + """ + RO = kspace.shape[0] + E1 = kspace.shape[1] + + assert kspace.ndim <= 4 + assert fRO.shape[0] == RO + assert fE1.shape[0] == E1 + + filter2D = np.outer(fRO, fE1) + + if kspace.ndim == 2: + kspace_filtered = kspace * filter2D + if kspace.ndim == 3: + kspace_filtered = kspace * filter2D[:, :, np.newaxis] + if kspace.ndim == 4: + kspace_filtered = kspace * filter2D[:, :, np.newaxis, np.newaxis] + + return kspace_filtered + + +# -------------------------------------------------------------- + + +def apply_kspace_filter_3D(kspace, fRO, fE1, fE2): + """ + Apply the 3D kspace filter. + + Args: + kspace ([RO, E1, E2, CHA]): kspace, can be 3D or 4D + fRO ([RO]): kspace fitler along RO + fE1 ([E1]): kspace filter along E1 + fE2 ([E2]): kspace filter along E2 + + Returns: + kspace_filtered: filtered ksapce + """ + RO = kspace.shape[0] + E1 = kspace.shape[1] + E2 = kspace.shape[2] + + assert kspace.ndim <= 4 + assert fRO.shape[0] == RO + assert fE1.shape[0] == E1 + assert fE2.shape[0] == E2 + + filter3D = np.outer(fRO, fE1, fE2) + + if kspace.ndim == 3: + kspace_filtered = kspace * filter3D + if kspace.ndim == 4: + kspace_filtered = kspace * filter3D[:, :, :, np.newaxis] + + return kspace_filtered + + +# -------------------------------------------------------------- + + +def apply_image_filter_2D(data, sigma_RO=1.25, sigma_E1=1.25): + """ + Apply a 2D Gaussian filter to the image data on the first two dimensions. + + Args: + data (ndarray): [RO, E1, T, ...] + sigma_RO (float, optional): The standard deviation for the Gaussian filter along the RO direction. Defaults to 1.25. + sigma_E1 (float, optional): The standard deviation for the Gaussian filter along the E1 direction. Defaults to 1.25. + + Returns: + ndarray: The filtered image data. + """ + fRO = generate_symmetric_filter( + data.shape[0], filterType="Gaussian", sigma=sigma_RO, snr_scaling=False + ) + fE1 = generate_symmetric_filter( + data.shape[1], filterType="Gaussian", sigma=sigma_E1, snr_scaling=False + ) + data_filtered = ifft2c(apply_kspace_filter_2D(fft2c(data), fRO, fE1)) + return data_filtered, fRO, fE1 + + +# -------------------------------------------------------------- + + +def apply_image_filter_T(data, sigma_T=1.25): + """ + Apply a 2D Gaussian filter to the image data along the 3rd dimension. + + Args: + data (ndarray): [RO, E1, T, ...] + sigma_T (float, optional): The standard deviation for the Gaussian filter along the T direction. Defaults to 1.25. + + Returns: + ndarray: The filtered image data. + """ + fT = generate_symmetric_filter( + data.shape[2], filterType="Gaussian", sigma=sigma_T, snr_scaling=False + ) + + im = np.transpose(data, (2, 0, 1)) + im = ifft1c(apply_kspace_filter_1D(fft1c(im), fT)) + return np.transpose(im, (1, 2, 0)), fT + + +# -------------------------------------------------------------- + +if __name__ == "__main__": + pass diff --git a/src/snraware/projects/mri/snr/imaging.py b/src/snraware/projects/mri/snr/imaging.py new file mode 100644 index 0000000..ab15eda --- /dev/null +++ b/src/snraware/projects/mri/snr/imaging.py @@ -0,0 +1,203 @@ +"""SNR unit utility functions.""" + +import math + +import numpy as np + +from .fftc import fft2c, ifft2c + +__all__ = [ + "adjust_matrix_size", + "apply_matrix_size_reduction_2D", + "apply_resolution_reduction_2D", + "zero_padding_resize_2D", +] + +# -------------------------------------------------------------- + + +def apply_resolution_reduction_2D(im, ratio_RO, ratio_E1, snr_scaling=True, norm="ortho"): + """ + Reduce image resolution by setting outer kspace as zeros. The image matrix size stays the same. + + Inputs: + im: complex image [RO, E1, ...] + ratio_RO, ratio_E1: ratio to reduce resolution, e.g. 0.75 for 75% resolution + snr_scaling : if True, apply SNR scaling + norm : backward or ortho + + snr_scaling should be False and norm should be backward to preserve signal level + Returns: + res: complex image with reduced phase resolution [RO, E1, ...] + fRO, fE1 : equivalent kspace filter + """ + kspace = fft2c(im, norm=norm) + + RO = kspace.shape[0] + E1 = kspace.shape[1] + + assert ratio_RO <= 1.0 and ratio_RO > 0 + assert ratio_E1 <= 1.0 and ratio_E1 > 0 + + num_masked_RO = int((RO - ratio_RO * RO) // 2) + num_masked_E1 = int((E1 - ratio_E1 * E1) // 2) + + fRO = np.ones(RO) + fE1 = np.ones(E1) + + if kspace.ndim == 2: + if num_masked_RO > 0: + kspace[0:num_masked_RO, :] = 0 + kspace[RO - num_masked_RO : RO, :] = 0 + + if num_masked_E1 > 0: + kspace[:, 0:num_masked_E1] = 0 + kspace[:, E1 - num_masked_E1 : E1] = 0 + + if kspace.ndim == 3: + if num_masked_RO > 0: + kspace[0:num_masked_RO, :, :] = 0 + kspace[RO - num_masked_RO : RO, :, :] = 0 + + if num_masked_E1 > 0: + kspace[:, 0:num_masked_E1, :] = 0 + kspace[:, E1 - num_masked_E1 : E1, :] = 0 + + if kspace.ndim == 4: + if num_masked_RO > 0: + kspace[0:num_masked_RO, :, :, :] = 0 + kspace[RO - num_masked_RO : RO, :, :, :] = 0 + + if num_masked_E1 > 0: + kspace[:, 0:num_masked_E1, :, :] = 0 + kspace[:, E1 - num_masked_E1 : E1, :, :] = 0 + + fRO[0:num_masked_RO] = 0 + fRO[RO - num_masked_RO : RO] = 0 + + fE1[0:num_masked_E1] = 0 + fE1[E1 - num_masked_E1 : E1] = 0 + + if snr_scaling is True: + ratio = math.sqrt(RO * E1) / math.sqrt((RO - 2 * num_masked_RO) * (E1 - 2 * num_masked_E1)) + im_low_res = ifft2c(kspace) * ratio + else: + im_low_res = ifft2c(kspace, norm=norm) + + return im_low_res, fRO, fE1 + + +# -------------------------------------------------------------- + + +def apply_matrix_size_reduction_2D(im, dst_RO, dst_E1, norm="ortho"): + """ + Reduce the matrix size, keep the field-of-view unchanged. + + Inputs: + im: complex image [RO, E1, ...] + dst_RO, dst_E1: target matrix size + norm : backward or ortho + + Returns: + res: complex image with reduced matrix size [dst_RO, dst_E1, ...] + """ + RO = im.shape[0] + E1 = im.shape[1] + + assert dst_RO <= RO + assert dst_E1 <= E1 + + kspace = fft2c(im, norm=norm) + + num_ro = int((RO - dst_RO) // 2) + num_e1 = int((E1 - dst_E1) // 2) + + if kspace.ndim == 2: + kspace_dst = kspace[num_ro : num_ro + dst_RO, num_e1 : num_e1 + dst_E1] + if kspace.ndim == 3: + kspace_dst = kspace[num_ro : num_ro + dst_RO, num_e1 : num_e1 + dst_E1, :] + if kspace.ndim == 4: + kspace_dst = kspace[num_ro : num_ro + dst_RO, num_e1 : num_e1 + dst_E1, :, :] + + res = ifft2c(kspace_dst, norm=norm) + + return res + + +# -------------------------------------------------------------- + + +def zero_padding_resize_2D(im, dst_RO, dst_E1, snr_scaling=True, norm="ortho"): + """ + Zero padding resize up the image. + + Args: + im ([RO, E1, ...]): complex image + dst_RO (int): destination size + dst_E1 (int): destination size + norm : backward or ortho + """ + RO = im.shape[0] + E1 = im.shape[1] + + assert dst_RO >= RO and dst_E1 >= E1 + + kspace = fft2c(im, norm=norm) + + new_shape = list(im.shape) + new_shape[0] = dst_RO + new_shape[1] = dst_E1 + padding = (np.array(new_shape) - np.array(im.shape)) // 2 + + if im.ndim == 2: + data_padded = np.pad(kspace, [(padding[0], padding[0]), (padding[1], padding[1])]) + + if im.ndim == 3: + data_padded = np.pad(kspace, [(padding[0], padding[0]), (padding[1], padding[1]), (0, 0)]) + + if im.ndim == 4: + data_padded = np.pad( + kspace, [(padding[0], padding[0]), (padding[1], padding[1]), (0, 0), (0, 0)] + ) + + if snr_scaling is True: + scaling = np.sqrt(dst_RO * dst_E1) / np.sqrt(RO * E1) + data_padded *= scaling + + im_padded = ifft2c(data_padded, norm=norm) + + return im_padded + + +# -------------------------------------------------------------- + + +def adjust_matrix_size(data, ratio): + """ + Adjust image matrix size by either reducing the matrix size or zero-padding resizing up. + + Args: + data ([RO, E1]): complex image + ratio (float): <1.0, reduce matrix size; >1.0, increase matrix size; 1.0, do nothing + """ + RO, E1 = data.shape[:2] + dst_RO = round(ratio * RO) + dst_E1 = round(ratio * E1) + + if RO == dst_RO and E1 == dst_E1: + return data + + if ratio < 1.0: + res_im = apply_matrix_size_reduction_2D(data, dst_RO, dst_E1) + + if ratio > 1.0: + res_im = zero_padding_resize_2D(data, dst_RO, dst_E1) + + return res_im + + +# -------------------------------------------------------------- + +if __name__ == "__main__": + pass diff --git a/src/snraware/projects/mri/snr/noise.py b/src/snraware/projects/mri/snr/noise.py new file mode 100644 index 0000000..dc8ab45 --- /dev/null +++ b/src/snraware/projects/mri/snr/noise.py @@ -0,0 +1,234 @@ +"""Noise augmentation utilities.""" + +import time + +import numpy as np + +from .fftc import fft1c, fft2c, ifft1c, ifft2c +from .filter import ( + apply_kspace_filter_1D, + apply_kspace_filter_2D, + generate_asymmetric_filter, + generate_symmetric_filter, +) +from .imaging import apply_resolution_reduction_2D + +__all__ = [ + "NoiseGenerator", + "generate_pf_filter_in_random", + "sample_complex_noise", +] + +# -------------------------------------------------------------- + + +def sample_complex_noise(noise_sigma, size, rng=None): + """ + Given the noise sigma, create white complex noise. + + Args: + noise_sigma (float): noise sigma. + size (tuple): size of the target tensor. + rng (np.random.Generator): random number generator instance. + + Returns: + np.ndarray: complex noise array. + """ + if rng is None: + rng = np.random.default_rng() + nns = rng.standard_normal(size=size) + rng.standard_normal(size=size) * 1j + return (noise_sigma * nns).astype(np.complex64) + + +def generate_pf_filter_in_random(RO, E1, rng, pf_filter_ratio): + # compute pf filter + pf_lottery = rng.integers(0, 3).item() # 0, only 1st dim; 1, only 2nd dim; 2, both dim + pf_ratio_RO = pf_filter_ratio[rng.integers(0, len(pf_filter_ratio)).item()] + pf_ratio_E1 = pf_filter_ratio[rng.integers(0, len(pf_filter_ratio)).item()] + + if rng.random() < 0.5: # picke pre or post-zero PF mode + start = 0 + end = int(pf_ratio_RO * RO) + else: + start = RO - int(pf_ratio_RO * RO) + end = RO - 1 + pf_fRO = generate_asymmetric_filter(RO, start, end, filterType="TapperedHanning", width=10) + + if rng.random() < 0.5: + start = 0 + end = int(pf_ratio_E1 * E1) + else: + start = E1 - int(pf_ratio_E1 * E1) + end = E1 - 1 + pf_fE1 = generate_asymmetric_filter(E1, start, end, filterType="TapperedHanning", width=10) + + if pf_lottery == 0: # only apply PF on RO, not E1 + pf_ratio_E1 = 1.0 + pf_fE1 = np.ones(E1) + + if pf_lottery == 1: # only apply PF on E1, not RO + pf_ratio_RO = 1.0 + pf_fRO = np.ones(RO) + + return pf_fRO, pf_fE1, pf_ratio_RO, pf_ratio_E1 + + +# -------------------------------------------------------------- + + +class NoiseGenerator: + def __init__( + self, + min_noise_level=1.0, + max_noise_level=32.0, + kspace_filter_sigma=None, + pf_filter_ratio=None, + kspace_filter_sigma_T=None, + prob_apply_filter_T=0.5, + phase_resolution_ratio=None, + readout_resolution_ratio=None, + only_white_noise=False, + ): + """ + The noise generator produces colored noise by randomly selecting filter strengths and resolution reduction parameters. + The SNRUnit scaling is applied to ensure that each processing step maintains the fixed noise level. + + The generated noise array has shape [T, RO, E1, REP]. For each repetition, a unique combination of parameters is sampled to create colored noise. + + Args: + RO (int, optional): readout dimension size. Defaults to 192. + E1 (int, optional): phase encoding dimension size. Defaults to 144. + T (int, optional): temporal or frame dimension size. Defaults to 30. + REP (int, optional): repetitions. Defaults to 1. + min_noise_level (float, optional): minimum noise level to create. Defaults to 1.0. + max_noise_level (float, optional): maximum noise level. Defaults to 32.0. The noise level is sampled randomly in the range [min_noise_level, max_noise_level) + kspace_filter_sigma (list, optional): k-space filter sigma values. Defaults to [0, 0.8, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]. + pf_filter_ratio (list, optional): phase filter ratio values. Defaults to [1.0, 0.875, 0.75, 0.625, 0.55]. + kspace_filter_sigma_T (list, optional): k-space filter sigma values for the T dimension. Defaults to [0, 0.5, 0.65, 0.85, 1.0, 1.5, 2.0, 2.25]. + prob_apply_filter_T (float, optional): probability of applying the T dimension filter. Defaults to 0.5. + + phase_resolution_ratio (list, optional): phase resolution ratio values. Defaults to [1.0, 0.85, 0.7, 0.65, 0.55]. + readout_resolution_ratio (list, optional): readout resolution ratio values. Defaults to [1.0, 0.85, 0.7, 0.65, 0.55]. + only_white_noise (bool, optional): whether to use only white noise. Defaults to False. + """ + if readout_resolution_ratio is None: + readout_resolution_ratio = [1.0, 0.85, 0.7, 0.65, 0.55] + if phase_resolution_ratio is None: + phase_resolution_ratio = [1.0, 0.85, 0.7, 0.65, 0.55] + if kspace_filter_sigma_T is None: + kspace_filter_sigma_T = [0, 0.5, 0.65, 0.85, 1.0, 1.5, 2.0, 2.25] + if pf_filter_ratio is None: + pf_filter_ratio = [1.0, 0.875, 0.75, 0.625, 0.55] + if kspace_filter_sigma is None: + kspace_filter_sigma = [0, 0.8, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0] + self.min_noise_level = min_noise_level + self.max_noise_level = max_noise_level + self.kspace_filter_sigma = kspace_filter_sigma + self.pf_filter_ratio = pf_filter_ratio + self.kspace_filter_sigma_T = kspace_filter_sigma_T + self.phase_resolution_ratio = phase_resolution_ratio + self.readout_resolution_ratio = readout_resolution_ratio + self.prob_apply_filter_T = prob_apply_filter_T + + self.only_white_noise = only_white_noise + + self.rng = np.random.Generator(np.random.PCG64(int(time.time()))) + self.actual_sigma_white_noise_real = 0.0 + self.actual_sigma_white_noise_imag = 0.0 + + def generate(self, RO=192, E1=144, T=30, REP=1): + """ + Generate colored noise for MRI simulations. + + Returns: + np.ndarray: The generated noise array [RO, E1, T, REP]. + noise_sigma (float): the noise level. + """ + # randomly sample the noise level + noise_sigma = ( + self.max_noise_level - self.min_noise_level + ) * np.random.random_sample() + self.min_noise_level + + # sample the noise at this level + nns = sample_complex_noise(noise_sigma, (RO, E1, T, REP), self.rng) + + # if only white noise is needed, no further processing is required + if self.only_white_noise: + return nns, noise_sigma + + # due to the limitation of number of samples, compute the actual noise level in this particular array + self.actual_sigma_white_noise_real = np.mean(np.std(np.real(nns), axis=3)) + self.actual_sigma_white_noise_imag = np.mean(np.std(np.imag(nns), axis=3)) + + for i in range(REP): + # --------------------------------------------- + # apply resolution reduction + ratio_RO = self.readout_resolution_ratio[ + self.rng.integers(0, len(self.readout_resolution_ratio)).item() + ] + ratio_E1 = self.phase_resolution_ratio[ + self.rng.integers(0, len(self.phase_resolution_ratio)).item() + ] + + # no need to apply snr scaling here, since multiple filters may be applied + # the precise snr unit will be performed once after applying all filters by counting the number of independent samples + nns_rep, fdRO, fdE1 = apply_resolution_reduction_2D( + nns[:, :, :, i], ratio_RO, ratio_E1, snr_scaling=False + ) + + # --------------------------------------------- + # compute pf filter + pf_fRO, pf_fE1, _pf_ratio_RO, _pf_ratio_E1 = generate_pf_filter_in_random( + RO, E1, self.rng, self.pf_filter_ratio + ) + + # --------------------------------------------- + # compute kspace filter + ro_filter_sigma = self.kspace_filter_sigma[ + self.rng.integers(0, len(self.kspace_filter_sigma)).item() + ] + fRO = generate_symmetric_filter(RO, filterType="Gaussian", sigma=ro_filter_sigma) + + e1_filter_sigma = self.kspace_filter_sigma[ + self.rng.integers(0, len(self.kspace_filter_sigma)).item() + ] + fE1 = generate_symmetric_filter(E1, filterType="Gaussian", sigma=e1_filter_sigma) + + fT = None + if np.random.uniform() < self.prob_apply_filter_T: + T_filter_sigma = self.kspace_filter_sigma_T[ + self.rng.integers(0, len(self.kspace_filter_sigma_T)).item() + ] + fT = generate_symmetric_filter(T, filterType="Gaussian", sigma=T_filter_sigma) + + # --------------------------------------------- + # compute final filter + fROs_used = fRO * pf_fRO * fdRO + fE1s_used = fE1 * pf_fE1 * fdE1 + # make sure filter is noise preserving + ratio_RO = 1 / np.sqrt(1 / RO * np.sum(fROs_used * fROs_used)) + ratio_E1 = 1 / np.sqrt(1 / E1 * np.sum(fE1s_used * fE1s_used)) + + fROs_used *= ratio_RO + fE1s_used *= ratio_E1 + + # apply the filter + # here we think the noise is sampled for kspace (to mimic the imaging process) + # and we apply the filter in the image domain + nns_rep = ifft2c(apply_kspace_filter_2D(fft2c(nns_rep), fROs_used, fE1s_used)) + + if fT is not None: # if still need to apply filter along temporal dimension + nns_rep = ifft1c( + apply_kspace_filter_1D(fft1c(np.transpose(nns_rep, [2, 0, 1])), fT) + ) + nns_rep = np.transpose(nns_rep, [1, 2, 0]) + + nns[:, :, :, i] = nns_rep + + return nns, noise_sigma + + +# -------------------------------------------------------------- + +if __name__ == "__main__": + pass diff --git a/test/components/test_backbone_hrnet.py b/test/components/test_backbone_hrnet.py new file mode 100644 index 0000000..f5813e4 --- /dev/null +++ b/test/components/test_backbone_hrnet.py @@ -0,0 +1,104 @@ +import os +from pathlib import Path + +import hydra +import numpy as np +import pytest +import torch +from colorama import Fore, Style +from hydra import compose, initialize +from omegaconf import OmegaConf + +from snraware.components.model import HRnet +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestHRnet: + def setup_class(self): + set_seed(785456) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/backbone_hrnet" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + + with initialize(version_base=None, config_path="../../src/snraware/components/configs"): + cfg = compose(config_name="config", overrides=["backbone=hrnet"]) + print(OmegaConf.to_yaml(cfg)) + + self.cfg = cfg + self.config = hydra.utils.instantiate(cfg.backbone) + + def teardown_class(self): + self.config = None + + @pytest.mark.gpu + @pytest.mark.parametrize( + "backbone", + [ + ["S3ShT1L1G1V2V3", "V2V3T1L1G1S3Sh"], + ["S3ShS3ShS3Sh", "S3ShS3ShS3Sh"], + ["V3V3V3", "V3V3V3"], + ["T1L1G1T1", "T1L1G1T1"], + ["T1L1G1", "T1L1G1"], + ["T1T1T1", "T1T1T1"], + ["T1V2V2", "T1V2V2"], + ["T1L1G1T1L1G1", "T1L1G1T1L1G1T1L1G1"], + ["C2C2C2", "C2C2C2"], + ["C3C3C3", "C3C3C3"], + ], + ) + def test(self, backbone): + with_timer = True + device = get_device() + + _B, C, T, H, W = 1, 2, 16, 16, 16 + test_in = torch.from_numpy(self.test_in).to(dtype=torch.float32, device=device) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + self.cfg.backbone.block.cell.window_size = [H // 8, W // 8, T // 8] + self.cfg.backbone.block.cell.patch_size = [2, 2, 2] + + set_seed(785456) + + print( + f"{Fore.GREEN}-------------> HRNet -- {backbone} <----------------------{Style.RESET_ALL}" + ) + + self.cfg.backbone.block_str = backbone + self.config = hydra.utils.instantiate(self.cfg.backbone) + + model = HRnet(config=self.config, input_feature_channels=C, H=H, W=W, D=T) + model.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = model(test_in)[-1] + end_timer( + enable=with_timer, t=t0, msg=f"{backbone} - forward pass - test_in {test_in.shape}" + ) + + gt_fname = os.path.join(self.data_root, f"test_out_{backbone}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load(gt_fname) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 2e-3 + ) + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in, test_out[:, :C, :, :, :]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg=f"{backbone} - backward pass") + + del model + torch.cuda.empty_cache() + + print(f"{'***' * 20}") diff --git a/test/components/test_backbone_soanet.py b/test/components/test_backbone_soanet.py new file mode 100644 index 0000000..34690d5 --- /dev/null +++ b/test/components/test_backbone_soanet.py @@ -0,0 +1,106 @@ +import os +from pathlib import Path + +import hydra +import numpy as np +import pytest +import torch +from colorama import Fore, Style +from hydra import compose, initialize +from omegaconf import OmegaConf + +from snraware.components.model import SOAnet +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestSOAnet: + def setup_class(self): + set_seed(7878756) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/backbone_soanet" + + self.test_in_fname = os.path.join(self.data_root, "test_in.npy") + self.test_in = np.load(self.test_in_fname) + + with initialize(version_base=None, config_path="../../src/snraware/components/configs"): + cfg = compose(config_name="config", overrides=["backbone=soanet"]) + print(OmegaConf.to_yaml(cfg)) + + self.cfg = cfg + self.config = hydra.utils.instantiate(cfg.backbone) + + def teardown_class(self): + pass + + @pytest.mark.gpu + @pytest.mark.parametrize("downsample", [1, 0]) + @pytest.mark.parametrize( + "backbone", + [ + ["T1L1G1T1"], + ["T1L1G1", "T1L1G1", "T1L1G1", "T1L1G1"], + ["T1T1T1", "T1T1T1", "T1T1T1", "T1T1T1"], + ["V3V3V3"], + ["S3ShS3Sh"], + ["C2C2C2"], + ["C3C3C3"], + ], + ) + def test(self, downsample, backbone): + with_timer = True + device = get_device() + + _B, C, T, H, W = 1, 4, 8, 32, 32 + test_in = torch.from_numpy(self.test_in).to(dtype=torch.float32, device=device) + + self.cfg.backbone.block.cell.window_size = [H // 8, W // 8, T // 8] + self.cfg.backbone.block.cell.patch_size = [2, 2, 2] + + self.cfg.backbone.downsample = downsample + + bk = backbone + + set_seed(7878756) + + print( + f"{Fore.GREEN}-------------> SOANet -- {bk} -- {downsample} <----------------------{Style.RESET_ALL}" + ) + + self.cfg.backbone.block_str = bk + self.cfg.backbone.num_stages = len(bk) + self.config = hydra.utils.instantiate(self.cfg.backbone) + + model = SOAnet(config=self.config, input_feature_channels=C, H=H, W=W, D=T) + model.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = model(test_in)[-1] + end_timer(enable=with_timer, t=t0, msg=f"{bk} - forward pass - test_in {test_in.shape}") + + H_out, W_out = test_out.shape[3], test_out.shape[4] + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in[:, :, :, :H_out, :W_out], test_out[:, :C, :, :, :]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg=f"{bk} - backward pass") + + fname = f"{self.cfg.backbone.block_str[0]}_downsample_{self.cfg.backbone.downsample}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load(gt_fname) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 2e-3 + ) + + del model + torch.cuda.empty_cache() diff --git a/test/components/test_backbone_unet.py b/test/components/test_backbone_unet.py new file mode 100644 index 0000000..60ab945 --- /dev/null +++ b/test/components/test_backbone_unet.py @@ -0,0 +1,101 @@ +import os +from pathlib import Path + +import hydra +import numpy as np +import pytest +import torch +from colorama import Fore, Style +from hydra import compose, initialize +from omegaconf import OmegaConf + +from snraware.components.model import Unet +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestUnet: + def setup_class(self): + set_seed(7878756) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/backbone_unet" + + self.test_in_fname = os.path.join(self.data_root, "test_in.npy") + self.test_in = np.load(self.test_in_fname) + + with initialize(version_base=None, config_path="../../src/snraware/components/configs"): + cfg = compose(config_name="config", overrides=["backbone=unet"]) + print(OmegaConf.to_yaml(cfg)) + + self.cfg = cfg + self.config = hydra.utils.instantiate(cfg.backbone) + + def teardown_class(self): + pass + + @pytest.mark.gpu + @pytest.mark.parametrize( + "backbone", + [ + ["S3ShT1L1G1V2V3", "V2V3T1L1G1S3Sh"], + ["V3V3V3", "V3V3V3"], + ["T1L1G1T1", "T1L1G1T1"], + ["T1L1G1", "T1L1G1"], + ["T1T1T1", "T1T1T1"], + ["T1V2V2", "T1V2V2"], + ["C2C2C2", "C2C2C2"], + ["C3C3C3", "C3C3C3"], + ], + ) + def test(self, backbone): + with_timer = True + device = get_device() + + _B, C, T, H, W = 1, 2, 16, 32, 32 + test_in = torch.from_numpy(self.test_in).to(dtype=torch.float32, device=device) + + self.cfg.backbone.block.cell.window_size = [H // 4, W // 4, T // 4] + self.cfg.backbone.block.cell.patch_size = [2, 2, 2] + + # run the tests + bk = backbone + + set_seed(7878756) + + print(f"{Fore.GREEN}-------------> UNet -- {bk} <----------------------{Style.RESET_ALL}") + + self.cfg.backbone.block_str = bk + self.cfg.backbone.num_resolution_levels = len(bk) + self.config = hydra.utils.instantiate(self.cfg.backbone) + + model = Unet(config=self.config, input_feature_channels=C, H=H, W=W, D=T) + model.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = model(test_in)[0] + end_timer(enable=with_timer, t=t0, msg=f"{bk} - forward pass - test_in {test_in.shape}") + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in, test_out[:, :C, :, :, :]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg=f"{bk} - backward pass") + + fname = f"{self.cfg.backbone.block_str[0]}_{self.cfg.backbone.num_of_channels}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load(gt_fname) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 2e-3 + ) + + del model + torch.cuda.empty_cache() diff --git a/test/components/test_block.py b/test/components/test_block.py new file mode 100644 index 0000000..085a51a --- /dev/null +++ b/test/components/test_block.py @@ -0,0 +1,108 @@ +import os +from pathlib import Path + +import hydra +import numpy as np +import pytest +import torch +from colorama import Fore, Style +from hydra import compose, initialize +from omegaconf import OmegaConf + +from snraware.components.model import Block +from snraware.components.model.config import create_block_config +from snraware.components.setup import get_device, set_seed + +# ----------------------------------------------------------------- + + +class TestBlock: + def setup_class(self): + set_seed(4587962) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/block" + + with initialize(version_base=None, config_path="../../src/snraware/components/configs"): + cfg = compose(config_name="config") + print(OmegaConf.to_yaml(cfg)) + + self.cfg = hydra.utils.instantiate(cfg.backbone) + + def teardown_class(self): + pass + + @pytest.mark.gpu + @pytest.mark.parametrize( + "block_str", + [ + "V3", + "C2", + "C3", + "T1", + "T0", + "L1", + "L0", + "G1", + "G0", + "V2", + "V3", + "S3", + "Sh", + "L1G1T1", + "T1L1G1T1", + "T1T1T1", + "V2V2V2V2V2", + "V3V3V3V3V3", + "S3ShS3ShS3ShS3ShS3Sh", + "T1L1G1V2V3", + "S3ShT1L1G1", + "V2V3S3ShS3ShT1T1T1", + "T1V2S3ShL1V2S3ShG1V2S3Sh", + ], + ) + def test(self, block_str): + set_seed(7878756) + + B, T, C, H, W = 1, 16, 3, 64, 64 + C_out = 8 + test_in = torch.rand(B, T, C, H, W) + + device = get_device() + test_in = test_in.to(device=device, dtype=torch.float32) + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + + self.cfg.block.cell.n_head = 8 + self.cfg.block.cell.cosine_att = True + self.cfg.block.cell.norm_mode = "layer" + self.cfg.block.block_dense_connection = True + + with torch.inference_mode(): + fname = f"{block_str}" + print(f"{Fore.YELLOW}--> {fname}{Style.RESET_ALL}") + + block_config = create_block_config(block_str=[block_str], block=self.cfg.block) + a_block = Block( + block_config=block_config[0], + C_in=C, + C_out=C_out, + H=H, + W=W, + D=T, + config=self.cfg.block, + ) + a_block.to(device=device) + test_out = a_block(torch.clone(test_in)) + + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load(gt_fname) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 2e-3 + ) diff --git a/test/components/test_cell.py b/test/components/test_cell.py new file mode 100644 index 0000000..73b2c68 --- /dev/null +++ b/test/components/test_cell.py @@ -0,0 +1,160 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model import Cell, Parallel_Cell +from snraware.components.setup import get_device, set_seed + +# ----------------------------------------------------------------- + + +class TestCell: + def setup_class(self): + set_seed(1854417) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/cell" + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + B, T, C, H, W = 1, 16, 2, 64, 64 + C_out = 8 + test_in = torch.rand(B, T, C, H, W).to(torch.float32) + + device = get_device() + test_in = test_in.to(device=device) + + att_types = [ + "vit_3d", + "local", + "global", + "vit_2d", + "swin_3d", + "swin_3d_shifted", + "temporal", + "conv2d", + "conv3d", + ] + norm_types = ["instance2d", "batch2d", "layer", "instance3d", "batch3d"] + + att_types = ["local", "global", "temporal", "conv2d", "conv3d"] + norm_types = ["instance2d", "batch2d", "layer"] + cosine_atts = ["True", "False"] + att_with_relative_position_biases = ["True", "False"] + mixer_types = ["conv"] + with_flash_attentions = [False] + stride_ss = [[1, 1, 1]] + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + + with torch.inference_mode(): + for with_flash_attention in with_flash_attentions: + for norm_type in norm_types: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for mixer_type in mixer_types: + for stride_s in stride_ss: + for att_type in att_types: + fname = f"{norm_type}_{att_type}_{mixer_type}_{cosine_att}_{att_with_relative_position_bias}_{stride_s}_{with_flash_attention}_False" + print(fname) + + x = torch.clone(test_in) + + CNNT_Cell = Cell( + C_in=C, + C_out=C_out, + H=H, + W=W, + D=T, + window_size=[H // 8, W // 8, T // 4], + patch_size=[4, 4, 2], + num_wind=[8, 8, 4], + num_patch=[H // 32, W // 32, T // 8], + att_mode=att_type, + mixer_type=mixer_type, + norm_mode=norm_type, + stride_s=stride_s, + cosine_att=cosine_att, + att_with_relative_position_bias=att_with_relative_position_bias, + use_flash_attention=with_flash_attention, + ).to(device=device) + test_out = CNNT_Cell(x) + + gt_fname = os.path.join( + self.data_root, f"test_out_STCNNT_Cell_{fname}.npy" + ) + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join( + self.data_root, f"test_out_STCNNT_Cell_{fname}.npy" + ) + ) + + assert ( + np.linalg.norm( + test_out_gt - test_out.detach().cpu().numpy() + ) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + for with_flash_attention in with_flash_attentions: + for att_type in att_types: + for norm_type in norm_types: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for mixer_type in mixer_types: + for stride_s in stride_ss: + fname = f"{norm_type}_{att_type}_{mixer_type}_{cosine_att}_{att_with_relative_position_bias}_{stride_s}_{with_flash_attention}_False" + print(fname) + + p_cell = Parallel_Cell( + C_in=C, + C_out=C_out, + H=H, + W=W, + D=T, + window_size=[H // 8, W // 8, T // 4], + patch_size=[4, 4, 2], + num_wind=[8, 8, 4], + num_patch=[H // 32, W // 32, T // 8], + att_mode=att_type, + mixer_type=mixer_type, + norm_mode=norm_type, + stride_s=stride_s, + cosine_att=cosine_att, + att_with_relative_position_bias=att_with_relative_position_bias, + use_flash_attention=with_flash_attention, + ).to(device=device) + test_out = p_cell(test_in) + + gt_fname = os.path.join( + self.data_root, + f"test_out_STCNNT_Parallel_Cell_{fname}.npy", + ) + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join( + self.data_root, + f"test_out_STCNNT_Parallel_Cell_{fname}.npy", + ) + ) + + assert ( + np.linalg.norm( + test_out_gt - test_out.detach().cpu().numpy() + ) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) diff --git a/test/components/test_config.py b/test/components/test_config.py new file mode 100644 index 0000000..8acf553 --- /dev/null +++ b/test/components/test_config.py @@ -0,0 +1,27 @@ +# test the parameter configuration with hydra +import hydra +from hydra import compose, initialize +from omegaconf import OmegaConf + + +def test_config_with_overrides(): + with initialize(version_base=None, config_path="../../src/snraware/components/configs"): + cfg = compose( + config_name="config", + overrides=[ + "backbone=hrnet", + "backbone.block.cell.attention_type=conv", + "backbone.num_of_channels=15", + "backbone.block.cell_type=parallel", + "backbone.block.cell.att_dropout_p=0.7", + ], + ) + print(OmegaConf.to_yaml(cfg)) + assert cfg.backbone.num_of_channels == 15 + assert cfg.backbone.block.cell_type == "parallel" + assert cfg.backbone.block.cell.att_dropout_p == 0.7 + + config = hydra.utils.instantiate(cfg.backbone) + assert config.num_of_channels == 15 + assert config.block.cell_type == "parallel" + assert config.block.cell.att_dropout_p == 0.7 diff --git a/test/components/test_convolution_module.py b/test/components/test_convolution_module.py new file mode 100644 index 0000000..d9de796 --- /dev/null +++ b/test/components/test_convolution_module.py @@ -0,0 +1,95 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model.attention import ConvolutionModule +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestConvolutionModule: + def setup_class(self): + set_seed(78562) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/convolution" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + import itertools + + B, T, C, H, W = 2, 16, 3, 16, 16 + C_out = 32 + + kernel_size = (3, 3, 3) + stride = (1, 1, 1) + padding = (1, 1, 1) + + with_timer = True + device = get_device() + + test_in = torch.rand(B, T, C, H, W, device=device) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + + conv_types = ["conv2d", "conv3d"] + separables = [True, False] + norm_modes = ["instance2d", "batch2d", "layer"] + activation_funcs = ["prelu", "gelu", "relu"] + + for conv_type, separable, norm_mode, activation_func in itertools.product( + conv_types, separables, norm_modes, activation_funcs + ): + print(conv_type, separable, norm_mode, activation_func) + + model = ConvolutionModule( + conv_type=conv_type, + C_in=C, + C_out=C_out, + H=H, + W=W, + D=T, + kernel_size=kernel_size, + stride=stride, + padding=padding, + norm_mode=norm_mode, + activation_func=activation_func, + ) + model.to(device) + + t0 = start_timer(enable=with_timer) + test_out = model(test_in) + end_timer(enable=with_timer, t=t0, msg=f"forward pass - {test_in.shape}") + + fname = f"{conv_type}_{separable}_{norm_mode}_{activation_func}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load(os.path.join(self.data_root, f"test_out_{fname}.npy")) + test_out_gt = np.transpose(test_out_gt, [0, 2, 1, 3, 4]) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + t0 = start_timer(enable=with_timer) + loss = torch.nn.MSELoss() + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg="backward pass") + + print("Passed all tests") diff --git a/test/components/test_global_3d_attention.py b/test/components/test_global_3d_attention.py new file mode 100644 index 0000000..356dd70 --- /dev/null +++ b/test/components/test_global_3d_attention.py @@ -0,0 +1,165 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model.attention import Global3DAttention +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestGlobal3DAttention: + def setup_class(self): + set_seed(1987865) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/global_3d" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + self.test_in2 = np.load(os.path.join(self.data_root, "test_in2.npy")) + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + t = np.arange(256) + t = np.reshape(t, (16, 16)) + + t = torch.from_numpy(t).to(dtype=torch.float32) + t = torch.cat((t[None, :], t[None, :]), dim=0) + + B, D = 2, 16 + C_out = 8 + test_in = t.repeat(B, D, 1, 1, 1) + print(test_in.shape) + + C = test_in.shape[-3] + H = test_in.shape[-2] + W = test_in.shape[-1] + + att = Global3DAttention( + H=H, + W=W, + D=D, + window_size=None, + patch_size=None, + num_wind=[4, 4, 2], + num_patch=[2, 2, 2], + attention_type="conv", + C_in=C, + C_out=C_out, + ) + + a = att.im2grid(torch.permute(test_in, [0, 2, 1, 3, 4])) + b = att.grid2im(a) + b = torch.permute(b, [0, 2, 1, 3, 4]) + + assert torch.allclose(test_in, b) + + attention_types = ["conv", "lin"] + normalize_Q_Ks = [True, False] + cosine_atts = [True, False] + att_with_relative_position_biases = [False] + att_with_output_projs = [True, False] + stride_qks = [[1, 1, 1]] + + with_timer = True + + device = get_device() + + B, T, C, H1, W1 = 1, 16, 2, 64, 64 + C_out = 8 + test_in = torch.rand(B, T, C, H1, W1).to(device=device) + print(test_in.shape) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + B, T, C, H2, W2 = 1, 16, 2, 128, 128 + C_out = 8 + test_in2 = torch.rand(B, T, C, H2, W2).to(device=device) + print(test_in2.shape) + assert np.linalg.norm(self.test_in2 - test_in2.cpu().numpy()) < 1e-3 + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + test_in2 = torch.permute(test_in2, [0, 2, 1, 3, 4]) + + for attention_type in attention_types: + for normalize_Q_K in normalize_Q_Ks: + for att_with_output_proj in att_with_output_projs: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for stride_qk in stride_qks: + fname = f"{attention_type}_{normalize_Q_K}_{att_with_output_proj}_{cosine_att}_{att_with_relative_position_bias}_{stride_qk}" + print(f"run - {fname}") + + att = Global3DAttention( + window_size=None, + num_wind=[8, 8, 4], + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H1, + W=W1, + D=T, + stride_qk=stride_qk, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + att.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = att(test_in) + end_timer( + enable=with_timer, + t=t0, + msg=f"forward pass - test_in {test_in.shape}", + ) + + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join(self.data_root, f"test_out_{fname}.npy") + ) + test_out_gt = np.transpose(test_out_gt, [0, 2, 1, 3, 4]) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg="backward pass") + + print(f"attention_type is {attention_type}, mse is {mse.item()}") + + if attention_type == "conv": + att = Global3DAttention( + window_size=None, + num_wind=[8, 8, 4], + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H2, + W=W2, + D=T, + stride_qk=stride_qk, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + att.to(device=device) + + test_out = att(test_in2) diff --git a/test/components/test_heads.py b/test/components/test_heads.py new file mode 100644 index 0000000..9beb88c --- /dev/null +++ b/test/components/test_heads.py @@ -0,0 +1,104 @@ +import torch +from hydra import compose, initialize + +from snraware.components.heads import ( + PoolLinear, + PreConv2D, + SimpleConv2d, +) +from snraware.components.setup import set_seed + +# ----------------------------------------------------------------- + + +def create_heads(config, component_type, C_in, C_out, H, W, D): + """ + Create model head components from configuration. + Return value is the created component. + + C_in is the number of input channels. + H, W, D is the tensor size. + + All components process 5D tensor [B, C, D, H, W] + """ + # Pre heads + if component_type == "PreConv2D": + model = PreConv2D(C=C_in, C_out=C_out, bias=config.heads.PreConv2D.conv_with_bias) + elif component_type == "PoolLinear": + model = PoolLinear( + config=config, C=C_in, num_classes=C_out, add_tanh=config.heads.PoolLinear.add_tanh + ) + elif component_type == "SimpleConv2d": + model = SimpleConv2d(config=config, C=C_in, num_classes=C_out) + + else: + raise NotImplementedError(f"Model not implemented: {component_type}") + + return model + + +class TestModel: + config = None + + def setup_class(self): + set_seed(64861651) + torch.set_printoptions(precision=10) + + with initialize(version_base=None, config_path="../../src/snraware/components/configs"): + cfg = compose(config_name="config") + self.config = cfg + + def teardown_class(self): + pass + + def test_heads(self): + B, C_in, D, H, W = 12, 2, 16, 64, 64 + backbone_C = 32 + + PreConv2D = create_heads( + config=self.config, + component_type="PreConv2D", + C_in=C_in, + C_out=backbone_C, + H=H, + W=W, + D=D, + ) + + num_classes = 10 + PoolLinear = create_heads( + config=self.config, + component_type="PoolLinear", + C_in=backbone_C, + C_out=num_classes, + H=H, + W=W, + D=D, + ) + + num_seg_classes = 4 + SimpleConv2d = create_heads( + config=self.config, + component_type="SimpleConv2d", + C_in=backbone_C, + C_out=num_seg_classes, + H=H, + W=W, + D=D, + ) + + input = torch.randn(B, C_in, D, H, W, dtype=torch.float32) + backbone_output = torch.randn(B, backbone_C, D, H, W, dtype=torch.float32) + + with torch.inference_mode(): + y = PreConv2D(input) + assert y.shape == (B, backbone_C, D, H, W) + + y = PoolLinear(backbone_output) + assert y.shape == (B, num_classes) + + y = SimpleConv2d(backbone_output) + assert y.shape == (B, num_seg_classes, D, H, W) + + +# --------------------------------------------------------------- diff --git a/test/components/test_local_3d_attention.py b/test/components/test_local_3d_attention.py new file mode 100644 index 0000000..1c7f005 --- /dev/null +++ b/test/components/test_local_3d_attention.py @@ -0,0 +1,165 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model.attention import Local3DAttention +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestLocal3DAttention: + def setup_class(self): + set_seed(1467865) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/local_3d" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + self.test_in2 = np.load(os.path.join(self.data_root, "test_in2.npy")) + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + t = np.arange(256) + t = np.reshape(t, (16, 16)) + + t = torch.from_numpy(t).to(dtype=torch.float32) + t = torch.cat((t[None, :], t[None, :]), dim=0) + + B, D = 2, 16 + C_out = 8 + test_in = t.repeat(B, D, 1, 1, 1) + print(test_in.shape) + + C = test_in.shape[-3] + H = test_in.shape[-2] + W = test_in.shape[-1] + + att = Local3DAttention( + H=H, + W=W, + D=D, + window_size=None, + patch_size=None, + num_wind=[4, 4, 2], + num_patch=[2, 2, 2], + attention_type="conv", + C_in=C, + C_out=C_out, + ) + + a = att.im2grid(torch.permute(test_in, [0, 2, 1, 3, 4])) + b = att.grid2im(a) + b = torch.permute(b, [0, 2, 1, 3, 4]) + + assert torch.allclose(test_in, b) + + attention_types = ["conv", "lin"] + normalize_Q_Ks = [True, False] + cosine_atts = [True, False] + att_with_relative_position_biases = [False] + att_with_output_projs = [True, False] + stride_qks = [[1, 1, 1]] + + with_timer = True + + device = get_device() + + B, T, C, H1, W1 = 1, 16, 2, 64, 64 + C_out = 8 + test_in = torch.rand(B, T, C, H1, W1).to(device=device) + print(test_in.shape) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + B, T, C, H2, W2 = 1, 16, 2, 128, 128 + C_out = 8 + test_in2 = torch.rand(B, T, C, H2, W2).to(device=device) + print(test_in2.shape) + assert np.linalg.norm(self.test_in2 - test_in2.cpu().numpy()) < 1e-3 + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + test_in2 = torch.permute(test_in2, [0, 2, 1, 3, 4]) + + for attention_type in attention_types: + for normalize_Q_K in normalize_Q_Ks: + for att_with_output_proj in att_with_output_projs: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for stride_qk in stride_qks: + fname = f"{attention_type}_{normalize_Q_K}_{att_with_output_proj}_{cosine_att}_{att_with_relative_position_bias}_{stride_qk}" + print(f"run - {fname}") + + att = Local3DAttention( + window_size=None, + num_wind=[8, 8, 4], + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H1, + W=W1, + D=T, + stride_qk=stride_qk, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + att.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = att(test_in) + end_timer( + enable=with_timer, + t=t0, + msg=f"forward pass - test_in {test_in.shape}", + ) + + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join(self.data_root, f"test_out_{fname}.npy") + ) + test_out_gt = np.transpose(test_out_gt, [0, 2, 1, 3, 4]) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg="backward pass") + + print(f"attention_type is {attention_type}, mse is {mse.item()}") + + if attention_type == "conv": + att = Local3DAttention( + window_size=None, + num_wind=[8, 8, 4], + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H2, + W=W2, + D=T, + stride_qk=stride_qk, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + att.to(device=device) + + test_out = att(test_in2) diff --git a/test/components/test_spatial_global_attention.py b/test/components/test_spatial_global_attention.py new file mode 100644 index 0000000..d361c33 --- /dev/null +++ b/test/components/test_spatial_global_attention.py @@ -0,0 +1,163 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model.attention import SpatialGlobalAttention +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestSpatialGlobalAttention: + def setup_class(self): + set_seed(358) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/spatial_global" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + self.test_in2 = np.load(os.path.join(self.data_root, "test_in2.npy")) + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + print("Begin Testing") + + t = np.arange(256) + t = np.reshape(t, (16, 16)) + + t = torch.from_numpy(t).to(dtype=torch.float32) + t = torch.cat((t[None, :], t[None, :]), dim=0) + + B, T, C, H, W = 2, 4, 2, 16, 16 + C_out = 8 + test_in = t.repeat(B, T, 1, 1, 1) + print(test_in.shape) + + spacial_vit = SpatialGlobalAttention( + H=H, + W=W, + window_size=[8, 8], + patch_size=[4, 4], + stride_qk=(2, 2), + num_wind=None, + num_patch=None, + attention_type="conv", + C_in=C, + C_out=C_out, + ) + + a = spacial_vit.im2grid(test_in) + b = spacial_vit.grid2im(a) + + # gt = torch.tensor( + # [ + # [ + # [ + # [64.0, 65.0, 66.0, 67.0], + # [80.0, 81.0, 82.0, 83.0], + # [96.0, 97.0, 98.0, 99.0], + # [112.0, 113.0, 114.0, 115.0], + # ], + # [ + # [72.0, 73.0, 74.0, 75.0], + # [88.0, 89.0, 90.0, 91.0], + # [104.0, 105.0, 106.0, 107.0], + # [120.0, 121.0, 122.0, 123.0], + # ], + # ], + # [ + # [ + # [192.0, 193.0, 194.0, 195.0], + # [208.0, 209.0, 210.0, 211.0], + # [224.0, 225.0, 226.0, 227.0], + # [240.0, 241.0, 242.0, 243.0], + # ], + # [ + # [200.0, 201.0, 202.0, 203.0], + # [216.0, 217.0, 218.0, 219.0], + # [232.0, 233.0, 234.0, 235.0], + # [248.0, 249.0, 250.0, 251.0], + # ], + # ], + # ] + # ) + + # assert torch.norm(a[0, 0, 1, 0, :, :, :, :, 0] - gt) < 1e-3 + assert torch.norm(b - test_in) < 1e-3 + + attention_types = ["conv", "lin"] + normalize_Q_Ks = [True, False] + cosine_atts = [True, False] + att_with_relative_position_biases = [True, False] + att_with_output_projs = [True, False] + stride_qks = [[1, 1]] + + with_timer = True + device = get_device() + + B, C, T, H1, W1 = 1, 2, 16, 64, 64 + C_out = 4 + test_in = torch.rand(B, C, T, H1, W1).to(device=device) + print(test_in.shape) + np.save(os.path.join(self.data_root, "test_in.npy"), test_in.cpu().numpy()) + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + for attention_type in attention_types: + for normalize_Q_K in normalize_Q_Ks: + for att_with_output_proj in att_with_output_projs: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for stride_qk in stride_qks: + m = SpatialGlobalAttention( + window_size=[16, 16], + patch_size=[8, 8], + num_wind=None, + num_patch=None, + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H1, + W=W1, + stride_qk=stride_qk, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + + m.to(device=device) + t0 = start_timer(enable=with_timer) + test_out = m(test_in) + end_timer( + enable=with_timer, t=t0, msg=f"forward pass - {test_in.shape}" + ) + + fname = f"{attention_type}_{normalize_Q_K}_{att_with_output_proj}_{cosine_att}_{att_with_relative_position_bias}_{stride_qk}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join(self.data_root, f"test_out_{fname}.npy") + ) + # test_out_gt = np.transpose(test_out_gt, [0, 2, 1, 3, 4]) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg="backward pass") diff --git a/test/components/test_spatial_local_attention.py b/test/components/test_spatial_local_attention.py new file mode 100644 index 0000000..4912c4f --- /dev/null +++ b/test/components/test_spatial_local_attention.py @@ -0,0 +1,178 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model.attention import SpatialLocalAttention +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestSpatialLocalAttention: + def setup_class(self): + set_seed(358) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/spatial_local" + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + print("Begin Testing") + + t = np.arange(256) + t = np.reshape(t, (16, 16)) + + t = torch.from_numpy(t).to(dtype=torch.float32) + t = torch.cat((t[None, :], t[None, :]), dim=0) + + B, T, C, H, W = 2, 4, 2, 16, 16 + C_out = 8 + test_in = t.repeat(B, T, 1, 1, 1) + print(test_in.shape) + + spacial_vit = SpatialLocalAttention( + H=H, + W=W, + window_size=None, + patch_size=None, + num_wind=[2, 2], + num_patch=[2, 2], + attention_type="conv", + C_in=C, + C_out=C_out, + ) + + a = spacial_vit.im2grid( + test_in + ) # b t num_win_h num_win_w num_patch_h num_patch_w patch_size_h patch_size_w c + b = spacial_vit.grid2im(a) + + assert torch.allclose(test_in, b) + + gt = torch.tensor( + [ + [ + [ + [128.0, 129.0, 130.0, 131.0], + [144.0, 145.0, 146.0, 147.0], + [160.0, 161.0, 162.0, 163.0], + [176.0, 177.0, 178.0, 179.0], + ], + [ + [132.0, 133.0, 134.0, 135.0], + [148.0, 149.0, 150.0, 151.0], + [164.0, 165.0, 166.0, 167.0], + [180.0, 181.0, 182.0, 183.0], + ], + ], + [ + [ + [192.0, 193.0, 194.0, 195.0], + [208.0, 209.0, 210.0, 211.0], + [224.0, 225.0, 226.0, 227.0], + [240.0, 241.0, 242.0, 243.0], + ], + [ + [196.0, 197.0, 198.0, 199.0], + [212.0, 213.0, 214.0, 215.0], + [228.0, 229.0, 230.0, 231.0], + [244.0, 245.0, 246.0, 247.0], + ], + ], + ] + ) + + assert torch.norm(a[0, 0, 1, 0, :, :, :, :, 1] - gt) < 1e-3 + assert torch.norm(b - test_in) < 1e-3 + + attention_types = ["conv", "lin"] + normalize_Q_Ks = [True, False] + cosine_atts = [True, False] + att_with_relative_position_biases = [True, False] + att_with_output_projs = [True, False] + stride_qks = [[1, 1], [2, 2]] + + with_timer = True + device = get_device() + + B, T, C, H1, W1 = 1, 4, 2, 64, 64 + C_out = 16 + n_head = 16 + test_in = torch.rand(B, T, C, H1, W1).to(device=device) + print(test_in.shape) + + B, T, C, H2, W2 = 1, 4, 2, 32, 32 + C_out = 16 + test_in2 = torch.rand(B, T, C, H2, W2).to(device=device) + print(test_in2.shape) + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + test_in2 = torch.permute(test_in2, [0, 2, 1, 3, 4]) + + for attention_type in attention_types: + for normalize_Q_K in normalize_Q_Ks: + for att_with_output_proj in att_with_output_projs: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for stride_qk in stride_qks: + m = SpatialLocalAttention( + H=H1, + W=W1, + window_size=None, + patch_size=None, + num_wind=[4, 4], + num_patch=[2, 2], + attention_type=attention_type, + C_in=C, + C_out=C_out, + n_head=n_head, + stride_qk=stride_qk, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + m.to(device=device) + t0 = start_timer(enable=with_timer) + test_out = m(test_in) + end_timer( + enable=with_timer, t=t0, msg=f"forward pass - {test_in.shape}" + ) + + fname = f"{attention_type}_{normalize_Q_K}_{att_with_output_proj}_{cosine_att}_{att_with_relative_position_bias}_{stride_qk}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join(self.data_root, f"test_out_{fname}.npy") + ) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + t0 = start_timer(enable=with_timer) + loss = torch.nn.MSELoss() + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer( + enable=with_timer, + t=t0, + msg=f"backward pass - stride_qk {stride_qk}", + ) + + t0 = start_timer(enable=with_timer) + test_out = m(test_in2) + end_timer( + enable=with_timer, t=t0, msg=f"forward pass -{test_in2.shape}" + ) diff --git a/test/components/test_spatial_vit_attention.py b/test/components/test_spatial_vit_attention.py new file mode 100644 index 0000000..5f07850 --- /dev/null +++ b/test/components/test_spatial_vit_attention.py @@ -0,0 +1,171 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model.attention import SpatialViTAttention +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestSpatialViT: + def setup_class(self): + set_seed(1232543) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/vit2d" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + self.test_in2 = np.load(os.path.join(self.data_root, "test_in2.npy")) + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + print("Begin Testing") + + t = np.arange(256) + t = np.reshape(t, (16, 16)) + + w = 8 + + t = torch.from_numpy(t).to(dtype=torch.float32) + t = torch.cat((t[None, :], t[None, :]), dim=0) + + B, T, C, H, W = 2, 16, 2, 64, 64 + C_out = 8 + test_in = t.repeat(B, T, 1, 1, 1) + print(test_in.shape) + + vit = SpatialViTAttention( + window_size=[w, w], + num_wind=None, + attention_type="conv", + C_in=C, + C_out=C_out, + H=H, + W=W, + stride_qk=(1, 1), + n_head=8, + cosine_att=True, + normalize_Q_K=True, + att_with_relative_position_bias=False, + att_with_output_proj=True, + with_timer=True, + ) + + a = vit.im2grid(test_in) + b = vit.grid2im(a) + + assert torch.allclose(test_in, b) + + attention_types = ["conv", "lin"] + normalize_Q_Ks = [True, False] + cosine_atts = [True, False] + att_with_relative_position_biases = [False] + att_with_output_projs = [True, False] + stride_qks = [[1, 1, 1], [2, 2, 2]] + + with_timer = True + + device = get_device() + + B, T, C, H1, W1 = 1, 16, 2, 32, 32 + C_out = 8 + test_in = torch.rand(B, T, C, H1, W1).to(device=device) + print(test_in.shape) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + B, T, C, H2, W2 = 1, 16, 2, 128, 128 + C_out = 8 + test_in2 = torch.rand(B, T, C, H2, W2).to(device=device) + print(test_in2.shape) + assert np.linalg.norm(self.test_in2 - test_in2.cpu().numpy()) < 1e-3 + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + test_in2 = torch.permute(test_in2, [0, 2, 1, 3, 4]) + + for attention_type in attention_types: + for normalize_Q_K in normalize_Q_Ks: + for att_with_output_proj in att_with_output_projs: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for stride_qk in stride_qks: + vit = SpatialViTAttention( + window_size=[w, w], + num_wind=None, + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H1, + W=W1, + stride_qk=stride_qk[:2], + n_head=8, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=False, + ) + vit.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = vit(test_in) + end_timer( + enable=with_timer, t=t0, msg=f"forward pass - {test_in.shape}" + ) + + fname = f"{attention_type}_{normalize_Q_K}_{att_with_output_proj}_{cosine_att}_{att_with_relative_position_bias}_{stride_qk}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join(self.data_root, f"test_out_{fname}.npy") + ) + test_out_gt = np.transpose(test_out_gt, [0, 2, 1, 3, 4]) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg="backward pass") + + print(f"attention_type is {attention_type}, mse is {mse.item()}") + + # ------------------------------------------------- + + vit = SpatialViTAttention( + window_size=[w, w], + num_wind=None, + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H2, + W=W2, + stride_qk=stride_qk[:2], + n_head=8, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + with_timer=False, + ) + vit.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = vit(test_in2) + end_timer( + enable=with_timer, t=t0, msg=f"forward pass - {test_in2.shape}" + ) diff --git a/test/components/test_swin_3d_attention.py b/test/components/test_swin_3d_attention.py new file mode 100644 index 0000000..7591ca5 --- /dev/null +++ b/test/components/test_swin_3d_attention.py @@ -0,0 +1,138 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch +from colorama import Fore, Style + +from snraware.components.model.attention import Swin3DAttention +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestSwin3DAttention: + def setup_class(self): + set_seed(1467865) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/swin_3d" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + self.test_in2 = np.load(os.path.join(self.data_root, "test_in2.npy")) + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + t = np.arange(256) + t = np.reshape(t, (16, 16)) + + t = torch.from_numpy(t).to(dtype=torch.float32) + t = torch.cat((t[None, :], t[None, :]), dim=0) + + B, D = 1, 16 + C_out = 4 + test_in = t.repeat(B, D, 1, 1, 1) + print(test_in.shape) + + C = test_in.shape[-3] + H = test_in.shape[-2] + W = test_in.shape[-1] + + att = Swin3DAttention( + H=H, + W=W, + D=D, + window_size=None, + num_wind=[4, 4, 2], + attention_type="conv", + C_in=C, + C_out=C_out, + ) + + a = att.im2grid(torch.permute(test_in, [0, 2, 1, 3, 4])) + b = att.grid2im(a) + b = torch.permute(b, [0, 2, 1, 3, 4]) + + assert torch.allclose(test_in, b) + + attention_types = ["conv", "lin"] + normalize_Q_Ks = [True, False] + cosine_atts = [True, False] + att_with_relative_position_biases = [False] + att_with_output_projs = [True, False] + stride_qks = [[1, 1, 1]] + + with_timer = True + + device = get_device() + + B, T, C, H1, W1 = 1, 16, 2, 32, 32 + C_out = 32 + test_in = torch.rand(B, T, C, H1, W1).to(device=device) + print(test_in.shape) + # np.save(os.path.join(self.data_root, "test_in.npy"), test_in.detach().cpu().numpy()) + # self.test_in = np.load(os.path.join(self.data_root, 'test_in.npy')) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + + for attention_type in attention_types: + for normalize_Q_K in normalize_Q_Ks: + for att_with_output_proj in att_with_output_projs: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for stride_qk in stride_qks: + fname = f"{attention_type}_{normalize_Q_K}_{att_with_output_proj}_{cosine_att}_{att_with_relative_position_bias}_{stride_qk}" + print( + f"{Fore.YELLOW}run - {fname}, {torch.linalg.norm(test_in)}{Style.RESET_ALL}" + ) + + att = Swin3DAttention( + window_size=None, + num_wind=[4, 4, 2], + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H1, + W=W1, + D=T, + stride_qk=stride_qk, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + att.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = att(test_in) + end_timer( + enable=with_timer, + t=t0, + msg=f"forward pass - test_in {test_in.shape}", + ) + + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load(gt_fname) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg="backward pass") + + print(f"attention_type is {attention_type}, mse is {mse.item()}") diff --git a/test/components/test_temporal_attention.py b/test/components/test_temporal_attention.py new file mode 100644 index 0000000..33bb9d9 --- /dev/null +++ b/test/components/test_temporal_attention.py @@ -0,0 +1,111 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model.attention import TemporalChannelCnnAttention, TemporalCnnAttention +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestTemporalAttention: + def setup_class(self): + set_seed(89845) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/temporal" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test(self): + print("Begin Testing") + + B, T, C, H, W = 2, 16, 3, 16, 16 + C_out = 32 + + with_timer = True + device = get_device() + + test_in = torch.rand(B, T, C, H, W, device=device) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + print("Begin Testing") + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + + causals = [True, False] + normalize_Q_Ks = [True, False] + att_with_output_projs = [True, False] + stride_qks = [[1, 1], [2, 2]] + for causal in causals: + for normalize_Q_K in normalize_Q_Ks: + for stride_qk in stride_qks: + for att_with_output_proj in att_with_output_projs: + # -------------------------------------- + t0 = start_timer(enable=with_timer) + temporal = TemporalCnnAttention( + C, + C_out=C_out, + is_causal=causal, + normalize_Q_K=normalize_Q_K, + att_with_output_proj=att_with_output_proj, + ).to(device=device) + test_out = temporal(test_in) + end_timer(enable=with_timer, t=t0, msg=f"forward pass - {test_in.shape}") + + fname = f"TemporalCnnStandardAttention_{normalize_Q_K}_{att_with_output_proj}_{causal}_{stride_qk}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join(self.data_root, f"test_out_{fname}.npy") + ) + test_out_gt = np.transpose(test_out_gt, [0, 2, 1, 3, 4]) + # assert np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy())/np.linalg.norm(test_out_gt) < 1e-3 + + t0 = start_timer(enable=with_timer) + loss = torch.nn.MSELoss() + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg="backward pass") + + # -------------------------------------- + t0 = start_timer(enable=with_timer) + temporal = TemporalChannelCnnAttention( + C, + C_out=C_out, + is_causal=causal, + normalize_Q_K=normalize_Q_K, + att_with_output_proj=att_with_output_proj, + ).to(device=device) + test_out = temporal(test_in) + end_timer(enable=with_timer, t=t0, msg=f"forward pass - {test_in.shape}") + + fname = f"TemporalChannelCnnAttention_{normalize_Q_K}_{att_with_output_proj}_{causal}_{stride_qk}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + # np.save(gt_fname, test_out.detach().cpu().numpy()) + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join(self.data_root, f"test_out_{fname}.npy") + ) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + t0 = start_timer(enable=with_timer) + loss = torch.nn.MSELoss() + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer(enable=with_timer, t=t0, msg="backward pass") diff --git a/test/components/test_vit_3d_attention.py b/test/components/test_vit_3d_attention.py new file mode 100644 index 0000000..d3141d6 --- /dev/null +++ b/test/components/test_vit_3d_attention.py @@ -0,0 +1,171 @@ +import os +from pathlib import Path + +import numpy as np +import pytest +import torch + +from snraware.components.model.attention import ViT3DAttention +from snraware.components.setup import end_timer, get_device, set_seed, start_timer + +# ----------------------------------------------------------------- + + +class TestViT3D: + def setup_class(self): + set_seed(358) + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/../data/vit3d" + + self.test_in = np.load(os.path.join(self.data_root, "test_in.npy")) + self.test_in2 = np.load(os.path.join(self.data_root, "test_in2.npy")) + + def teardown_class(self): + pass + + @pytest.mark.gpu + def test_Vit3D(self): + print("Begin Testing") + + t = np.arange(256) + t = np.reshape(t, (16, 16)) + + w = 8 + + t = torch.from_numpy(t).to(dtype=torch.float32) + t = torch.cat((t[None, :], t[None, :]), dim=0) + + B, T, C, H, W = 2, 16, 2, 32, 32 + C_out = 8 + test_in = t.repeat(B, T, 1, 1, 1) + print(test_in.shape) + + spacial_vit = ViT3DAttention( + window_size=[w, w, 4], + num_wind=None, + attention_type="conv", + C_in=C, + C_out=C_out, + H=H, + W=W, + D=T, + stride_qk=(2, 2, 2), + cosine_att=True, + normalize_Q_K=True, + att_with_relative_position_bias=False, + att_with_output_proj=True, + with_timer=True, + ) + + a = spacial_vit.im2grid(torch.permute(test_in, [0, 2, 1, 3, 4])) + b = spacial_vit.grid2im(a) + b = torch.permute(b, [0, 2, 1, 3, 4]) + + assert torch.allclose(test_in, b) + + attention_types = ["conv", "lin"] + normalize_Q_Ks = [True, False] + cosine_atts = [True, False] + att_with_relative_position_biases = [False] + att_with_output_projs = [True, False] + stride_qks = [[1, 1, 1], [2, 2, 2]] + + with_timer = True + + device = get_device() + + B, T, C, H1, W1 = 1, 16, 2, 32, 32 + C_out = 8 + test_in = torch.rand(B, T, C, H1, W1).to(device=device) + print(test_in.shape) + assert np.linalg.norm(self.test_in - test_in.cpu().numpy()) < 1e-3 + + B, T, C, H2, W2 = 1, 16, 2, 128, 128 + C_out = 8 + test_in2 = torch.rand(B, T, C, H2, W2).to(device=device) + print(test_in2.shape) + assert np.linalg.norm(self.test_in2 - test_in2.cpu().numpy()) < 1e-3 + + test_in = torch.permute(test_in, [0, 2, 1, 3, 4]) + test_in2 = torch.permute(test_in2, [0, 2, 1, 3, 4]) + + for attention_type in attention_types: + for normalize_Q_K in normalize_Q_Ks: + for att_with_output_proj in att_with_output_projs: + for cosine_att in cosine_atts: + for att_with_relative_position_bias in att_with_relative_position_biases: + for stride_qk in stride_qks: + spacial_vit = ViT3DAttention( + window_size=None, + num_wind=[8, 8, 4], + attention_type=attention_type, + C_in=C, + C_out=C_out, + H=H1, + W=W1, + D=T, + stride_qk=stride_qk, + cosine_att=cosine_att, + normalize_Q_K=normalize_Q_K, + att_with_relative_position_bias=att_with_relative_position_bias, + att_with_output_proj=att_with_output_proj, + ) + spacial_vit.to(device=device) + + t0 = start_timer(enable=with_timer) + test_out = spacial_vit(test_in) + end_timer( + enable=with_timer, + t=t0, + msg=f"forward pass - stride_qk {stride_qk}", + ) + + fname = f"{attention_type}_{normalize_Q_K}_{att_with_output_proj}_{cosine_att}_{att_with_relative_position_bias}_{stride_qk}" + gt_fname = os.path.join(self.data_root, f"test_out_{fname}.npy") + assert os.path.exists(gt_fname) + test_out_gt = np.load( + os.path.join(self.data_root, f"test_out_{fname}.npy") + ) + test_out_gt = np.transpose(test_out_gt, [0, 2, 1, 3, 4]) + assert ( + np.linalg.norm(test_out_gt - test_out.detach().cpu().numpy()) + / np.linalg.norm(test_out_gt) + < 1e-3 + ) + + Bo, Co, To, Ho, Wo = test_out.shape + assert ( + B == Bo and T == To and Co == C_out and H1 == Ho and W1 == Wo + ) + + loss = torch.nn.MSELoss() + t0 = start_timer(enable=with_timer) + mse = loss(test_in, test_out[:, :C]) + mse.backward() + end_timer( + enable=with_timer, + t=t0, + msg=f"backward pass - stride_qk {stride_qk}", + ) + + print(f"attention_type is {attention_type}, mse is {mse.item()}") + + if attention_type == "conv": + test_out = spacial_vit(test_in2) + + Bo, Co, To, Ho, Wo = test_out.shape + assert ( + B == Bo + and T == To + and Co == C_out + and H2 == Ho + and W2 == Wo + ) + + print("Passed ViT3DAttention tests") + + print("Passed all tests") diff --git a/test/data/backbone_hrnet/test_in.npy b/test/data/backbone_hrnet/test_in.npy new file mode 100644 index 0000000..723c8c6 --- /dev/null +++ b/test/data/backbone_hrnet/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81619d62af585731846d80f7c655f0d73898fe3c518d3e7022cfba82be95adad +size 131200 diff --git a/test/data/backbone_hrnet/test_out_C2C2C2_32.npy b/test/data/backbone_hrnet/test_out_C2C2C2_32.npy new file mode 100644 index 0000000..735cb1c --- /dev/null +++ b/test/data/backbone_hrnet/test_out_C2C2C2_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:037c767eed6837fee4807e5bc8b49870840efc0843fa5b26902293434026118e +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_C3C3C3_32.npy b/test/data/backbone_hrnet/test_out_C3C3C3_32.npy new file mode 100644 index 0000000..7cf4e0d --- /dev/null +++ b/test/data/backbone_hrnet/test_out_C3C3C3_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:225764a8bc2a820312f4f99ba49939b2cb0648de0b19715222d0c5ae13093d09 +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_S3ShS3ShS3Sh_32.npy b/test/data/backbone_hrnet/test_out_S3ShS3ShS3Sh_32.npy new file mode 100644 index 0000000..855708f --- /dev/null +++ b/test/data/backbone_hrnet/test_out_S3ShS3ShS3Sh_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd42892329d787dcedd0212df7119daf938e73cd23bc9c6d1a6d98e5e0a8509a +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_S3ShT1L1G1V2V3_32.npy b/test/data/backbone_hrnet/test_out_S3ShT1L1G1V2V3_32.npy new file mode 100644 index 0000000..04f0d3a --- /dev/null +++ b/test/data/backbone_hrnet/test_out_S3ShT1L1G1V2V3_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d77eb3047fd7f29d4a858dddd362594fb92860627ee1145a1f3ee34d190730c +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_T1L1G1T1L1G1_32.npy b/test/data/backbone_hrnet/test_out_T1L1G1T1L1G1_32.npy new file mode 100644 index 0000000..f0f2b50 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_T1L1G1T1L1G1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba65face60c6a369b0deb4a78c96bad41ee0794a028f8ca860809f4b93a91c5a +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_T1L1G1T1_32.npy b/test/data/backbone_hrnet/test_out_T1L1G1T1_32.npy new file mode 100644 index 0000000..5ede367 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_T1L1G1T1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15165e8e5456ad48acf4caa3f40d31ecc6227bd6089232aa86592a70ac609223 +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_T1L1G1_32.npy b/test/data/backbone_hrnet/test_out_T1L1G1_32.npy new file mode 100644 index 0000000..19ce920 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_T1L1G1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed533fce0731991eeb9b4bb8af6017238a9478c4c2d32cf05ad0ac41000e4430 +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_T1T1T1_32.npy b/test/data/backbone_hrnet/test_out_T1T1T1_32.npy new file mode 100644 index 0000000..22aa74d --- /dev/null +++ b/test/data/backbone_hrnet/test_out_T1T1T1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7c50b51b210052d710d2b18292b3433e270e362673b99d4da772934b36d9f05 +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_T1V1V1_32.npy b/test/data/backbone_hrnet/test_out_T1V1V1_32.npy new file mode 100644 index 0000000..0efb955 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_T1V1V1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4754fa1b8edc0e28275d5fa6e6d9e80c6633af13fba0441b3c4a426f38b49940 +size 8388736 diff --git a/test/data/backbone_hrnet/test_out_T1V2V2_32.npy b/test/data/backbone_hrnet/test_out_T1V2V2_32.npy new file mode 100644 index 0000000..964faa2 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_T1V2V2_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:186eaaa5a10f0e827778048787f40bcefeacc044cb45f4538cb3f1c0c0d21b3f +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_V3V3V3_32.npy b/test/data/backbone_hrnet/test_out_V3V3V3_32.npy new file mode 100644 index 0000000..28d51aa --- /dev/null +++ b/test/data/backbone_hrnet/test_out_V3V3V3_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7f6f05929740a1939bf2bc0b05cadfbc402474dc90155a2de2dcd5186307269 +size 2097280 diff --git a/test/data/backbone_hrnet/test_out_['C2C2C2', 'C2C2C2'].npy b/test/data/backbone_hrnet/test_out_['C2C2C2', 'C2C2C2'].npy new file mode 100644 index 0000000..965a0a6 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['C2C2C2', 'C2C2C2'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c186bfdea79c9a295867ad31fad1a4354be965f2be3afb57d8ec7070075801a7 +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['C3C3C3', 'C3C3C3'].npy b/test/data/backbone_hrnet/test_out_['C3C3C3', 'C3C3C3'].npy new file mode 100644 index 0000000..7b023d5 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['C3C3C3', 'C3C3C3'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20e9bda1a0c5aef19c265442c9c5c838d08c2c45c148e49b0b5e5d4d24254a2a +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['S3ShS3ShS3Sh', 'S3ShS3ShS3Sh'].npy b/test/data/backbone_hrnet/test_out_['S3ShS3ShS3Sh', 'S3ShS3ShS3Sh'].npy new file mode 100644 index 0000000..02333c2 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['S3ShS3ShS3Sh', 'S3ShS3ShS3Sh'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc8b911516f6150296a148d8a9614a07a104a53099a8bf558cf95c8ead4df667 +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['S3ShT1L1G1V2V3', 'V2V3T1L1G1S3Sh'].npy b/test/data/backbone_hrnet/test_out_['S3ShT1L1G1V2V3', 'V2V3T1L1G1S3Sh'].npy new file mode 100644 index 0000000..9e5ac07 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['S3ShT1L1G1V2V3', 'V2V3T1L1G1S3Sh'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfa521c6198a2bb206499b7435646dec10bb6bb3784d384f5eaef32c3181cd5b +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['T1L1G1', 'T1L1G1'].npy b/test/data/backbone_hrnet/test_out_['T1L1G1', 'T1L1G1'].npy new file mode 100644 index 0000000..dd92fae --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['T1L1G1', 'T1L1G1'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1be38bfcf47f4449f9bbe30dd7b2a414a59ab421976b3cf23d31c170dd2a3603 +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['T1L1G1T1', 'T1L1G1T1'].npy b/test/data/backbone_hrnet/test_out_['T1L1G1T1', 'T1L1G1T1'].npy new file mode 100644 index 0000000..6224f42 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['T1L1G1T1', 'T1L1G1T1'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cef0202a6d9cc9f192cc4972c61fc6b5441e1d7b68985bb0a2d9a5db04c2a77 +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['T1L1G1T1L1G1', 'T1L1G1T1L1G1T1L1G1'].npy b/test/data/backbone_hrnet/test_out_['T1L1G1T1L1G1', 'T1L1G1T1L1G1T1L1G1'].npy new file mode 100644 index 0000000..ebe4120 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['T1L1G1T1L1G1', 'T1L1G1T1L1G1T1L1G1'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5254c22b4449a3e639b3046879c083dd137925ec8615f72dd89b5d6c04420276 +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['T1T1T1', 'T1T1T1'].npy b/test/data/backbone_hrnet/test_out_['T1T1T1', 'T1T1T1'].npy new file mode 100644 index 0000000..1faff6b --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['T1T1T1', 'T1T1T1'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39a6ea96e6e6e02d9bb630dc8078825444ce42b4ee7b473601bae1347582e6e7 +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['T1V2V2', 'T1V2V2'].npy b/test/data/backbone_hrnet/test_out_['T1V2V2', 'T1V2V2'].npy new file mode 100644 index 0000000..86213a3 --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['T1V2V2', 'T1V2V2'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b24558dbdb81815e6a087a70cf2d3d29137908e14c1db714c3017368153a069 +size 6291584 diff --git a/test/data/backbone_hrnet/test_out_['V3V3V3', 'V3V3V3'].npy b/test/data/backbone_hrnet/test_out_['V3V3V3', 'V3V3V3'].npy new file mode 100644 index 0000000..30b3a0d --- /dev/null +++ b/test/data/backbone_hrnet/test_out_['V3V3V3', 'V3V3V3'].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acf549ad8699caa5a580c02fad33432e7795823d2a0255e4b49b82165aba2009 +size 6291584 diff --git a/test/data/backbone_soanet/test_in.npy b/test/data/backbone_soanet/test_in.npy new file mode 100644 index 0000000..8be4ea5 --- /dev/null +++ b/test/data/backbone_soanet/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f12cddf33fed3829339d5c32369e64554a814b05b4b67cbd3a95349a17418c35 +size 262272 diff --git a/test/data/backbone_soanet/test_out_C2C2C2_downsample_0.npy b/test/data/backbone_soanet/test_out_C2C2C2_downsample_0.npy new file mode 100644 index 0000000..93a3a58 --- /dev/null +++ b/test/data/backbone_soanet/test_out_C2C2C2_downsample_0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32ed8333c62b464791563ce7189a4f0a43dfb1e4b0819d633dc35e62748b8ac9 +size 2097280 diff --git a/test/data/backbone_soanet/test_out_C2C2C2_downsample_1.npy b/test/data/backbone_soanet/test_out_C2C2C2_downsample_1.npy new file mode 100644 index 0000000..93a3a58 --- /dev/null +++ b/test/data/backbone_soanet/test_out_C2C2C2_downsample_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32ed8333c62b464791563ce7189a4f0a43dfb1e4b0819d633dc35e62748b8ac9 +size 2097280 diff --git a/test/data/backbone_soanet/test_out_C3C3C3_downsample_0.npy b/test/data/backbone_soanet/test_out_C3C3C3_downsample_0.npy new file mode 100644 index 0000000..742fafc --- /dev/null +++ b/test/data/backbone_soanet/test_out_C3C3C3_downsample_0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f328a76f1944041856890ec80edc7d0a5fe65f6efbf71b0be3ae20da9c21f3f +size 2097280 diff --git a/test/data/backbone_soanet/test_out_C3C3C3_downsample_1.npy b/test/data/backbone_soanet/test_out_C3C3C3_downsample_1.npy new file mode 100644 index 0000000..742fafc --- /dev/null +++ b/test/data/backbone_soanet/test_out_C3C3C3_downsample_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f328a76f1944041856890ec80edc7d0a5fe65f6efbf71b0be3ae20da9c21f3f +size 2097280 diff --git a/test/data/backbone_soanet/test_out_S3ShS3Sh_downsample_0.npy b/test/data/backbone_soanet/test_out_S3ShS3Sh_downsample_0.npy new file mode 100644 index 0000000..fb18b29 --- /dev/null +++ b/test/data/backbone_soanet/test_out_S3ShS3Sh_downsample_0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e2143d884bbb138337249d6a81345dbfd1b7423907d623717599b28b1a0449 +size 2097280 diff --git a/test/data/backbone_soanet/test_out_S3ShS3Sh_downsample_1.npy b/test/data/backbone_soanet/test_out_S3ShS3Sh_downsample_1.npy new file mode 100644 index 0000000..fb18b29 --- /dev/null +++ b/test/data/backbone_soanet/test_out_S3ShS3Sh_downsample_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e2143d884bbb138337249d6a81345dbfd1b7423907d623717599b28b1a0449 +size 2097280 diff --git a/test/data/backbone_soanet/test_out_T1L1G1T1L1G1_downsample_0.npy b/test/data/backbone_soanet/test_out_T1L1G1T1L1G1_downsample_0.npy new file mode 100644 index 0000000..575320e --- /dev/null +++ b/test/data/backbone_soanet/test_out_T1L1G1T1L1G1_downsample_0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3aeb46d51d4aadeb04aa729435675e2c37f4e5fe4d02f65df44ca0841b29861e +size 16777344 diff --git a/test/data/backbone_soanet/test_out_T1L1G1T1L1G1_downsample_1.npy b/test/data/backbone_soanet/test_out_T1L1G1T1L1G1_downsample_1.npy new file mode 100644 index 0000000..6d31576 --- /dev/null +++ b/test/data/backbone_soanet/test_out_T1L1G1T1L1G1_downsample_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96441e2385d73d033265d39b9effdf9c4be5a4651c5155584103a7bba1e57f7f +size 262272 diff --git a/test/data/backbone_soanet/test_out_T1L1G1T1_downsample_0.npy b/test/data/backbone_soanet/test_out_T1L1G1T1_downsample_0.npy new file mode 100644 index 0000000..d6f3296 --- /dev/null +++ b/test/data/backbone_soanet/test_out_T1L1G1T1_downsample_0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3a8b5f9384bf5b5c95c1d30630d3fdde74050c8371d6fc18f6d2107a6978535 +size 2097280 diff --git a/test/data/backbone_soanet/test_out_T1L1G1T1_downsample_1.npy b/test/data/backbone_soanet/test_out_T1L1G1T1_downsample_1.npy new file mode 100644 index 0000000..d6f3296 --- /dev/null +++ b/test/data/backbone_soanet/test_out_T1L1G1T1_downsample_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3a8b5f9384bf5b5c95c1d30630d3fdde74050c8371d6fc18f6d2107a6978535 +size 2097280 diff --git a/test/data/backbone_soanet/test_out_T1L1G1_downsample_0.npy b/test/data/backbone_soanet/test_out_T1L1G1_downsample_0.npy new file mode 100644 index 0000000..dc9b2a3 --- /dev/null +++ b/test/data/backbone_soanet/test_out_T1L1G1_downsample_0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d16da84ea810ba99b58b98118bfeaab099e2d323e9782719b259aae7debc54db +size 16777344 diff --git a/test/data/backbone_soanet/test_out_T1L1G1_downsample_1.npy b/test/data/backbone_soanet/test_out_T1L1G1_downsample_1.npy new file mode 100644 index 0000000..15063a5 --- /dev/null +++ b/test/data/backbone_soanet/test_out_T1L1G1_downsample_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cb797fbf9f382f7b642d9487fcd67f6e0346e75e52ac5f0b63362c65876dc36 +size 262272 diff --git a/test/data/backbone_soanet/test_out_T1T1T1_downsample_0.npy b/test/data/backbone_soanet/test_out_T1T1T1_downsample_0.npy new file mode 100644 index 0000000..213ee83 --- /dev/null +++ b/test/data/backbone_soanet/test_out_T1T1T1_downsample_0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:562436dffca31e73a09deb47afc81e0544b7947246961f181de0ff91bcd8d8c2 +size 16777344 diff --git a/test/data/backbone_soanet/test_out_T1T1T1_downsample_1.npy b/test/data/backbone_soanet/test_out_T1T1T1_downsample_1.npy new file mode 100644 index 0000000..2c43396 --- /dev/null +++ b/test/data/backbone_soanet/test_out_T1T1T1_downsample_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:211fb7222a7d0e64d32d708827781d31d0aaea151d5e57847478493547438764 +size 262272 diff --git a/test/data/backbone_soanet/test_out_V3V3V3_downsample_0.npy b/test/data/backbone_soanet/test_out_V3V3V3_downsample_0.npy new file mode 100644 index 0000000..8775873 --- /dev/null +++ b/test/data/backbone_soanet/test_out_V3V3V3_downsample_0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06d586e556745c0dbea50128723959f093d3917e73862992803063f0e293821f +size 2097280 diff --git a/test/data/backbone_soanet/test_out_V3V3V3_downsample_1.npy b/test/data/backbone_soanet/test_out_V3V3V3_downsample_1.npy new file mode 100644 index 0000000..8775873 --- /dev/null +++ b/test/data/backbone_soanet/test_out_V3V3V3_downsample_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06d586e556745c0dbea50128723959f093d3917e73862992803063f0e293821f +size 2097280 diff --git a/test/data/backbone_unet/test_in.npy b/test/data/backbone_unet/test_in.npy new file mode 100644 index 0000000..deeed99 --- /dev/null +++ b/test/data/backbone_unet/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8399d1498d6f2f636c048cf51effd3b3faf70aa219408b0d6a6a05c120e80a22 +size 262272 diff --git a/test/data/backbone_unet/test_out_C2C2C2_32.npy b/test/data/backbone_unet/test_out_C2C2C2_32.npy new file mode 100644 index 0000000..cb21a9d --- /dev/null +++ b/test/data/backbone_unet/test_out_C2C2C2_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c73306aaa7973d0db7dd4d7c9e452ad9bf55d2572e9b9e12cc4c94fcf451065 +size 4194432 diff --git a/test/data/backbone_unet/test_out_C3C3C3_32.npy b/test/data/backbone_unet/test_out_C3C3C3_32.npy new file mode 100644 index 0000000..ada1a45 --- /dev/null +++ b/test/data/backbone_unet/test_out_C3C3C3_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d48c2c6532c30934857135c8d9366bf33e3cd2c6a8b8f46f78fb5cc7743a481 +size 4194432 diff --git a/test/data/backbone_unet/test_out_S3ShS3ShS3Sh_32.npy b/test/data/backbone_unet/test_out_S3ShS3ShS3Sh_32.npy new file mode 100644 index 0000000..486bc17 --- /dev/null +++ b/test/data/backbone_unet/test_out_S3ShS3ShS3Sh_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bc3a2b9ce873d7005694b663abb1d43a2e978c6f7b04c9940db26465063cd10 +size 4194432 diff --git a/test/data/backbone_unet/test_out_S3ShT1L1G1V2V3_32.npy b/test/data/backbone_unet/test_out_S3ShT1L1G1V2V3_32.npy new file mode 100644 index 0000000..f4724d9 --- /dev/null +++ b/test/data/backbone_unet/test_out_S3ShT1L1G1V2V3_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffc6bb5a214e81f36fce61b4925f73773a70a600d8797d41159c80d3f563b58e +size 4194432 diff --git a/test/data/backbone_unet/test_out_T1L1G1T1L1G1_32.npy b/test/data/backbone_unet/test_out_T1L1G1T1L1G1_32.npy new file mode 100644 index 0000000..2f25519 --- /dev/null +++ b/test/data/backbone_unet/test_out_T1L1G1T1L1G1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b91645d7785121ad077c0ace9f0414d00863820898c3adf0112cdcd829a8cc +size 4194432 diff --git a/test/data/backbone_unet/test_out_T1L1G1T1_32.npy b/test/data/backbone_unet/test_out_T1L1G1T1_32.npy new file mode 100644 index 0000000..3f10f29 --- /dev/null +++ b/test/data/backbone_unet/test_out_T1L1G1T1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c498908e8a814b2b119d99e61e66569650ec113a6ce1bf6c2130e9494843e409 +size 4194432 diff --git a/test/data/backbone_unet/test_out_T1L1G1_32.npy b/test/data/backbone_unet/test_out_T1L1G1_32.npy new file mode 100644 index 0000000..6a2d0ed --- /dev/null +++ b/test/data/backbone_unet/test_out_T1L1G1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8d387efcbd10918fa50af49b76f20b200fc51aa4d070f82cddee11533ae109f +size 4194432 diff --git a/test/data/backbone_unet/test_out_T1T1T1_32.npy b/test/data/backbone_unet/test_out_T1T1T1_32.npy new file mode 100644 index 0000000..ce852e0 --- /dev/null +++ b/test/data/backbone_unet/test_out_T1T1T1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ed73b87af5b93e6bc44bacaf9a645b7797dce3d4230997243d5638f88e38c58 +size 4194432 diff --git a/test/data/backbone_unet/test_out_T1V2V2_32.npy b/test/data/backbone_unet/test_out_T1V2V2_32.npy new file mode 100644 index 0000000..afc6c88 --- /dev/null +++ b/test/data/backbone_unet/test_out_T1V2V2_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:364554b6c84f0eefa8fe5449926b5fa314b5d5d9f37006f41ac0adbd6ee23e1e +size 4194432 diff --git a/test/data/backbone_unet/test_out_V1V1T1_32.npy b/test/data/backbone_unet/test_out_V1V1T1_32.npy new file mode 100644 index 0000000..ff67935 --- /dev/null +++ b/test/data/backbone_unet/test_out_V1V1T1_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa0c58bd2f96272a5c3494b4be01987c7ec71c180026ff957f8a4bf5bb020022 +size 8388736 diff --git a/test/data/backbone_unet/test_out_V3V3V3_32.npy b/test/data/backbone_unet/test_out_V3V3V3_32.npy new file mode 100644 index 0000000..5c1c7f3 --- /dev/null +++ b/test/data/backbone_unet/test_out_V3V3V3_32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91bb52b1f1adf75e3ab8685ddc315ccd4fd7f15d614687f53a19c98c47a0afba +size 4194432 diff --git a/test/data/block/test_out_C2.npy b/test/data/block/test_out_C2.npy new file mode 100644 index 0000000..c7bb137 --- /dev/null +++ b/test/data/block/test_out_C2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c4147d48eca562a268e700240787ceed1615d25d6bbc1ba6d5395bf63154594 +size 2097280 diff --git a/test/data/block/test_out_C3.npy b/test/data/block/test_out_C3.npy new file mode 100644 index 0000000..5b282d6 --- /dev/null +++ b/test/data/block/test_out_C3.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd206dfc106dff2836417aae1a11113b55505f49a0d62d987abb08f9574c27ed +size 2097280 diff --git a/test/data/block/test_out_G0.npy b/test/data/block/test_out_G0.npy new file mode 100644 index 0000000..624b562 --- /dev/null +++ b/test/data/block/test_out_G0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:530dfda62e96266178b80f48d79543e41795c143cbe3cd2b6b32b56d5cfae358 +size 2097280 diff --git a/test/data/block/test_out_G1.npy b/test/data/block/test_out_G1.npy new file mode 100644 index 0000000..624b562 --- /dev/null +++ b/test/data/block/test_out_G1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:530dfda62e96266178b80f48d79543e41795c143cbe3cd2b6b32b56d5cfae358 +size 2097280 diff --git a/test/data/block/test_out_L0.npy b/test/data/block/test_out_L0.npy new file mode 100644 index 0000000..fad8287 --- /dev/null +++ b/test/data/block/test_out_L0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fc461be321c140e46afc18383cfdde92d9854b3f3c9f2c0f47c4d89853757f9 +size 2097280 diff --git a/test/data/block/test_out_L1.npy b/test/data/block/test_out_L1.npy new file mode 100644 index 0000000..fad8287 --- /dev/null +++ b/test/data/block/test_out_L1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fc461be321c140e46afc18383cfdde92d9854b3f3c9f2c0f47c4d89853757f9 +size 2097280 diff --git a/test/data/block/test_out_L1G1T1.npy b/test/data/block/test_out_L1G1T1.npy new file mode 100644 index 0000000..3c1e896 --- /dev/null +++ b/test/data/block/test_out_L1G1T1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3697395b602f3d73ccfd496d6e780c40c4dfcd8bde2a85fd99c3f050f1d7cd35 +size 2097280 diff --git a/test/data/block/test_out_S3.npy b/test/data/block/test_out_S3.npy new file mode 100644 index 0000000..0646226 --- /dev/null +++ b/test/data/block/test_out_S3.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4da66bbb651cf4fdfeaa13f9ef0ce9dd066465dedc1c0c78ced37a3e0642e91 +size 2097280 diff --git a/test/data/block/test_out_S3ShS3ShS3ShS3ShS3Sh.npy b/test/data/block/test_out_S3ShS3ShS3ShS3ShS3Sh.npy new file mode 100644 index 0000000..02ecd06 --- /dev/null +++ b/test/data/block/test_out_S3ShS3ShS3ShS3ShS3Sh.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af72a56384797db57cd0d87585de1ceb37b8ea198bff359c1f3e5a554cf3ea9b +size 2097280 diff --git a/test/data/block/test_out_S3ShT1L1G1.npy b/test/data/block/test_out_S3ShT1L1G1.npy new file mode 100644 index 0000000..f7f40da --- /dev/null +++ b/test/data/block/test_out_S3ShT1L1G1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c2b1b03c6e79aa58e0d5d0e989e9788e0b557b4288b21a58269cca4dc31406c +size 2097280 diff --git a/test/data/block/test_out_Sh.npy b/test/data/block/test_out_Sh.npy new file mode 100644 index 0000000..add9b8a --- /dev/null +++ b/test/data/block/test_out_Sh.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90be89d5e7aca7d4992632194e2f8e3609419fff6aed1f3e2c6437e71c9147de +size 2097280 diff --git a/test/data/block/test_out_T0.npy b/test/data/block/test_out_T0.npy new file mode 100644 index 0000000..d00bf68 --- /dev/null +++ b/test/data/block/test_out_T0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bf152568aa638e50821d6bec622b66f6a78cce70bdeafb2d9f77623be7defa7 +size 2097280 diff --git a/test/data/block/test_out_T1.npy b/test/data/block/test_out_T1.npy new file mode 100644 index 0000000..d00bf68 --- /dev/null +++ b/test/data/block/test_out_T1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bf152568aa638e50821d6bec622b66f6a78cce70bdeafb2d9f77623be7defa7 +size 2097280 diff --git a/test/data/block/test_out_T1L1G1T1.npy b/test/data/block/test_out_T1L1G1T1.npy new file mode 100644 index 0000000..85eb217 --- /dev/null +++ b/test/data/block/test_out_T1L1G1T1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b1b151d40bd8d21bb30df5a85e90524236df0da1af500bc6bb3cae908ac7e98 +size 2097280 diff --git a/test/data/block/test_out_T1L1G1V2V3.npy b/test/data/block/test_out_T1L1G1V2V3.npy new file mode 100644 index 0000000..e587ac0 --- /dev/null +++ b/test/data/block/test_out_T1L1G1V2V3.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8c6bf5aac372a6aee664ffc3b5fbb3e70d0d348c094b7bece52a986e99c1dd8 +size 2097280 diff --git a/test/data/block/test_out_T1T1T1.npy b/test/data/block/test_out_T1T1T1.npy new file mode 100644 index 0000000..e60d4ea --- /dev/null +++ b/test/data/block/test_out_T1T1T1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fd5f50b10fdb68559a9bffe7c3197b8676e7597e0bf2bbf12e9e725d02c74e0 +size 2097280 diff --git a/test/data/block/test_out_T1V2S3ShL1V2S3ShG1V2S3Sh.npy b/test/data/block/test_out_T1V2S3ShL1V2S3ShG1V2S3Sh.npy new file mode 100644 index 0000000..6a7eee7 --- /dev/null +++ b/test/data/block/test_out_T1V2S3ShL1V2S3ShG1V2S3Sh.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5623de2391a3f15389d0e7a9e79f14f602903755c9a0fcdf1d37456171256e9 +size 2097280 diff --git a/test/data/block/test_out_V2.npy b/test/data/block/test_out_V2.npy new file mode 100644 index 0000000..f9fa16a --- /dev/null +++ b/test/data/block/test_out_V2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a93c936f901704ac8b82efd3f799477ff57a6c1eed4991774ea8754620f25af +size 2097280 diff --git a/test/data/block/test_out_V2V2V2V2V2.npy b/test/data/block/test_out_V2V2V2V2V2.npy new file mode 100644 index 0000000..4dc4a69 --- /dev/null +++ b/test/data/block/test_out_V2V2V2V2V2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dfb4934995fd8bc1fccb950a897dedc351803ed470a1c7ab37fecf421cb7017 +size 2097280 diff --git a/test/data/block/test_out_V2V3S3ShS3ShT1T1T1.npy b/test/data/block/test_out_V2V3S3ShS3ShT1T1T1.npy new file mode 100644 index 0000000..7d3f364 --- /dev/null +++ b/test/data/block/test_out_V2V3S3ShS3ShT1T1T1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e0c4b468bab1825462029032e385b2956c15b72e14685a29ab4b7efbfa6cecf +size 2097280 diff --git a/test/data/block/test_out_V3.npy b/test/data/block/test_out_V3.npy new file mode 100644 index 0000000..33c55b8 --- /dev/null +++ b/test/data/block/test_out_V3.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76ec5df6ede98686e66def26f696afc77866948739c2952505f8f74e799cadbf +size 2097280 diff --git a/test/data/block/test_out_V3V3V3V3V3.npy b/test/data/block/test_out_V3V3V3V3V3.npy new file mode 100644 index 0000000..f16a795 --- /dev/null +++ b/test/data/block/test_out_V3V3V3V3V3.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27ec911d22cb95bd01bd0af4e8ab00f4a659641ce4531dc6a7fcb27fc2e600b3 +size 2097280 diff --git a/test/data/cell/test_in.npy b/test/data/cell/test_in.npy new file mode 100644 index 0000000..0fc025f --- /dev/null +++ b/test/data/cell/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52a9068789f50c7c7b882f0fa45bb231fde5457bb76f245ffa5b1156fdcb8a10 +size 524416 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..2398c1a --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0c5ea9affe22b239c9121269016c13170698d7d28ba36927dda16c1e835e6c1 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..70280ba --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84e6d645a4d0fbb162f703e676e14d74356588f43a493d1459b8d5d7546486c5 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..fb02402 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40f9b91db61279d59ccae7577cfab577cc975e52092273ef698b84f2eb2f4044 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..ac4e162 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bed1b0e5dfa3271298835ab9c23724727cae5de11965bfc393deadddb5da707f +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..a99d04c --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5403ae1bc631c3b01fdd22a359b16e1dc57874dd3b35f8b4a3546fbc7cfefc97 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..e5b473c --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42c9291f5473d8450dab204f5a0ddbb9d3b58a2962f4a2419d01f5250dfe4f26 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..7722a53 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b11bd4c512bc5efad76c4048fc5ca75f444c4b6d9f7019a076733ab51f91966 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..d02fbab --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17bee8df3e96b9123901bb7656a733fba93e5583d49482b2e887f3495739e743 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..99ddb8a --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b4564c23cd1a16ca78734a0c45123cbedc2a1e07cb4b1dd143b88d2688a7cfd +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..32e0fb9 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cab595d70d7a4201da4d4e8b7a7c219470abbb47dc3bfb243c1b260721aafe7b +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..b7f8870 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d249e737f02ecae02ca948b040490d0ca7c54d2bfc1862a52cff687c33e63e0 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..a22399d --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_global_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6219374b8ae1d15a5d9a478fe35c0b928daa5e8d23979f6a4434ecb156547b5e +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..e7a5a73 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32e80d861fbea0e33a2ba08310aa0f7d65622ebc05e78c43a724c7d73464a700 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..0461428 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe17895e723778f68ca545ec4c9998b15ec4dac395bc0b26027f53f53f4584f7 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..6299d47 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc988bc391e50d8e739d6f7f9af2f02a5df348d8928c2daa464ba13c1858d55 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..bafe556 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_local_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:349eee98fca70a2a66832ca185c4236f9745e8d4a6726ea609b1232e2d397592 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..eac0a76 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2009b5beab69b4847db0b9a35c806439171ce12b56770288a579a19d48c83b4 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..de0bb6d --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d801c2431e8d11b717077f1d56ef33f7a86320500bfc74c64e89b512bccba067 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..ad38e7a --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f80325bc6b7e83e77b9899c3c20842574d786299c53fcfe005c0ff64905171d +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..f5f8e90 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_batch2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aed60add8e4928c93b443d4a466b6c47c43c11d1b35292c646677fe54cca3995 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..be9d517 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:788514efe04d819f0c32151e902fd70603c18f3dea6220935b7f63d5445b703a +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..6f677b8 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6e8199d0529417533ef1964644a2d152833d2e5ed567dba54d08b6396fcf306 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..ff31109 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f998b0ef111d9232cd5848cc6b413004f94d46c7043a6e5be7697dae2bf844a +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..aa5e88d --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d93da960dff2c730c32dbee0d1c18c1f2a8b948b1df63d0c34f03692e7787e55 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..78ee96d --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad7a45df022d691dd43864d9af108002349ce8cb82d850568f074cd3dfe9927a +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..bb48ce4 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5645c116cfdf407d0c986b94ec43751ed80001bde418f35cbd64402f39067e32 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..5047c39 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3a5175d9e4dd54cb4603bbdffbfc2f373b3c049f9fd4776256b59d55bfb0abb +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..09c1547 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ef430d39f469e8b726b63b60970e1023850301b53a9198a900883e4e59d8179 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..a3e486e --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:066957191f95dc04ab1c4240b9ab9efac35691f872fa89cfdcb3444b05dc5e90 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..296bfdf --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38256aca68e30618ddabc849f4a5f5adc666cc8977d228c2f98f73818ddd6c0f +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..700c97a --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a8987cb9bd8854ce258003a319928a04d32e1d92e43099096db980efa5d8552 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..26fcb9b --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_global_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b59177f7bfca869b19037d576553867433766d9c33443ba22fc114827f386370 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..7d794c4 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cba0a940eadabbcf4151576b6b9bc91299edc0f797ae2b2514b99551dbc7f24d +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9928721 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01ddfe4bc62dc11ac845216b3ce13734ea9c12e46b6e04f541664b1335a01168 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..b49e800 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c219e0d479c452a94a8fbbabe1bab140f306866548e604aa9fa011ecd8bffd6 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..7662eeb --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_local_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ee6e5aa69b3b27397ba07261558cd399fa94b24c7ac1bea5c4b0ce10f0edc8d +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..828293b --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46ccce13f34602986ca1579d0381cdb472292c1c8f0d454baeb7207ece4917d7 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..24df3eb --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2a19b49762660d3ea7997d63ed8da03ba5ad7e98fa258eef871e1c18c802897 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..1a590a7 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f11063321bcffbd052b832209ec94fda0aa934dc73aed1e9caa7a4dddee8106d +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..08eeb46 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_instance2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e8d085dac19ef9123c6a66e95806098b91476dcacb5787b95ce917ba36869a9 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..71e298f --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e76ce023e3b6611fbb4c4a093c07955a176723369dfcd148020ec7f882a4c98 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..78bba31 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e4470e6a40f019615e5811c907a726ad64dc895200c5135b493ed9bd78f4e66 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..e537e13 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58b86d1823abd384354aa269de64b74ac7e93e99e17902ac5498ed573e9e0d0d +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9a78879 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_conv2d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0125feba86c161e5932ff8c72d4c1bcf397b26e7743f95d4261b8aab8e79a97c +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..35b6b80 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e024e435b6e4e5da7da44edcfca73699feed5317e362cec6278ec54eecb86d99 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..c3755e9 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b05e535d260e3f555c67e17609db3c4702f92300e571aa029284488f3cdca27 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..7eb3493 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3936f9288777e5c3265c53bdf7409e922730c430481f49b0207c7cef0921ad5c +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..826ad07 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_conv3d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d7461d9daa3ad6ae781feb2b0f06346802b1276142a25b816118cecb9554838 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9d2308f --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e55908d899b86d8fa202c84c0d31b0d24a4e6623428a9d4515803f548b446dc6 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..c00446f --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f86ca5901df4eb71df6a15fe976eb1686749f8364dd01e0acc89db88a9a0fd31 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..a5a86f8 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cee094fe7272d667206adb342b1e87c79e0ce6e588a467497c53d371bd26be3f +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..7d9e5bb --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_global_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57f615a5cde5b6bad9d9f7a50cd12cec7b8745c4f20b3ca545fb7b71f3974eb8 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..65c4bd6 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a41afe33726259394f6c5e6ace2d22c63048b6953fb60906ef8140ff1259a36 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..99c6106 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ec4871b5efc23d3b7f5a0b05476d1d589d9549f033e02e87dd84caaa4194d74 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..27b422b --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e376322e0e64ea9ecab99838a72d3a5f7e5c63309fe9905378fd2a0afa1f9b1e +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9d052d7 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_local_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:738af156375ee5aa22d2f2941b360505e6e7be1db34d730e7d3c68b807627d66 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..fe2753c --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e123c9e746674d3b2d4e1e07d6fec3f74dad304629a664e9f665c61bb0d1168 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..cf798d2 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eff2525c1d49850ed426c6f04798a0854301eb16639ae1a3aa8eb85a399945c6 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..798bfaa --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1692b5b4b8dbbcf4edc333f4f43a0ac1cdf7d9492ff814e1ed6cf9bad813cb0 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..3415201 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Cell_layer_temporal_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a940d3f9302e1f10c72b1faf82a53e29f92bc9553094f673712a6fe442e7aa75 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..3167145 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:108e5ba43ed61514b09236b2e70603bbeb62d4ae7fefc1128c3b353a9e3e1981 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..cc301da --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2a294a296e7c6de6800abee1ce676260e72bfd7bc088ba81bd9858dfe0e761e +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9475a04 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4551ebbd46e98f482533f2a8e778db5887989c54ce3dd9f4e41d14673519cfd6 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..42c2860 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98c813d5073750e8925028c748dd9e857da76476b57ecc03e23025d6beb90032 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..6edd110 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ed043a457ec18ef868fb8ba40a9b51c6900b1adaa0e7a7944697fd87933c3ef +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..8dc70e7 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a082d5b1595d4c5d29fdce72e350655bb09dbab4e9a900851eae97e52c11f16c +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..366c750 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c52a331ec763e0b6458dd64ee9657427ef96b536176e63e986d8d4d3a3deb399 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..cfc7ed0 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc9602cbaffacc0e2854ff67c00a1c73b1fa48f51e11ee12589b55921d18f459 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..79b26e0 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16236cde24a01fdcd61c941f18af0559941c68a9d90edb76bade0b0859f31fb9 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..e1c326c --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cff13364e4a850a0692d1f43e1940bdd8a3a2a4f93702b5fea8e9d8958145ba +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..cf23958 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:303fedf709cb9625871444fbbb9d2c155c401537371b5d676d0a30b77853ef91 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..e213f30 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_global_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a967db5297634baf09ce730977501b9c94e27133b61bf4314f19f5295fc2bcb2 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..7688b93 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97e54d57aa998c48d461e360d12ae6e098f76502620ee54b6f70764c6cd5d392 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..fc9a75a --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03bee6b3ea1a84bf1a291fe0975c736b6f687b713c206b909eca12f14062981e +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9cffa25 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7e41f990e591eac0f8b5a8747bb191ae04232e6d32c3ffd07f97e44f5c4489b +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..f0aa0ba --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_local_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7570260d3de8cf8c61035eeee5f8f49a75514553e7b9cd808495e7bc0e2d1f8b +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..e5fd991 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44a5276ed195a3b515bf140ed6433efb72343edb5264ceb1a55d9129c18d317a +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..dfb9820 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b7af0cca61fc485ca590a100c84264bd90cb6ad81d61652d850b53769d4b355 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..3a17aab --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d52d16d838a2f97b8bbe06a9a07872d227c347ab2eddde3d5338785e3ea7257 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..99a9250 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_batch2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7ee9152d21937fa9ea7b3bd33e365391b47f3c276a57bc585118616edf684ee +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..5c7eb36 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:109fd1d357ae45ad5a6c4fd1b1061dc80c3dbe9fbb1e76a7df7b44cb6094fd10 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..6d906ad --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ef98b749510e7e57a1557de9fd23cb0f9a489dbe7e4097810c91076e1211b04 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..b90d48f --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f6a80a7ce464f86d02f86eac83a0a47627f3cced5ca19fe017024f3271f4b76 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..b01cb64 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv2d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98f298b5bb1a8fc76373d1e07ec8ba7ca36bb6b6c746d53ca8e0fd792368398e +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..7da0cbd --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3849d72ead4a8be064e7215d8f860e856fd13436d38da867717674f8dd7fcd9 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..5bac371 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34e382f73a0c03fd2b53e6ff93cf5d3999d98328e31834b4d65a1a09eddde689 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..8a85331 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33c00b6e72d06e4cb2e41cb9e8f39cf66f248843e544d0ceee7353843e370b51 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..20b3b11 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_conv3d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a975124aafc4243569da8d309cb0dedf471ec1726962cf1f456068d7f62f1c1a +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..462ec84 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f77fc1ae0a94c93429b74d59ea8a28346d575ca33924d21b798b8020d78681a +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..be737d2 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8153d15c2aecf82270bb860e7c68e9ea0e6b03bab96e020fb5bcdb521c00d5d5 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..bc13d73 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a51bba6cb064c937962b3a164f83f19fd529453ac0ae74430d54cfd1f2eff3a8 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..f5c0800 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_global_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09fa7347d1f652384c60d187a35dd881228f893e8b8c2b5b4c81c049b328d943 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..62b90fa --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40cc58b9295dab0a8cadf183eec71c9eef32943a7c13b428821b5aa44b26aefd +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..577500e --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7ad09c0e1eb0c2be9174eccf177e6b5382699a92e3ada055211ef8aaf382c98 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..b8a3fc9 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25e4ea5eed9019aca4ddf058d2ba9542b271398f9cbf035f65e8274f4d6cb3aa +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..98abe5d --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_local_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aafc85c3ba5c5024628b939173934ad7d2dac368a570d8e60f197406f940682f +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..75a0e65 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10d78dfe72d273a1cda52c5fc62405297402b248d5a55647846ef0079ea67e75 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..2a28657 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ed7cd031f64f6b519427fa81d951ad5212bb6c42155b5f4362cd41d9a7e6e8 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..df5f352 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b625a55b728c959773cfc70780cc53e0536a49f054192b42a02b5be7270074c +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9fc5540 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_instance2d_temporal_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2be626a7f2e2efaa43553158d694f5b9e0387923d94215294546a1de527d1cab +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..4d4c293 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78c57c19cdbae7bc68b6722c9e00d26c1bf4ee338b4c2ccee9b7cc8724423e47 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..527da12 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb69894c0d01ad67f28a69d6b794a0d80a75222aa59eeb6151e91d80631a395f +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..657d155 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d917c7d75d08527d17aedb9f2eb2f3ccbe67f198c019e8756771d0c4c737bb77 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..576d53a --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv2d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a57163f84b357b3a39811be3f5c5bd54b292a2e075441eb4a08e373cd8864feb +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..7adfa64 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef50043eee7de993880325f5813e93d781f56fbb32c5e80e4ea2b281261d3795 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..74bb4a1 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07e405a69f04aea8e7a3b38950312ebea4d652def2ef94d9521b47b5c959b1f7 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..0097a27 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d04358183ce4f0fa879155d515e5128d2e1ca57c2f43aad90fb033e5c5ecfa1 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..3d3aaf1 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_conv3d_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:caab81529e4f4a18be6a4a5c2c19bb8fe9becb719ff1fa053419e8875460258a +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..829593b --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b135905bc61451c01681f4c2b95ebbec5be48a0fccb02a391022adcde93708ef +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..e540c2b --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13eb0403d87f2b85a1898295572af8d5268f6f5cadf804a39382fb8dd6af44f4 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..997535f --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3753d38c701b2a420a2210c613c9af342cc53ae10a5fe6523db525ce3e7fc6a +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..ee4f950 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_global_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e58c594fc21c59a78d78cd8ac6cd7f357296cf9bc0184cc0738725074cf24cc9 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..68e5375 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f8cf87f39ca6ae8030bc257efb58ac9166a29029784100d64714e4133c1a61c +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..ee32f42 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14808d3730fe5f22205c014208266a7ce73841b492904bc5c50b9c1dcebe6193 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9f72bcb --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3d88429d573ac45489b5094bd0d454836bd84a1701ad5c057e486649ff6b03c +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..feb136e --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_local_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76f6dc61a7cccc17bfef1b4a3b94fc855163c4a8fd1468f62e1893ef1e860906 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_False_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_False_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..3dfd493 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_False_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d665376a03ed1154eee262f12d33f2326a3a08bd95b8ada214436fb5c6b911dd +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_False_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_False_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..97bca7c --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_False_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5eddcd6b5f385d0104793f3622bc30e4b54b21e461c3efed1c71cab6dfe91eb +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_True_False_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_True_False_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..9573815 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_True_False_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:206fe8c4831cc9b6b23ba5e6b8c66abb81508b009ffb0d05ae8378b7fecd3c65 +size 2097280 diff --git a/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_True_True_[1, 1, 1]_False_False.npy b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_True_True_[1, 1, 1]_False_False.npy new file mode 100644 index 0000000..2dafb40 --- /dev/null +++ b/test/data/cell/test_out_STCNNT_Parallel_Cell_layer_temporal_conv_True_True_[1, 1, 1]_False_False.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fe677e9fc162f3b5b291bc7ceb6558275207961e1a5dcb22692eef1ba996176 +size 2097280 diff --git a/test/data/convolution/test_in.npy b/test/data/convolution/test_in.npy new file mode 100644 index 0000000..3e77e8c --- /dev/null +++ b/test/data/convolution/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3929e80cf87764897392fc7afc84281d280618577c68ad2056ee7103703b9f99 +size 98432 diff --git a/test/data/convolution/test_out_conv2d_False_batch2d_gelu.npy b/test/data/convolution/test_out_conv2d_False_batch2d_gelu.npy new file mode 100644 index 0000000..3445523 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_batch2d_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0200b120c5cffe1121bf66cfcc7248dbc629675973f577b446798c80d63eb20 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_False_batch2d_prelu.npy b/test/data/convolution/test_out_conv2d_False_batch2d_prelu.npy new file mode 100644 index 0000000..222b001 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_batch2d_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c955c9830f5ec0c4bb58b89f76f637792c7ee5b0e8eca8f2f5ad141f2cd821ee +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_False_batch2d_relu.npy b/test/data/convolution/test_out_conv2d_False_batch2d_relu.npy new file mode 100644 index 0000000..b3bbf68 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_batch2d_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30a9815b39bed56d085d373140a84eebebb5a670cd58f0762b7d29fffc93ca7e +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_False_instance2d_gelu.npy b/test/data/convolution/test_out_conv2d_False_instance2d_gelu.npy new file mode 100644 index 0000000..5b41ede --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_instance2d_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07cc726ff564354dd747cf02ca45bb2967fa3b54bee2167cce0c65f2f93350d0 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_False_instance2d_prelu.npy b/test/data/convolution/test_out_conv2d_False_instance2d_prelu.npy new file mode 100644 index 0000000..0b497a4 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_instance2d_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e10d0be8d237b7a1b8f0018fe4026db2e6545747c64c8e197fa8874206dcfce +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_False_instance2d_relu.npy b/test/data/convolution/test_out_conv2d_False_instance2d_relu.npy new file mode 100644 index 0000000..c5e1cdb --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_instance2d_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17225dae7b871bd6662bfcdb87740081d16d88e3f16b2048a5158ffda41969d6 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_False_layer_gelu.npy b/test/data/convolution/test_out_conv2d_False_layer_gelu.npy new file mode 100644 index 0000000..ed208ad --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_layer_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34aee1a817564ff6a4204f892aa3be5c387b1176c74f7ba9176a661b59901d5e +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_False_layer_prelu.npy b/test/data/convolution/test_out_conv2d_False_layer_prelu.npy new file mode 100644 index 0000000..fa5928c --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_layer_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d4a2d28e258e3c5621e6438bbdaeeb1e36688ee60eefac91563a71ef4946099 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_False_layer_relu.npy b/test/data/convolution/test_out_conv2d_False_layer_relu.npy new file mode 100644 index 0000000..83d652c --- /dev/null +++ b/test/data/convolution/test_out_conv2d_False_layer_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:611a4f793cfa4ca155727d97b921a09dd2083528513778ea27f3238ee67c9725 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_batch2d_gelu.npy b/test/data/convolution/test_out_conv2d_True_batch2d_gelu.npy new file mode 100644 index 0000000..588abf9 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_batch2d_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48a466a85dfa1a73ef9fec64b54cb46d4a4c365848838261d985ca4c778032a4 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_batch2d_prelu.npy b/test/data/convolution/test_out_conv2d_True_batch2d_prelu.npy new file mode 100644 index 0000000..18b07b9 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_batch2d_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9a3f7e2986be9c4289407eed5ff6fb624767ef88812a1e4d5e85317f7ddc003 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_batch2d_relu.npy b/test/data/convolution/test_out_conv2d_True_batch2d_relu.npy new file mode 100644 index 0000000..de68bce --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_batch2d_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32bec540f09870361edf0dcb2731d7a1f75c5894152d445fb35e25c9929bc5c3 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_instance2d_gelu.npy b/test/data/convolution/test_out_conv2d_True_instance2d_gelu.npy new file mode 100644 index 0000000..638bffc --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_instance2d_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e1d9dbdab530fd44bf33904e86e9e9fe64b03f9fa2aef2a2142bca3af1b01e5 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_instance2d_prelu.npy b/test/data/convolution/test_out_conv2d_True_instance2d_prelu.npy new file mode 100644 index 0000000..2ac14d3 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_instance2d_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd534318d42dd2faf167fb796ae2b2a8d546ecd5c74eb804dd3278c208fa477d +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_instance2d_relu.npy b/test/data/convolution/test_out_conv2d_True_instance2d_relu.npy new file mode 100644 index 0000000..c33aa28 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_instance2d_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7e7b301cd2ae4a667f26018f850ac2e08b228c0df4b153c453e134841a5dbdc +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_layer_gelu.npy b/test/data/convolution/test_out_conv2d_True_layer_gelu.npy new file mode 100644 index 0000000..e979d8a --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_layer_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7a6d093a36f72bd9b1b452881647e06e6d917866559e151f9de732868c433b7 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_layer_prelu.npy b/test/data/convolution/test_out_conv2d_True_layer_prelu.npy new file mode 100644 index 0000000..68c0d76 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_layer_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22a55c6677ab12ffcce35a949fff1ee2f872fc8cea49e0af3885ba7086600dc3 +size 1048704 diff --git a/test/data/convolution/test_out_conv2d_True_layer_relu.npy b/test/data/convolution/test_out_conv2d_True_layer_relu.npy new file mode 100644 index 0000000..6a7e9f8 --- /dev/null +++ b/test/data/convolution/test_out_conv2d_True_layer_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78827fad8f9cfad2d20a821bdeb5f654305249f609844eba7493c8c7a7da3343 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_batch2d_gelu.npy b/test/data/convolution/test_out_conv3d_False_batch2d_gelu.npy new file mode 100644 index 0000000..d5e3b27 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_batch2d_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:454ef1b4f29eafae5ac18fd7a9b59c579f92fbc5193536ddcc214c545b862e3c +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_batch2d_prelu.npy b/test/data/convolution/test_out_conv3d_False_batch2d_prelu.npy new file mode 100644 index 0000000..f72483a --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_batch2d_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c90e2e6a9783c23a4c2f44dace969ea2144401a78109ffbc929e226add70699 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_batch2d_relu.npy b/test/data/convolution/test_out_conv3d_False_batch2d_relu.npy new file mode 100644 index 0000000..f6e5417 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_batch2d_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bdfa2e029ce0a81a2ee2276f9ebfe7ba1aa3e9057ecabadc5b5e08689413519 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_instance2d_gelu.npy b/test/data/convolution/test_out_conv3d_False_instance2d_gelu.npy new file mode 100644 index 0000000..5d213ae --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_instance2d_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa546f77314dd526b7ac7e3da2f6ae0cbf2babc0f51c7aa99b0721cedea8955d +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_instance2d_prelu.npy b/test/data/convolution/test_out_conv3d_False_instance2d_prelu.npy new file mode 100644 index 0000000..5ad699a --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_instance2d_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16941f9f623a20866c9be5608c7015f40574a11ed54b36ac75cad29923dceb89 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_instance2d_relu.npy b/test/data/convolution/test_out_conv3d_False_instance2d_relu.npy new file mode 100644 index 0000000..e114528 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_instance2d_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60bef53653e3c71463dbf361bf64128458a70a5dc91fbe9aa07868c8535480d7 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_layer_gelu.npy b/test/data/convolution/test_out_conv3d_False_layer_gelu.npy new file mode 100644 index 0000000..f94439f --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_layer_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fb9eadc92414065af6c6ce7eb1932d86368d1a6e948a7c8bb1314f848f233bc +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_layer_prelu.npy b/test/data/convolution/test_out_conv3d_False_layer_prelu.npy new file mode 100644 index 0000000..3cb59fb --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_layer_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a6241ed1ba68e6f0778ab2152190c69f5fa6140f91cdd83acf900452c3a3763 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_False_layer_relu.npy b/test/data/convolution/test_out_conv3d_False_layer_relu.npy new file mode 100644 index 0000000..52d8876 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_False_layer_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28a46186fb3540577acdb2e3a358e15eb4ae4eae79157b1f404c856f80190c64 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_batch2d_gelu.npy b/test/data/convolution/test_out_conv3d_True_batch2d_gelu.npy new file mode 100644 index 0000000..b820e22 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_batch2d_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:465768cee0e268dfb67cb747e9f0a4eee0b2aa42f0585da66a642f23c92ca264 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_batch2d_prelu.npy b/test/data/convolution/test_out_conv3d_True_batch2d_prelu.npy new file mode 100644 index 0000000..127c064 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_batch2d_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61e9784eb71666afd0ea402b8d61ea4291b7e1c6b183f43e8ab30e0e62278ed3 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_batch2d_relu.npy b/test/data/convolution/test_out_conv3d_True_batch2d_relu.npy new file mode 100644 index 0000000..e4e1795 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_batch2d_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71c01216a9a77fcb84071601c9c7b883b9ac5333db3fc269bf8d07592b0da073 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_instance2d_gelu.npy b/test/data/convolution/test_out_conv3d_True_instance2d_gelu.npy new file mode 100644 index 0000000..baa0700 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_instance2d_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6216a216a8b760f65ffb3edf075d81846e3200d463fd9f9d548f20f94a6c044 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_instance2d_prelu.npy b/test/data/convolution/test_out_conv3d_True_instance2d_prelu.npy new file mode 100644 index 0000000..063cccf --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_instance2d_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0bb915c2139cbe9ecffe3e911d8f67e7e23f9c9bdcf3afea49b946a8d78642b +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_instance2d_relu.npy b/test/data/convolution/test_out_conv3d_True_instance2d_relu.npy new file mode 100644 index 0000000..ee17535 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_instance2d_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d832c9d804a36c6cbedd0914a00401ef15c6e7b121aae95ab80e297ec475f692 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_layer_gelu.npy b/test/data/convolution/test_out_conv3d_True_layer_gelu.npy new file mode 100644 index 0000000..12d82f8 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_layer_gelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:708cc7f6bc09fda19b5fa11cb74ea54e7135482707031dafb8c7d7ff40af2b8c +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_layer_prelu.npy b/test/data/convolution/test_out_conv3d_True_layer_prelu.npy new file mode 100644 index 0000000..2a9db17 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_layer_prelu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c9fbbfeb56c4ed10516ea6b2019d33b3f817cbcef167e6798ec7f1c44c0bb65 +size 1048704 diff --git a/test/data/convolution/test_out_conv3d_True_layer_relu.npy b/test/data/convolution/test_out_conv3d_True_layer_relu.npy new file mode 100644 index 0000000..6041186 --- /dev/null +++ b/test/data/convolution/test_out_conv3d_True_layer_relu.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c68299b1757058371b2240553e2c3c64481ba4abc790d857f917e8d4d5fe83ba +size 1048704 diff --git a/test/data/global_3d/test_in.npy b/test/data/global_3d/test_in.npy new file mode 100644 index 0000000..e8bb7c8 --- /dev/null +++ b/test/data/global_3d/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6c2d27dcd2b223265f537b66dd41272efce73b4fb045357dc3571a3bdc1041d +size 524416 diff --git a/test/data/global_3d/test_in2.npy b/test/data/global_3d/test_in2.npy new file mode 100644 index 0000000..f7ee729 --- /dev/null +++ b/test/data/global_3d/test_in2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd8f16340481868d4eb5ada12f17dbd294761a3a2234bbd6ea14778d88deeb4b +size 2097280 diff --git a/test/data/global_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy b/test/data/global_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..a45d2ee --- /dev/null +++ b/test/data/global_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25f1761b20e39f651b0a46795b5d30cd017d93441d66c265f4f7a4fde77d3007 +size 2097280 diff --git a/test/data/global_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy b/test/data/global_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..b505009 --- /dev/null +++ b/test/data/global_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:daebd0157851ce4cd8bbb3ed84c3914e149dd8f1e11f393b3c4cc3c602c6b0b8 +size 2097280 diff --git a/test/data/global_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy b/test/data/global_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..8a1d0d1 --- /dev/null +++ b/test/data/global_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22e4af0d90f5f1032f0a3edf484a22a99779dd17ce07a43eeb5956ee837fba27 +size 2097280 diff --git a/test/data/global_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy b/test/data/global_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..0562406 --- /dev/null +++ b/test/data/global_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa03ad71c5cdb02cf26a30806f53ab06c893692805b771a9a37b890980c14616 +size 2097280 diff --git a/test/data/global_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy b/test/data/global_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..5989524 --- /dev/null +++ b/test/data/global_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:888e19ad0f5bfd7b60b7ff1406eacb1e933fe7e4f1aca2e164be8e6bb0f033d2 +size 2097280 diff --git a/test/data/global_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy b/test/data/global_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..a014912 --- /dev/null +++ b/test/data/global_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bf73a94527fa65f835ed08d39ed799e8535e175d2d4d4e4347de3ae81dc0655 +size 2097280 diff --git a/test/data/global_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy b/test/data/global_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..a9ead8b --- /dev/null +++ b/test/data/global_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:997bc4e11f0bb3dc141b9e741a97a1dc6fa56ead38b880559f8b01c041cdaaaf +size 2097280 diff --git a/test/data/global_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy b/test/data/global_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..7840af1 --- /dev/null +++ b/test/data/global_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59c908d5e44f3dbf8365115c893fde210891fcfb2b2a63c92e6048c3e03e4722 +size 2097280 diff --git a/test/data/global_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy b/test/data/global_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..8a3247f --- /dev/null +++ b/test/data/global_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cdfbab3f9073869be2946b89953f1a394936508ae00fc0af1442d14eec6e1c70 +size 2097280 diff --git a/test/data/global_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy b/test/data/global_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..ded3d34 --- /dev/null +++ b/test/data/global_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dc92c531623b33c1a835089dd7e346046ee25139c4dcb010b00ed7796f6f2cc +size 2097280 diff --git a/test/data/global_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy b/test/data/global_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..b3069c7 --- /dev/null +++ b/test/data/global_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15b73d32b8483a353af6cf6ede7ecd2eb100f1d5da3e738fb43df6908943f954 +size 2097280 diff --git a/test/data/global_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy b/test/data/global_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..8383d2b --- /dev/null +++ b/test/data/global_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8c70e9dcf51b870965167ffc24a4378e17bbd26f726b6b81b11d15caa816d40 +size 2097280 diff --git a/test/data/global_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy b/test/data/global_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..d41fae8 --- /dev/null +++ b/test/data/global_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:661e2ad5bdc3403072b71a1a398cdd4603c3ab0a9bec981da93b06ba31389064 +size 2097280 diff --git a/test/data/global_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy b/test/data/global_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..e0f88a2 --- /dev/null +++ b/test/data/global_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d75fd3505a1453be24160a880daabd6fb0af2735b338e5cb1f67092d3715b840 +size 2097280 diff --git a/test/data/global_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy b/test/data/global_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..2da9c39 --- /dev/null +++ b/test/data/global_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a633215b72fbe6abeb28baa0a92eecefb540b8435d6cf661c99e6d8130e1e60c +size 2097280 diff --git a/test/data/global_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy b/test/data/global_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..4ecd53d --- /dev/null +++ b/test/data/global_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80ddec57efffb15f53e4025fbdb15ec4d9dc9318f63d662938b60259536a34ea +size 2097280 diff --git a/test/data/local_3d/test_in.npy b/test/data/local_3d/test_in.npy new file mode 100644 index 0000000..55d6563 --- /dev/null +++ b/test/data/local_3d/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e20b149166d1d80e7809077a11185a41ded12a8675e5a3d986ea7dd9672aa1c2 +size 524416 diff --git a/test/data/local_3d/test_in2.npy b/test/data/local_3d/test_in2.npy new file mode 100644 index 0000000..0a7681e --- /dev/null +++ b/test/data/local_3d/test_in2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:445e09d7ca3411ff5911f832175f54137473c8d06ccf62eeda008128fd19d451 +size 2097280 diff --git a/test/data/local_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy b/test/data/local_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..e378d58 --- /dev/null +++ b/test/data/local_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c104134721101d55545845aa8d7ec1adade5d68f785dcaadc0c6ee1560b225a1 +size 2097280 diff --git a/test/data/local_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy b/test/data/local_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..5bd13a1 --- /dev/null +++ b/test/data/local_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea2c08a09154f7f23e7e410ce93ddda6e7801d7c689db8660e18f709f49a37ee +size 2097280 diff --git a/test/data/local_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy b/test/data/local_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..6166371 --- /dev/null +++ b/test/data/local_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efc440eb90d0ce1a07ebc2d945d868c848408d8bcf051bded6d2c911005c839b +size 2097280 diff --git a/test/data/local_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy b/test/data/local_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..d10ee11 --- /dev/null +++ b/test/data/local_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6c41d9fb2a2f43b37b475f1584d0a1721660d02cd7884b2f9e2f1eabed276a0 +size 2097280 diff --git a/test/data/local_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy b/test/data/local_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..926a54b --- /dev/null +++ b/test/data/local_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf348e47bc016f67519bf9d5bb81acd5e1259cc2dd154bf97643e0b2f05261a9 +size 2097280 diff --git a/test/data/local_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy b/test/data/local_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..8343224 --- /dev/null +++ b/test/data/local_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37bcca44fffcfc84a3f47ea6dbdb11ac7b17f3f815cdadfd9b8ecf28f3b53a0a +size 2097280 diff --git a/test/data/local_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy b/test/data/local_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..7af8c29 --- /dev/null +++ b/test/data/local_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5670740b23847394ca6baaf7370979cc90d84a992fc21f750b8f3dc72178ee3c +size 2097280 diff --git a/test/data/local_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy b/test/data/local_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..e01d3c3 --- /dev/null +++ b/test/data/local_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88f605950a4b6ece4d7bc39f32d40898ca51316dc780798d794c4bf31e46559c +size 2097280 diff --git a/test/data/local_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy b/test/data/local_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..81a3581 --- /dev/null +++ b/test/data/local_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e16a0b1094e890b02651b9911c367838e94c835672b4eff6bfe4e863fd4a11f2 +size 2097280 diff --git a/test/data/local_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy b/test/data/local_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..115dc4e --- /dev/null +++ b/test/data/local_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c576bfec0f1972d61430225dbd366881159922a83893a39156f8204e2ec20291 +size 2097280 diff --git a/test/data/local_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy b/test/data/local_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..36fb3ac --- /dev/null +++ b/test/data/local_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fa20f51cbe0c5aac48fd2d53db29c289e0ab243d5e0a5b57c68a2418bb07edd +size 2097280 diff --git a/test/data/local_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy b/test/data/local_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..fadedbc --- /dev/null +++ b/test/data/local_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58c3a6dbb95365480f69526f34470a7ac189809c219a149aca5ed9b8d1a66fea +size 2097280 diff --git a/test/data/local_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy b/test/data/local_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..a794551 --- /dev/null +++ b/test/data/local_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5c491230344ba15a5f3e7432bc2136804636108ee8be61952402a560f3697c6 +size 2097280 diff --git a/test/data/local_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy b/test/data/local_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..37a078e --- /dev/null +++ b/test/data/local_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6eb5b8fee73ef0c0286a2a411f96abb22e580f5201ad3d91fe3d802d48bd1e9b +size 2097280 diff --git a/test/data/local_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy b/test/data/local_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..349fad2 --- /dev/null +++ b/test/data/local_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1361460cf26086920237f431d422cc42e7c06fbe7590a034d1fa7e66311265f7 +size 2097280 diff --git a/test/data/local_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy b/test/data/local_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..ad761e0 --- /dev/null +++ b/test/data/local_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bd2eb3de7708cf9e1a40bfe45b8df7ef6fb640413961366247048157844d0ba +size 2097280 diff --git a/test/data/loss/data_imag.npy b/test/data/loss/data_imag.npy new file mode 100644 index 0000000..8e1fa9d --- /dev/null +++ b/test/data/loss/data_imag.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02a6455b86db22feb553366a70310c9af7655d7541e5b9ec1b9991ecbd55c2ce +size 8388688 diff --git a/test/data/loss/data_real.npy b/test/data/loss/data_real.npy new file mode 100644 index 0000000..2bfc178 --- /dev/null +++ b/test/data/loss/data_real.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18926b4e326fe1c8970f6e761b15f02c33c72c1e3310b65061810942b489d16d +size 8388688 diff --git a/test/data/snr_unit_data/gmap.npy b/test/data/snr_unit_data/gmap.npy new file mode 100644 index 0000000..a890d03 --- /dev/null +++ b/test/data/snr_unit_data/gmap.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:242dc516f351ec154a5c3411ef13b7ca297e08075b8a052824511a10f27a225a +size 5898320 diff --git a/test/data/snr_unit_data/im_low_matrix.npy b/test/data/snr_unit_data/im_low_matrix.npy new file mode 100644 index 0000000..dba3f4d --- /dev/null +++ b/test/data/snr_unit_data/im_low_matrix.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d3e1467cbe48be288d96b816b60838c7203a6a8c22a9b34ca460fa10f29bf26 +size 3745568 diff --git a/test/data/snr_unit_data/mask.npy b/test/data/snr_unit_data/mask.npy new file mode 100644 index 0000000..b6b0b58 --- /dev/null +++ b/test/data/snr_unit_data/mask.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0df17b0322dd0a9aae35872c0ca5bd2ea2c3ae4593771615898eb5d5d3a7f8e2 +size 147536 diff --git a/test/data/snr_unit_data/mask_low_matrix.npy b/test/data/snr_unit_data/mask_low_matrix.npy new file mode 100644 index 0000000..e2f571d --- /dev/null +++ b/test/data/snr_unit_data/mask_low_matrix.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8729f1d32a0e52ffc3adae0b8731ac0ba8b31cf7d7cc16659f2de7cfa4b47f31 +size 93764 diff --git a/test/data/snr_unit_data/unwrappedIm_imag.npy b/test/data/snr_unit_data/unwrappedIm_imag.npy new file mode 100644 index 0000000..9b1c783 --- /dev/null +++ b/test/data/snr_unit_data/unwrappedIm_imag.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b53c288b7c4cf9dbcfa9920fc7b2932d923e2aa7542334ec85f81d095eb91dc +size 5898320 diff --git a/test/data/snr_unit_data/unwrappedIm_real.npy b/test/data/snr_unit_data/unwrappedIm_real.npy new file mode 100644 index 0000000..7a0e929 --- /dev/null +++ b/test/data/snr_unit_data/unwrappedIm_real.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6ec7bd1a4556231949e916628521447b6ea382ab0de87876b639e8a3914a591 +size 5898320 diff --git a/test/data/spatial_global/test_in.npy b/test/data/spatial_global/test_in.npy new file mode 100644 index 0000000..a1a5c3d --- /dev/null +++ b/test/data/spatial_global/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e0bd269d9e1467f1695c76426d22f71cf03f656f3beb980686359724ad741d3 +size 524416 diff --git a/test/data/spatial_global/test_in2.npy b/test/data/spatial_global/test_in2.npy new file mode 100644 index 0000000..b4d8fe8 --- /dev/null +++ b/test/data/spatial_global/test_in2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:883ce30c765d33bfa18162e5ded7832a56e3640e02666a20ff4ca84d9aeb7580 +size 4194432 diff --git a/test/data/spatial_global/test_out_conv_False_False_False_False_[1, 1].npy b/test/data/spatial_global/test_out_conv_False_False_False_False_[1, 1].npy new file mode 100644 index 0000000..3b3817c --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:413115d899b8e1364df8cc02cf1369fbd9ab9c7fdbd0dcc2146f9fecb62109a2 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_False_False_False_[2, 2].npy b/test/data/spatial_global/test_out_conv_False_False_False_False_[2, 2].npy new file mode 100644 index 0000000..205fbb0 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fccbfbb60bc1bf404ab21dd1bc85b53d57e6a094114bf0e853276c626915b1bc +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_False_False_True_[1, 1].npy b/test/data/spatial_global/test_out_conv_False_False_False_True_[1, 1].npy new file mode 100644 index 0000000..298ed80 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:673065cc4763369eebad9c5c35ada176ebb64306af8e92c62b8e83bde216f258 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_False_False_True_[2, 2].npy b/test/data/spatial_global/test_out_conv_False_False_False_True_[2, 2].npy new file mode 100644 index 0000000..3d9380f --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce694220114194877718294004fd2928408edfd963527d5c65cf496f4ab30d83 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_False_True_False_[1, 1].npy b/test/data/spatial_global/test_out_conv_False_False_True_False_[1, 1].npy new file mode 100644 index 0000000..e07dc8c --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f290ead7a707fe13a9b1aca755f6e1fb9c31aa3dcbd5f9d66417613bdd9d476f +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_False_True_False_[2, 2].npy b/test/data/spatial_global/test_out_conv_False_False_True_False_[2, 2].npy new file mode 100644 index 0000000..faa8afc --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28c993e4d143134fdfe09b42141560e6f8acc8f71da09a47df85ea257f6ba551 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_False_True_True_[1, 1].npy b/test/data/spatial_global/test_out_conv_False_False_True_True_[1, 1].npy new file mode 100644 index 0000000..b4cd2db --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06d5d99bf5f7b4d527c6ffab51b0f91df9d1a9586c3f0181314141f1bad5920d +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_False_True_True_[2, 2].npy b/test/data/spatial_global/test_out_conv_False_False_True_True_[2, 2].npy new file mode 100644 index 0000000..c81fe91 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ed28c5072c5fe7a6fabb3bfa351c3188abc7edb067eccee0f0c3afdc549a215 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_True_False_False_[1, 1].npy b/test/data/spatial_global/test_out_conv_False_True_False_False_[1, 1].npy new file mode 100644 index 0000000..4c528ad --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08a768890204ca4206f2a415a6e674bc581f51671ae1a1e6d3aceebef9b3ff29 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_True_False_False_[2, 2].npy b/test/data/spatial_global/test_out_conv_False_True_False_False_[2, 2].npy new file mode 100644 index 0000000..1dd2949 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6845540aba4165dadd9a0c1c0b18c12c0dbd26661af7ef4340191c68323ab7b8 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_True_False_True_[1, 1].npy b/test/data/spatial_global/test_out_conv_False_True_False_True_[1, 1].npy new file mode 100644 index 0000000..79eca6f --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eda751b92bebf16b7445f2e9f45a4913e156eb9d40ba98346af029726565dc90 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_True_False_True_[2, 2].npy b/test/data/spatial_global/test_out_conv_False_True_False_True_[2, 2].npy new file mode 100644 index 0000000..b68cbfa --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2479ed67ac04f13c1a700d6e60f824c3b6701e3e3e80855dfa607e7fe99ebc4b +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_True_True_False_[1, 1].npy b/test/data/spatial_global/test_out_conv_False_True_True_False_[1, 1].npy new file mode 100644 index 0000000..d770522 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b750d213707fab9c22c6a6861f3c47d6aa61d7f2b4169709233f6767c1b1847 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_True_True_False_[2, 2].npy b/test/data/spatial_global/test_out_conv_False_True_True_False_[2, 2].npy new file mode 100644 index 0000000..e03b7ec --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c712b94ec428fb3b93f2842c458e490846ab150a8416c791dce7433827e7e144 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_True_True_True_[1, 1].npy b/test/data/spatial_global/test_out_conv_False_True_True_True_[1, 1].npy new file mode 100644 index 0000000..4041dc1 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:921d1254ef2678202d41e89367c36c850b172d222e8c0ac92c1c4c18307df885 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_False_True_True_True_[2, 2].npy b/test/data/spatial_global/test_out_conv_False_True_True_True_[2, 2].npy new file mode 100644 index 0000000..cbb6183 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_False_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ecd80a9b8752c3a459f0d2a877cd83fa05f31c47510f3856fbb361a6094ceac +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_False_False_False_[1, 1].npy b/test/data/spatial_global/test_out_conv_True_False_False_False_[1, 1].npy new file mode 100644 index 0000000..f24774e --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:949961516ab6ba12ee2c9413aa5e86d938ee254056d98adc3aa7e55b61529207 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_False_False_False_[2, 2].npy b/test/data/spatial_global/test_out_conv_True_False_False_False_[2, 2].npy new file mode 100644 index 0000000..2d6310d --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54c665c0946adf4ffd4ab72722b8afe259b6fc14d051c1a6815affc6aaa7d1f4 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_False_False_True_[1, 1].npy b/test/data/spatial_global/test_out_conv_True_False_False_True_[1, 1].npy new file mode 100644 index 0000000..5400729 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3bab0cef0f305e9cb410362e6bb7df5b1d915610853ccc6689722e395ecec0e +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_False_False_True_[2, 2].npy b/test/data/spatial_global/test_out_conv_True_False_False_True_[2, 2].npy new file mode 100644 index 0000000..d3f2379 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce4069b4e7958d21a8590887583ba4579ca117d7b218a111754629bd9afd9da4 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_False_True_False_[1, 1].npy b/test/data/spatial_global/test_out_conv_True_False_True_False_[1, 1].npy new file mode 100644 index 0000000..3781d75 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dfc0b6b3b71413f182d7ef4f5d999dd1ef19e318055af1b61bff6a34b097fc3d +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_False_True_False_[2, 2].npy b/test/data/spatial_global/test_out_conv_True_False_True_False_[2, 2].npy new file mode 100644 index 0000000..6c50a05 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e058bc122ae0c8fd4be20dbda40613d5ff4be7c8c89bba65a387b49642c6c2c5 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_False_True_True_[1, 1].npy b/test/data/spatial_global/test_out_conv_True_False_True_True_[1, 1].npy new file mode 100644 index 0000000..4e4180c --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3381d77df2d699e9b2d9083f356fde1edfdf317ea9ab1b642cd42e172d5ea8a0 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_False_True_True_[2, 2].npy b/test/data/spatial_global/test_out_conv_True_False_True_True_[2, 2].npy new file mode 100644 index 0000000..03a543a --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc6c5c2bcef91c0cab1c1ab2d3bf835b928b901f2e916c85a43e29bcd82733f +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_True_False_False_[1, 1].npy b/test/data/spatial_global/test_out_conv_True_True_False_False_[1, 1].npy new file mode 100644 index 0000000..6cce159 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c9cab03d8df15d6f6e4a7bc08e544b0812090a3990ce07cf836066faf4855b1 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_True_False_False_[2, 2].npy b/test/data/spatial_global/test_out_conv_True_True_False_False_[2, 2].npy new file mode 100644 index 0000000..834540a --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68c24c3f8974d64c4035824624035230db062fb1c7c30d2eee71cc3d704ed8b7 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_True_False_True_[1, 1].npy b/test/data/spatial_global/test_out_conv_True_True_False_True_[1, 1].npy new file mode 100644 index 0000000..4955abd --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b73292c46b03473dfa5d2b180f07a2275061d1a79fb004f6dbc751217c07933 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_True_False_True_[2, 2].npy b/test/data/spatial_global/test_out_conv_True_True_False_True_[2, 2].npy new file mode 100644 index 0000000..0866beb --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69ca61c689ba18f0d148945f78e9bb75c627445e1932e5f5d4f9b98ee1eb094f +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_True_True_False_[1, 1].npy b/test/data/spatial_global/test_out_conv_True_True_True_False_[1, 1].npy new file mode 100644 index 0000000..cce94d7 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d6df2ad858d6bdad3b81b66c25a23095a0a9d09c883e0bbcc01feaa553cdfeb +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_True_True_False_[2, 2].npy b/test/data/spatial_global/test_out_conv_True_True_True_False_[2, 2].npy new file mode 100644 index 0000000..5ac196e --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71062cb3205dad3acca9be95ad246cc7f8a2d837fc28b92f223ea99d49393b81 +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_True_True_True_[1, 1].npy b/test/data/spatial_global/test_out_conv_True_True_True_True_[1, 1].npy new file mode 100644 index 0000000..f6741f5 --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee9fe269213f1c755bf8801bda67160c9dc48019ef3db715946e533a0262ceda +size 1048704 diff --git a/test/data/spatial_global/test_out_conv_True_True_True_True_[2, 2].npy b/test/data/spatial_global/test_out_conv_True_True_True_True_[2, 2].npy new file mode 100644 index 0000000..a3a2fdb --- /dev/null +++ b/test/data/spatial_global/test_out_conv_True_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:431cb237831cdc0ff74d2429679e82e8fe1dd1e3d9be443575d913c2b8ef799d +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_False_False_False_[1, 1].npy b/test/data/spatial_global/test_out_lin_False_False_False_False_[1, 1].npy new file mode 100644 index 0000000..491767b --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b138e8032e57430f4d47671524787212a455c75cbd7bbed7348f3a42e845d6c6 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_False_False_False_[2, 2].npy b/test/data/spatial_global/test_out_lin_False_False_False_False_[2, 2].npy new file mode 100644 index 0000000..afc70a0 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74e3789f94a6c482642dfc39069fb54c9504b26d57143ed53c1161c0c909be92 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_False_False_True_[1, 1].npy b/test/data/spatial_global/test_out_lin_False_False_False_True_[1, 1].npy new file mode 100644 index 0000000..ba5bc42 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d69b38f14a2d67535247d5e9f55e2a4c80f565c480e445354a2186d855eb9bf7 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_False_False_True_[2, 2].npy b/test/data/spatial_global/test_out_lin_False_False_False_True_[2, 2].npy new file mode 100644 index 0000000..4b0259c --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3cede1ce837659e593f075411ccbb9670737959c2cde04fa971ae29b8b07ed5 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_False_True_False_[1, 1].npy b/test/data/spatial_global/test_out_lin_False_False_True_False_[1, 1].npy new file mode 100644 index 0000000..0735912 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55e16ba54a225a4e925bf7ec64541ca1a69d602c84718516c35f0ca9a88b341a +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_False_True_False_[2, 2].npy b/test/data/spatial_global/test_out_lin_False_False_True_False_[2, 2].npy new file mode 100644 index 0000000..2f1bea8 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb3b645ad9a8093b8854086797cceb710d50911658256dd5d400a8fb170a9a36 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_False_True_True_[1, 1].npy b/test/data/spatial_global/test_out_lin_False_False_True_True_[1, 1].npy new file mode 100644 index 0000000..e51ff93 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fadf140deabafb5bc182b119f57e19fc1ee7c2eea6e52eaa1fb10d3663dc1c4 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_False_True_True_[2, 2].npy b/test/data/spatial_global/test_out_lin_False_False_True_True_[2, 2].npy new file mode 100644 index 0000000..cd4bb13 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73a44d14e0758ed8e06f43682e6b93b236025ecd38a7213dd273df20824c0308 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_True_False_False_[1, 1].npy b/test/data/spatial_global/test_out_lin_False_True_False_False_[1, 1].npy new file mode 100644 index 0000000..0c06d06 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a97c4d7dac4d24e22d3ba9560d0d7a4aa5a0bfe964ea19d707de16482f2e508a +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_True_False_False_[2, 2].npy b/test/data/spatial_global/test_out_lin_False_True_False_False_[2, 2].npy new file mode 100644 index 0000000..978484e --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01b1d5a4339e0680816de7c41b3a76a35d8227b8047a0c7f99b96cd1787b678a +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_True_False_True_[1, 1].npy b/test/data/spatial_global/test_out_lin_False_True_False_True_[1, 1].npy new file mode 100644 index 0000000..13196fa --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01ef895728dba65083fbb10026c3bf8aeae05bffd7467612d87ff5450d4520d4 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_True_False_True_[2, 2].npy b/test/data/spatial_global/test_out_lin_False_True_False_True_[2, 2].npy new file mode 100644 index 0000000..e50b9a2 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9536cabcc8eabb41866d1f014de208472794b8d563a3799ca9fb9101e47f368f +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_True_True_False_[1, 1].npy b/test/data/spatial_global/test_out_lin_False_True_True_False_[1, 1].npy new file mode 100644 index 0000000..6a50900 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afa0a5583fab48f59017112ba857b9b89cf6a6b3d68a23f515eb08074af91dd0 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_True_True_False_[2, 2].npy b/test/data/spatial_global/test_out_lin_False_True_True_False_[2, 2].npy new file mode 100644 index 0000000..f39fb92 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8e65fafce01c7c3c082bd6481d245c8ed7773cbba8381c62cdb57ddf2549604 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_True_True_True_[1, 1].npy b/test/data/spatial_global/test_out_lin_False_True_True_True_[1, 1].npy new file mode 100644 index 0000000..b6ff268 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:260c2c3e417a9a4a2bae51b4a8005851dee8f68aad5f71e819ac988a7b26f11a +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_False_True_True_True_[2, 2].npy b/test/data/spatial_global/test_out_lin_False_True_True_True_[2, 2].npy new file mode 100644 index 0000000..77fea5b --- /dev/null +++ b/test/data/spatial_global/test_out_lin_False_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85eecd6e4445181b2949447e450135a915ce00488d345a2c6ab31b8c77276748 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_False_False_False_[1, 1].npy b/test/data/spatial_global/test_out_lin_True_False_False_False_[1, 1].npy new file mode 100644 index 0000000..3340d62 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9bfada73326ca2cf26721cda327da9415ded3e4f4fc8547636c3fde307ed061 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_False_False_False_[2, 2].npy b/test/data/spatial_global/test_out_lin_True_False_False_False_[2, 2].npy new file mode 100644 index 0000000..30f1214 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf1d5e26b98009697796a16906705d9cfa2b7dfadf27b2ce07375cbfb5c23ee5 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_False_False_True_[1, 1].npy b/test/data/spatial_global/test_out_lin_True_False_False_True_[1, 1].npy new file mode 100644 index 0000000..036b9ad --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f4a1fa07a3b7c755f773862783a192d2d3eb0c5feaf1effc7670b378d60d006 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_False_False_True_[2, 2].npy b/test/data/spatial_global/test_out_lin_True_False_False_True_[2, 2].npy new file mode 100644 index 0000000..8ed38ce --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2ae665d6fc6c351d6cc2869bb84cdf3db84500addc9e2502321b597b38d11c3 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_False_True_False_[1, 1].npy b/test/data/spatial_global/test_out_lin_True_False_True_False_[1, 1].npy new file mode 100644 index 0000000..238b719 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:172f6b353ad3418b8b9b6f2b4ad927adbcfd3eb8e6bbd2b419c9184c3ee5c770 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_False_True_False_[2, 2].npy b/test/data/spatial_global/test_out_lin_True_False_True_False_[2, 2].npy new file mode 100644 index 0000000..78a97a7 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f87bde84cfeb98e147db81bb1b47db78ba5a108736dea6024339c9c7a3577b3 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_False_True_True_[1, 1].npy b/test/data/spatial_global/test_out_lin_True_False_True_True_[1, 1].npy new file mode 100644 index 0000000..796a116 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:963dd0b6c14d7d84422782eba6390d3897c6914231728c81b7f569b16a9b6e12 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_False_True_True_[2, 2].npy b/test/data/spatial_global/test_out_lin_True_False_True_True_[2, 2].npy new file mode 100644 index 0000000..26cdeaf --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bcca92376929fd04edbda8e69f4551ffe7297c8e4c24591e233581205d465fb +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_True_False_False_[1, 1].npy b/test/data/spatial_global/test_out_lin_True_True_False_False_[1, 1].npy new file mode 100644 index 0000000..7e7aac8 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:113649a311e22e145f046e29f38bbdcc0fef14bb6be19e463784eb43c704eecb +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_True_False_False_[2, 2].npy b/test/data/spatial_global/test_out_lin_True_True_False_False_[2, 2].npy new file mode 100644 index 0000000..6911813 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3267bc5dff8382cbc3ddcd283ddbdcc6f4d2d6408dd279844faf503555ef76f6 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_True_False_True_[1, 1].npy b/test/data/spatial_global/test_out_lin_True_True_False_True_[1, 1].npy new file mode 100644 index 0000000..7ab744d --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:203a87352a615084f8f68842c9b0c36e019263a70fd7b53cfd8bef94b075f72f +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_True_False_True_[2, 2].npy b/test/data/spatial_global/test_out_lin_True_True_False_True_[2, 2].npy new file mode 100644 index 0000000..359d211 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8850b47751857546348166623db64e5230d38c2daa093dc0ece8a142b1985f5 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_True_True_False_[1, 1].npy b/test/data/spatial_global/test_out_lin_True_True_True_False_[1, 1].npy new file mode 100644 index 0000000..5675a35 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:596b048683b944035a9530cfb54be973e2e1c22bb0534418de345247e2527ff2 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_True_True_False_[2, 2].npy b/test/data/spatial_global/test_out_lin_True_True_True_False_[2, 2].npy new file mode 100644 index 0000000..882b5c0 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:199464ae6202f91066bfb2fdb213688eae27ab673f54e7a60d9c2702fe6355a8 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_True_True_True_[1, 1].npy b/test/data/spatial_global/test_out_lin_True_True_True_True_[1, 1].npy new file mode 100644 index 0000000..3a31f3b --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bddf221f501f8398995006b20b900844909fc1580dd99966558ccb8431ad0c53 +size 1048704 diff --git a/test/data/spatial_global/test_out_lin_True_True_True_True_[2, 2].npy b/test/data/spatial_global/test_out_lin_True_True_True_True_[2, 2].npy new file mode 100644 index 0000000..2c58b80 --- /dev/null +++ b/test/data/spatial_global/test_out_lin_True_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff6d130c273c7730528f668752a7685f65b42929f61e6f2ad64038c9d1b4ddfd +size 1048704 diff --git a/test/data/spatial_local/test_in.npy b/test/data/spatial_local/test_in.npy new file mode 100644 index 0000000..58e3fbb --- /dev/null +++ b/test/data/spatial_local/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b67ecafb00dbdc71451ac10fd9e0dd3fc6e56c2865ae47b3043fd6478c8e9c6 +size 131200 diff --git a/test/data/spatial_local/test_in2.npy b/test/data/spatial_local/test_in2.npy new file mode 100644 index 0000000..824e550 --- /dev/null +++ b/test/data/spatial_local/test_in2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d5b7a48c90466fc4193914115750f6e571b0773a002fa73e9123e527901a1cb +size 32896 diff --git a/test/data/spatial_local/test_out_conv_False_False_False_False_[1, 1].npy b/test/data/spatial_local/test_out_conv_False_False_False_False_[1, 1].npy new file mode 100644 index 0000000..1bbd3a2 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec224630ac2ada93b040fa1638fefc06586839e317f8a4a589625822eaf947b0 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_False_False_False_[2, 2].npy b/test/data/spatial_local/test_out_conv_False_False_False_False_[2, 2].npy new file mode 100644 index 0000000..54e0642 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b328ee58a15b73abff0058f3a6f5f751856d060732cfe9f25a05cf09c4b5a2fa +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_False_False_True_[1, 1].npy b/test/data/spatial_local/test_out_conv_False_False_False_True_[1, 1].npy new file mode 100644 index 0000000..5f54468 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3850c72d3ab3f5957906260de14391d5b298fd5a5aafed137c8bc2ed74780488 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_False_False_True_[2, 2].npy b/test/data/spatial_local/test_out_conv_False_False_False_True_[2, 2].npy new file mode 100644 index 0000000..583c3cd --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b70a2ce951de5d1c4faa6aa5bf1ea5af733e88b087dec7ed54caa2d71c3ffafa +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_False_True_False_[1, 1].npy b/test/data/spatial_local/test_out_conv_False_False_True_False_[1, 1].npy new file mode 100644 index 0000000..072e4c7 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:477c7a2dd348d3a39fcee2afed9043c180d3101bd82dd36ebce443b8921fcd4c +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_False_True_False_[2, 2].npy b/test/data/spatial_local/test_out_conv_False_False_True_False_[2, 2].npy new file mode 100644 index 0000000..826299b --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ed2639816497bae56368728aeeca939904572281da3baafd3665c3ca1fe55ef +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_False_True_True_[1, 1].npy b/test/data/spatial_local/test_out_conv_False_False_True_True_[1, 1].npy new file mode 100644 index 0000000..7767926 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7dd5354ba7ba170af7e369fb27974152644017acda8547da6649955e880f5f93 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_False_True_True_[2, 2].npy b/test/data/spatial_local/test_out_conv_False_False_True_True_[2, 2].npy new file mode 100644 index 0000000..51eb096 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3373f6dfd34fb4ac702b20519379c13135b9c35b987acc0c45d7113ec1e8e3a4 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_True_False_False_[1, 1].npy b/test/data/spatial_local/test_out_conv_False_True_False_False_[1, 1].npy new file mode 100644 index 0000000..f0c3c37 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9b8ce9ee417874212ca992110a8d6d0fc1e6cd3706266d08a5df59972e64ec5 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_True_False_False_[2, 2].npy b/test/data/spatial_local/test_out_conv_False_True_False_False_[2, 2].npy new file mode 100644 index 0000000..0a8907f --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2076387c5f6f67a49950894d6d9100df033f3bda1852ff1a9e4684c628b5a6e +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_True_False_True_[1, 1].npy b/test/data/spatial_local/test_out_conv_False_True_False_True_[1, 1].npy new file mode 100644 index 0000000..567bdea --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39d7cd323fecb921525805313217462bc8f021ddc83bf7a88acd03990b44cd57 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_True_False_True_[2, 2].npy b/test/data/spatial_local/test_out_conv_False_True_False_True_[2, 2].npy new file mode 100644 index 0000000..7c3d5ad --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f42f3937b8994b76aacb51286edaa7fc3f5fe06a5ab8bebcfede8b9c1a2216a +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_True_True_False_[1, 1].npy b/test/data/spatial_local/test_out_conv_False_True_True_False_[1, 1].npy new file mode 100644 index 0000000..13904f4 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa0105883a63170aaf1a619ffec0443ce14231869cb8f6117f7bbc93d71fbd71 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_True_True_False_[2, 2].npy b/test/data/spatial_local/test_out_conv_False_True_True_False_[2, 2].npy new file mode 100644 index 0000000..f8e4c67 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3391d37c38b56dd030b900a47b2a7e6bbda3a25d8d8b1e6b391f0b5575ed5b60 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_True_True_True_[1, 1].npy b/test/data/spatial_local/test_out_conv_False_True_True_True_[1, 1].npy new file mode 100644 index 0000000..96d2489 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f35b32ffa4556338914ba2c59fd7d86856755724c37a63cf6106f8a04669202 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_False_True_True_True_[2, 2].npy b/test/data/spatial_local/test_out_conv_False_True_True_True_[2, 2].npy new file mode 100644 index 0000000..43c59d8 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_False_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8055a7b909fda15d6a9d27172dfc120b2636da9bb8808f6051e865c4339c4248 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_False_False_False_[1, 1].npy b/test/data/spatial_local/test_out_conv_True_False_False_False_[1, 1].npy new file mode 100644 index 0000000..20f92f9 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f96cca2c0cb143bdc970c936314f506c0d9fb5bcf5273e1d82b0a523e1ce865 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_False_False_False_[2, 2].npy b/test/data/spatial_local/test_out_conv_True_False_False_False_[2, 2].npy new file mode 100644 index 0000000..b8f5ccc --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c54989ee8a4125047d60ea7ec92005f27042ada6dc33923a08d01605e1debd8a +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_False_False_True_[1, 1].npy b/test/data/spatial_local/test_out_conv_True_False_False_True_[1, 1].npy new file mode 100644 index 0000000..fb596dc --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e39415ce4c006abbebfc742ccb8f59e71790be9feda77d764be6d3ec4b3c2e76 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_False_False_True_[2, 2].npy b/test/data/spatial_local/test_out_conv_True_False_False_True_[2, 2].npy new file mode 100644 index 0000000..98655bc --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c06baf09503d959be862c2db1b3cb98edaf79e68ab88a6703c8993d86620cfd +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_False_True_False_[1, 1].npy b/test/data/spatial_local/test_out_conv_True_False_True_False_[1, 1].npy new file mode 100644 index 0000000..099842d --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ead4b0e07f991a92325d0f702b25200e130e5bec0d3320d6f1d4abb70363ed0a +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_False_True_False_[2, 2].npy b/test/data/spatial_local/test_out_conv_True_False_True_False_[2, 2].npy new file mode 100644 index 0000000..cc6dc20 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7d48b1fa10ecc31e6f52c8ff907bacc4e14b1de3a5ea742801a788111ff4373 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_False_True_True_[1, 1].npy b/test/data/spatial_local/test_out_conv_True_False_True_True_[1, 1].npy new file mode 100644 index 0000000..b016d1d --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85b29078e0525d115d8cd353f04bb6b2ad6d2007a7db46018bd1cd9c0764e849 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_False_True_True_[2, 2].npy b/test/data/spatial_local/test_out_conv_True_False_True_True_[2, 2].npy new file mode 100644 index 0000000..202f6db --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4decfc9fb1b65912f1c207d8834267bfd8ea58cfaee5c9a53fa9b95ea919122 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_True_False_False_[1, 1].npy b/test/data/spatial_local/test_out_conv_True_True_False_False_[1, 1].npy new file mode 100644 index 0000000..f449128 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e89814c1a459b9b911c7a5a0cb903d255138e7256d4fa083acfd170752041cf +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_True_False_False_[2, 2].npy b/test/data/spatial_local/test_out_conv_True_True_False_False_[2, 2].npy new file mode 100644 index 0000000..816b89d --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccb86b1af808ac0b0bdf860e837fba0a94465daf573bebf1965868000fde4d0d +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_True_False_True_[1, 1].npy b/test/data/spatial_local/test_out_conv_True_True_False_True_[1, 1].npy new file mode 100644 index 0000000..907684f --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6444b7b97502c15df854de18665d3e8e0de875ed9199edff2e620823bc2e710 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_True_False_True_[2, 2].npy b/test/data/spatial_local/test_out_conv_True_True_False_True_[2, 2].npy new file mode 100644 index 0000000..8e1939c --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4abb407028a6b778c3114481b29fd930cffca94cf4a71c564139c7ed6a87f1d0 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_True_True_False_[1, 1].npy b/test/data/spatial_local/test_out_conv_True_True_True_False_[1, 1].npy new file mode 100644 index 0000000..bdb7a3b --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:107aa0f606b0d11b91984b31a2590e1948fd7ef05b6b1c3ce41015cc842c3f6c +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_True_True_False_[2, 2].npy b/test/data/spatial_local/test_out_conv_True_True_True_False_[2, 2].npy new file mode 100644 index 0000000..2ecffb5 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8e57828cd7a5c2cd81b0a5181e71b6f8d101d42a38f5e43fce0e73ff42f34b2 +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_True_True_True_[1, 1].npy b/test/data/spatial_local/test_out_conv_True_True_True_True_[1, 1].npy new file mode 100644 index 0000000..0cdf142 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:506d8ea1fe41192581d7a022bd5a0c583fc35ebbbbb4f722111f1666cfc25e2f +size 1048704 diff --git a/test/data/spatial_local/test_out_conv_True_True_True_True_[2, 2].npy b/test/data/spatial_local/test_out_conv_True_True_True_True_[2, 2].npy new file mode 100644 index 0000000..eb63661 --- /dev/null +++ b/test/data/spatial_local/test_out_conv_True_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c05b8e247f3ce7f76c24424722a954f168cb70e4d57943e92dc9b2ec2d730d4 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_False_False_False_[1, 1].npy b/test/data/spatial_local/test_out_lin_False_False_False_False_[1, 1].npy new file mode 100644 index 0000000..97c0680 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:848d880b420af0165a2075aa02e3072a649bed4a29bdebf17277fb8faee8a99d +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_False_False_False_[2, 2].npy b/test/data/spatial_local/test_out_lin_False_False_False_False_[2, 2].npy new file mode 100644 index 0000000..0ceed33 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fe0074c0f9388cf07d7148a0dec76f9702b16b3a5c9f2a2924bbe656df35378 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_False_False_True_[1, 1].npy b/test/data/spatial_local/test_out_lin_False_False_False_True_[1, 1].npy new file mode 100644 index 0000000..9bf75d9 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:476456b443aeededb8a7497bdac71a10ea80eddddaff939487a472b34091a908 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_False_False_True_[2, 2].npy b/test/data/spatial_local/test_out_lin_False_False_False_True_[2, 2].npy new file mode 100644 index 0000000..084a625 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc1cd3d1e223665d52d4251c53d062cd0e317e41b9c117700c13455ff49c749e +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_False_True_False_[1, 1].npy b/test/data/spatial_local/test_out_lin_False_False_True_False_[1, 1].npy new file mode 100644 index 0000000..254617b --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39d522a4c52d373731fee4eb82a8989fdefb3ab991f9a05c5fd1bd926def57ba +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_False_True_False_[2, 2].npy b/test/data/spatial_local/test_out_lin_False_False_True_False_[2, 2].npy new file mode 100644 index 0000000..79ed2c9 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8701349bc45783db3098915179553cd2a1bfaddaf6ea2744a8a0d0cce8858dff +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_False_True_True_[1, 1].npy b/test/data/spatial_local/test_out_lin_False_False_True_True_[1, 1].npy new file mode 100644 index 0000000..f37551d --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a24789dfa01137e82dce66e32781f2fc09b71cbcefa3739e79c41ad658d3c69 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_False_True_True_[2, 2].npy b/test/data/spatial_local/test_out_lin_False_False_True_True_[2, 2].npy new file mode 100644 index 0000000..b3e2b6a --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:077c01e6e685460dfe76e7dd7acfaa424e213f9c7c7cc6adf9292b780f02809a +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_True_False_False_[1, 1].npy b/test/data/spatial_local/test_out_lin_False_True_False_False_[1, 1].npy new file mode 100644 index 0000000..63300ef --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f95ee7edfd20e3152102d7029fe611dd5d1b1ba1c39631caa9a6791b8e6722a8 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_True_False_False_[2, 2].npy b/test/data/spatial_local/test_out_lin_False_True_False_False_[2, 2].npy new file mode 100644 index 0000000..0061566 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d3c0742890c47b5c8c7792fb215f943c46257ea067bc5e8ea95db345bc84ca7 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_True_False_True_[1, 1].npy b/test/data/spatial_local/test_out_lin_False_True_False_True_[1, 1].npy new file mode 100644 index 0000000..08f6695 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c75748498dd5e0dee1a217fc3e8b28b1508749251e8433455b25392a9c37a661 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_True_False_True_[2, 2].npy b/test/data/spatial_local/test_out_lin_False_True_False_True_[2, 2].npy new file mode 100644 index 0000000..7f91ea4 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94d2cb71500e961b0f9f72002b8bde49ef71df46b15dd04d535e37aac98cd18d +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_True_True_False_[1, 1].npy b/test/data/spatial_local/test_out_lin_False_True_True_False_[1, 1].npy new file mode 100644 index 0000000..1bd73fc --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f158f6400daca1618f9f3196d11d78ed8400309b4b6103b4e0653494d473ff2f +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_True_True_False_[2, 2].npy b/test/data/spatial_local/test_out_lin_False_True_True_False_[2, 2].npy new file mode 100644 index 0000000..575c480 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c369d67e31760490391950fc029de5aff831a4b4258092b8578aeb4b0615c57 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_True_True_True_[1, 1].npy b/test/data/spatial_local/test_out_lin_False_True_True_True_[1, 1].npy new file mode 100644 index 0000000..b6769bd --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecd01f9bf9ce3ba954d45f39a8d7fbb76111fcb81f4b066bae3d59fb5d8f476d +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_False_True_True_True_[2, 2].npy b/test/data/spatial_local/test_out_lin_False_True_True_True_[2, 2].npy new file mode 100644 index 0000000..fe0afab --- /dev/null +++ b/test/data/spatial_local/test_out_lin_False_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39d6e4eca1f2458586d8768cd5cb4528da1a413de65a47ad4188f823c4c4cdf3 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_False_False_False_[1, 1].npy b/test/data/spatial_local/test_out_lin_True_False_False_False_[1, 1].npy new file mode 100644 index 0000000..4836d31 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b87e77bec4cadd2bb6b21cdc16b52de1aafb5872fe6dd0bf7f03fb4f10c14a4 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_False_False_False_[2, 2].npy b/test/data/spatial_local/test_out_lin_True_False_False_False_[2, 2].npy new file mode 100644 index 0000000..d4b45b2 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da0d9e3576856deb8bbb94b2dd3415ae6774ef93e4a246db43de0a5ff5779a25 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_False_False_True_[1, 1].npy b/test/data/spatial_local/test_out_lin_True_False_False_True_[1, 1].npy new file mode 100644 index 0000000..b97b950 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d9d0fb51d879eab3f6cc3e3dbf0ed031b8bb32f853c8a16f08146f498be5813 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_False_False_True_[2, 2].npy b/test/data/spatial_local/test_out_lin_True_False_False_True_[2, 2].npy new file mode 100644 index 0000000..b725a62 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fe91e97c04125ed197ebd243706b2b64f3233dd07d4cf1d775ae04f668b703a +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_False_True_False_[1, 1].npy b/test/data/spatial_local/test_out_lin_True_False_True_False_[1, 1].npy new file mode 100644 index 0000000..304a907 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a8a90ef5bd228ea1bad7908e10d66905420559d8c770f6c07cb81f87bde1797 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_False_True_False_[2, 2].npy b/test/data/spatial_local/test_out_lin_True_False_True_False_[2, 2].npy new file mode 100644 index 0000000..72525ad --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42d111ec4efc4f04922e90c8df7b92181ecd9c40d099b5786b8d11c681e0b44a +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_False_True_True_[1, 1].npy b/test/data/spatial_local/test_out_lin_True_False_True_True_[1, 1].npy new file mode 100644 index 0000000..252596d --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4f1aebd2d463a7d962dfa4c0053ada2601f6070db27f9b02a0ce85c2f453a49 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_False_True_True_[2, 2].npy b/test/data/spatial_local/test_out_lin_True_False_True_True_[2, 2].npy new file mode 100644 index 0000000..85ef5c5 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ceb13397f047efa92491aa6c8e7c38e7fc27d39040d7c9bd4973db8aae6997e +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_True_False_False_[1, 1].npy b/test/data/spatial_local/test_out_lin_True_True_False_False_[1, 1].npy new file mode 100644 index 0000000..d7b4086 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5e9193a5db495dadeda6001652279a093f2156bc41f3f0c9577c8dbd6b022ce +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_True_False_False_[2, 2].npy b/test/data/spatial_local/test_out_lin_True_True_False_False_[2, 2].npy new file mode 100644 index 0000000..919def4 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f08734d490299a59fb8ca33c4bcd58b180139e6c8cc99c00ad777b90fb09bc54 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_True_False_True_[1, 1].npy b/test/data/spatial_local/test_out_lin_True_True_False_True_[1, 1].npy new file mode 100644 index 0000000..8e13d12 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3b00f42730ccde74eeb9f9965fafc117f4700ff1a02270a62292fa0bc3f6ea9 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_True_False_True_[2, 2].npy b/test/data/spatial_local/test_out_lin_True_True_False_True_[2, 2].npy new file mode 100644 index 0000000..5f76ec9 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33038de6b422043db852c76b60c77ba6e0c180fab235d4b1850cf956357ecd84 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_True_True_False_[1, 1].npy b/test/data/spatial_local/test_out_lin_True_True_True_False_[1, 1].npy new file mode 100644 index 0000000..4f79a60 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf6c9c4d68a74298f59427208c4171aa96c1b045553614a2a75eb279d930a1ed +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_True_True_False_[2, 2].npy b/test/data/spatial_local/test_out_lin_True_True_True_False_[2, 2].npy new file mode 100644 index 0000000..2be45f2 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:250a0d671d3d75f7e0f8b31003fa1a49948ff5289504918caa3f8470eab2c132 +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_True_True_True_[1, 1].npy b/test/data/spatial_local/test_out_lin_True_True_True_True_[1, 1].npy new file mode 100644 index 0000000..b4e4706 --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6d9e77ea0e7481091462e5c35d8016581e1425e97139b3e0b13403b82ac0b1e +size 1048704 diff --git a/test/data/spatial_local/test_out_lin_True_True_True_True_[2, 2].npy b/test/data/spatial_local/test_out_lin_True_True_True_True_[2, 2].npy new file mode 100644 index 0000000..d013dce --- /dev/null +++ b/test/data/spatial_local/test_out_lin_True_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0a9dfd93bb48b54a937663970462b616ca8585f26897103f5dff352a1c66b93 +size 1048704 diff --git a/test/data/swin_3d/test_in.npy b/test/data/swin_3d/test_in.npy new file mode 100644 index 0000000..ba2c433 --- /dev/null +++ b/test/data/swin_3d/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f2d285ed1d120dc4a16752a5ed1629d76e5177d9810a93b0017cc4d0b67c4b1 +size 131200 diff --git a/test/data/swin_3d/test_in2.npy b/test/data/swin_3d/test_in2.npy new file mode 100644 index 0000000..7b53c2e --- /dev/null +++ b/test/data/swin_3d/test_in2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ed56c0830caa0cc7ee40bf2c8031adba797c9acb098db5f60ef981f14807462 +size 524416 diff --git a/test/data/swin_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..e114146 --- /dev/null +++ b/test/data/swin_3d/test_out_conv_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e2fbee0223b9b6893c72cc2b20db924b423713442a5a21b8e8a289fefc7433d +size 2097280 diff --git a/test/data/swin_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..ffca47d --- /dev/null +++ b/test/data/swin_3d/test_out_conv_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:464dc69983b80e41b0a2c5547e3857121e87dd1d92e657518f2468f20c3ab3b1 +size 2097280 diff --git a/test/data/swin_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..9ba3031 --- /dev/null +++ b/test/data/swin_3d/test_out_conv_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4408b384abfc29b91fe57785c88262ffdbe2c1527ba58b1b6ed0a4b861985d7d +size 2097280 diff --git a/test/data/swin_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..87b95db --- /dev/null +++ b/test/data/swin_3d/test_out_conv_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f4fd14bafbc9bfc5e7a007e2a2df354f122c9dccb71edb604b1aa135de31beb +size 2097280 diff --git a/test/data/swin_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..29e138f --- /dev/null +++ b/test/data/swin_3d/test_out_conv_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75df1d2162b8885fca43cf342e26145e37fad826f5f750ee86ea5411edcc2c3f +size 2097280 diff --git a/test/data/swin_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..fd84b9c --- /dev/null +++ b/test/data/swin_3d/test_out_conv_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f51fe5269fed8d9f53ccddf47dd6e7a1a5e241cbdcbd0089cd327fdba036b063 +size 2097280 diff --git a/test/data/swin_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..a5df640 --- /dev/null +++ b/test/data/swin_3d/test_out_conv_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a31c57e292f05147b3a30c3ce54a5211a38f15b62afba329f07d234a2694d96 +size 2097280 diff --git a/test/data/swin_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..afc1b43 --- /dev/null +++ b/test/data/swin_3d/test_out_conv_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94815c104cc91764f8f6d816fbc75dc48d9ef3ae6210b6a2b06c0296e1115636 +size 2097280 diff --git a/test/data/swin_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..9cd4137 --- /dev/null +++ b/test/data/swin_3d/test_out_lin_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2863da5c0ed00711bbc94fa811e8173b8afcbface633a17b86724d006f32870 +size 2097280 diff --git a/test/data/swin_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..7730997 --- /dev/null +++ b/test/data/swin_3d/test_out_lin_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46431ef5337300ee540fd2c56068d9aefc0cd1097d050ebfbca68be3eb3b7975 +size 2097280 diff --git a/test/data/swin_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..d44e91b --- /dev/null +++ b/test/data/swin_3d/test_out_lin_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f743668fd31ef93557100861bea9f6f02570d6834b317073b60da8f326d46c4d +size 2097280 diff --git a/test/data/swin_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..98dc552 --- /dev/null +++ b/test/data/swin_3d/test_out_lin_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ca7f6c06598bf24dd18bc16cfd8d51264fd22a615c167c152aeee5ed8b9c151 +size 2097280 diff --git a/test/data/swin_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..2b9daa4 --- /dev/null +++ b/test/data/swin_3d/test_out_lin_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e015d684dd2a0353a854d0f7487584bc04137b07bf31060fdad4fa066262177 +size 2097280 diff --git a/test/data/swin_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..cf9dd43 --- /dev/null +++ b/test/data/swin_3d/test_out_lin_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f831fb182bd45c3fdeec218874ecef8452cd1e37b8d5804693fa2819047d4dbc +size 2097280 diff --git a/test/data/swin_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..8909882 --- /dev/null +++ b/test/data/swin_3d/test_out_lin_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d6c4ac2a0651a9823c086b147bc0544d96f3fa836ebafd1fc535d34bedc1cfb +size 2097280 diff --git a/test/data/swin_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy b/test/data/swin_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..5133b16 --- /dev/null +++ b/test/data/swin_3d/test_out_lin_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2562003e87a50cae3f6998f295d289cc94b174882f2cb22631330773afbe5b2 +size 2097280 diff --git a/test/data/temporal/test_in.npy b/test/data/temporal/test_in.npy new file mode 100644 index 0000000..0d763b3 --- /dev/null +++ b/test/data/temporal/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19ef3f2dedc1cb4734f897fe3ed6df82c8e400ca545ca9a750ce38f6e7094ae6 +size 98432 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_False_[1, 1].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_False_[1, 1].npy new file mode 100644 index 0000000..60726cc --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f2d1ced0761413ca572be73a1bdc9cf04bfdda7b96d1e3a8a514e1597029fc3 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_False_[2, 2].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_False_[2, 2].npy new file mode 100644 index 0000000..550d2cf --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebceacf9bd57b0b2ea5afecf9a0e30deb17dc72267f60a45dde3eda799d1bbe1 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_True_[1, 1].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_True_[1, 1].npy new file mode 100644 index 0000000..92dd7f5 --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ebeca6404bef557630b056af4a034a7d0687c503779b76fc06af290c38bdf6a +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_True_[2, 2].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_True_[2, 2].npy new file mode 100644 index 0000000..83b9bdc --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5eb19d985ce3564dbc6a18d2a92a8cdd01f23fb9d83aa4adccb1c00ff65ce38 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_False_[1, 1].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_False_[1, 1].npy new file mode 100644 index 0000000..2e23e3b --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d9e04311d6798c7c3a4e98f958a55c2dbf87514a292f03ca1aaa647eee7081c +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_False_[2, 2].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_False_[2, 2].npy new file mode 100644 index 0000000..6619de6 --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b62711fe3f24fcf5de1c06aaba5b38cc9e6f2d5a61c5cc507d53fbf3f8c77f9f +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_True_[1, 1].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_True_[1, 1].npy new file mode 100644 index 0000000..f340b9b --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aebb2a1b522d78c4ce83ded420623822aca0a924b93735d06d78a51854060935 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_True_[2, 2].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_True_[2, 2].npy new file mode 100644 index 0000000..55f8e1a --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4774f4e819d49cf13f250304064411feca459aa0520cb17fe5f18eeb5c718dbb +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_False_[1, 1].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_False_[1, 1].npy new file mode 100644 index 0000000..04484f3 --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cb8e4b6eb4508c30403dc1dd879557993505914be156cae0ae53a4017992137 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_False_[2, 2].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_False_[2, 2].npy new file mode 100644 index 0000000..1f38988 --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7ce5ba5b7ad2d51c4e892dece42277c83528fca4e2c987a92f9325e2dbe7b4 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_True_[1, 1].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_True_[1, 1].npy new file mode 100644 index 0000000..b80fc5e --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:561ee014b891faaceff5abae055ea9a436c6579a2bf1434cb16f03c5021c3467 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_True_[2, 2].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_True_[2, 2].npy new file mode 100644 index 0000000..42fa73e --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e73a385a4b304100882ce18b7875385afba578fd5a95b9d783e181d3517ecc2 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_False_[1, 1].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_False_[1, 1].npy new file mode 100644 index 0000000..5b619f3 --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3f43c1019766cadb66672fab0e3bbdaa879e052be6465eb9be9e5db80770e86 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_False_[2, 2].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_False_[2, 2].npy new file mode 100644 index 0000000..e6b3829 --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31968ce56712c14a36b169598059b9db45fa96c8f0cba63dfae617b139e5cca5 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_True_[1, 1].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_True_[1, 1].npy new file mode 100644 index 0000000..a858880 --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:814c983cef2341dfaaab528dc9bb9364b647353327ca1e056d82cd51e5da6910 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_True_[2, 2].npy b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_True_[2, 2].npy new file mode 100644 index 0000000..a0bd047 --- /dev/null +++ b/test/data/temporal/test_out_TemporalChannelCnnAttention_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b21026ad1603379a13016587179306c11aa8b768fc6d20deb4c764e6f153e37 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_False_[1, 1].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_False_[1, 1].npy new file mode 100644 index 0000000..e08d27c --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63fc25f5ec270fb0e64b78ffc658f94cf8d3a743f3039d8e095898e52fae2f03 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_False_[2, 2].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_False_[2, 2].npy new file mode 100644 index 0000000..4d72f20 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:548fd6284f6e26a95f80bd87ed0fca8e7e4f552497cb3b0b9a8f0b555c9c7f41 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_True_[1, 1].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_True_[1, 1].npy new file mode 100644 index 0000000..1156728 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4bd446a81b4c37fc9248ccb97f81838d023bf40e9d32655fb790bbbe21be208 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_True_[2, 2].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_True_[2, 2].npy new file mode 100644 index 0000000..6579e6f --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77d9d4f7d34de2984e8700ea30026476d481532735f3061bf7d73eb563d84fa +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_False_[1, 1].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_False_[1, 1].npy new file mode 100644 index 0000000..a3d3712 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9357c96732de496cbe87df8fc0a7ff2c05b15d594e1a5c15e90b9b1a903501d5 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_False_[2, 2].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_False_[2, 2].npy new file mode 100644 index 0000000..e918f60 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65cbae6752775ce62528340c1c70cb1fc3ad9bc241d89476df985e99f2a4945a +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_True_[1, 1].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_True_[1, 1].npy new file mode 100644 index 0000000..af9e31f --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3870c1059269cd02e83b32756114c9f7f344dfbe852dc9eb6bba8bdf6fbd1fa7 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_True_[2, 2].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_True_[2, 2].npy new file mode 100644 index 0000000..115f172 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_False_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9775496e2d600d695067ec3244377f842e1dff2bccfa92d5a79a8de48730f6f +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_False_[1, 1].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_False_[1, 1].npy new file mode 100644 index 0000000..badcbfb --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:856736966b1b9ecc5eb68167339ddb3654d086a1e427cd2ace524233534130c4 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_False_[2, 2].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_False_[2, 2].npy new file mode 100644 index 0000000..581d1e6 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c55522f2dce63928f2cf0bc343094a93a885ef76ec5f9ab162cb4261e6a57b0 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_True_[1, 1].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_True_[1, 1].npy new file mode 100644 index 0000000..4064702 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:273f91385ba0082d9cc54100be5bea82b56737e23f107bfa45b60caa690954f0 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_True_[2, 2].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_True_[2, 2].npy new file mode 100644 index 0000000..97798cc --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_False_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a96b2264b8a3f6e33dc109b166039f04d69ab00a8c40d391f0716aa0a612c108 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_False_[1, 1].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_False_[1, 1].npy new file mode 100644 index 0000000..da7b0cb --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_False_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18a53468d30f679b9ec46c5e8e326e6bef7b0a8b4fddca729a942879ec4e978f +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_False_[2, 2].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_False_[2, 2].npy new file mode 100644 index 0000000..0522e69 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_False_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42f46c6a603a6e4af6effa175eae002ad1ba56a477aeb7caf38761951096f052 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_True_[1, 1].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_True_[1, 1].npy new file mode 100644 index 0000000..a3dc1b3 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_True_[1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e02c259af353d814e7060a86ec3e7c7c647ae7f90f7e3d1236c60b58beba36b9 +size 1048704 diff --git a/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_True_[2, 2].npy b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_True_[2, 2].npy new file mode 100644 index 0000000..e8e8256 --- /dev/null +++ b/test/data/temporal/test_out_TemporalCnnStandardAttention_True_True_True_[2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6ebde1f9c732a37542d177a249bee86455698e7f9e67490a4f38e55380f95a9 +size 1048704 diff --git a/test/data/vit2d/test_in.npy b/test/data/vit2d/test_in.npy new file mode 100644 index 0000000..caec3b1 --- /dev/null +++ b/test/data/vit2d/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3f419ae54136247193b5131b5d9cbf39e0e1f6eb3785e83d95e04ed81b6d972 +size 131200 diff --git a/test/data/vit2d/test_in2.npy b/test/data/vit2d/test_in2.npy new file mode 100644 index 0000000..0433e4b --- /dev/null +++ b/test/data/vit2d/test_in2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98c9bb15c32550c7ee9a3eafb6ddbb51af4f24de38d1ecabb0eb1950aaf95dbc +size 2097280 diff --git a/test/data/vit2d/test_out_conv_False_False_False_False_[1, 1, 1].npy b/test/data/vit2d/test_out_conv_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..2855546 --- /dev/null +++ b/test/data/vit2d/test_out_conv_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5249b1755a53ecc8822250dbfe20ca88f8e267a172fcaf80773a264514379da7 +size 524416 diff --git a/test/data/vit2d/test_out_conv_False_False_False_False_[2, 2, 2].npy b/test/data/vit2d/test_out_conv_False_False_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..c5c47bf --- /dev/null +++ b/test/data/vit2d/test_out_conv_False_False_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b49d0ecf7ff6d5930467522637fb2218dc7b40ffb35a01f467187d7731db185 +size 524416 diff --git a/test/data/vit2d/test_out_conv_False_False_True_False_[1, 1, 1].npy b/test/data/vit2d/test_out_conv_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..78a4c9b --- /dev/null +++ b/test/data/vit2d/test_out_conv_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00415c0755ab4efb416c1313f415f22cbb9568901f9e79d97a0b1baeeff90c67 +size 524416 diff --git a/test/data/vit2d/test_out_conv_False_False_True_False_[2, 2, 2].npy b/test/data/vit2d/test_out_conv_False_False_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..dced4e1 --- /dev/null +++ b/test/data/vit2d/test_out_conv_False_False_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d112a81632278d3efaa56d3d1de205cf5fab53ad6e5db999a2681ea50bd3ecf +size 524416 diff --git a/test/data/vit2d/test_out_conv_False_True_False_False_[1, 1, 1].npy b/test/data/vit2d/test_out_conv_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..6011f29 --- /dev/null +++ b/test/data/vit2d/test_out_conv_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84bbea8fa9e235b1067358414d7871c9a8d6a1410f40a7663f3b170dca3ee458 +size 524416 diff --git a/test/data/vit2d/test_out_conv_False_True_False_False_[2, 2, 2].npy b/test/data/vit2d/test_out_conv_False_True_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..3199ee6 --- /dev/null +++ b/test/data/vit2d/test_out_conv_False_True_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cfe389843c646775976a1f0a55dfb3a0faac02a65a7af530169fdf52fbe0c84 +size 524416 diff --git a/test/data/vit2d/test_out_conv_False_True_True_False_[1, 1, 1].npy b/test/data/vit2d/test_out_conv_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..883280c --- /dev/null +++ b/test/data/vit2d/test_out_conv_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6874f9048a0c0ddfcced1452525ca0986cd37f955619dc45a07b3039e9afb5ea +size 524416 diff --git a/test/data/vit2d/test_out_conv_False_True_True_False_[2, 2, 2].npy b/test/data/vit2d/test_out_conv_False_True_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..b816ed8 --- /dev/null +++ b/test/data/vit2d/test_out_conv_False_True_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cb2519c543e29103e4963d4565a5c5a0b1124137b4fc4a67ae04c3cf5f8c996 +size 524416 diff --git a/test/data/vit2d/test_out_conv_True_False_False_False_[1, 1, 1].npy b/test/data/vit2d/test_out_conv_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..068ec15 --- /dev/null +++ b/test/data/vit2d/test_out_conv_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:966fb89c3e6887763b4ed65f31e83c745c0ae757b5aabcb110445c750db44a87 +size 524416 diff --git a/test/data/vit2d/test_out_conv_True_False_False_False_[2, 2, 2].npy b/test/data/vit2d/test_out_conv_True_False_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..21d23e3 --- /dev/null +++ b/test/data/vit2d/test_out_conv_True_False_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6af3d4d4f96d3eba03d418e18bd3620bbc2e11bd1b813f4463bf441bb031a93a +size 524416 diff --git a/test/data/vit2d/test_out_conv_True_False_True_False_[1, 1, 1].npy b/test/data/vit2d/test_out_conv_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..9d228c0 --- /dev/null +++ b/test/data/vit2d/test_out_conv_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fac4985734df8b69974c4749f65d73d5af02377d521ad5b48316e16e9650cca +size 524416 diff --git a/test/data/vit2d/test_out_conv_True_False_True_False_[2, 2, 2].npy b/test/data/vit2d/test_out_conv_True_False_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..6e00212 --- /dev/null +++ b/test/data/vit2d/test_out_conv_True_False_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a61572e00633e35c43bb0b76817683aa68d8af14637b47634e169f3190661b8 +size 524416 diff --git a/test/data/vit2d/test_out_conv_True_True_False_False_[1, 1, 1].npy b/test/data/vit2d/test_out_conv_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..a68a406 --- /dev/null +++ b/test/data/vit2d/test_out_conv_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ba544803911fd933b7db0d7eda605409c951e1ea4121b40d888e36594d0b78b +size 524416 diff --git a/test/data/vit2d/test_out_conv_True_True_False_False_[2, 2, 2].npy b/test/data/vit2d/test_out_conv_True_True_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..4485533 --- /dev/null +++ b/test/data/vit2d/test_out_conv_True_True_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a801c784eb9893ec3f548355746dee12f5d0dbe7ed6c6b1c4354cd8152d47ff1 +size 524416 diff --git a/test/data/vit2d/test_out_conv_True_True_True_False_[1, 1, 1].npy b/test/data/vit2d/test_out_conv_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..bc879d6 --- /dev/null +++ b/test/data/vit2d/test_out_conv_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6d2b4b1e82e6857731716e2ac212de2a31e15261255c7e7d991a90d8c1cbce1 +size 524416 diff --git a/test/data/vit2d/test_out_conv_True_True_True_False_[2, 2, 2].npy b/test/data/vit2d/test_out_conv_True_True_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..81da856 --- /dev/null +++ b/test/data/vit2d/test_out_conv_True_True_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03639e397dd2a9c073adc45966027bfc000b22a67f759a55898a44db57c67119 +size 524416 diff --git a/test/data/vit2d/test_out_lin_False_False_False_False_[1, 1, 1].npy b/test/data/vit2d/test_out_lin_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..9bdbc8b --- /dev/null +++ b/test/data/vit2d/test_out_lin_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce404e823882776a5f81f087f60b8fd635fc22a99c6e21618b1c70b942287478 +size 524416 diff --git a/test/data/vit2d/test_out_lin_False_False_False_False_[2, 2, 2].npy b/test/data/vit2d/test_out_lin_False_False_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..1b52f5b --- /dev/null +++ b/test/data/vit2d/test_out_lin_False_False_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e086ed22010b99f40edd4348c95bfd6714fdeaa0e977b560565cd1f08005d71 +size 524416 diff --git a/test/data/vit2d/test_out_lin_False_False_True_False_[1, 1, 1].npy b/test/data/vit2d/test_out_lin_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..34c5f5c --- /dev/null +++ b/test/data/vit2d/test_out_lin_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46ff253059b974de8d3d0ca0dee4526cf7dc3649848196100ba3b5af8200866a +size 524416 diff --git a/test/data/vit2d/test_out_lin_False_False_True_False_[2, 2, 2].npy b/test/data/vit2d/test_out_lin_False_False_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..2fb147b --- /dev/null +++ b/test/data/vit2d/test_out_lin_False_False_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90a4dfc27cb075ee948c8358bf06ccb4eec568c5dd7a3d3b3af6b8ec0cb07a9a +size 524416 diff --git a/test/data/vit2d/test_out_lin_False_True_False_False_[1, 1, 1].npy b/test/data/vit2d/test_out_lin_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..d9ee967 --- /dev/null +++ b/test/data/vit2d/test_out_lin_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad90950cffd2cab2d24cbd2a06bc2720160ff47331295bc1970c866cc630d157 +size 524416 diff --git a/test/data/vit2d/test_out_lin_False_True_False_False_[2, 2, 2].npy b/test/data/vit2d/test_out_lin_False_True_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..044ec2c --- /dev/null +++ b/test/data/vit2d/test_out_lin_False_True_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c82bd981e027bad35ce75b6ede2f06c7de0ab1b7f9e463a82c607ddefdf4926a +size 524416 diff --git a/test/data/vit2d/test_out_lin_False_True_True_False_[1, 1, 1].npy b/test/data/vit2d/test_out_lin_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..70a009d --- /dev/null +++ b/test/data/vit2d/test_out_lin_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edfec971e48ba39a0c98309116edba7ae5b52acbb6ed4256303763b40db4bc91 +size 524416 diff --git a/test/data/vit2d/test_out_lin_False_True_True_False_[2, 2, 2].npy b/test/data/vit2d/test_out_lin_False_True_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..fdc37b6 --- /dev/null +++ b/test/data/vit2d/test_out_lin_False_True_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd85ba10566e45e3773c4c794ce42454745f85ee1e99bef4bd40defab36da4cc +size 524416 diff --git a/test/data/vit2d/test_out_lin_True_False_False_False_[1, 1, 1].npy b/test/data/vit2d/test_out_lin_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..bd9326b --- /dev/null +++ b/test/data/vit2d/test_out_lin_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b2b3d0a11451071aff86652679e789072cede28d240551e8cf116aff8570810 +size 524416 diff --git a/test/data/vit2d/test_out_lin_True_False_False_False_[2, 2, 2].npy b/test/data/vit2d/test_out_lin_True_False_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..2fcad5a --- /dev/null +++ b/test/data/vit2d/test_out_lin_True_False_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d0939408cc366078aadb6808447d172f80c9e6e64196811c0188777b9960811 +size 524416 diff --git a/test/data/vit2d/test_out_lin_True_False_True_False_[1, 1, 1].npy b/test/data/vit2d/test_out_lin_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..4e861a2 --- /dev/null +++ b/test/data/vit2d/test_out_lin_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e4c3056217bce3df882fa36f7071352d6d713e788081534c399bc94f6ed2d5c +size 524416 diff --git a/test/data/vit2d/test_out_lin_True_False_True_False_[2, 2, 2].npy b/test/data/vit2d/test_out_lin_True_False_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..6e451bb --- /dev/null +++ b/test/data/vit2d/test_out_lin_True_False_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e92f438e5f34220178af89bca19cb9fb4da47225f7dd829dddd22adc7eb4179 +size 524416 diff --git a/test/data/vit2d/test_out_lin_True_True_False_False_[1, 1, 1].npy b/test/data/vit2d/test_out_lin_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..048ec8b --- /dev/null +++ b/test/data/vit2d/test_out_lin_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f24ba27affc76702b9a225a1ef0ab06304c027171b2f6f47806c1b2a51fc1eb +size 524416 diff --git a/test/data/vit2d/test_out_lin_True_True_False_False_[2, 2, 2].npy b/test/data/vit2d/test_out_lin_True_True_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..a7d9577 --- /dev/null +++ b/test/data/vit2d/test_out_lin_True_True_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5520414525768559931698c9efdc6f5eca848753ef5cd349be14a9fa70ad99d +size 524416 diff --git a/test/data/vit2d/test_out_lin_True_True_True_False_[1, 1, 1].npy b/test/data/vit2d/test_out_lin_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..ea3d05d --- /dev/null +++ b/test/data/vit2d/test_out_lin_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2bdc542eced2df4e41b6ae3eeb0a2dc04da5db43748ac0082804163dee65e7e +size 524416 diff --git a/test/data/vit2d/test_out_lin_True_True_True_False_[2, 2, 2].npy b/test/data/vit2d/test_out_lin_True_True_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..0ef3795 --- /dev/null +++ b/test/data/vit2d/test_out_lin_True_True_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b776735c9047237e0fa2d31cbac21132f0ca77fd709a567ea1dc378aa2557e1d +size 524416 diff --git a/test/data/vit3d/test_in.npy b/test/data/vit3d/test_in.npy new file mode 100644 index 0000000..52fe797 --- /dev/null +++ b/test/data/vit3d/test_in.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa68c153ec832fe5d37bc3aa913b0a315e85ce689915701fa77cad552be6fb56 +size 131200 diff --git a/test/data/vit3d/test_in2.npy b/test/data/vit3d/test_in2.npy new file mode 100644 index 0000000..9978da1 --- /dev/null +++ b/test/data/vit3d/test_in2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cd85096c8510a7e056714f5311a98e003d2856c194dcabb711ccb327d4ed205 +size 2097280 diff --git a/test/data/vit3d/test_out_conv_False_False_False_False_[1, 1, 1].npy b/test/data/vit3d/test_out_conv_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..df508e9 --- /dev/null +++ b/test/data/vit3d/test_out_conv_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d98780420330cc8f9a5cbb948dc4687a804c142acfd23a91d8cdb75aa4cab8c +size 524416 diff --git a/test/data/vit3d/test_out_conv_False_False_False_False_[2, 2, 2].npy b/test/data/vit3d/test_out_conv_False_False_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..3434152 --- /dev/null +++ b/test/data/vit3d/test_out_conv_False_False_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef1921494f6981e820550bdeff674ddc76ca0a44125592829f7e9391c6cdaeac +size 524416 diff --git a/test/data/vit3d/test_out_conv_False_False_True_False_[1, 1, 1].npy b/test/data/vit3d/test_out_conv_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..c8fa06c --- /dev/null +++ b/test/data/vit3d/test_out_conv_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:877858f159a852346e27b0106b12b75c1535c6073d7dd485bd7b4f654ec1e041 +size 524416 diff --git a/test/data/vit3d/test_out_conv_False_False_True_False_[2, 2, 2].npy b/test/data/vit3d/test_out_conv_False_False_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..356d693 --- /dev/null +++ b/test/data/vit3d/test_out_conv_False_False_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f2ee2bb8e9d6cb9a1e50089979b299a481f9a68e538b0da4b25de4411689847 +size 524416 diff --git a/test/data/vit3d/test_out_conv_False_True_False_False_[1, 1, 1].npy b/test/data/vit3d/test_out_conv_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..48fe0a7 --- /dev/null +++ b/test/data/vit3d/test_out_conv_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:751c026549426dcf816fddac210b842d43a75cf67f932125af97bf013f5734d1 +size 524416 diff --git a/test/data/vit3d/test_out_conv_False_True_False_False_[2, 2, 2].npy b/test/data/vit3d/test_out_conv_False_True_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..f15d85a --- /dev/null +++ b/test/data/vit3d/test_out_conv_False_True_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:960f565b1080c84bb4445e26f61afd91e6c80de2fbac1e724d7b3c63f0245a96 +size 524416 diff --git a/test/data/vit3d/test_out_conv_False_True_True_False_[1, 1, 1].npy b/test/data/vit3d/test_out_conv_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..3fc5584 --- /dev/null +++ b/test/data/vit3d/test_out_conv_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7116b2249921863ad6238b49b0e215ffc09a0b829fdd5ef2f58533d8ecb809b +size 524416 diff --git a/test/data/vit3d/test_out_conv_False_True_True_False_[2, 2, 2].npy b/test/data/vit3d/test_out_conv_False_True_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..e6b8716 --- /dev/null +++ b/test/data/vit3d/test_out_conv_False_True_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53313546a3acb886094d5a531c30d2760c77d018c33ee4fd9582d51a5c143c0c +size 524416 diff --git a/test/data/vit3d/test_out_conv_True_False_False_False_[1, 1, 1].npy b/test/data/vit3d/test_out_conv_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..630a112 --- /dev/null +++ b/test/data/vit3d/test_out_conv_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:608b1c11706c741a9f0db69901b02fcf9a85b4ec2367e525140bcb7385d9553a +size 524416 diff --git a/test/data/vit3d/test_out_conv_True_False_False_False_[2, 2, 2].npy b/test/data/vit3d/test_out_conv_True_False_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..10dfc82 --- /dev/null +++ b/test/data/vit3d/test_out_conv_True_False_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faeda7c4ed47ba132fffe8e19cd3a361be8b6656ba99b984edc72740a3aed876 +size 524416 diff --git a/test/data/vit3d/test_out_conv_True_False_True_False_[1, 1, 1].npy b/test/data/vit3d/test_out_conv_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..7adedc0 --- /dev/null +++ b/test/data/vit3d/test_out_conv_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb54a6be3a12543b46ba37f649e30d8595aac32acc2ab8ad8880de60a692f732 +size 524416 diff --git a/test/data/vit3d/test_out_conv_True_False_True_False_[2, 2, 2].npy b/test/data/vit3d/test_out_conv_True_False_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..32e57f5 --- /dev/null +++ b/test/data/vit3d/test_out_conv_True_False_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:437a1b25353ce355141ce46e5276c3f6cfef3bf3b714d3ffa15923e1f786ac51 +size 524416 diff --git a/test/data/vit3d/test_out_conv_True_True_False_False_[1, 1, 1].npy b/test/data/vit3d/test_out_conv_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..028ed76 --- /dev/null +++ b/test/data/vit3d/test_out_conv_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15a055f984ae11e667934308c9a63ea9ddff91397ef49a95bf1c7163f9a0dda7 +size 524416 diff --git a/test/data/vit3d/test_out_conv_True_True_False_False_[2, 2, 2].npy b/test/data/vit3d/test_out_conv_True_True_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..f4f0d1f --- /dev/null +++ b/test/data/vit3d/test_out_conv_True_True_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:364dcac703d5d78b3c8df5c6a2541b0e961e9d39c6a2197f6448e5c2cb877581 +size 524416 diff --git a/test/data/vit3d/test_out_conv_True_True_True_False_[1, 1, 1].npy b/test/data/vit3d/test_out_conv_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..63ae438 --- /dev/null +++ b/test/data/vit3d/test_out_conv_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecfd58efc5a959d5587ee10bc85b762e9ae1987cbc7555b0c49c74c72902cc44 +size 524416 diff --git a/test/data/vit3d/test_out_conv_True_True_True_False_[2, 2, 2].npy b/test/data/vit3d/test_out_conv_True_True_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..0c11115 --- /dev/null +++ b/test/data/vit3d/test_out_conv_True_True_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:323151e46c621bc2f42908d3e604f98cf16aba3e3c2d19e2cdabf80375c7468b +size 524416 diff --git a/test/data/vit3d/test_out_lin_False_False_False_False_[1, 1, 1].npy b/test/data/vit3d/test_out_lin_False_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..60027f3 --- /dev/null +++ b/test/data/vit3d/test_out_lin_False_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aab372df5fba90d7b50e341bd9c1c610c2c4de0504ccf0f330e33feba960d4ce +size 524416 diff --git a/test/data/vit3d/test_out_lin_False_False_False_False_[2, 2, 2].npy b/test/data/vit3d/test_out_lin_False_False_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..a717e44 --- /dev/null +++ b/test/data/vit3d/test_out_lin_False_False_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f407dd2064f1472e5050ba7c96a0dd0584247d990e4a9bf500a100db57a507f0 +size 524416 diff --git a/test/data/vit3d/test_out_lin_False_False_True_False_[1, 1, 1].npy b/test/data/vit3d/test_out_lin_False_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..ca1d99b --- /dev/null +++ b/test/data/vit3d/test_out_lin_False_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:299921b94801dd1beac1fec982526c13100c11ae7e6bc5880586a6f75fdeb234 +size 524416 diff --git a/test/data/vit3d/test_out_lin_False_False_True_False_[2, 2, 2].npy b/test/data/vit3d/test_out_lin_False_False_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..c7a535d --- /dev/null +++ b/test/data/vit3d/test_out_lin_False_False_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b4dc40bd8997418d526b5e0dda76aaf68f7578c500b9a703354006eece2e588 +size 524416 diff --git a/test/data/vit3d/test_out_lin_False_True_False_False_[1, 1, 1].npy b/test/data/vit3d/test_out_lin_False_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..356e2d8 --- /dev/null +++ b/test/data/vit3d/test_out_lin_False_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1197aa7c92624f62c1c288c64b1e56772eefb85fdebba51cf74b0588c0368a7 +size 524416 diff --git a/test/data/vit3d/test_out_lin_False_True_False_False_[2, 2, 2].npy b/test/data/vit3d/test_out_lin_False_True_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..35571ab --- /dev/null +++ b/test/data/vit3d/test_out_lin_False_True_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bafc9586e90bf600442d41703bd5d9982389a63120ab3f3a6c1cef259193270a +size 524416 diff --git a/test/data/vit3d/test_out_lin_False_True_True_False_[1, 1, 1].npy b/test/data/vit3d/test_out_lin_False_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..9e16cd1 --- /dev/null +++ b/test/data/vit3d/test_out_lin_False_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fe2b41356c809de2ea2b82b0cf321b75a99e9b587905dfd8af348cb4655cda5 +size 524416 diff --git a/test/data/vit3d/test_out_lin_False_True_True_False_[2, 2, 2].npy b/test/data/vit3d/test_out_lin_False_True_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..de1b86c --- /dev/null +++ b/test/data/vit3d/test_out_lin_False_True_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c65909fc0b73ed0ced702e119f67b2ed3599b4236b7fb7008593893c72ec242b +size 524416 diff --git a/test/data/vit3d/test_out_lin_True_False_False_False_[1, 1, 1].npy b/test/data/vit3d/test_out_lin_True_False_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..53adf5a --- /dev/null +++ b/test/data/vit3d/test_out_lin_True_False_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5986e9d8466770198679ef8861b5eea18bf177ee03f229578ceb56f86c22fe02 +size 524416 diff --git a/test/data/vit3d/test_out_lin_True_False_False_False_[2, 2, 2].npy b/test/data/vit3d/test_out_lin_True_False_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..07c8c09 --- /dev/null +++ b/test/data/vit3d/test_out_lin_True_False_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3cfac0080141ec0409db0c51e34216a0ef135ca241bca421559d5577829e105 +size 524416 diff --git a/test/data/vit3d/test_out_lin_True_False_True_False_[1, 1, 1].npy b/test/data/vit3d/test_out_lin_True_False_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..3a51c4e --- /dev/null +++ b/test/data/vit3d/test_out_lin_True_False_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ef3616caba34d2c65de118a007db72457eb996714b93f40793e51013537ddd +size 524416 diff --git a/test/data/vit3d/test_out_lin_True_False_True_False_[2, 2, 2].npy b/test/data/vit3d/test_out_lin_True_False_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..5139786 --- /dev/null +++ b/test/data/vit3d/test_out_lin_True_False_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbe2fed4e18c2d88dde3a462500b031f7a084483ed6b4dcfeb141786865c6db4 +size 524416 diff --git a/test/data/vit3d/test_out_lin_True_True_False_False_[1, 1, 1].npy b/test/data/vit3d/test_out_lin_True_True_False_False_[1, 1, 1].npy new file mode 100644 index 0000000..557a3c0 --- /dev/null +++ b/test/data/vit3d/test_out_lin_True_True_False_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a4a6ec282365a031b48c52340fad4047e496d6c768f3f0f15059e6a28214d2d +size 524416 diff --git a/test/data/vit3d/test_out_lin_True_True_False_False_[2, 2, 2].npy b/test/data/vit3d/test_out_lin_True_True_False_False_[2, 2, 2].npy new file mode 100644 index 0000000..ca96125 --- /dev/null +++ b/test/data/vit3d/test_out_lin_True_True_False_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:140ee862e2f1fd4b3c0faa330169f75eafe837c3aa1750d9aa6d20238ba3be1e +size 524416 diff --git a/test/data/vit3d/test_out_lin_True_True_True_False_[1, 1, 1].npy b/test/data/vit3d/test_out_lin_True_True_True_False_[1, 1, 1].npy new file mode 100644 index 0000000..c775754 --- /dev/null +++ b/test/data/vit3d/test_out_lin_True_True_True_False_[1, 1, 1].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62a490c26b17dc98252d30a865357439d8c219ee73451ee3b606eb29c4ac2d2b +size 524416 diff --git a/test/data/vit3d/test_out_lin_True_True_True_False_[2, 2, 2].npy b/test/data/vit3d/test_out_lin_True_True_True_False_[2, 2, 2].npy new file mode 100644 index 0000000..0aefab8 --- /dev/null +++ b/test/data/vit3d/test_out_lin_True_True_True_False_[2, 2, 2].npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f39cdbb6b709c44638c546e585f53be5087f82d941882fc152acfc815413d4fb +size 524416 diff --git a/test/test_e2e_mri_denoising.py b/test/test_e2e_mri_denoising.py new file mode 100644 index 0000000..87e629f --- /dev/null +++ b/test/test_e2e_mri_denoising.py @@ -0,0 +1,199 @@ +import os +import shutil +from pathlib import Path + +import numpy as np +import pytest +import torch +from hydra import compose, initialize + +from snraware.components.setup import end_timer, start_timer +from snraware.projects.mri.denoising.inference_model import ( + load_lit_model, + load_model, + load_scripted_model, +) +from snraware.projects.mri.denoising.run import run_training +from snraware.projects.mri.denoising.run_inference import apply_model + +# ----------------------------------------------------------------- + + +class TestDenoising: + data_root = None + test_root = None + output_dir = None + + def setup_class(self): + torch.set_printoptions(precision=10) + + test_path = Path(__file__).parents[0].resolve() + self.data_root = str(test_path / "data/mri/denoising/tra") + self.test_root = str(test_path / "data/mri/denoising/test") + self.output_dir = str(test_path / "../.run/output") + + shutil.rmtree(self.output_dir, ignore_errors=True) + os.makedirs(self.output_dir, exist_ok=True) + + def teardown_class(self): + pass + + def load_and_test_saved_model(self, cfg): + # load saved model and config + model_scripted_fname = os.path.join( + cfg.logging.output_dir, f"model_{cfg.logging.project}_{cfg.logging.run_name}.pts" + ) + model_fname = os.path.join( + cfg.logging.output_dir, f"model_{cfg.logging.project}_{cfg.logging.run_name}.pth" + ) + config_fname = os.path.join( + cfg.logging.output_dir, f"config_{cfg.logging.project}_{cfg.logging.run_name}.yaml" + ) + model_lit_fname = os.path.join(cfg.logging.output_dir, "checkpoints", "last.ckpt") + + assert os.path.exists(model_scripted_fname), ( + f"Expected model file {model_scripted_fname} exists" + ) + assert os.path.exists(model_fname), f"Expected model file {model_fname} exists" + assert os.path.exists(config_fname), f"Expected config file {config_fname} exists" + assert os.path.exists(model_lit_fname), f"Expected model file {model_lit_fname} exists" + + model, _config = load_model(model_fname, config_fname) + scripted_model = load_scripted_model(model_scripted_fname) + lit_model, _ = load_lit_model(model_lit_fname, config_fname) + + # Run inference on a sample image + data = np.random.randn(128, 120, 30) + 1j * np.random.randn(128, 120, 30) + gmap = 1.2 * np.ones([128, 120, 30]) + t0 = start_timer(enable=True) + denoised_image = apply_model( + model, + data, + gmap, + scaling_factor=1.0, + cutout=tuple(cfg.dataset.cutout_shape), + overlap=(16, 16, 8), + batch_size=1, + device="cuda", + verbose=False, + ) + print( + f"Torch model, inference time {end_timer(t=t0, enable=True, verbose=False) / 1e3:.2f} sec" + ) + + t0 = start_timer(enable=True) + denoised_image_scripted = apply_model( + scripted_model, + data, + gmap, + scaling_factor=1.0, + cutout=tuple(cfg.dataset.cutout_shape), + overlap=(16, 16, 8), + batch_size=1, + device="cuda", + verbose=False, + ) + print( + f"Torch scripted model, inference time {end_timer(t=t0, enable=True, verbose=False) / 1e3:.2f} sec" + ) + + t0 = start_timer(enable=True) + denoised_image_lit = apply_model( + lit_model, + data, + gmap, + scaling_factor=1.0, + cutout=tuple(cfg.dataset.cutout_shape), + overlap=(16, 16, 8), + batch_size=1, + device="cuda", + verbose=False, + ) + print( + f"Lit model, inference time {end_timer(t=t0, enable=True, verbose=False) / 1e3:.2f} sec" + ) + + diff = np.linalg.norm(denoised_image - denoised_image_scripted) + assert diff / np.linalg.norm(denoised_image) < 1e-3 + + diff = np.linalg.norm(denoised_image - denoised_image_lit) + assert diff / np.linalg.norm(denoised_image) < 1e-3 + + @pytest.mark.gpu + @pytest.mark.slow + def test_training(self, request): + selected_markers = request.config.getoption("-m") + if "gpu" not in selected_markers or "slow" not in selected_markers: + pytest.skip("Skipping because both markers 'gpu' and 'slow' are not set") + + with initialize( + version_base=None, config_path="../src/snraware/projects/mri/denoising/configs" + ): + cfg = compose( + config_name="config", + overrides=[ + "backbone.block.cell.n_head=32", + "batch_size=1", + "optim.lr=1e-5", + "trainer.max_epochs=8", + "trainer.limit_train_batches=1.0", + "trainer.limit_val_batches=1.0", + "trainer.limit_test_batches=0.3", + "trainer.check_val_every_n_epoch=2", + "trainer.log_every_n_steps=2", + f"train_data_dir={self.data_root}", + f"test_data_dir={self.test_root}", + f"logging.output_dir={self.output_dir}", + "dataset.max_noise_level=32.0", + "seed=342080908", + ], + ) + + test_res = run_training(cfg) + test_res = test_res[0] + assert test_res["test/PSNR"] > 46, f"Expected test PSNR > 46, got {test_res['test/PSNR']}" + assert test_res["test/ssim"] < 0.2, ( + f"Expected test ssim loss < 0.2, got {test_res['test/ssim']}" + ) + + self.load_and_test_saved_model(cfg) + + @pytest.mark.gpu + def test_training_single_epoch(self, request): + selected_markers = request.config.getoption("-m") + if "gpu" not in selected_markers: + pytest.skip("Skipping because marker 'gpu' is not set") + + if os.path.exists(self.data_root) is False or os.path.exists(self.test_root) is False: + pytest.skip("Skipping because test data not found") + + with initialize( + version_base=None, config_path="../src/snraware/projects/mri/denoising/configs" + ): + cfg = compose( + config_name="config", + overrides=[ + "backbone.block.cell.n_head=16", + "batch_size=1", + "optim.lr=1e-5", + "trainer.max_epochs=1", + "trainer.limit_train_batches=0.2", + "trainer.limit_val_batches=0.1", + "trainer.limit_test_batches=0.1", + "trainer.check_val_every_n_epoch=1", + "trainer.log_every_n_steps=10", + "trainer.devices=1", + f"train_data_dir={self.data_root}", + f"test_data_dir={self.test_root}", + f"logging.output_dir={self.output_dir}", + "dataset.max_noise_level=32.0", + "seed=342080908", + ], + ) + + test_res = run_training(cfg) + assert test_res[0]["test/PSNR"] > 40, ( + f"Expected test PSNR > 40, got {test_res[0]['test/PSNR']}" + ) + + self.load_and_test_saved_model(cfg) diff --git a/test/test_e2e_simple_regression.py b/test/test_e2e_simple_regression.py new file mode 100644 index 0000000..c79325d --- /dev/null +++ b/test/test_e2e_simple_regression.py @@ -0,0 +1,297 @@ +from pathlib import Path + +import hydra +import lightning as L +import pytest +import torch +from hydra import compose, initialize +from lightning.pytorch.callbacks import ModelCheckpoint, ModelSummary, TQDMProgressBar +from omegaconf import OmegaConf + +from snraware.components.heads import PreConv2D, SimpleConv2d +from snraware.components.model import HRnet, SOAnet, Unet +from snraware.components.optim import OptimScheduler + +# ----------------------------------------------------------------- + + +def create_backbone(config, component_type, C_in, H, W, D): + """ + Create model components from configuration. + Return value is the created component and backbone_C_out for number of output channels from the backbone. + backbone_C_out can vary depending on the backbone architecture. + """ + if component_type.lower() == "hrnet": + model = HRnet(config=config, input_feature_channels=C_in, H=H, W=W, D=D) + + elif component_type.lower() == "unet": + model = Unet(config=config, input_feature_channels=C_in, H=H, W=W, D=D) + + elif component_type.lower() == "soanet": + model = SOAnet(config=config, input_feature_channels=C_in, H=H, W=W, D=D) + + else: + raise NotImplementedError(f"Not implemented: {component_type}") + + backbone_C_out = model.get_number_of_output_channels() + return model, backbone_C_out + + +class SimpleRegressionDataset(torch.utils.data.Dataset): + def __init__(self, N, C, D, H, W): + self.num = N + self.shape = [C, D, H, W] + + def __len__(self): + return self.num + + def __getitem__(self, idx): + a = torch.randn(self.shape, dtype=torch.float32) + b = 10 * a + 2.0 + return a, b + + +class SimpleRegressionDataModule(L.LightningDataModule): + def __init__(self, config, C, D, H, W): + super().__init__() + self.config = config + + self.C = C + self.D = D + self.H = H + self.W = W + + def prepare_data(self): + pass + + def setup(self, stage: str): + if stage == "fit" or stage is None: + self.train_set = SimpleRegressionDataset( + N=1024, C=self.C, D=self.D, H=self.H, W=self.W + ) + self.val_set = SimpleRegressionDataset(N=256, C=self.C, D=self.D, H=self.H, W=self.W) + if stage == "test" or stage is None: + self.test_set = SimpleRegressionDataset(N=256, C=self.C, D=self.D, H=self.H, W=self.W) + + def train_dataloader(self): + return torch.utils.data.DataLoader( + self.train_set, + batch_size=self.config.batch_size, + shuffle=True, + num_workers=self.config.num_workers, + drop_last=True, + ) + + def val_dataloader(self): + return torch.utils.data.DataLoader( + self.val_set, + batch_size=self.config.batch_size, + shuffle=False, + num_workers=self.config.num_workers, + ) + + def test_dataloader(self): + return torch.utils.data.DataLoader( + self.test_set, + batch_size=self.config.batch_size, + shuffle=False, + num_workers=self.config.num_workers, + ) + + +# ----------------------------------------------------------------- + + +class SimpleRegressionModel(torch.nn.Module): + def __init__(self, config, C_in, D, H, W, C_out): + super().__init__() + + self.config = config + + backbone_C = config.backbone.num_of_channels + + # create a pre head + self.pre_head = PreConv2D( + C=C_in, C_out=backbone_C, bias=self.config.heads.PreConv2D.conv_with_bias + ) + + # create the backbone model + backbone_config = hydra.utils.instantiate(config.backbone) + self.bk, bk_C_out = create_backbone( + backbone_config, component_type=config.backbone.name, C_in=backbone_C, H=H, W=W, D=D + ) + + # create the post head + self.post_head = SimpleConv2d(config=config, C=bk_C_out, num_classes=C_out) + + def forward(self, x): + x = self.pre_head(x) + x = self.bk(x) + x = self.post_head(x) + return x + + +class LitSimpleRegression(L.LightningModule): + def __init__(self, model, config): + super().__init__() + self.config = config + self.model = model + self.loss_fn = torch.nn.MSELoss() + self.batch_size = self.model.config.batch_size + self.scheduler_type = self.model.config.scheduler.name + + def forward(self, x): + return self.model(x) + + def training_step(self, batch): + images, targets = batch + outputs = self.model(images) + + loss = self.loss_fn(outputs, targets) + self.log( + "train_loss", + loss, + prog_bar=True, + on_step=True, + on_epoch=False, + batch_size=self.batch_size, + ) + + curr_lrs = self.optim_sched.report_lr() + self.log( + "lr", + curr_lrs[0][0], + prog_bar=True, + on_step=True, + on_epoch=False, + batch_size=self.batch_size, + ) + + return loss + + def validation_step(self, batch, batch_idx): + images, targets = batch + outputs = self.model(images) + mse = self.loss_fn(outputs, targets) + self.log( + "val_mse", mse, prog_bar=True, on_step=False, on_epoch=True, batch_size=self.batch_size + ) + + def test_step(self, batch, batch_idx): + images, targets = batch + outputs = self.model(images) + mse = self.loss_fn(outputs, targets) + self.log("test_mse", mse, batch_size=self.batch_size) + + def configure_optimizers(self): + total_num_steps = self.trainer.estimated_stepping_batches + print(f"Total number of steps: {total_num_steps}") + self.optim_sched = OptimScheduler(self.config, self.model, total_num_steps) + return [self.optim_sched.optim], [ + {"scheduler": self.optim_sched.sched, "interval": "step", "frequency": 1} + ] + + +# ----------------------------------------------------------------- + + +class TestTrain: + test_path = None + + def setup_class(self): + torch.set_printoptions(precision=10) + + test_path = Path(__file__).parents[0].resolve() + self.output_dir = str(test_path / "../.run/output") + + def teardown_class(self): + pass + + @pytest.mark.gpu + @pytest.mark.parametrize( + "backbone", + ["soanet", "hrnet", "unet"], + ) + def test_trainer(self, backbone, request): + selected_markers = request.config.getoption("-m") + if "gpu" not in selected_markers: + pytest.skip("Skipping because marker 'gpu' is not set") + + overrides = [ + f"backbone={backbone}", + "backbone.block.cell.window_size=[4,4,1]", + "backbone.block.cell.patch_size=[4,4,2]", + "backbone.block.cell.n_head=16", + "backbone.num_of_channels=16", + "batch_size=2", + "num_workers=2", + "optim.lr=2e-4", + "trainer.max_epochs=6", + "trainer.check_val_every_n_epoch=1", + "trainer.log_every_n_steps=50", + "trainer.accelerator='auto'", + "seed=54841631", + "logging.project=simple-regression-demo", + "logging.run_name=test_trainer", + f"logging.output_dir={self.output_dir}", + ] + + if backbone == "soanet": + overrides.append("backbone.downsample=false") + + with initialize( + version_base=None, config_path="../src/snraware/projects/mri/denoising/configs" + ): + cfg = compose(config_name="config", overrides=overrides) + self.config = cfg + + C_in, D, H, W = 1, 8, 32, 32 + C_out = C_in + + torch.set_float32_matmul_precision("high") + + # Print configuration + print("Training Configuration:") + print(OmegaConf.to_yaml(self.config, resolve=True)) + + if self.config.seed is not None: + L.seed_everything(self.config.seed) + + # set up dataset + data_module = SimpleRegressionDataModule(config=self.config, C=C_in, D=D, H=H, W=W) + + # callback to save the best model + best_checkpoint_callback = ModelCheckpoint( + save_top_k=1, + monitor="val_mse", + mode="min", + dirpath=self.config.logging.output_dir, + filename="best_checkpoint-{backbone}-{epoch:02d}-{val_mse:.2f}", + ) + + # set up the trainer + trainer = hydra.utils.instantiate( + self.config.trainer, + callbacks=[ + best_checkpoint_callback, + TQDMProgressBar(refresh_rate=20, leave=True), + ModelSummary(max_depth=3), + ], + logger=None, # No logger for this test + ) + + # set up model + model = SimpleRegressionModel(config=self.config, C_in=C_in, D=D, H=H, W=W, C_out=C_out) + + # set up lightning components + lit_model = LitSimpleRegression(model=model, config=self.config) + + # fit the model + trainer.fit(model=lit_model, datamodule=data_module) + + # test the model + test_res = trainer.test(model=lit_model, datamodule=data_module, ckpt_path="best") + + assert test_res[0]["test_mse"] < 0.5, ( + "Test MSE is higher than 0.5, indicating poor model performance." + ) diff --git a/test/test_loss.py b/test/test_loss.py new file mode 100644 index 0000000..b5c200d --- /dev/null +++ b/test/test_loss.py @@ -0,0 +1,126 @@ +import os +from pathlib import Path + +import numpy as np +import scipy +import torch +from colorama import Fore, Style + +from snraware.projects.loss.imaging_loss import Combined_Loss + +# ----------------------------------------------------------------- + + +class TestLoss: + test_path = None + data_root = None + data = None + inputs = None + noise = None + noise2 = None + noise3 = None + + def setup_class(self): + torch.set_printoptions(precision=10) + + Current_DIR = Path(__file__).parents[0].resolve() + + self.test_path = str(Current_DIR) + self.data_root = str(Current_DIR) + "/data/loss" + + self.data = np.load(os.path.join(self.data_root, "data_real.npy")) + 1j * np.load( + os.path.join(self.data_root, "data_imag.npy") + ) + + # create degraded version + sigmas = [1.0, 2.0, 3.0, 8.0, 20.0] + + self.inputs = list() + for sig in sigmas: + self.inputs.append(scipy.ndimage.gaussian_filter(self.data, sig)) + + self.noise = list() + self.noise2 = list() + self.noise3 = list() + for ind, sd in enumerate([1.0, 5.0, 10.0, 15.0, 40.0]): + self.noise.append(sd * np.random.randn(*self.inputs[ind].shape)) + self.noise2.append(sd * np.random.randn(*self.inputs[ind].shape)) + self.noise3.append(sd * np.random.randn(*self.inputs[ind].shape)) + + def teardown_class(self): + pass + + def run_loss(self, complex=True): + complex_losses = [ + "msssim", + "ssim", + "ssim3D", + "mse", + "rmse", + "l1", + "charbonnier", + "psnr", + "perpendicular", + "gaussian", + "gaussian3D", + "dwt", + ["mse", "rmse", "l1", "charbonnier"], + ["ssim", "ssim3D", "psnr"], + ["perpendicular", "ssim", "dwt"], + ] + real_losses = [ + "ssim", + "ssim3D", + "mse", + "rmse", + "l1", + "charbonnier", + "psnr", + "msssim", + "gaussian", + "gaussian3D", + "dwt", + ["mse", "rmse", "l1", "charbonnier"], + ["ssim", "ssim3D", "psnr"], + ["ssim", "dwt"], + ] + + for a_loss in complex_losses if complex else real_losses: + print(f"test_loss, complex {complex}, test {Fore.YELLOW}{a_loss}{Style.RESET_ALL}...") + loss_f = Combined_Loss( + losses=a_loss if isinstance(a_loss, list) else [a_loss], + loss_weights=[1.0] * 6, + complex_i=complex, + device="cpu", + ) + v = list() + for im, nn in zip(self.inputs, self.noise, strict=False): + data = np.copy(self.data) + data = np.expand_dims(np.transpose(data, [2, 0, 1]), axis=[0, 1]) + x = np.expand_dims(np.transpose(im, [2, 0, 1]), axis=[0, 1]) + n = np.expand_dims(np.transpose(nn, [2, 0, 1]), axis=[0, 1]) + + if complex: + data = np.concatenate([np.real(data), np.imag(data)], axis=1) + x = np.concatenate([np.real(x), np.imag(x)], axis=1) + n = np.concatenate([np.real(n), np.imag(n)], axis=1) + a_v = loss_f( + outputs=torch.from_numpy(x + n).type(torch.FloatTensor), + targets=torch.from_numpy(data).type(torch.FloatTensor), + ) + else: + a_v = loss_f( + outputs=torch.from_numpy(np.abs(x + n)).type(torch.FloatTensor), + targets=torch.from_numpy(np.abs(data)).type(torch.FloatTensor), + ) + + v.append(a_v.cpu().item()) + + print(f"loss values: {v}") + assert v[-1] > v[-2] and v[-2] > v[-3] and v[-3] > v[-4] and v[-4] > v[-5] + + def test_loss_complex(self): + self.run_loss(complex=True) + + def test_loss_magnitude(self): + self.run_loss(complex=False) diff --git a/test/test_snr_noise.py b/test/test_snr_noise.py new file mode 100644 index 0000000..91cd780 --- /dev/null +++ b/test/test_snr_noise.py @@ -0,0 +1,323 @@ +import math +import os +from pathlib import Path + +import cv2 +import numpy as np + +from snraware.projects.mri.snr.fftc import fft2c, ifft2c +from snraware.projects.mri.snr.filter import ( + apply_kspace_filter_2D, + generate_asymmetric_filter, + generate_symmetric_filter, +) +from snraware.projects.mri.snr.imaging import ( + apply_matrix_size_reduction_2D, + apply_resolution_reduction_2D, + zero_padding_resize_2D, +) +from snraware.projects.mri.snr.noise import NoiseGenerator, sample_complex_noise + + +# ----------------------------------------------------------------- +class TestSNRNoise: + test_path = None + data_root = None + im = np.array(None, dtype=object) + kspace = np.array(None, dtype=object) + gmap = np.array(None, dtype=object) + mask = np.array(None, dtype=object) + + def setup_class(self): + test_path = Path(__file__).parents[0].resolve() + data_root = str(test_path / "data/snr_unit_data") + + unwrappedIm_real = np.load(data_root + "/unwrappedIm_real.npy") + unwrappedIm_imag = np.load(data_root + "/unwrappedIm_imag.npy") + + unwrappedIm = unwrappedIm_real + 1j * unwrappedIm_imag + print("unwrappedIm is ", unwrappedIm.shape) + + unwrappedIm.astype(np.complex256) + + gmap = np.load(os.path.join(data_root, "gmap.npy")) + print("gmap is ", gmap.shape) + + gmap.astype(np.float64) + + mask = np.load(os.path.join(data_root, "mask.npy")) + print("mask is ", mask.shape) + + if gmap.ndim == 2: + gmap = gmap[:, :, np.newaxis] + + kspace = fft2c(unwrappedIm) + + self.im = unwrappedIm + self.kspace = kspace + self.gmap = gmap + self.mask = mask + + def teardown_class(self): + pass + + # --------------------------------------------------------------- + def test_snr_unit(self): + """Test the data has SNR unit.""" + snr_im = self.im / self.gmap + std_map = np.std(np.abs(snr_im), axis=2) + + noise_level = np.mean(std_map[self.mask > 0]) + print("noise level is ", noise_level) + assert abs(noise_level - 1) < 0.03 + + # --------------------------------------------------------------- + def test_signal_level_after_reducing_matrix_size(self): + """Test the resizing function preserves the signal level.""" + RO, E1 = self.im.shape[:2] + + # reduce the size of input image + im_low_matrix = apply_matrix_size_reduction_2D( + self.im, int(0.8 * RO), int(0.8 * E1), norm="ortho" + ) + mask_low_matrix = cv2.resize( + self.mask, dsize=[int(0.8 * RO), int(0.8 * E1)], interpolation=cv2.INTER_NEAREST + ) + + signal_level = np.abs(np.mean(self.im[self.mask > 0.1])) + signal_level_low_matrix = np.abs(np.mean(im_low_matrix[mask_low_matrix > 0.1])) + + # the signal preservation resizing should not alter signal level, except the changes due to masking + assert abs(signal_level - signal_level_low_matrix) / abs(signal_level) < 0.05 + + # --------------------------------------------------------------- + def test_signal_level_after_reducing_resolution(self): + """Test the resizing function preserves the signal level.""" + im_low_matrix = apply_matrix_size_reduction_2D( + self.im, int(0.8 * self.im.shape[1]), int(0.8 * self.im.shape[2]), norm="ortho" + ) + mask_low_matrix = cv2.resize( + self.mask, dsize=im_low_matrix.shape[1::-1], interpolation=cv2.INTER_NEAREST + ) + signal_level_low_matrix = np.abs(np.mean(im_low_matrix[mask_low_matrix > 0.1])) + + ratio_RO = 0.57 + ratio_E1 = 0.65 + im_low_matrix_low_res, _fRO, _fE1 = apply_resolution_reduction_2D( + im_low_matrix, ratio_RO, ratio_E1, snr_scaling=False, norm="backward" + ) + + signal_level_low_matrix_low_res = np.abs( + np.mean(im_low_matrix_low_res[mask_low_matrix > 0.1]) + ) + assert ( + abs(signal_level_low_matrix - signal_level_low_matrix_low_res) + / abs(signal_level_low_matrix) + < 0.02 + ) + + # --------------------------------------------------------------- + def test_signal_level_after_partial_fourier(self): + """Test the partial fourier preserves the signal level.""" + im_low_matrix = apply_matrix_size_reduction_2D( + self.im, int(0.8 * self.im.shape[1]), int(0.8 * self.im.shape[2]), norm="ortho" + ) + mask_low_matrix = cv2.resize( + self.mask, dsize=im_low_matrix.shape[1::-1], interpolation=cv2.INTER_NEAREST + ) + signal_level_low_matrix = np.abs(np.mean(im_low_matrix[mask_low_matrix > 0.1])) + + pf_ratio_RO = 0.57 + pf_ratio_E1 = 0.65 + + RO, E1 = im_low_matrix.shape[:2] + + start_RO = RO - int(pf_ratio_RO * RO) + end_RO = RO - 1 + + start_E1 = 0 + end_E1 = int(pf_ratio_E1 * E1) + + kspace = fft2c(im_low_matrix) + kspace[:start_RO, :, :] = 0 + kspace[end_RO + 1 :, :, :] = 0 + kspace[:, :start_E1, :] = 0 + kspace[:, end_E1 + 1 :, :] = 0 + + im_low_matrix_pf = ifft2c(kspace) + + signal_level_low_matrix_pf = np.abs(np.mean(im_low_matrix_pf[mask_low_matrix > 0.1])) + assert ( + abs(signal_level_low_matrix - signal_level_low_matrix_pf) + / abs(signal_level_low_matrix) + < 0.02 + ) + + # --------------------------------------------------------------- + def test_noise_creation(self): + """Test the noise creation function.""" + RO = 192 + E1 = 144 + + noise_sigma = 3.7 + nn = sample_complex_noise(noise_sigma, size=(RO, E1, 1024)) + + std_r = np.mean(np.std(np.real(nn), axis=2)) + assert abs(std_r - noise_sigma) < 0.01 + + std_i = np.mean(np.std(np.imag(nn), axis=2)) + assert abs(std_i - noise_sigma) < 0.01 + + # --------------------------------------------------------------- + def test_kspace_filter_on_RO(self): + RO, E1 = self.kspace.shape[:2] + + fRO = generate_symmetric_filter(RO, filterType="Gaussian", sigma=1.5) + fE1 = generate_symmetric_filter(E1, filterType="None", sigma=1.5) + + kspace_filtered = apply_kspace_filter_2D(self.kspace, fRO, fE1) + im_filtered = ifft2c(kspace_filtered) + + snr_im = im_filtered / self.gmap + std_map = np.std(np.abs(snr_im), axis=2) + noise_level = np.mean(std_map[self.mask > 0]) + assert abs(noise_level - 1.0) < 0.02 + + # --------------------------------------------------------------- + def test_kspace_filter(self): + RO, E1 = self.kspace.shape[:2] + + fRO = generate_symmetric_filter(RO, filterType="Gaussian", sigma=1.23) + fE1 = generate_symmetric_filter(E1, filterType="Gaussian", sigma=1.45) + + kspace_filtered = apply_kspace_filter_2D(self.kspace, fRO, fE1) + im_filtered = ifft2c(kspace_filtered) + + snr_im = im_filtered / self.gmap + std_map = np.std(np.abs(snr_im), axis=2) + noise_level = np.mean(std_map[self.mask > 0]) + assert abs(noise_level - 1.0) < 0.02 + + # --------------------------------------------------------------- + def test_partial_fourier_filter(self): + RO, E1 = self.kspace.shape[:2] + + fRO = generate_asymmetric_filter( + RO, 0, int(0.8 * RO), filterType="TapperedHanning", width=10 + ) + fE1 = generate_asymmetric_filter(E1, int(0.2 * E1), E1, filterType="None", width=20) + + kspace_filtered = apply_kspace_filter_2D(self.kspace, fRO, fE1) + im_filtered = ifft2c(kspace_filtered) + + snr_im = im_filtered / self.gmap + std_map = np.std(np.abs(snr_im), axis=2) + noise_level = np.mean(std_map[self.mask > 0]) + assert abs(noise_level - 1.0) < 0.02 + + # --------------------------------------------------------------- + def test_kspace_and_partial_fourier_filter(self): + RO, E1 = 512, 256 + + # create some filters + pf_fRO = generate_asymmetric_filter( + RO, 0, int(0.8 * RO), filterType="TapperedHanning", width=10 + ) + pf_fE1 = generate_asymmetric_filter(E1, 0, E1 - 1, filterType="TapperedHanning", width=20) + fRO = generate_symmetric_filter(RO, filterType="Gaussian", sigma=1.23) + fE1 = generate_symmetric_filter(E1, filterType="Gaussian", sigma=1.45) + + # mix the pf and kspace filters + fRO_used = fRO * pf_fRO + fE1_used = fE1 * pf_fE1 + + ratio_RO = 1 / math.sqrt(1 / RO * np.sum(fRO_used * fRO_used)) + ratio_E1 = 1 / math.sqrt(1 / E1 * np.sum(fE1_used * fE1_used)) + fRO_used *= ratio_RO + fE1_used *= ratio_E1 + + # create testing noise + noise_sigma = 11.5 + nn = sample_complex_noise(noise_sigma, size=(RO, E1, 256)) + + # apply the filter + kspace_filtered = apply_kspace_filter_2D(fft2c(nn), fRO_used, fE1_used) + nn_filtered = ifft2c(kspace_filtered) + + std_r_filtered = np.mean(np.std(np.real(nn_filtered), axis=2)) + std_i_filtered = np.mean(np.std(np.imag(nn_filtered), axis=2)) + + # the noise level should be preserved + assert abs(std_r_filtered - noise_sigma) < 0.04 + assert abs(std_i_filtered - noise_sigma) < 0.04 + + # --------------------------------------------------------------- + def test_zero_padding_resize(self): + RO, E1 = self.im.shape[:2] + + mask_resized = zero_padding_resize_2D(self.mask, 2 * RO, 2 * E1) + im_resized = zero_padding_resize_2D(self.im, 2 * RO, 2 * E1) + + std_map = np.std(np.abs(im_resized), axis=2) + noise_level = np.mean(std_map[abs(mask_resized) > 0.1]) + assert abs(noise_level - 1.0) < 0.02 + + # --------------------------------------------------------------- + def test_reduce_resolution(self): + """ + Test the resolution reduction function. + The noise level should be preserved after reducing resolution. + """ + RO, E1 = 192, 148 + + ratio_RO = 0.85 + ratio_E1 = 0.65 + + noise_sigma = 3.7 + nn = sample_complex_noise(noise_sigma, size=(RO, E1, 256)) + nn, _, _ = apply_resolution_reduction_2D(nn, ratio_RO, ratio_E1, snr_scaling=True) + + std_r = np.mean(np.std(np.real(nn), axis=2)) + std_i = np.mean(np.std(np.imag(nn), axis=2)) + + assert abs(std_r - noise_sigma) < 0.02 + assert abs(std_i - noise_sigma) < 0.02 + + # --------------------------------------------------------------- + def test_reduce_matrix_size(self): + RO, E1 = self.im.shape[:2] + + im_low_matrix = apply_matrix_size_reduction_2D(self.im, int(0.8 * RO), int(0.8 * E1)) + mask_low_matrix = cv2.resize( + self.mask, dsize=im_low_matrix.shape[:2], interpolation=cv2.INTER_NEAREST + ) + + std_map = np.std(np.abs(im_low_matrix), axis=2) + noise_level = np.mean(std_map[mask_low_matrix > 0.1]) + assert abs(noise_level - 1.0) < 0.02 + + # --------------------------------------------------------------- + def test_noise_generation(self): + REP = 1024 + + sigmas = np.linspace(1.0, 31.0, 30) + for sigma in sigmas: + print(f"Testing noise generation with sigma: {sigma}") + nn_gen = NoiseGenerator(min_noise_level=sigma, max_noise_level=sigma) + + nns, noise_sigma = nn_gen.generate(RO=32, E1=32, T=8, REP=REP) + + assert nns.shape == (32, 32, 8, REP) + assert abs(noise_sigma - sigma) < 0.01 + + std_nns_real = np.mean(np.std(np.real(nns), axis=3)) + std_nns_imag = np.mean(np.std(np.imag(nns), axis=3)) + + assert ( + abs(std_nns_real - nn_gen.actual_sigma_white_noise_real) / abs(std_nns_real) < 0.02 + ) + assert ( + abs(std_nns_imag - nn_gen.actual_sigma_white_noise_imag) / abs(std_nns_imag) < 0.02 + ) + + # --------------------------------------------------------------- diff --git a/uv.lock b/uv.lock index b0cbd7e..59f20d9 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,215 @@ version = 1 revision = 3 requires-python = ">=3.12, <4.0" +resolution-markers = [ + "sys_platform == 'darwin'", + "platform_machine == 'aarch64' and sys_platform == 'linux'", + "platform_machine != 'aarch64' and sys_platform == 'linux'", + "sys_platform != 'darwin' and sys_platform != 'linux'", +] + +[[package]] +name = "adlfs" +version = "2025.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "azure-core" }, + { name = "azure-datalake-store" }, + { name = "azure-identity" }, + { name = "azure-storage-blob" }, + { name = "fsspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/af/4d74c92254fdeabc19e54df4c9146855c2c1027bd4052477e3a27b05de54/adlfs-2025.8.0.tar.gz", hash = "sha256:6fe5857866c18990f632598273e6a8b15edc6baf8614272ede25624057b83e64", size = 52007, upload-time = "2025-08-30T12:07:40.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/e0/e98227759d88184406f757d149843fa8f3d8bab71f5a2ebd3f6f3b3970b1/adlfs-2025.8.0-py3-none-any.whl", hash = "sha256:c12a9203a31485cad19599bf2ad30d8efcf4cf0fd2446c60fcdc18604fae07b1", size = 44076, upload-time = "2025-08-30T12:07:39.694Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, + { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, + { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, + { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload-time = "2025-10-28T20:57:02.455Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload-time = "2025-10-28T20:57:04.784Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload-time = "2025-10-28T20:57:06.894Z" }, + { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload-time = "2025-10-28T20:57:08.685Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload-time = "2025-10-28T20:57:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload-time = "2025-10-28T20:57:12.563Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload-time = "2025-10-28T20:57:14.623Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload-time = "2025-10-28T20:57:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload-time = "2025-10-28T20:57:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload-time = "2025-10-28T20:57:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload-time = "2025-10-28T20:57:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload-time = "2025-10-28T20:57:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload-time = "2025-10-28T20:57:26.257Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload-time = "2025-10-28T20:57:28.349Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload-time = "2025-10-28T20:57:30.233Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload-time = "2025-10-28T20:57:32.105Z" }, + { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload-time = "2025-10-28T20:57:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload-time = "2025-10-28T20:57:36.415Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload-time = "2025-10-28T20:57:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload-time = "2025-10-28T20:57:40.122Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload-time = "2025-10-28T20:57:42.28Z" }, + { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload-time = "2025-10-28T20:57:44.869Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload-time = "2025-10-28T20:57:47.216Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload-time = "2025-10-28T20:57:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload-time = "2025-10-28T20:57:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload-time = "2025-10-28T20:57:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload-time = "2025-10-28T20:57:55.617Z" }, + { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload-time = "2025-10-28T20:57:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload-time = "2025-10-28T20:57:59.525Z" }, + { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload-time = "2025-10-28T20:58:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload-time = "2025-10-28T20:58:03.972Z" }, + { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload-time = "2025-10-28T20:58:06.189Z" }, + { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload-time = "2025-10-28T20:58:08.636Z" }, + { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload-time = "2025-10-28T20:58:11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload-time = "2025-10-28T20:58:13.358Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload-time = "2025-10-28T20:58:15.339Z" }, + { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload-time = "2025-10-28T20:58:17.693Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload-time = "2025-10-28T20:58:20.113Z" }, + { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload-time = "2025-10-28T20:58:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload-time = "2025-10-28T20:58:24.672Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload-time = "2025-10-28T20:58:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload-time = "2025-10-28T20:58:29.787Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload-time = "2025-10-28T20:58:32.529Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload-time = "2025-10-28T20:58:34.618Z" }, + { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload-time = "2025-10-28T20:58:38.835Z" }, + { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload-time = "2025-10-28T20:58:41.507Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload-time = "2025-10-28T20:58:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload-time = "2025-10-28T20:58:45.787Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload-time = "2025-10-28T20:58:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload-time = "2025-10-28T20:58:50.642Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload-time = "2025-10-28T20:58:52.782Z" }, +] + +[[package]] +name = "aiohttp-retry" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/61/ebda4d8e3d8cfa1fd3db0fb428db2dd7461d5742cea35178277ad180b033/aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1", size = 13608, upload-time = "2024-11-06T10:44:54.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/99/84ba7273339d0f3dfa57901b846489d2e5c2cd731470167757f1935fffbd/aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54", size = 9981, upload-time = "2024-11-06T10:44:52.917Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "amqp" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } + +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, +] + +[[package]] +name = "argcomplete" +version = "3.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, +] + +[[package]] +name = "asyncssh" +version = "2.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/b8/065c20bb5c9b8991648c0f25b13e445b4f51556cc3fdd0ad13ce4787c156/asyncssh-2.21.1.tar.gz", hash = "sha256:9943802955e2131536c2b1e71aacc68f56973a399937ed0b725086d7461c990c", size = 540515, upload-time = "2025-09-28T16:36:19.468Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/89/4a9a61bc120ca68bce92b0ea176ddc0e550e58c60ab820603bd5246e7261/asyncssh-2.21.1-py3-none-any.whl", hash = "sha256:f218f9f303c78df6627d0646835e04039a156d15e174ad63c058d62de61e1968", size = 375529, upload-time = "2025-09-28T16:36:17.68Z" }, +] + +[[package]] +name = "atpublic" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/78/a7c9b6d6581353204a7a099567783dd3352405b1662988892b9e67039c6c/atpublic-6.0.2.tar.gz", hash = "sha256:f90dcd17627ac21d5ce69e070d6ab89fb21736eb3277e8b693cc8484e1c7088c", size = 17708, upload-time = "2025-09-24T18:30:13.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/da/8916af0a074d24354d685fe4178a52d3fafd07b62e6f81124fdeac15594d/atpublic-6.0.2-py3-none-any.whl", hash = "sha256:156cfd3854e580ebfa596094a018fe15e4f3fa5bade74b39c3dabb54f12d6565", size = 6423, upload-time = "2025-09-24T18:30:15.214Z" }, +] [[package]] name = "attrs" @@ -11,6 +220,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] +[[package]] +name = "azure-core" +version = "1.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c4/d4ff3bc3ddf155156460bff340bbe9533f99fac54ddea165f35a8619f162/azure_core-1.36.0.tar.gz", hash = "sha256:22e5605e6d0bf1d229726af56d9e92bc37b6e726b141a18be0b4d424131741b7", size = 351139, upload-time = "2025-10-15T00:33:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/3c/b90d5afc2e47c4a45f4bba00f9c3193b0417fad5ad3bb07869f9d12832aa/azure_core-1.36.0-py3-none-any.whl", hash = "sha256:fee9923a3a753e94a259563429f3644aaf05c486d45b1215d098115102d91d3b", size = 213302, upload-time = "2025-10-15T00:33:51.058Z" }, +] + +[[package]] +name = "azure-datalake-store" +version = "0.0.53" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "msal" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/ff/61369d06422b5ac48067215ff404841342651b14a89b46c8d8e1507c8f17/azure-datalake-store-0.0.53.tar.gz", hash = "sha256:05b6de62ee3f2a0a6e6941e6933b792b800c3e7f6ffce2fc324bc19875757393", size = 71430, upload-time = "2023-05-10T21:17:05.665Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/2a/75f56b14f115189155cf12e46b366ad1fe3357af5a1a7c09f7446662d617/azure_datalake_store-0.0.53-py2.py3-none-any.whl", hash = "sha256:a30c902a6e360aa47d7f69f086b426729784e71c536f330b691647a51dc42b2b", size = 55308, upload-time = "2023-05-10T21:17:02.629Z" }, +] + +[[package]] +name = "azure-identity" +version = "1.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "msal" }, + { name = "msal-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/8d/1a6c41c28a37eab26dc85ab6c86992c700cd3f4a597d9ed174b0e9c69489/azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456", size = 279826, upload-time = "2025-10-06T20:30:02.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" }, +] + +[[package]] +name = "azure-storage-blob" +version = "12.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/7c/2fd872e11a88163f208b9c92de273bf64bb22d0eef9048cc6284d128a77a/azure_storage_blob-12.27.1.tar.gz", hash = "sha256:a1596cc4daf5dac9be115fcb5db67245eae894cf40e4248243754261f7b674a6", size = 597579, upload-time = "2025-10-29T12:27:16.185Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9e/1c90a122ea6180e8c72eb7294adc92531b0e08eb3d2324c2ba70d37f4802/azure_storage_blob-12.27.1-py3-none-any.whl", hash = "sha256:65d1e25a4628b7b6acd20ff7902d8da5b4fde8e46e19c8f6d213a3abc3ece272", size = 428954, upload-time = "2025-10-29T12:27:18.072Z" }, +] + [[package]] name = "babel" version = "2.17.0" @@ -34,6 +301,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" }, ] +[[package]] +name = "billiard" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/6a/1405343016bce8354b29d90aad6b0bf6485b5e60404516e4b9a3a9646cf0/billiard-4.2.2.tar.gz", hash = "sha256:e815017a062b714958463e07ba15981d802dc53d41c5b69d28c5a7c238f8ecf3", size = 155592, upload-time = "2025-09-20T14:44:40.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/80/ef8dff49aae0e4430f81842f7403e14e0ca59db7bbaf7af41245b67c6b25/billiard-4.2.2-py3-none-any.whl", hash = "sha256:4bc05dcf0d1cc6addef470723aac2a6232f3c7ed7475b0b580473a9145829457", size = 86896, upload-time = "2025-09-20T14:44:39.157Z" }, +] + +[[package]] +name = "bokeh" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "jinja2" }, + { name = "narwhals" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pyyaml" }, + { name = "tornado", marker = "sys_platform != 'emscripten'" }, + { name = "xyzservices" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/bd/8455ecfaa8100dbfbb2af40061c689a7a9c808f4f8c9582f0efd0c8c9a19/bokeh-3.8.0.tar.gz", hash = "sha256:bfdf5e9df910653b097f70cd38f4c2399d91af6e54a618126e2387cc33c9ec03", size = 6529746, upload-time = "2025-08-29T12:16:55.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/9a/8e641b5415e12036d8a206147b8229d917a767b7d939521458d90feddcf5/bokeh-3.8.0-py3-none-any.whl", hash = "sha256:117c5e559231ad39fef87891a1a1b62b3bfefbaa47d536023537338f46015841", size = 7205343, upload-time = "2025-08-29T12:16:52.77Z" }, +] + [[package]] name = "bracex" version = "2.6" @@ -43,6 +340,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, ] +[[package]] +name = "celery" +version = "5.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "billiard" }, + { name = "click" }, + { name = "click-didyoumean" }, + { name = "click-plugins" }, + { name = "click-repl" }, + { name = "kombu" }, + { name = "python-dateutil" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" }, +] + [[package]] name = "certifi" version = "2025.10.5" @@ -52,6 +368,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, ] +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -121,6 +494,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, ] +[[package]] +name = "click-didyoumean" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, +] + +[[package]] +name = "click-plugins" +version = "1.1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -130,6 +540,93 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "colorlog" +version = "6.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, +] + +[[package]] +name = "configobj" +version = "5.0.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/c4/c7f9e41bc2e5f8eeae4a08a01c91b2aea3dfab40a3e14b25e87e7db8d501/configobj-5.0.9.tar.gz", hash = "sha256:03c881bbf23aa07bccf1b837005975993c4ab4427ba57f959afdd9d1a2386848", size = 101518, upload-time = "2024-09-21T12:47:46.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/c4/0679472c60052c27efa612b4cd3ddd2a23e885dcdc73461781d2c802d39e/configobj-5.0.9-py2.py3-none-any.whl", hash = "sha256:1ba10c5b6ee16229c79a05047aeda2b55eb4e80d7c7d8ecf17ec1ca600c79882", size = 35615, upload-time = "2024-11-26T14:03:32.972Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + [[package]] name = "coverage" version = "7.11.0" @@ -205,66 +702,920 @@ wheels = [ ] [[package]] -name = "ghp-import" -version = "2.1.0" +name = "cryptography" +version = "46.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, ] [[package]] -name = "griffe" -version = "1.14.0" +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "dictdiffer" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/7b/35cbccb7effc5d7e40f4c55e2b79399e1853041997fcda15c9ff160abba0/dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578", size = 31513, upload-time = "2021-07-22T13:24:29.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/ef/4cb333825d10317a36a1154341ba37e6e9c087bac99c1990ef07ffdb376f/dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595", size = 16754, upload-time = "2021-07-22T13:24:26.783Z" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "dpath" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/ce/e1fd64d36e4a5717bd5e6b2ad188f5eaa2e902fde871ea73a79875793fc9/dpath-2.2.0.tar.gz", hash = "sha256:34f7e630dc55ea3f219e555726f5da4b4b25f2200319c8e6902c394258dd6a3e", size = 28266, upload-time = "2024-06-12T22:08:03.686Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/d1/8952806fbf9583004ab479d8f58a9496c3d35f6b6009ddd458bdd9978eaf/dpath-2.2.0-py3-none-any.whl", hash = "sha256:b330a375ded0a0d2ed404440f6c6a715deae5313af40bbb01c8a41d891900576", size = 17618, upload-time = "2024-06-12T22:08:01.881Z" }, +] + +[[package]] +name = "dulwich" +version = "0.24.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama" }, + { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/98/b6b8bf80e61d1aacf59aad4e45e6a1e7c39751c7bcaeb1136821bae82cd8/dulwich-0.24.8.tar.gz", hash = "sha256:c9f4748bbcca56fb57458c71c0d30e2351ac15e0583d428c739c09228be68f05", size = 998526, upload-time = "2025-10-29T22:31:26.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, + { url = "https://files.pythonhosted.org/packages/d5/56/344962feb31a5923235f06f85c55754664603a07976f35b32d0a0beae1a4/dulwich-0.24.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ac85e3ea42878fa91b6a9282652327df54f5abea4aaf674036e1000608a15f0", size = 1230461, upload-time = "2025-10-29T22:30:55.784Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ba/3b105687dd924a7e07e97a75cdcdbc548c34862bb6b5da6c53910bd4b88c/dulwich-0.24.8-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b31e08bcd0a4eb29915987fa5273a827cccca7ee83eb27ef17bd5899f5668055", size = 1313289, upload-time = "2025-10-29T22:30:57.127Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/732b159f9724ddc6b15e93056fbfac27b9f5f7fff8b2f6524c0815c901da/dulwich-0.24.8-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:267e79367cd6f438091248c1e826c6cf7abd84d5b641b9fe46fbc4d9119e11ed", size = 1342865, upload-time = "2025-10-29T22:30:58.851Z" }, + { url = "https://files.pythonhosted.org/packages/49/ab/42b037616ebbc3b31723518f53eb1cb667b3504745f9a8089a062ae3fef9/dulwich-0.24.8-cp312-cp312-win32.whl", hash = "sha256:6a51a41e858e0427b14bb19df7ac1207275dd6b5cc1976f54710bf15cb4c5614", size = 900605, upload-time = "2025-10-29T22:31:00.435Z" }, + { url = "https://files.pythonhosted.org/packages/50/f9/cfc561df968ba3dcadf1666437502c6245ee823c8f12a0bae1f5d2b4df03/dulwich-0.24.8-cp312-cp312-win_amd64.whl", hash = "sha256:6016e3f7a0f1dd5e19df14b772cb8f42bfde0cd55c504642c05e1e8328de21e3", size = 917763, upload-time = "2025-10-29T22:31:02.019Z" }, + { url = "https://files.pythonhosted.org/packages/4a/95/cb0e005f055bdf84d5e9dd45e657ecea94fd8279f61a4921eafbeec569af/dulwich-0.24.8-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:19be46710a9d810a66d4a925754abf3818ba22d61134e7d7e1d7b1585c9445b6", size = 1339439, upload-time = "2025-10-29T22:31:03.249Z" }, + { url = "https://files.pythonhosted.org/packages/d5/3c/33de9890a9c02b3f026fccb83a5672434cc5d667af53ac042812d29182e0/dulwich-0.24.8-cp313-cp313-android_21_x86_64.whl", hash = "sha256:17d8223cc69cf79ddd7a2f0893e223708f7efc2ea372f919ddcc0d852b3f5d06", size = 1339433, upload-time = "2025-10-29T22:31:04.679Z" }, + { url = "https://files.pythonhosted.org/packages/57/e3/5f3b699486fddf021bb2af2edc7cdde37e5124682813b82c53be12864648/dulwich-0.24.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a0a780b512d4144336eac2f1f6b982eb78c313442e88ba0db853a224e2b918ef", size = 1230834, upload-time = "2025-10-29T22:31:06.556Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9a/45fdf7e12a5b1cc9971a14644c4125eda8279f70f89dd1b38259700cf3e3/dulwich-0.24.8-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2e3a9a713fda94f3216da4743db3cc8d670330f44c4d98580ac4600242dba2c4", size = 1313152, upload-time = "2025-10-29T22:31:07.898Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6d/459aeaa32c503a51649179e43ea4ffb35bee9b4ca62955e8dcce6abefab1/dulwich-0.24.8-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b03474c16bcfa3524241b4ae89007931d79ac37c0b19e059133a6e63b7039845", size = 1341924, upload-time = "2025-10-29T22:31:09.661Z" }, + { url = "https://files.pythonhosted.org/packages/44/15/a02a2a7d29c28440a54f9b8494d87022bf215ca63ac9032e8c15f9f743a8/dulwich-0.24.8-cp313-cp313-win32.whl", hash = "sha256:ec0f62538b6fb26cdd1b2fb70788ccfdb17df26a4ba1ca70e623e196c4004f5c", size = 900724, upload-time = "2025-10-29T22:31:10.946Z" }, + { url = "https://files.pythonhosted.org/packages/af/15/0a589d57e79b15a33e09849f2273d73edce623b4e6ac2e8f37573cacad51/dulwich-0.24.8-cp313-cp313-win_amd64.whl", hash = "sha256:16e335bce0d5192d476db0ca81de1f90fb56863ad7d0b985b0333a8194c73c64", size = 917718, upload-time = "2025-10-29T22:31:12.211Z" }, + { url = "https://files.pythonhosted.org/packages/68/ea/9227599be958b387d678cdd1d371b97dbc928597848dc25be41a664932c0/dulwich-0.24.8-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:19855e8a0ce299cdcdafdc8bc4f6653bea9e02124a5022e13cda8103fb36912d", size = 1337599, upload-time = "2025-10-29T22:31:14.004Z" }, + { url = "https://files.pythonhosted.org/packages/48/f8/c308bf0e4ad47f66292bbca5aaa2b019ee73d71f6ce3ef41ef0e5d7d6d58/dulwich-0.24.8-cp314-cp314-android_24_x86_64.whl", hash = "sha256:da03c7a6629b7ed37e7139739a175f2c9678080a45444418c54ab28d2ec6524b", size = 1337590, upload-time = "2025-10-29T22:31:15.746Z" }, + { url = "https://files.pythonhosted.org/packages/28/d7/ace65f3aa8af1994443dfa741311e8995e0e269397d8336533f064458404/dulwich-0.24.8-py3-none-any.whl", hash = "sha256:6ffdd616135bcb31eb2edcccf82d4408720f1db69f596f687ffa2d26c2f5e6f4", size = 566478, upload-time = "2025-10-29T22:31:25.124Z" }, ] [[package]] -name = "idna" -version = "3.11" +name = "dvc" +version = "3.63.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +dependencies = [ + { name = "attrs" }, + { name = "celery" }, + { name = "colorama" }, + { name = "configobj" }, + { name = "distro" }, + { name = "dpath" }, + { name = "dulwich" }, + { name = "dvc-data" }, + { name = "dvc-http" }, + { name = "dvc-objects" }, + { name = "dvc-render" }, + { name = "dvc-studio-client" }, + { name = "dvc-task" }, + { name = "flatten-dict" }, + { name = "flufl-lock" }, + { name = "fsspec" }, + { name = "funcy" }, + { name = "grandalf" }, + { name = "gto" }, + { name = "hydra-core" }, + { name = "iterative-telemetry" }, + { name = "kombu" }, + { name = "networkx" }, + { name = "omegaconf" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "psutil" }, + { name = "pydot" }, + { name = "pygtrie" }, + { name = "pyparsing" }, + { name = "requests" }, + { name = "rich" }, + { name = "ruamel-yaml" }, + { name = "scmrepo" }, + { name = "shortuuid" }, + { name = "shtab" }, + { name = "tabulate" }, + { name = "tomlkit" }, + { name = "tqdm" }, + { name = "voluptuous" }, + { name = "zc-lockfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/d5/88ba8456536e9550ab44bdd3d430351626c20cb08a0593840e319933d773/dvc-3.63.0.tar.gz", hash = "sha256:b845cf8825e1b07d427e8d04754a0e01f141708bcbb1dae91e18db9e640ae68e", size = 668892, upload-time = "2025-09-02T13:35:35.66Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/35/15/15e4d0c4872b3a55c7a0b94c3f31df6bf28a6610d977ad96f1872e36034c/dvc-3.63.0-py3-none-any.whl", hash = "sha256:14e2cf206ee1f65a2afddf2b756bbc25816b32177c56067e1ccce2c65fbdb89f", size = 466193, upload-time = "2025-09-02T13:35:33.001Z" }, +] + +[package.optional-dependencies] +azure = [ + { name = "dvc-azure" }, ] [[package]] -name = "iniconfig" -version = "2.3.0" +name = "dvc-azure" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +dependencies = [ + { name = "adlfs" }, + { name = "azure-identity" }, + { name = "dvc" }, + { name = "knack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/cd/5cf47247c82e7b8eba42890a23e6700f4baade329d24722140d290c32dc3/dvc-azure-3.1.0.tar.gz", hash = "sha256:52cbc70d5414b50219b3db0a16f68ad25daba76e3f220aebe4e49b3c6498ae20", size = 15802, upload-time = "2024-02-05T07:38:23.791Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/33/48/5caa31494c937d649a65831a9f8de210f08ae6d8ad8d76afa88d664ddecb/dvc_azure-3.1.0-py3-none-any.whl", hash = "sha256:07295bedc5c596c38b7cc1a4353307cd045a322fef59eae3e64e95b143d495f2", size = 12772, upload-time = "2024-02-05T07:38:21.748Z" }, ] [[package]] -name = "jinja2" -version = "3.1.6" +name = "dvc-data" +version = "3.16.12" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markupsafe" }, + { name = "attrs" }, + { name = "dictdiffer" }, + { name = "diskcache" }, + { name = "dvc-objects" }, + { name = "fsspec" }, + { name = "orjson", marker = "implementation_name == 'cpython'" }, + { name = "pygtrie" }, + { name = "sqltrie" }, + { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/49/9476147025cbabfa2695700dd0b4564bbeee085729bb2faa221605d85e3c/dvc_data-3.16.12.tar.gz", hash = "sha256:f92cc03ffdddb5bd3a7a7da78d595dec6915311256a4cfefe250967d6ce3d194", size = 81910, upload-time = "2025-08-18T11:27:33.983Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7c/2acac71d2366328ae9da1a0b68292fed07aef28ed6114ce3320f3253c8a2/dvc_data-3.16.12-py3-none-any.whl", hash = "sha256:39c183caecd142cf44bc16186c8e5ef3bb4d739111e41f80682c999db30b8cee", size = 78201, upload-time = "2025-08-18T11:27:32.353Z" }, ] [[package]] -name = "lxml" -version = "6.0.2" +name = "dvc-http" +version = "2.32.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +dependencies = [ + { name = "aiohttp-retry" }, + { name = "fsspec", extra = ["http"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/e6/4fb38ab911a9d90fbe2c7759c430814fe2253760304a9de0d3ebd6e27c20/dvc-http-2.32.0.tar.gz", hash = "sha256:f714f8435634aab943c625f659ddac1188c6ddaf3ff161b39715b83ff39637fc", size = 14603, upload-time = "2023-12-13T10:53:16.393Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, + { url = "https://files.pythonhosted.org/packages/89/04/2fe178c037c69cce0c8e9863f90512ca46aa2c763d67bc0e0e0fdac146ae/dvc_http-2.32.0-py3-none-any.whl", hash = "sha256:1bfd57a9eae3cbfa1db564d90d87003841921a644ab35f3f7735c641cc93d72e", size = 12597, upload-time = "2023-12-13T10:53:14.925Z" }, +] + +[[package]] +name = "dvc-objects" +version = "5.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/d4/61678357b6ce0661249e6f09069859b5b1bcc4eeede6a869bab7cae2b546/dvc_objects-5.1.2.tar.gz", hash = "sha256:3d4ac3ece4addf280dd1e06bda58b3f7864eb877de42d1e1f94c501d89b31440", size = 43215, upload-time = "2025-09-27T13:50:08.861Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/96/b73f8dab522e4116dbcef83fab5e5aa1ada263e246c6f0126c7fd04be6ec/dvc_objects-5.1.2-py3-none-any.whl", hash = "sha256:73f1644fceb65f0908e6de974e0207f3d9daa1ae1b834f78198cd1feca9488d1", size = 33651, upload-time = "2025-09-27T13:50:07.04Z" }, +] + +[[package]] +name = "dvc-render" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/15/605312dbdc0931547987ee25a9a3f6fcabf48ca1436039abcd524156b8e2/dvc-render-1.0.2.tar.gz", hash = "sha256:40d1cd81760daf34b48fa8362b5002fcbe415e3cdbcf42369b6347d01497ffc0", size = 37772, upload-time = "2024-04-10T14:29:01.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/e4/d79fe332346a47b5468751292c0e45e496e10441e548ef447df1b6adb018/dvc_render-1.0.2-py3-none-any.whl", hash = "sha256:7e3e3cec1200fda41a99984190f14871f3cb878db7f94c853305056f69614ddb", size = 22070, upload-time = "2024-04-10T14:28:58.351Z" }, +] + +[[package]] +name = "dvc-studio-client" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dulwich" }, + { name = "requests" }, + { name = "voluptuous" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/52/f00bc978bfa313929221df1b6a1d82256b1c2727c55594dbbf9520f0adfd/dvc_studio_client-0.22.0.tar.gz", hash = "sha256:45d554a0386dd18bdfe17968e93f9b075563c888088b51bfa58713f64ed58ac8", size = 29432, upload-time = "2025-07-28T16:23:52.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/8b/42cb2c96555cf63b5c57c3b21f3901bb30a9ae963ecba86a8265b61eee7d/dvc_studio_client-0.22.0-py3-none-any.whl", hash = "sha256:99cb8874a1e5fc05de126a36a82b421f7af5c36d23c22024284733fc4d98029b", size = 16432, upload-time = "2025-07-28T16:23:51.256Z" }, +] + +[[package]] +name = "dvc-task" +version = "0.40.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "celery" }, + { name = "funcy" }, + { name = "kombu" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "shortuuid" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/ef/da712c4d9c7d6cacac27d7b2779e6a97c3381ef2c963c33719d39113b6a3/dvc_task-0.40.2.tar.gz", hash = "sha256:909af541bf5fde83439da56c4c0ebac592af178a59b702708fadaacfd6e7b704", size = 36147, upload-time = "2024-10-08T12:47:31.915Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/bf/f23e8eff38556d479ab421f8b9ac9a9a0b44f8400098c934dce0607da1de/dvc_task-0.40.2-py3-none-any.whl", hash = "sha256:3891b94cf9d349072ee32ce47217b73530b1905e6dd5a1e378bd74afc8b4c030", size = 21392, upload-time = "2024-10-08T12:47:30.317Z" }, +] + +[[package]] +name = "einops" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/81/df4fbe24dff8ba3934af99044188e20a98ed441ad17a274539b74e82e126/einops-0.8.1.tar.gz", hash = "sha256:de5d960a7a761225532e0f1959e5315ebeafc0cd43394732f103ca44b9837e84", size = 54805, upload-time = "2025-02-09T03:17:00.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", size = 64359, upload-time = "2025-02-09T03:17:01.998Z" }, +] + +[[package]] +name = "entrypoints" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/8d/a7121ffe5f402dc015277d2d31eb82d2187334503a011c18f2e78ecbb9b2/entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4", size = 13974, upload-time = "2022-02-02T21:30:28.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/a8/365059bbcd4572cbc41de17fd5b682be5868b218c3c5479071865cab9078/entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f", size = 5294, upload-time = "2022-02-02T21:30:26.024Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, +] + +[[package]] +name = "flatten-dict" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/c6/5fe21639369f2ea609c964e20870b5c6c98a134ef12af848a7776ddbabe3/flatten-dict-0.4.2.tar.gz", hash = "sha256:506a96b6e6f805b81ae46a0f9f31290beb5fa79ded9d80dbe1b7fa236ab43076", size = 10362, upload-time = "2021-08-08T09:56:51.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl", hash = "sha256:7e245b20c4c718981212210eec4284a330c9f713e632e98765560e05421e48ad", size = 9656, upload-time = "2021-08-08T09:56:54.313Z" }, +] + +[[package]] +name = "flufl-lock" +version = "8.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "atpublic" }, + { name = "psutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/78/80f98f67deb8ba9b67e00a91ceb1ded5a7b8eb2b7801b89625d3396fc9d4/flufl_lock-8.2.0.tar.gz", hash = "sha256:15b333c35fab1a36b223840057258aeb4cd79f0fbaf82c144f23cdf6cf14d5e3", size = 33514, upload-time = "2025-05-08T23:32:51.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/a1/15e07d6c8b33485c4eed49a170faea16d4c6c4fd9f2cb6242adfaed180e7/flufl_lock-8.2.0-py3-none-any.whl", hash = "sha256:59361e277a50efceff288b8e9d36dd43254ad11a88d42d7716195b848a3fce7c", size = 11251, upload-time = "2025-05-08T23:32:49.939Z" }, +] + +[[package]] +name = "fonttools" +version = "4.60.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953, upload-time = "2025-09-29T21:11:39.616Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706, upload-time = "2025-09-29T21:11:41.826Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716, upload-time = "2025-09-29T21:11:43.893Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175, upload-time = "2025-09-29T21:11:46.439Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031, upload-time = "2025-09-29T21:11:48.977Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966, upload-time = "2025-09-29T21:11:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750, upload-time = "2025-09-29T21:11:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026, upload-time = "2025-09-29T21:11:58.852Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777, upload-time = "2025-09-29T21:12:01.22Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080, upload-time = "2025-09-29T21:12:03.785Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082, upload-time = "2025-09-29T21:12:06.382Z" }, + { url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125, upload-time = "2025-09-29T21:12:09.314Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454, upload-time = "2025-09-29T21:12:11.931Z" }, + { url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495, upload-time = "2025-09-29T21:12:15.241Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028, upload-time = "2025-09-29T21:12:17.96Z" }, + { url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200, upload-time = "2025-09-29T21:12:20.14Z" }, + { url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" }, + { url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" }, + { url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" }, + { url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" }, + { url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285, upload-time = "2025-10-30T14:58:44.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966, upload-time = "2025-10-30T14:58:42.53Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] +tqdm = [ + { name = "tqdm" }, +] + +[[package]] +name = "funcy" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/b8/c6081521ff70afdff55cd9512b2220bbf4fa88804dae51d1b57b4b58ef32/funcy-2.0.tar.gz", hash = "sha256:3963315d59d41c6f30c04bc910e10ab50a3ac4a225868bfa96feed133df075cb", size = 537931, upload-time = "2023-03-28T06:22:46.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl", hash = "sha256:53df23c8bb1651b12f095df764bfb057935d49537a56de211b098f4c79614bb0", size = 30891, upload-time = "2023-03-28T06:22:42.576Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + +[[package]] +name = "grandalf" +version = "0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/0e/4ac934b416857969f9135dec17ac80660634327e003a870835dd1f382659/grandalf-0.8.tar.gz", hash = "sha256:2813f7aab87f0d20f334a3162ccfbcbf085977134a17a5b516940a93a77ea974", size = 38128, upload-time = "2023-01-26T07:37:06.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/30/44c7eb0a952478dbb5f2f67df806686d6a7e4b19f6204e091c4f49dc7c69/grandalf-0.8-py3-none-any.whl", hash = "sha256:793ca254442f4a79252ea9ff1ab998e852c1e071b863593e5383afee906b4185", size = 41802, upload-time = "2023-01-10T15:16:19.753Z" }, +] + +[[package]] +name = "griffe" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, +] + +[[package]] +name = "gto" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "entrypoints" }, + { name = "funcy" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "rich" }, + { name = "ruamel-yaml" }, + { name = "scmrepo" }, + { name = "semver" }, + { name = "tabulate" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/06/d2ec91a6c1e6b1a55c419e8599df7ac3430323a1bb1e5c01a1f83f8ecb64/gto-1.9.0.tar.gz", hash = "sha256:3beb5c652a98585ad083dbb6879a580ffe926271661d9b7a50e428cd591005ea", size = 58999, upload-time = "2025-10-08T17:05:28.568Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/b3/6086ab9cfd4a27517a1269e8b7c48621beb79ccc0affd2485b9747976bfe/gto-1.9.0-py3-none-any.whl", hash = "sha256:e94371a67c25256f973722c5891e551ca3cd8cc25864dcf468f2b16e6bcca6b8", size = 45038, upload-time = "2025-10-08T17:05:26.947Z" }, +] + +[[package]] +name = "h5py" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, + { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, + { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, + { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, + { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368, upload-time = "2025-10-16T10:35:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793, upload-time = "2025-10-16T10:35:05.623Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199, upload-time = "2025-10-16T10:35:08.972Z" }, + { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224, upload-time = "2025-10-16T10:35:12.808Z" }, + { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207, upload-time = "2025-10-16T10:35:16.24Z" }, + { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426, upload-time = "2025-10-16T10:35:19.831Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704, upload-time = "2025-10-16T10:35:22.658Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544, upload-time = "2025-10-16T10:35:25.695Z" }, +] + +[[package]] +name = "hydra-colorlog" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorlog" }, + { name = "hydra-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/fc/ab5ca078d5184ae3280d5654734860198c643cd2caaaaaece63283d9f76d/hydra-colorlog-1.2.0.tar.gz", hash = "sha256:d44f85008fabd2448c7e3b496c31b44d7610560f6fff74f3673afaa949870899", size = 3060, upload-time = "2022-05-17T22:16:25.407Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/50/a08b0453c18088da369991d015ef3902a9ab532b515a41f1bfbe1394854d/hydra_colorlog-1.2.0-py3-none-any.whl", hash = "sha256:33d05fc11ca9bc7a5d69cfb3c8fb395a1bc52fa1dfe7aca6a6f5ffb57f6e7c4b", size = 3638, upload-time = "2022-05-17T22:16:23.598Z" }, +] + +[[package]] +name = "hydra-core" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "omegaconf" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/8e/07e42bc434a847154083b315779b0a81d567154504624e181caf2c71cd98/hydra-core-1.3.2.tar.gz", hash = "sha256:8a878ed67216997c3e9d88a8e72e7b4767e81af37afb4ea3334b269a4390a824", size = 3263494, upload-time = "2023-02-23T18:33:43.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl", hash = "sha256:fa0238a9e31df3373b35b0bfb672c34cc92718d21f81311d8996a16de1141d8b", size = 154547, upload-time = "2023-02-23T18:33:40.801Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imageio" +version = "2.37.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600, upload-time = "2025-11-04T14:29:39.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646, upload-time = "2025-11-04T14:29:37.948Z" }, +] + +[[package]] +name = "imageio-ffmpeg" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/bd/c3343c721f2a1b0c9fc71c1aebf1966a3b7f08c2eea8ed5437a2865611d6/imageio_ffmpeg-0.6.0.tar.gz", hash = "sha256:e2556bed8e005564a9f925bb7afa4002d82770d6b08825078b7697ab88ba1755", size = 25210, upload-time = "2025-01-16T21:34:32.747Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl", hash = "sha256:9d2baaf867088508d4a3458e61eeb30e945c4ad8016025545f66c4b5aaef0a61", size = 24932969, upload-time = "2025-01-16T21:34:20.464Z" }, + { url = "https://files.pythonhosted.org/packages/40/5c/f3d8a657d362cc93b81aab8feda487317da5b5d31c0e1fdfd5e986e55d17/imageio_ffmpeg-0.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b1ae3173414b5fc5f538a726c4e48ea97edc0d2cdc11f103afee655c463fa742", size = 21113891, upload-time = "2025-01-16T21:34:00.277Z" }, + { url = "https://files.pythonhosted.org/packages/33/e7/1925bfbc563c39c1d2e82501d8372734a5c725e53ac3b31b4c2d081e895b/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1d47bebd83d2c5fc770720d211855f208af8a596c82d17730aa51e815cdee6dc", size = 25632706, upload-time = "2025-01-16T21:33:53.475Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2d/43c8522a2038e9d0e7dbdf3a61195ecc31ca576fb1527a528c877e87d973/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c7e46fcec401dd990405049d2e2f475e2b397779df2519b544b8aab515195282", size = 29498237, upload-time = "2025-01-16T21:34:13.726Z" }, + { url = "https://files.pythonhosted.org/packages/a0/13/59da54728351883c3c1d9fca1710ab8eee82c7beba585df8f25ca925f08f/imageio_ffmpeg-0.6.0-py3-none-win32.whl", hash = "sha256:196faa79366b4a82f95c0f4053191d2013f4714a715780f0ad2a68ff37483cc2", size = 19652251, upload-time = "2025-01-16T21:34:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl", hash = "sha256:02fa47c83703c37df6bfe4896aab339013f62bf02c5ebf2dce6da56af04ffc0a", size = 31246824, upload-time = "2025-01-16T21:34:28.6Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "intel-cmplr-lib-ur" +version = "2025.3.0" +source = { registry = "https://urob.github.io/numpy-mkl" } +dependencies = [ + { name = "umf" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/33/94785f0575f7319e57788169ef15e504c1e9e9744f04e0b9e00390e1f533/intel_cmplr_lib_ur-2025.3.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:35e45764907c100090d8dcb75d4d31aa7dc330e5f2d887032c9eb47d3e29d43e" }, + { url = "https://files.pythonhosted.org/packages/c7/a4/a7f61cc9beca3b6aba669381dfcaf3d36cd1f5f5f7716ba32ca091258c3d/intel_cmplr_lib_ur-2025.3.0-py2.py3-none-win_amd64.whl", hash = "sha256:4557a0281128e60628d9fcdca4ce38de40f13b32e8f4bf38ba82045f147c58e2" }, +] + +[[package]] +name = "intel-openmp" +version = "2025.3.0" +source = { registry = "https://urob.github.io/numpy-mkl" } +dependencies = [ + { name = "intel-cmplr-lib-ur" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/87/68241d0b532f0e41d5b2928b640b00f9bb48945df9921df1d878f42c1d38/intel_openmp-2025.3.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:948d9d6a22ed7643305a717c2ac2678bae8c11adafd5e4d30f903cf9933b4a60" }, + { url = "https://files.pythonhosted.org/packages/79/69/05addedd727061b61a85b4fd989754edb628b5be1cd5d161727f98cf4d86/intel_openmp-2025.3.0-py2.py3-none-win_amd64.whl", hash = "sha256:8ff899d01a41b92ffe0e618b221ea633127d16df726086082d381c431c31339d" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "iterative-telemetry" +version = "0.0.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appdirs" }, + { name = "distro" }, + { name = "filelock" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/b6/f17d6e80252b7be6ca4d9463db226ce7863d26287f16f1347e981cd2f3d8/iterative_telemetry-0.0.10.tar.gz", hash = "sha256:7fde6111de6fa4acf5a95a6190cc9cc5d17d835a815f0a18ece201f6031f4ed6", size = 20080, upload-time = "2025-02-11T02:47:53.391Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/82/7331bbf84f1ccce7a2dd09a580c7bad38417cf35c84dc0b81bce2cf779b9/iterative_telemetry-0.0.10-py3-none-any.whl", hash = "sha256:e58ffb60d22c3de8dad6a114697cc61f6c14911cae484bf90df394e0d6553603", size = 10644, upload-time = "2025-02-11T02:47:51.273Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, +] + +[[package]] +name = "knack" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argcomplete" }, + { name = "jmespath" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "pyyaml" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/e1/22a24a310799459b66a91709a95b4cb57da85326b73e04af97cdf3aae7a4/knack-0.13.0.tar.gz", hash = "sha256:dda35b4ff4c576b2501a18f0ec2f2fe0a3a5f9cce8265d4066d311e5ed4b5bc6", size = 72509, upload-time = "2025-10-23T03:04:23.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b1/d248ea2b869a8fda49699c52b3f80eb39d61e6589b8292a24190d89702ac/knack-0.13.0-py3-none-any.whl", hash = "sha256:ce531e59ce95a04e9a0d531623fd63fb88cabca3c52690aa365c0b0158ca6e2e", size = 60919, upload-time = "2025-10-23T03:04:24.555Z" }, +] + +[[package]] +name = "kombu" +version = "5.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "amqp" }, + { name = "packaging" }, + { name = "tzdata" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363", size = 461992, upload-time = "2025-06-01T10:19:22.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, +] + +[[package]] +name = "lightning" +version = "2.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pytorch-lightning" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/dd/86bb3bebadcdbc6e6e5a63657f0a03f74cd065b5ea965896679f76fec0b4/lightning-2.5.5.tar.gz", hash = "sha256:4d3d66c5b1481364a7e6a1ce8ddde1777a04fa740a3145ec218a9941aed7dd30", size = 640770, upload-time = "2025-09-05T16:01:21.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/d0/4b4fbafc3b18df91207a6e46782d9fd1905f9f45cb2c3b8dfbb239aef781/lightning-2.5.5-py3-none-any.whl", hash = "sha256:69eb248beadd7b600bf48eff00a0ec8af171ec7a678d23787c4aedf12e225e8f", size = 828490, upload-time = "2025-09-05T16:01:17.845Z" }, +] + +[[package]] +name = "lightning-utilities" +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/39/6fc58ca81492db047149b4b8fd385aa1bfb8c28cd7cacb0c7eb0c44d842f/lightning_utilities-0.15.2.tar.gz", hash = "sha256:cdf12f530214a63dacefd713f180d1ecf5d165338101617b4742e8f22c032e24", size = 31090, upload-time = "2025-08-06T13:57:39.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/73/3d757cb3fc16f0f9794dd289bcd0c4a031d9cf54d8137d6b984b2d02edf3/lightning_utilities-0.15.2-py3-none-any.whl", hash = "sha256:ad3ab1703775044bbf880dbf7ddaaac899396c96315f3aa1779cec9d618a9841", size = 29431, upload-time = "2025-08-06T13:57:38.046Z" }, +] + +[[package]] +name = "lxml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, @@ -339,312 +1690,1335 @@ wheels = [ ] [[package]] -name = "markdown" -version = "3.10" +name = "markdown" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/b3/09eb0f7796932826ec20c25b517d568627754f6c6462fca19e12c02f2e12/matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748", size = 8272389, upload-time = "2025-10-09T00:26:42.474Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/1ae80ddafb8652fd8046cb5c8460ecc8d4afccb89e2c6d6bec61e04e1eaf/matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f", size = 8128247, upload-time = "2025-10-09T00:26:44.77Z" }, + { url = "https://files.pythonhosted.org/packages/7d/18/95ae2e242d4a5c98bd6e90e36e128d71cf1c7e39b0874feaed3ef782e789/matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0", size = 8696996, upload-time = "2025-10-09T00:26:46.792Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3d/5b559efc800bd05cb2033aa85f7e13af51958136a48327f7c261801ff90a/matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695", size = 9530153, upload-time = "2025-10-09T00:26:49.07Z" }, + { url = "https://files.pythonhosted.org/packages/88/57/eab4a719fd110312d3c220595d63a3c85ec2a39723f0f4e7fa7e6e3f74ba/matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65", size = 9593093, upload-time = "2025-10-09T00:26:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/31/3c/80816f027b3a4a28cd2a0a6ef7f89a2db22310e945cd886ec25bfb399221/matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee", size = 8122771, upload-time = "2025-10-09T00:26:53.296Z" }, + { url = "https://files.pythonhosted.org/packages/de/77/ef1fc78bfe99999b2675435cc52120887191c566b25017d78beaabef7f2d/matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8", size = 7992812, upload-time = "2025-10-09T00:26:54.882Z" }, + { url = "https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f", size = 8273212, upload-time = "2025-10-09T00:26:56.752Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c", size = 8128713, upload-time = "2025-10-09T00:26:59.001Z" }, + { url = "https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1", size = 8698527, upload-time = "2025-10-09T00:27:00.69Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632", size = 9529690, upload-time = "2025-10-09T00:27:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/b80fc2c1f269f21ff3d193ca697358e24408c33ce2b106a7438a45407b63/matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84", size = 9593732, upload-time = "2025-10-09T00:27:04.653Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815", size = 8122727, upload-time = "2025-10-09T00:27:06.814Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a6/2faaf48133b82cf3607759027f82b5c702aa99cdfcefb7f93d6ccf26a424/matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7", size = 7992958, upload-time = "2025-10-09T00:27:08.567Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f0/b018fed0b599bd48d84c08794cb242227fe3341952da102ee9d9682db574/matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355", size = 8316849, upload-time = "2025-10-09T00:27:10.254Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b7/bb4f23856197659f275e11a2a164e36e65e9b48ea3e93c4ec25b4f163198/matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b", size = 8178225, upload-time = "2025-10-09T00:27:12.241Z" }, + { url = "https://files.pythonhosted.org/packages/62/56/0600609893ff277e6f3ab3c0cef4eafa6e61006c058e84286c467223d4d5/matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67", size = 8711708, upload-time = "2025-10-09T00:27:13.879Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1a/6bfecb0cafe94d6658f2f1af22c43b76cf7a1c2f0dc34ef84cbb6809617e/matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67", size = 9541409, upload-time = "2025-10-09T00:27:15.684Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/95122a407d7f2e446fd865e2388a232a23f2b81934960ea802f3171518e4/matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84", size = 9594054, upload-time = "2025-10-09T00:27:17.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/76/75b194a43b81583478a81e78a07da8d9ca6ddf50dd0a2ccabf258059481d/matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2", size = 8200100, upload-time = "2025-10-09T00:27:20.039Z" }, + { url = "https://files.pythonhosted.org/packages/f5/9e/6aefebdc9f8235c12bdeeda44cc0383d89c1e41da2c400caf3ee2073a3ce/matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf", size = 8042131, upload-time = "2025-10-09T00:27:21.608Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4b/e5bc2c321b6a7e3a75638d937d19ea267c34bd5a90e12bee76c4d7c7a0d9/matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100", size = 8273787, upload-time = "2025-10-09T00:27:23.27Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/6efae459c56c2fbc404da154e13e3a6039129f3c942b0152624f1c621f05/matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f", size = 8131348, upload-time = "2025-10-09T00:27:24.926Z" }, + { url = "https://files.pythonhosted.org/packages/a6/5a/a4284d2958dee4116359cc05d7e19c057e64ece1b4ac986ab0f2f4d52d5a/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715", size = 9533949, upload-time = "2025-10-09T00:27:26.704Z" }, + { url = "https://files.pythonhosted.org/packages/de/ff/f3781b5057fa3786623ad8976fc9f7b0d02b2f28534751fd5a44240de4cf/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1", size = 9804247, upload-time = "2025-10-09T00:27:28.514Z" }, + { url = "https://files.pythonhosted.org/packages/47/5a/993a59facb8444efb0e197bf55f545ee449902dcee86a4dfc580c3b61314/matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722", size = 9595497, upload-time = "2025-10-09T00:27:30.418Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a5/77c95aaa9bb32c345cbb49626ad8eb15550cba2e6d4c88081a6c2ac7b08d/matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866", size = 8252732, upload-time = "2025-10-09T00:27:32.332Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/45d269b4268d222390d7817dae77b159651909669a34ee9fdee336db5883/matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb", size = 8124240, upload-time = "2025-10-09T00:27:33.94Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c7/ca01c607bb827158b439208c153d6f14ddb9fb640768f06f7ca3488ae67b/matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1", size = 8316938, upload-time = "2025-10-09T00:27:35.534Z" }, + { url = "https://files.pythonhosted.org/packages/84/d2/5539e66e9f56d2fdec94bb8436f5e449683b4e199bcc897c44fbe3c99e28/matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4", size = 8178245, upload-time = "2025-10-09T00:27:37.334Z" }, + { url = "https://files.pythonhosted.org/packages/77/b5/e6ca22901fd3e4fe433a82e583436dd872f6c966fca7e63cf806b40356f8/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318", size = 9541411, upload-time = "2025-10-09T00:27:39.387Z" }, + { url = "https://files.pythonhosted.org/packages/9e/99/a4524db57cad8fee54b7237239a8f8360bfcfa3170d37c9e71c090c0f409/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca", size = 9803664, upload-time = "2025-10-09T00:27:41.492Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a5/85e2edf76ea0ad4288d174926d9454ea85f3ce5390cc4e6fab196cbf250b/matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", size = 9594066, upload-time = "2025-10-09T00:27:43.694Z" }, + { url = "https://files.pythonhosted.org/packages/39/69/9684368a314f6d83fe5c5ad2a4121a3a8e03723d2e5c8ea17b66c1bad0e7/matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", size = 8342832, upload-time = "2025-10-09T00:27:45.543Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/e22e08da14bc1a0894184640d47819d2338b792732e20d292bf86e5ab785/matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", size = 8172585, upload-time = "2025-10-09T00:27:47.185Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mdx-truly-sane-lists" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/27/16456314311abac2cedef4527679924e80ac4de19dd926699c1b261e0b9b/mdx_truly_sane_lists-1.3.tar.gz", hash = "sha256:b661022df7520a1e113af7c355c62216b384c867e4f59fb8ee7ad511e6e77f45", size = 5359, upload-time = "2022-07-19T13:42:45.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/9e/dcd1027f7fd193aed152e01c6651a197c36b858f2cd1425ad04cb31a34fc/mdx_truly_sane_lists-1.3-py3-none-any.whl", hash = "sha256:b9546a4c40ff8f1ab692f77cee4b6bfe8ddf9cccf23f0a24e71f3716fe290a37", size = 6071, upload-time = "2022-07-19T13:42:43.375Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, +] + +[[package]] +name = "mkdocs-awesome-pages-plugin" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "natsort" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/e8/6ae9c18d8174a5d74ce4ade7a7f4c350955063968bc41ff1e5833cff4a2b/mkdocs_awesome_pages_plugin-2.10.1.tar.gz", hash = "sha256:cda2cb88c937ada81a4785225f20ef77ce532762f4500120b67a1433c1cdbb2f", size = 16303, upload-time = "2024-12-22T21:13:49.19Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/61/19fc1e9c579dbfd4e8a402748f1d63cab7aabe8f8d91eb0235e45b32d040/mkdocs_awesome_pages_plugin-2.10.1-py3-none-any.whl", hash = "sha256:c6939dbea37383fc3cf8c0a4e892144ec3d2f8a585e16fdc966b34e7c97042a7", size = 15118, upload-time = "2024-12-22T21:13:46.945Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +name = "mkdocs-glightbox" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "selectolax" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/26/c793459622da8e31f954c6f5fb51e8f098143fdfc147b1e3c25bf686f4aa/mkdocs_glightbox-0.5.2.tar.gz", hash = "sha256:c7622799347c32310878e01ccf14f70648445561010911c80590cec0353370ac", size = 510586, upload-time = "2025-10-23T14:55:18.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/ca/03624e017e5ee2d7ce8a08d89f81c1e535eb3c30d7b2dc4a435ea3fbbeae/mkdocs_glightbox-0.5.2-py3-none-any.whl", hash = "sha256:23a431ea802b60b1030c73323db2eed6ba859df1a0822ce575afa43e0ea3f47e", size = 26458, upload-time = "2025-10-23T14:55:17.43Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.6.23" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/de/cc1d5139c2782b1a49e1ed1845b3298ed6076b9ba1c740ad7c952d8ffcf9/mkdocs_material-9.6.23.tar.gz", hash = "sha256:62ebc9cdbe90e1ae4f4e9b16a6aa5c69b93474c7b9e79ebc0b11b87f9f055e00", size = 4048130, upload-time = "2025-11-01T16:33:11.782Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/df/bc583e857174b0dc6df67d555123533f09e7e1ac0f3fae7693fb6840c0a3/mkdocs_material-9.6.23-py3-none-any.whl", hash = "sha256:3bf3f1d82d269f3a14ed6897bfc3a844cc05e1dc38045386691b91d7e6945332", size = 9210689, upload-time = "2025-11-01T16:33:08.196Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocs-video" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/16/45213649b6756744f36f31014fc8673df1d7c998bb9a801c2d769fff4114/mkdocs-video-1.5.0.tar.gz", hash = "sha256:0defc018f4b7927f8afffc4d8e039c84dfba636dffc5e25e2bfa8d6350bc8eca", size = 5633, upload-time = "2023-03-17T17:36:43.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/3a/b51e305eca21cdd58f4eb06973099ba0ea679b1f437a92a49f8fc576310e/mkdocs_video-1.5.0-py3-none-any.whl", hash = "sha256:b35613d4dacbac2dfa94d8c2600383cda14ad99a1fa1542b5fc4e9c6d19e9fe1", size = 6570, upload-time = "2023-03-17T17:36:40.776Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "0.30.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/33/2fa3243439f794e685d3e694590d28469a9b8ea733af4b48c250a3ffc9a0/mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f", size = 106350, upload-time = "2025-09-19T10:49:26.446Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/2c/f0dc4e1ee7f618f5bff7e05898d20bf8b6e7fa612038f768bfa295f136a4/mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82", size = 36704, upload-time = "2025-09-19T10:49:24.805Z" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/ae/58ab2bfbee2792e92a98b97e872f7c003deb903071f75d8d83aa55db28fa/mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323", size = 207972, upload-time = "2025-08-28T16:11:19.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/8f/ce008599d9adebf33ed144e7736914385e8537f5fc686fdb7cceb8c22431/mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d", size = 138215, upload-time = "2025-08-28T16:11:18.176Z" }, +] + +[[package]] +name = "mkl" +version = "2025.3.0" +source = { registry = "https://urob.github.io/numpy-mkl" } +dependencies = [ + { name = "intel-openmp" }, + { name = "onemkl-license" }, + { name = "tbb" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/b4/ef531295ed33b929c6c5214421eeebe370f1be22536b6956b4aaf18fdbc5/mkl-2025.3.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:7b81f5d2e034637b187ca58666878c4e8889988a78e39e0b6ee92e846568f660" }, + { url = "https://files.pythonhosted.org/packages/75/0e/b863a60889643e21d5d8d6b619f913703257878979b1df22d344a56276d0/mkl-2025.3.0-py2.py3-none-win_amd64.whl", hash = "sha256:2dbf81b016150c2ce418d80707dcce11d0e3c56f20e1d54813174e7665405561" }, +] + +[[package]] +name = "mkl-fft" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkl-service" }, + { name = "numpy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/e8/11294c46170b991b9b1e7b3aa3a47af0097d3360b7d5e50c100af673fc19/mkl_fft-2.1.1-0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:96e431cd7dabbd52d8c4ef5d5cfff526c099215306e9d245a3f66ab0f6580d1f", size = 154961, upload-time = "2025-10-27T17:53:12.732Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9e/560e5b00e00b06b418e2ec15766322168ebc04bf7b9723267ce9c058f059/mkl_fft-2.1.1-0-cp312-cp312-win_amd64.whl", hash = "sha256:70a1b4baba54513f2b1572f1e33256f393a6fe0401ff0bec9a7fca007208a81b", size = 140430, upload-time = "2025-10-27T17:53:37.92Z" }, + { url = "https://files.pythonhosted.org/packages/bd/36/495507a88ca3e74503f036ef25891d47e76faf412d5bebcb080e16f585b3/mkl_fft-2.1.1-0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:864b5f239600ed7e18dc7d623bab9e2d59400b7c060831f630caac260775018d", size = 154273, upload-time = "2025-10-27T17:54:02.927Z" }, + { url = "https://files.pythonhosted.org/packages/0c/39/8770e5783242ddd89f49166a6e4ce65ba9f9136a9f40be0a69058139c2cf/mkl_fft-2.1.1-0-cp313-cp313-win_amd64.whl", hash = "sha256:784b10980e1c3d6c7001add8b60b1b06067b2c91e13091eb8fa39fbe55c59c77", size = 140292, upload-time = "2025-10-27T17:54:27.925Z" }, +] + +[[package]] +name = "mkl-service" +version = "2.6.0" +source = { registry = "https://urob.github.io/numpy-mkl" } +dependencies = [ + { name = "mkl" }, +] +wheels = [ + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/mkl_service-2.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15908dd44cc83aa822222c83cc15fa2f31201a43cc0a37949a64cf377bf27887" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/mkl_service-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec2728d541547a56ce0b8050f994225ef9ceee994abd711e7b1d9943040ae92" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/mkl_service-2.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:415d5b27ec47dfbde316c4fb387f13344917a3fb354324d89d8f5f7da58bcac5" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/mkl_service-2.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:54d7e8d984f39c6511ab2c2abceb80ad5f6e278d7f2c0bf9e57d5eb11110cfb5" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/mkl_service-2.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20773a79b0a15be7ea2decf3e3a127491a75816f92fce67f6acdd36d01fa4fb2" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/mkl_service-2.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d2c725f48aad3935e0ee259f0e156760f612c18eb7336374df8fd26fdc3a700" }, +] + +[[package]] +name = "moviepy" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +dependencies = [ + { name = "decorator" }, + { name = "imageio" }, + { name = "imageio-ffmpeg" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "proglog" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/61/15f9476e270f64c78a834e7459ca045d669f869cec24eed26807b8cd479d/moviepy-2.2.1.tar.gz", hash = "sha256:c80cb56815ece94e5e3e2d361aa40070eeb30a09d23a24c4e684d03e16deacb1", size = 58431438, upload-time = "2025-05-21T19:31:52.601Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/9a/73/7d3b2010baa0b5eb1e4dfa9e4385e89b6716be76f2fa21a6c0fe34b68e5a/moviepy-2.2.1-py3-none-any.whl", hash = "sha256:6b56803fec2ac54b557404126ac1160e65448e03798fa282bd23e8fab3795060", size = 129871, upload-time = "2025-05-21T19:31:50.11Z" }, ] [[package]] -name = "markupsafe" -version = "3.0.3" +name = "mpmath" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "msal" +version = "1.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, +] + +[[package]] +name = "msal-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "narwhals" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/dc/8db74daf8c2690ec696c1d772a33cc01511559ee8a9e92d7ed85a18e3c22/narwhals-2.10.2.tar.gz", hash = "sha256:ff738a08bc993cbb792266bec15346c1d85cc68fdfe82a23283c3713f78bd354", size = 584954, upload-time = "2025-11-04T16:36:42.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/a9/9e02fa97e421a355fc5e818e9c488080fce04a8e0eebb3ed75a84f041c4a/narwhals-2.10.2-py3-none-any.whl", hash = "sha256:059cd5c6751161b97baedcaf17a514c972af6a70f36a89af17de1a0caf519c43", size = 419573, upload-time = "2025-11-04T16:36:40.574Z" }, +] + +[[package]] +name = "natsort" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, +] + +[[package]] +name = "nibabel" +version = "5.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/61/33036cb89f1ec1fedbc4039602345d830b27cbd8a5c7bf28c2e5b5de3ea2/nibabel-5.3.2.tar.gz", hash = "sha256:0bdca6503b1c784b446c745a4542367de7756cfba0d72143b91f9ffb78be569b", size = 4504842, upload-time = "2024-10-23T14:19:55.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b2/dc384197be44e2a640bb43311850e23c2c30f3b82ce7c8cdabbf0e53045e/nibabel-5.3.2-py3-none-any.whl", hash = "sha256:52970a5a8a53b1b55249cba4d9bcfaa8cc57e3e5af35a29d7352237e8680a6f8", size = 3293839, upload-time = "2024-10-23T14:19:52.65Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.4" +source = { registry = "https://urob.github.io/numpy-mkl" } +dependencies = [ + { name = "mkl" }, + { name = "mkl-service" }, +] +wheels = [ + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbe8e5ab4fb4e5aeb7f31df1a3fe62b71e386977fde1d197df321d4e828510ea" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:38d950c6072edb1fc544d01348516511ce9a905375b06328bbf86b57da3546b3" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47c06c97541890db9fb9ac80baf8647989385ce46dc7a6b4a945cb71f476e56c" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:d162838135b209704dbf3962144329d65fdebd5d91b3e88debae24bdb2eb5c41" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cabd671a9fc06f331796e6a9eb891435eb7194e74fe82c97e86b165c03780985" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:8d2aeab5102ee4866edec255800bb87c0c8b19828bc48e20c7304dde45c15ed3" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.10.2.21" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.27.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu12" +version = "3.3.20" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5", size = 124657145, upload-time = "2025-08-04T20:25:19.995Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, +] + +[[package]] +name = "omegaconf" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120, upload-time = "2022-12-08T20:59:22.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, +] + +[[package]] +name = "onemkl-license" +version = "2025.3.0" +source = { registry = "https://urob.github.io/numpy-mkl" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/11/b43e8cde058c368ce7f8f9b1ca9f812f7397e4309148da7d24cb6b81b513/onemkl_license-2025.3.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:a810b25bb24a90db4492d81c71cde13183d8951ce26960d6dc56d4f4e3dc95ff" }, + { url = "https://files.pythonhosted.org/packages/0e/8e/98ad8181e4af2ea7a6c7827ef6d24c73b6ee96a2890eacf46103f596195d/onemkl_license-2025.3.0-py2.py3-none-win_amd64.whl", hash = "sha256:35a179620bcadbcc3d17d74f7f72cab75889f3736b89685f6a68158325b65e91" }, +] + +[[package]] +name = "opencv-python" +version = "4.11.0.86" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322, upload-time = "2025-01-16T13:52:25.887Z" }, + { url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197, upload-time = "2025-01-16T13:55:21.222Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439, upload-time = "2025-01-16T13:51:35.822Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597, upload-time = "2025-01-16T13:52:08.836Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337, upload-time = "2025-01-16T13:52:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044, upload-time = "2025-01-16T13:52:21.928Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/fe/ed708782d6709cc60eb4c2d8a361a440661f74134675c72990f2c48c785f/orjson-3.11.4.tar.gz", hash = "sha256:39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d", size = 5945188, upload-time = "2025-10-24T15:50:38.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/51/6b556192a04595b93e277a9ff71cd0cc06c21a7df98bcce5963fa0f5e36f/orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d4371de39319d05d3f482f372720b841c841b52f5385bd99c61ed69d55d9ab50", size = 243571, upload-time = "2025-10-24T15:49:10.008Z" }, + { url = "https://files.pythonhosted.org/packages/1c/2c/2602392ddf2601d538ff11848b98621cd465d1a1ceb9db9e8043181f2f7b/orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e41fd3b3cac850eaae78232f37325ed7d7436e11c471246b87b2cd294ec94853", size = 128891, upload-time = "2025-10-24T15:49:11.297Z" }, + { url = "https://files.pythonhosted.org/packages/4e/47/bf85dcf95f7a3a12bf223394a4f849430acd82633848d52def09fa3f46ad/orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600e0e9ca042878c7fdf189cf1b028fe2c1418cc9195f6cb9824eb6ed99cb938", size = 130137, upload-time = "2025-10-24T15:49:12.544Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4d/a0cb31007f3ab6f1fd2a1b17057c7c349bc2baf8921a85c0180cc7be8011/orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7bbf9b333f1568ef5da42bc96e18bf30fd7f8d54e9ae066d711056add508e415", size = 129152, upload-time = "2025-10-24T15:49:13.754Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ef/2811def7ce3d8576b19e3929fff8f8f0d44bc5eb2e0fdecb2e6e6cc6c720/orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4806363144bb6e7297b8e95870e78d30a649fdc4e23fc84daa80c8ebd366ce44", size = 136834, upload-time = "2025-10-24T15:49:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/00/d4/9aee9e54f1809cec8ed5abd9bc31e8a9631d19460e3b8470145d25140106/orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad355e8308493f527d41154e9053b86a5be892b3b359a5c6d5d95cda23601cb2", size = 137519, upload-time = "2025-10-24T15:49:16.557Z" }, + { url = "https://files.pythonhosted.org/packages/db/ea/67bfdb5465d5679e8ae8d68c11753aaf4f47e3e7264bad66dc2f2249e643/orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a7517482667fb9f0ff1b2f16fe5829296ed7a655d04d68cd9711a4d8a4e708", size = 136749, upload-time = "2025-10-24T15:49:17.796Z" }, + { url = "https://files.pythonhosted.org/packages/01/7e/62517dddcfce6d53a39543cd74d0dccfcbdf53967017c58af68822100272/orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97eb5942c7395a171cbfecc4ef6701fc3c403e762194683772df4c54cfbb2210", size = 136325, upload-time = "2025-10-24T15:49:19.347Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/40516739f99ab4c7ec3aaa5cc242d341fcb03a45d89edeeaabc5f69cb2cf/orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:149d95d5e018bdd822e3f38c103b1a7c91f88d38a88aada5c4e9b3a73a244241", size = 140204, upload-time = "2025-10-24T15:49:20.545Z" }, + { url = "https://files.pythonhosted.org/packages/82/18/ff5734365623a8916e3a4037fcef1cd1782bfc14cf0992afe7940c5320bf/orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:624f3951181eb46fc47dea3d221554e98784c823e7069edb5dbd0dc826ac909b", size = 406242, upload-time = "2025-10-24T15:49:21.884Z" }, + { url = "https://files.pythonhosted.org/packages/e1/43/96436041f0a0c8c8deca6a05ebeaf529bf1de04839f93ac5e7c479807aec/orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:03bfa548cf35e3f8b3a96c4e8e41f753c686ff3d8e182ce275b1751deddab58c", size = 150013, upload-time = "2025-10-24T15:49:23.185Z" }, + { url = "https://files.pythonhosted.org/packages/1b/48/78302d98423ed8780479a1e682b9aecb869e8404545d999d34fa486e573e/orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:525021896afef44a68148f6ed8a8bf8375553d6066c7f48537657f64823565b9", size = 139951, upload-time = "2025-10-24T15:49:24.428Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/ad613fdcdaa812f075ec0875143c3d37f8654457d2af17703905425981bf/orjson-3.11.4-cp312-cp312-win32.whl", hash = "sha256:b58430396687ce0f7d9eeb3dd47761ca7d8fda8e9eb92b3077a7a353a75efefa", size = 136049, upload-time = "2025-10-24T15:49:25.973Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/9cf47c3ff5f39b8350fb21ba65d789b6a1129d4cbb3033ba36c8a9023520/orjson-3.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:c6dbf422894e1e3c80a177133c0dda260f81428f9de16d61041949f6a2e5c140", size = 131461, upload-time = "2025-10-24T15:49:27.259Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3b/e2425f61e5825dc5b08c2a5a2b3af387eaaca22a12b9c8c01504f8614c36/orjson-3.11.4-cp312-cp312-win_arm64.whl", hash = "sha256:d38d2bc06d6415852224fcc9c0bfa834c25431e466dc319f0edd56cca81aa96e", size = 126167, upload-time = "2025-10-24T15:49:28.511Z" }, + { url = "https://files.pythonhosted.org/packages/23/15/c52aa7112006b0f3d6180386c3a46ae057f932ab3425bc6f6ac50431cca1/orjson-3.11.4-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2d6737d0e616a6e053c8b4acc9eccea6b6cce078533666f32d140e4f85002534", size = 243525, upload-time = "2025-10-24T15:49:29.737Z" }, + { url = "https://files.pythonhosted.org/packages/ec/38/05340734c33b933fd114f161f25a04e651b0c7c33ab95e9416ade5cb44b8/orjson-3.11.4-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:afb14052690aa328cc118a8e09f07c651d301a72e44920b887c519b313d892ff", size = 128871, upload-time = "2025-10-24T15:49:31.109Z" }, + { url = "https://files.pythonhosted.org/packages/55/b9/ae8d34899ff0c012039b5a7cb96a389b2476e917733294e498586b45472d/orjson-3.11.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38aa9e65c591febb1b0aed8da4d469eba239d434c218562df179885c94e1a3ad", size = 130055, upload-time = "2025-10-24T15:49:33.382Z" }, + { url = "https://files.pythonhosted.org/packages/33/aa/6346dd5073730451bee3681d901e3c337e7ec17342fb79659ec9794fc023/orjson-3.11.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2cf4dfaf9163b0728d061bebc1e08631875c51cd30bf47cb9e3293bfbd7dcd5", size = 129061, upload-time = "2025-10-24T15:49:34.935Z" }, + { url = "https://files.pythonhosted.org/packages/39/e4/8eea51598f66a6c853c380979912d17ec510e8e66b280d968602e680b942/orjson-3.11.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89216ff3dfdde0e4070932e126320a1752c9d9a758d6a32ec54b3b9334991a6a", size = 136541, upload-time = "2025-10-24T15:49:36.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/47/cb8c654fa9adcc60e99580e17c32b9e633290e6239a99efa6b885aba9dbc/orjson-3.11.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9daa26ca8e97fae0ce8aa5d80606ef8f7914e9b129b6b5df9104266f764ce436", size = 137535, upload-time = "2025-10-24T15:49:38.307Z" }, + { url = "https://files.pythonhosted.org/packages/43/92/04b8cc5c2b729f3437ee013ce14a60ab3d3001465d95c184758f19362f23/orjson-3.11.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c8b2769dc31883c44a9cd126560327767f848eb95f99c36c9932f51090bfce9", size = 136703, upload-time = "2025-10-24T15:49:40.795Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fd/d0733fcb9086b8be4ebcfcda2d0312865d17d0d9884378b7cffb29d0763f/orjson-3.11.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1469d254b9884f984026bd9b0fa5bbab477a4bfe558bba6848086f6d43eb5e73", size = 136293, upload-time = "2025-10-24T15:49:42.347Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/3c5514e806837c210492d72ae30ccf050ce3f940f45bf085bab272699ef4/orjson-3.11.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:68e44722541983614e37117209a194e8c3ad07838ccb3127d96863c95ec7f1e0", size = 140131, upload-time = "2025-10-24T15:49:43.638Z" }, + { url = "https://files.pythonhosted.org/packages/9c/dd/ba9d32a53207babf65bd510ac4d0faaa818bd0df9a9c6f472fe7c254f2e3/orjson-3.11.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8e7805fda9672c12be2f22ae124dcd7b03928d6c197544fe12174b86553f3196", size = 406164, upload-time = "2025-10-24T15:49:45.498Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f9/f68ad68f4af7c7bde57cd514eaa2c785e500477a8bc8f834838eb696a685/orjson-3.11.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04b69c14615fb4434ab867bf6f38b2d649f6f300af30a6705397e895f7aec67a", size = 149859, upload-time = "2025-10-24T15:49:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d2/7f847761d0c26818395b3d6b21fb6bc2305d94612a35b0a30eae65a22728/orjson-3.11.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:639c3735b8ae7f970066930e58cf0ed39a852d417c24acd4a25fc0b3da3c39a6", size = 139926, upload-time = "2025-10-24T15:49:48.321Z" }, + { url = "https://files.pythonhosted.org/packages/9f/37/acd14b12dc62db9a0e1d12386271b8661faae270b22492580d5258808975/orjson-3.11.4-cp313-cp313-win32.whl", hash = "sha256:6c13879c0d2964335491463302a6ca5ad98105fc5db3565499dcb80b1b4bd839", size = 136007, upload-time = "2025-10-24T15:49:49.938Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a9/967be009ddf0a1fffd7a67de9c36656b28c763659ef91352acc02cbe364c/orjson-3.11.4-cp313-cp313-win_amd64.whl", hash = "sha256:09bf242a4af98732db9f9a1ec57ca2604848e16f132e3f72edfd3c5c96de009a", size = 131314, upload-time = "2025-10-24T15:49:51.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/db/399abd6950fbd94ce125cb8cd1a968def95174792e127b0642781e040ed4/orjson-3.11.4-cp313-cp313-win_arm64.whl", hash = "sha256:a85f0adf63319d6c1ba06fb0dbf997fced64a01179cf17939a6caca662bf92de", size = 126152, upload-time = "2025-10-24T15:49:52.922Z" }, + { url = "https://files.pythonhosted.org/packages/25/e3/54ff63c093cc1697e758e4fceb53164dd2661a7d1bcd522260ba09f54533/orjson-3.11.4-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:42d43a1f552be1a112af0b21c10a5f553983c2a0938d2bbb8ecd8bc9fb572803", size = 243501, upload-time = "2025-10-24T15:49:54.288Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7d/e2d1076ed2e8e0ae9badca65bf7ef22710f93887b29eaa37f09850604e09/orjson-3.11.4-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:26a20f3fbc6c7ff2cb8e89c4c5897762c9d88cf37330c6a117312365d6781d54", size = 128862, upload-time = "2025-10-24T15:49:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/9f/37/ca2eb40b90621faddfa9517dfe96e25f5ae4d8057a7c0cdd613c17e07b2c/orjson-3.11.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3f20be9048941c7ffa8fc523ccbd17f82e24df1549d1d1fe9317712d19938e", size = 130047, upload-time = "2025-10-24T15:49:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/62/1021ed35a1f2bad9040f05fa4cc4f9893410df0ba3eaa323ccf899b1c90a/orjson-3.11.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aac364c758dc87a52e68e349924d7e4ded348dedff553889e4d9f22f74785316", size = 129073, upload-time = "2025-10-24T15:49:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3f/f84d966ec2a6fd5f73b1a707e7cd876813422ae4bf9f0145c55c9c6a0f57/orjson-3.11.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5c54a6d76e3d741dcc3f2707f8eeb9ba2a791d3adbf18f900219b62942803b1", size = 136597, upload-time = "2025-10-24T15:50:00.12Z" }, + { url = "https://files.pythonhosted.org/packages/32/78/4fa0aeca65ee82bbabb49e055bd03fa4edea33f7c080c5c7b9601661ef72/orjson-3.11.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28485bdca8617b79d44627f5fb04336897041dfd9fa66d383a49d09d86798bc", size = 137515, upload-time = "2025-10-24T15:50:01.57Z" }, + { url = "https://files.pythonhosted.org/packages/c1/9d/0c102e26e7fde40c4c98470796d050a2ec1953897e2c8ab0cb95b0759fa2/orjson-3.11.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfc2a484cad3585e4ba61985a6062a4c2ed5c7925db6d39f1fa267c9d166487f", size = 136703, upload-time = "2025-10-24T15:50:02.944Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/2de7188705b4cdfaf0b6c97d2f7849c17d2003232f6e70df98602173f788/orjson-3.11.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e34dbd508cb91c54f9c9788923daca129fe5b55c5b4eebe713bf5ed3791280cf", size = 136311, upload-time = "2025-10-24T15:50:04.441Z" }, + { url = "https://files.pythonhosted.org/packages/e0/52/847fcd1a98407154e944feeb12e3b4d487a0e264c40191fb44d1269cbaa1/orjson-3.11.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b13c478fa413d4b4ee606ec8e11c3b2e52683a640b006bb586b3041c2ca5f606", size = 140127, upload-time = "2025-10-24T15:50:07.398Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ae/21d208f58bdb847dd4d0d9407e2929862561841baa22bdab7aea10ca088e/orjson-3.11.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:724ca721ecc8a831b319dcd72cfa370cc380db0bf94537f08f7edd0a7d4e1780", size = 406201, upload-time = "2025-10-24T15:50:08.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/55/0789d6de386c8366059db098a628e2ad8798069e94409b0d8935934cbcb9/orjson-3.11.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:977c393f2e44845ce1b540e19a786e9643221b3323dae190668a98672d43fb23", size = 149872, upload-time = "2025-10-24T15:50:10.234Z" }, + { url = "https://files.pythonhosted.org/packages/cc/1d/7ff81ea23310e086c17b41d78a72270d9de04481e6113dbe2ac19118f7fb/orjson-3.11.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e539e382cf46edec157ad66b0b0872a90d829a6b71f17cb633d6c160a223155", size = 139931, upload-time = "2025-10-24T15:50:11.623Z" }, + { url = "https://files.pythonhosted.org/packages/77/92/25b886252c50ed64be68c937b562b2f2333b45afe72d53d719e46a565a50/orjson-3.11.4-cp314-cp314-win32.whl", hash = "sha256:d63076d625babab9db5e7836118bdfa086e60f37d8a174194ae720161eb12394", size = 136065, upload-time = "2025-10-24T15:50:13.025Z" }, + { url = "https://files.pythonhosted.org/packages/63/b8/718eecf0bb7e9d64e4956afaafd23db9f04c776d445f59fe94f54bdae8f0/orjson-3.11.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a54d6635fa3aaa438ae32e8570b9f0de36f3f6562c308d2a2a452e8b0592db1", size = 131310, upload-time = "2025-10-24T15:50:14.46Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bf/def5e25d4d8bfce296a9a7c8248109bf58622c21618b590678f945a2c59c/orjson-3.11.4-cp314-cp314-win_arm64.whl", hash = "sha256:78b999999039db3cf58f6d230f524f04f75f129ba3d1ca2ed121f8657e575d3d", size = 126151, upload-time = "2025-10-24T15:50:15.878Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] -name = "mdx-truly-sane-lists" -version = "1.3" +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown" }, + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/27/16456314311abac2cedef4527679924e80ac4de19dd926699c1b261e0b9b/mdx_truly_sane_lists-1.3.tar.gz", hash = "sha256:b661022df7520a1e113af7c355c62216b384c867e4f59fb8ee7ad511e6e77f45", size = 5359, upload-time = "2022-07-19T13:42:45.153Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/9e/dcd1027f7fd193aed152e01c6651a197c36b858f2cd1425ad04cb31a34fc/mdx_truly_sane_lists-1.3-py3-none-any.whl", hash = "sha256:b9546a4c40ff8f1ab692f77cee4b6bfe8ddf9cccf23f0a24e71f3716fe290a37", size = 6071, upload-time = "2022-07-19T13:42:43.375Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, ] [[package]] -name = "mergedeep" -version = "1.3.4" +name = "pathlib" +version = "1.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/aa/9b065a76b9af472437a0059f77e8f962fe350438b927cb80184c32f075eb/pathlib-1.0.1.tar.gz", hash = "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f", size = 49298, upload-time = "2014-09-03T15:41:57.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, + { url = "https://files.pythonhosted.org/packages/78/f9/690a8600b93c332de3ab4a344a4ac34f00c8f104917061f779db6a918ed6/pathlib-1.0.1-py3-none-any.whl", hash = "sha256:f35f95ab8b0f59e6d354090350b44a80a80635d22efdedfa84c7ad1cf0a74147", size = 14363, upload-time = "2022-05-04T13:37:20.585Z" }, ] [[package]] -name = "mkdocs" -version = "1.6.1" +name = "pathspec" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "ghp-import" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mergedeep" }, - { name = "mkdocs-get-deps" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "pyyaml" }, - { name = "pyyaml-env-tag" }, - { name = "watchdog" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] -name = "mkdocs-autorefs" -version = "1.4.3" +name = "pillow" +version = "11.3.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mkdocs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, ] [[package]] -name = "mkdocs-awesome-pages-plugin" -version = "2.10.1" +name = "piq" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mkdocs" }, - { name = "natsort" }, - { name = "wcmatch" }, + { name = "torchvision" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/e8/6ae9c18d8174a5d74ce4ade7a7f4c350955063968bc41ff1e5833cff4a2b/mkdocs_awesome_pages_plugin-2.10.1.tar.gz", hash = "sha256:cda2cb88c937ada81a4785225f20ef77ce532762f4500120b67a1433c1cdbb2f", size = 16303, upload-time = "2024-12-22T21:13:49.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/ae/9274f8e3f216759311a460a896594f04aade8a9f1171c0a732cc613c3b21/piq-0.8.0.tar.gz", hash = "sha256:ea74938ce11845d63fed3185694cdf9641f85bd19718dc3394f8008811b847d4", size = 91316, upload-time = "2023-07-04T21:28:05.877Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/61/19fc1e9c579dbfd4e8a402748f1d63cab7aabe8f8d91eb0235e45b32d040/mkdocs_awesome_pages_plugin-2.10.1-py3-none-any.whl", hash = "sha256:c6939dbea37383fc3cf8c0a4e892144ec3d2f8a585e16fdc966b34e7c97042a7", size = 15118, upload-time = "2024-12-22T21:13:46.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/49/e198c355bc01a1bb3aff5d41a8fe7c8a0502ad7a4063865d2c9dd270cb0f/piq-0.8.0-py3-none-any.whl", hash = "sha256:bad83f8e68bad6c09df09c2bdf50d1e8ccf5e852d7b64562317c27ed5eb63159", size = 106872, upload-time = "2023-07-04T21:28:03.532Z" }, ] [[package]] -name = "mkdocs-get-deps" -version = "0.2.0" +name = "platformdirs" +version = "4.5.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mergedeep" }, - { name = "platformdirs" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, ] [[package]] -name = "mkdocs-glightbox" -version = "0.5.2" +name = "plotly" +version = "6.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "selectolax" }, + { name = "narwhals" }, + { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/26/c793459622da8e31f954c6f5fb51e8f098143fdfc147b1e3c25bf686f4aa/mkdocs_glightbox-0.5.2.tar.gz", hash = "sha256:c7622799347c32310878e01ccf14f70648445561010911c80590cec0353370ac", size = 510586, upload-time = "2025-10-23T14:55:18.909Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/e6/b768650072837505804bed4790c5449ba348a3b720e27ca7605414e998cd/plotly-6.4.0.tar.gz", hash = "sha256:68c6db2ed2180289ef978f087841148b7efda687552276da15a6e9b92107052a", size = 7012379, upload-time = "2025-11-04T17:59:26.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/ca/03624e017e5ee2d7ce8a08d89f81c1e535eb3c30d7b2dc4a435ea3fbbeae/mkdocs_glightbox-0.5.2-py3-none-any.whl", hash = "sha256:23a431ea802b60b1030c73323db2eed6ba859df1a0822ce575afa43e0ea3f47e", size = 26458, upload-time = "2025-10-23T14:55:17.43Z" }, + { url = "https://files.pythonhosted.org/packages/78/ae/89b45ccccfeebc464c9233de5675990f75241b8ee4cd63227800fdf577d1/plotly-6.4.0-py3-none-any.whl", hash = "sha256:a1062eafbdc657976c2eedd276c90e184ccd6c21282a5e9ee8f20efca9c9a4c5", size = 9892458, upload-time = "2025-11-04T17:59:22.622Z" }, ] [[package]] -name = "mkdocs-material" -version = "9.6.23" +name = "pluggy" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "backrefs" }, - { name = "colorama" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "mkdocs" }, - { name = "mkdocs-material-extensions" }, - { name = "paginate" }, - { name = "pygments" }, - { name = "pymdown-extensions" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/de/cc1d5139c2782b1a49e1ed1845b3298ed6076b9ba1c740ad7c952d8ffcf9/mkdocs_material-9.6.23.tar.gz", hash = "sha256:62ebc9cdbe90e1ae4f4e9b16a6aa5c69b93474c7b9e79ebc0b11b87f9f055e00", size = 4048130, upload-time = "2025-11-01T16:33:11.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/df/bc583e857174b0dc6df67d555123533f09e7e1ac0f3fae7693fb6840c0a3/mkdocs_material-9.6.23-py3-none-any.whl", hash = "sha256:3bf3f1d82d269f3a14ed6897bfc3a844cc05e1dc38045386691b91d7e6945332", size = 9210689, upload-time = "2025-11-01T16:33:08.196Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" +name = "prettytable" +version = "3.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/b1/85e18ac92afd08c533603e3393977b6bc1443043115a47bb094f3b98f94f/prettytable-3.16.0.tar.gz", hash = "sha256:3c64b31719d961bf69c9a7e03d0c1e477320906a98da63952bc6698d6164ff57", size = 66276, upload-time = "2025-03-24T19:39:04.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, + { url = "https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl", hash = "sha256:b5eccfabb82222f5aa46b798ff02a8452cf530a352c31bddfa29be41242863aa", size = 33863, upload-time = "2025-03-24T19:39:02.359Z" }, ] [[package]] -name = "mkdocs-video" -version = "1.5.0" +name = "proglog" +version = "0.1.12" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lxml" }, - { name = "mkdocs" }, + { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/16/45213649b6756744f36f31014fc8673df1d7c998bb9a801c2d769fff4114/mkdocs-video-1.5.0.tar.gz", hash = "sha256:0defc018f4b7927f8afffc4d8e039c84dfba636dffc5e25e2bfa8d6350bc8eca", size = 5633, upload-time = "2023-03-17T17:36:43.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/af/c108866c452eda1132f3d6b3cb6be2ae8430c97e9309f38ca9dbd430af37/proglog-0.1.12.tar.gz", hash = "sha256:361ee074721c277b89b75c061336cb8c5f287c92b043efa562ccf7866cda931c", size = 8794, upload-time = "2025-05-09T14:36:18.316Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/3a/b51e305eca21cdd58f4eb06973099ba0ea679b1f437a92a49f8fc576310e/mkdocs_video-1.5.0-py3-none-any.whl", hash = "sha256:b35613d4dacbac2dfa94d8c2600383cda14ad99a1fa1542b5fc4e9c6d19e9fe1", size = 6570, upload-time = "2023-03-17T17:36:40.776Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1b/f7ea6cde25621cd9236541c66ff018f4268012a534ec31032bcb187dc5e7/proglog-0.1.12-py3-none-any.whl", hash = "sha256:ccaafce51e80a81c65dc907a460c07ccb8ec1f78dc660cfd8f9ec3a22f01b84c", size = 6337, upload-time = "2025-05-09T14:36:16.798Z" }, ] [[package]] -name = "mkdocstrings" -version = "0.30.1" +name = "prompt-toolkit" +version = "3.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mkdocs" }, - { name = "mkdocs-autorefs" }, - { name = "pymdown-extensions" }, + { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/33/2fa3243439f794e685d3e694590d28469a9b8ea733af4b48c250a3ffc9a0/mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f", size = 106350, upload-time = "2025-09-19T10:49:26.446Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/2c/f0dc4e1ee7f618f5bff7e05898d20bf8b6e7fa612038f768bfa295f136a4/mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82", size = 36704, upload-time = "2025-09-19T10:49:24.805Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] [[package]] -name = "mkdocstrings-python" -version = "1.18.2" +name = "propcache" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "griffe" }, - { name = "mkdocs-autorefs" }, - { name = "mkdocstrings" }, +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/ae/58ab2bfbee2792e92a98b97e872f7c003deb903071f75d8d83aa55db28fa/mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323", size = 207972, upload-time = "2025-08-28T16:11:19.847Z" } + +[[package]] +name = "protobuf" +version = "6.33.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954", size = 443463, upload-time = "2025-10-15T20:39:52.159Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/8f/ce008599d9adebf33ed144e7736914385e8537f5fc686fdb7cceb8c22431/mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d", size = 138215, upload-time = "2025-08-28T16:11:18.176Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035", size = 425593, upload-time = "2025-10-15T20:39:40.29Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee", size = 436882, upload-time = "2025-10-15T20:39:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455", size = 427521, upload-time = "2025-10-15T20:39:43.803Z" }, + { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90", size = 324445, upload-time = "2025-10-15T20:39:44.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298", size = 339159, upload-time = "2025-10-15T20:39:46.186Z" }, + { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef", size = 323172, upload-time = "2025-10-15T20:39:47.465Z" }, + { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" }, ] [[package]] -name = "natsort" -version = "8.4.0" +name = "psutil" +version = "7.1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, ] [[package]] -name = "nodeenv" -version = "1.9.1" +name = "pycparser" +version = "2.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] [[package]] -name = "packaging" -version = "25.0" +name = "pydantic" +version = "2.12.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, ] [[package]] -name = "paginate" -version = "0.5.7" +name = "pydantic-core" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, ] [[package]] -name = "pathspec" -version = "0.12.1" +name = "pydantic-settings" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, ] [[package]] -name = "platformdirs" -version = "4.5.0" +name = "pydot" +version = "4.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/35/b17cb89ff865484c6a20ef46bf9d95a5f07328292578de0b295f4a6beec2/pydot-4.0.1.tar.gz", hash = "sha256:c2148f681c4a33e08bf0e26a9e5f8e4099a82e0e2a068098f32ce86577364ad5", size = 162594, upload-time = "2025-06-17T20:09:56.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/7e/32/a7125fb28c4261a627f999d5fb4afff25b523800faed2c30979949d6facd/pydot-4.0.1-py3-none-any.whl", hash = "sha256:869c0efadd2708c0be1f916eb669f3d664ca684bc57ffb7ecc08e70d5e93fee6", size = 37087, upload-time = "2025-06-17T20:09:55.25Z" }, ] [[package]] -name = "pluggy" -version = "1.6.0" +name = "pygit2" +version = "1.19.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/4b/da6f1a1a48a4095e3c12c5fa1f2784f4423db3e16159e08740cc5c6ee639/pygit2-1.19.0.tar.gz", hash = "sha256:ca5db6f395a74166a019d777895f96bcb211ee60ce0be4132b139603e0066d83", size = 799757, upload-time = "2025-10-23T12:34:35.783Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/e50790d985932f31d329ab177bc714703a10fe9779dc3f76c101ff1a18ba/pygit2-1.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:770e1d506ceb08cc65ac0de4e8a9911a169603361c23c3386556a8aab7ee1f7e", size = 5510740, upload-time = "2025-10-23T12:33:38.12Z" }, + { url = "https://files.pythonhosted.org/packages/89/e2/56eb9de348221aa7b76d4fd07a663a1be2ccaad1571f68cc9bd83fc7a4ef/pygit2-1.19.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db2a75f4a2e896a8b09bcfdeb1d52c0fc72b403e0e9910c03307d5e577e3fb40", size = 5783741, upload-time = "2025-10-23T12:33:40.253Z" }, + { url = "https://files.pythonhosted.org/packages/c6/91/274f7ea354d9029a27133f3c1a222a0fb468b4bc40f535e389cda25a17c7/pygit2-1.19.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2600ed9acc3b6f52e49a65c2200e5eadd70ef6d022fd8e029adbfa6e6d9cbf50", size = 4620504, upload-time = "2025-10-23T12:33:41.48Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8b/e1a4728ef6f7c1522394a16b614f52a91295602126b81150acc944d2b858/pygit2-1.19.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:795dce83170f4b82fa275a6233ee3f70673a06a3e22a1c57221e16b9a140ef98", size = 5518086, upload-time = "2025-10-23T12:33:42.926Z" }, + { url = "https://files.pythonhosted.org/packages/63/ff/04d91f40caae04bf03394b181ed5e614e82928a99ea3659d38d12a124604/pygit2-1.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b26875600a8720196d0cdaacdb485761ac07334512a44da79d7b2398672549f7", size = 5782825, upload-time = "2025-10-23T12:33:44.135Z" }, + { url = "https://files.pythonhosted.org/packages/73/c7/2dea8ce2d0c6185911334f5f84eeb9e941a030e13596b43ee5fab00f550c/pygit2-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5dd263e0d3820892e0751b344eab30f5fb068f20a456e2b2fc2082160d263fe", size = 5484890, upload-time = "2025-10-23T12:33:48.171Z" }, + { url = "https://files.pythonhosted.org/packages/97/bc/2df6780be170187baeb2de4e4743c66cca8c35e838a8e66a9e53c0d53282/pygit2-1.19.0-cp312-cp312-win32.whl", hash = "sha256:1314c81d3608201be032ff1631392f92c767b65d3c81f7efb4e83a551b65290d", size = 942311, upload-time = "2025-10-23T12:33:49.257Z" }, + { url = "https://files.pythonhosted.org/packages/48/bd/aba9902ee25e7a6aaf43c10b979dbbabe64f920217e8817f4df942fb0b68/pygit2-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:f7efa3fe6d818b48561bc5b72bd991eb57c0baaafc864b64b27f9d064761e557", size = 1159573, upload-time = "2025-10-23T12:33:50.397Z" }, + { url = "https://files.pythonhosted.org/packages/2c/de/0063114b8140f0aa52bdc63d49405bc4a81268968319ce922ce374264554/pygit2-1.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:c433b9d448912ba7237cb26149b43252b6187acebfa205edf53cfde9e0e441bb", size = 966822, upload-time = "2025-10-23T12:33:51.764Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f7/c0f72a5a87537b5622931a59be33654fa6e7cce25497c42f54de45a54622/pygit2-1.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e84d4a1c8bcd462524bf9d0c5778c2219042c8aeeea82735e8f415d8f8519797", size = 5510727, upload-time = "2025-10-23T12:33:53.223Z" }, + { url = "https://files.pythonhosted.org/packages/ba/40/3df9f3cc9397aae3c6befcea9f815393c8d0a53e9717b6ab4ca51f62ff72/pygit2-1.19.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5998943514b89fd4bf8ab11320872bc4c0b6d3517b27beaf79ff9591d3142f1c", size = 5784559, upload-time = "2025-10-23T12:33:54.531Z" }, + { url = "https://files.pythonhosted.org/packages/64/71/bcbf4e144e843329f4167df2c54ec67dfffa8b71ee736508cada602f1894/pygit2-1.19.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:38ea299bd02584df017f44a0673157bb7b28a2ae69826cfbb456f7429e781d58", size = 4621054, upload-time = "2025-10-23T12:33:55.783Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b3/f0c69382492f1f19030ee580fb85bd4cbae157ecf3b24ac89362a315539c/pygit2-1.19.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46f5f287f19053eb1ba36d72a7458712bc5dcdb1b667ecf162a6cae7e643afe2", size = 5519203, upload-time = "2025-10-23T12:33:57.099Z" }, + { url = "https://files.pythonhosted.org/packages/06/0b/919be1b5a679391125e96acfa6f9930f07aa143854faf5d37e67950a150a/pygit2-1.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41d9effdc1e54e7bcd14013ea5a86c9dbf9bbc16b7c932d6e0ed96773e0baa68", size = 5784159, upload-time = "2025-10-23T12:33:58.343Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/19944d4697e0cf52abe6721bfefa4a826c7ec33144cdeab263180ee1311f/pygit2-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e74312467a45de208dc157d1053b851b4181c2fabcacc5d9c578a8ef1b367e13", size = 5486342, upload-time = "2025-10-23T12:33:59.515Z" }, + { url = "https://files.pythonhosted.org/packages/ec/67/3e2d8d68b857466bea178236dafbcac09476907ee5dc7e9fc6e178407890/pygit2-1.19.0-cp313-cp313-win32.whl", hash = "sha256:eb55020bf0bd36e9a4c167c88139a9e20e787b2c66b5c2f60a8a12f3e0334a82", size = 942284, upload-time = "2025-10-23T12:34:01.438Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/58ff127fa1177f63c6b56bf77882a357779090a70aaaadbdfe71bbd98a27/pygit2-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:37ba33c59b8b941f7a2fa1014c11bc847c315ebbaeb92341f7f39efeab75edb2", size = 1159559, upload-time = "2025-10-23T12:34:03.2Z" }, + { url = "https://files.pythonhosted.org/packages/cc/02/dabfd6880d948dd91f68a757f5a8c1280fe8e8e0f2786e9b85695c5627f7/pygit2-1.19.0-cp313-cp313-win_arm64.whl", hash = "sha256:f59b39b7f9583fd0e5dbd63b6f156b735293b3b4a1688534a5eb2c695975eb39", size = 966823, upload-time = "2025-10-23T12:34:04.432Z" }, + { url = "https://files.pythonhosted.org/packages/84/ee/6248d08f5a2b6a19e8cb706c64241158992996ddcfe93cd1bea7f87d258b/pygit2-1.19.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:db136a24a9b46327f334a960604f5ed5ca138cab57cf54906c82bae147b2f600", size = 5510739, upload-time = "2025-10-23T12:34:05.565Z" }, + { url = "https://files.pythonhosted.org/packages/de/b1/612a653abe3310efcb17a3efa9881c793dba8a563e781540b1a223c04e88/pygit2-1.19.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1487023a52d25d169fd3d5c146475dec0f39dd54586bd55aac810ae0d7809154", size = 5786665, upload-time = "2025-10-23T12:34:07.036Z" }, + { url = "https://files.pythonhosted.org/packages/66/7c/720c12f14ca0947c3d1d10832f6094c765bfed640b7ca160e45f4ec21e58/pygit2-1.19.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14bd00877c5b2571d1e8e2e203df35291198a3a9a90602121a8419b540174b8a", size = 4624940, upload-time = "2025-10-23T12:34:08.229Z" }, + { url = "https://files.pythonhosted.org/packages/e4/72/ad3061fccddc9eb06ef56b6a80ab506b61b2606732a4176c84222958705e/pygit2-1.19.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb47afb011ef6133e1b938018afa7d6435040d1ae1e84242bf0699dc6960a4cf", size = 5521370, upload-time = "2025-10-23T12:34:09.446Z" }, + { url = "https://files.pythonhosted.org/packages/85/d2/20c3436114325286f67e9fa4ae9a7eb44335a5fa6f9c72f8ea95f84a19ee/pygit2-1.19.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4d81f578aef1c04cd5169b96aa38f864c0ed1a37249e13bff69b49f12f6ae761", size = 5784686, upload-time = "2025-10-23T12:34:11.67Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f7/5302831d9e8bdbe6643321e3a3499c1df12bb8d519ca0250bc4fddc10b8f/pygit2-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8c97c281d4e24d45b1d93ea844b52ea157a7c1c408e31e53bbb7b969c3168a96", size = 5487011, upload-time = "2025-10-23T12:34:13.314Z" }, + { url = "https://files.pythonhosted.org/packages/09/e4/d64ca0240e5eecfa541b31869b4d67f1237b16469ce99d0f67734aa71f6f/pygit2-1.19.0-cp314-cp314-win32.whl", hash = "sha256:e69576401664911633351ebbe2a896861a8c1ff531d0375796e61483db39ebd7", size = 963580, upload-time = "2025-10-23T12:34:14.823Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1a/4fd4b60ac6b17581e8c63565a6ae8d529a1c699e021297c3e1ce9f510ed2/pygit2-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:e56a14fcbfb8e07e30d18f21cfb790a74506957fa3ce03c54c02745b5d0152e2", size = 1190416, upload-time = "2025-10-23T12:34:16.054Z" }, + { url = "https://files.pythonhosted.org/packages/9b/7b/814b5b86cb9735068d93ebc15d055445a82c0b3d9689d4095fdc2810890b/pygit2-1.19.0-cp314-cp314-win_arm64.whl", hash = "sha256:2e84ab99802d8de643c6f8aa5b689b033ee5d5dee70ae04432005299dec33ee4", size = 994715, upload-time = "2025-10-23T12:34:17.178Z" }, + { url = "https://files.pythonhosted.org/packages/a5/45/78b1928f2b7517ee792d72a7a5527862066e53aeac586cea9fa5bd0d21cb/pygit2-1.19.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:12c8f8672fb319ee4276fc028019de4d3e6b9cd94bffc74a1aaa81ffc6445dc7", size = 5517120, upload-time = "2025-10-23T12:34:18.442Z" }, + { url = "https://files.pythonhosted.org/packages/c1/92/df0f760b80b57bf878df11ce3212beaa1e18ec9ce622088776435a4d3ec1/pygit2-1.19.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccddde0826101035ca31f9df42c6a57704285e2387ab15cd314afa18f0521d95", size = 5846886, upload-time = "2025-10-23T12:34:19.906Z" }, + { url = "https://files.pythonhosted.org/packages/77/d1/b3af6681c698210fd9f80e8c0b8b05b3a4d7e9dfb9bd95741867adbd14c1/pygit2-1.19.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de1fe1a2dfd10a58d17e88c14b363aa809a480c181b56c47cbc4fa83b0b68918", size = 4682365, upload-time = "2025-10-23T12:34:21.606Z" }, + { url = "https://files.pythonhosted.org/packages/4d/92/aff356800b7189fb085fba9e82102666bbec207dbd48acbf869947f41200/pygit2-1.19.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b775d93b7ea9b8ff676002a857eabbe07fbc838802fd76b9b1e17109f571557", size = 5574375, upload-time = "2025-10-23T12:34:23.003Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a2/6beb46b3a04567182374273d0845f8460572b562ba28ea908b363f1eaf05/pygit2-1.19.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:21a9bf74720360fcb21a0e6ad750013ba3e0625cd484f1bb7ddfefdcd207c0f5", size = 5842375, upload-time = "2025-10-23T12:34:24.408Z" }, + { url = "https://files.pythonhosted.org/packages/1d/eb/3fffdb3e1aecba1c4124f5e73008bd63a2fd8d2b44f420d4612e6dcf5065/pygit2-1.19.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba17b947b2166000aeb9b19135160bb32aec38a7da9892d9fb476cdb3f518aba", size = 5539028, upload-time = "2025-10-23T12:34:26.307Z" }, + { url = "https://files.pythonhosted.org/packages/be/6e/dfc7693474e908ce4be5833b4b3a73868d1f8b58540af68b8361589c3168/pygit2-1.19.0-cp314-cp314t-win32.whl", hash = "sha256:772bf01936eb306c6dfb3cc3b955e2f8d3271d0eef2c23e24203352519421b20", size = 968740, upload-time = "2025-10-23T12:34:27.606Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/5e926b7756efb97db3c52a7ad45b7c3eec135cc5bc19e1647313bb95abf4/pygit2-1.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4516e87d35df9b2867b5f859a0d09cd86857f1e3ef4215d6c87850ce4869a793", size = 1196880, upload-time = "2025-10-23T12:34:28.875Z" }, + { url = "https://files.pythonhosted.org/packages/e3/56/28654e46e78743740252419a713edce22323dbcab8cc38e72e14490e5b27/pygit2-1.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:fceba6e279ab2be9ec762f2b3ff1b315cd922de879800d1f57a25eba4a90bc60", size = 995620, upload-time = "2025-10-23T12:34:30.635Z" }, ] [[package]] @@ -656,6 +3030,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pygtrie" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/13/55deec25bf09383216fa7f1dfcdbfca40a04aa00b6d15a5cbf25af8fce5f/pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2", size = 39266, upload-time = "2022-07-16T14:29:47.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/cd/bd196b2cf014afb1009de8b0f05ecd54011d881944e62763f3c1b1e8ef37/pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16", size = 25099, upload-time = "2022-09-23T20:30:05.12Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + [[package]] name = "pymdown-extensions" version = "10.16.1" @@ -669,6 +3066,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, ] +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + [[package]] name = "pyright" version = "1.1.407" @@ -734,6 +3140,124 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "pytorch-lightning" +version = "2.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/78/bce84aab9a5b3b2e9d087d4f1a6be9b481adbfaac4903bc9daaaf09d49a3/pytorch_lightning-2.5.5.tar.gz", hash = "sha256:d6fc8173d1d6e49abfd16855ea05d2eb2415e68593f33d43e59028ecb4e64087", size = 643703, upload-time = "2025-09-05T16:01:18.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/f6/99a5c66478f469598dee25b0e29b302b5bddd4e03ed0da79608ac964056e/pytorch_lightning-2.5.5-py3-none-any.whl", hash = "sha256:0b533991df2353c0c6ea9ca10a7d0728b73631fd61f5a15511b19bee2aef8af0", size = 832431, upload-time = "2025-09-05T16:01:16.234Z" }, +] + +[[package]] +name = "pytorch-wavelets" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "six" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/e3/1553046e97926b859edcab05d74b5b7543327c92b37c58919a3e49ff6151/pytorch_wavelets-1.3.0.tar.gz", hash = "sha256:8b5c63f87c2bb36e6b342a7bb294926bda5cd974614fb4848deab6ec2792f56f", size = 1029927, upload-time = "2023-04-13T06:26:52.086Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/65/b7da80705dc679999ef77c06ded71e060d4ea14ed80111c104223e130cc1/pytorch_wavelets-1.3.0-py3-none-any.whl", hash = "sha256:e4f8635872370d8de640ef7548edef4ef60d5c565553c463210e939c7901ee69", size = 54879, upload-time = "2023-04-13T06:26:45.63Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywavelets" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/75/50581633d199812205ea8cdd0f6d52f12a624886b74bf1486335b67f01ff/pywavelets-1.9.0.tar.gz", hash = "sha256:148d12203377772bea452a59211d98649c8ee4a05eff019a9021853a36babdc8", size = 3938340, upload-time = "2025-08-04T16:20:04.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/37/3fda13fb2518fdd306528382d6b18c116ceafefff0a7dccd28f1034f4dd2/pywavelets-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30baa0788317d3c938560c83fe4fc43817342d06e6c9662a440f73ba3fb25c9b", size = 4320835, upload-time = "2025-08-04T16:19:04.855Z" }, + { url = "https://files.pythonhosted.org/packages/36/65/a5549325daafc3eae4b52de076798839eaf529a07218f8fb18cccefe76a1/pywavelets-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:df7436a728339696a7aa955c020ae65c85b0d9d2b5ff5b4cf4551f5d4c50f2c7", size = 4290469, upload-time = "2025-08-04T16:19:06.178Z" }, + { url = "https://files.pythonhosted.org/packages/05/85/901bb756d37dfa56baa26ef4a3577aecfe9c55f50f51366fede322f8c91d/pywavelets-1.9.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07b26526db2476974581274c43a9c2447c917418c6bd03c8d305ad2a5cd9fac3", size = 4437717, upload-time = "2025-08-04T16:19:07.514Z" }, + { url = "https://files.pythonhosted.org/packages/0f/34/0f54dd9c288941294898877008bcb5c07012340cc9c5db9cff1bd185d449/pywavelets-1.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:573b650805d2f3c981a0e5ae95191c781a722022c37a0f6eba3fa7eae8e0ee17", size = 4483843, upload-time = "2025-08-04T16:19:08.857Z" }, + { url = "https://files.pythonhosted.org/packages/48/1f/cff6bb4ea64ff508d8cac3fe113c0aa95310a7446d9efa6829027cc2afdf/pywavelets-1.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3747ec804492436de6e99a7b6130480e53406d047e87dc7095ab40078a515a23", size = 4442236, upload-time = "2025-08-04T16:19:11.061Z" }, + { url = "https://files.pythonhosted.org/packages/ce/53/a3846eeefe0fb7ca63ae045f038457aa274989a15af793c1b824138caf98/pywavelets-1.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5163665686219c3f43fd5bbfef2391e87146813961dad0f86c62d4aed561f547", size = 4488077, upload-time = "2025-08-04T16:19:12.333Z" }, + { url = "https://files.pythonhosted.org/packages/f7/98/44852d2fe94455b72dece2db23562145179d63186a1c971125279a1c381f/pywavelets-1.9.0-cp312-cp312-win32.whl", hash = "sha256:80b8ab99f5326a3e724f71f23ba8b0a5b03e333fa79f66e965ea7bed21d42a2f", size = 4134094, upload-time = "2025-08-04T16:19:13.564Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a7/0d9ee3fe454d606e0f5c8e3aebf99d2ecddbfb681826a29397729538c8f1/pywavelets-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:92bfb8a117b8c8d3b72f2757a85395346fcbf37f50598880879ae72bd8e1c4b9", size = 4213900, upload-time = "2025-08-04T16:19:14.939Z" }, + { url = "https://files.pythonhosted.org/packages/db/a7/dec4e450675d62946ad975f5b4d924437df42d2fae46e91dfddda2de0f5a/pywavelets-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:74f8455c143818e4b026fc67b27fd82f38e522701b94b8a6d1aaf3a45fcc1a25", size = 4316201, upload-time = "2025-08-04T16:19:16.259Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0c/b54b86596c0df68027e48c09210e907e628435003e77048384a2dd6767e3/pywavelets-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c50320fe0a4a23ddd8835b3dc9b53b09ee05c7cc6c56b81d0916f04fc1649070", size = 4286838, upload-time = "2025-08-04T16:19:17.92Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9c/333969c3baad8af2e7999e83addcb7bb1d1fd48e2d812fb27e2e89582cb1/pywavelets-1.9.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6e059265223ed659e5214ab52a84883c88ddf3decbf08d7ec6abb8e4c5ed7be", size = 4430753, upload-time = "2025-08-04T16:19:19.529Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/a24c6ff03b026b826ad7b9267bd63cd34ce026795a0302f8a5403840b8e7/pywavelets-1.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ae10ed46c139c7ddb8b1249cfe0989f8ccb610d93f2899507b1b1573a0e424b5", size = 4491315, upload-time = "2025-08-04T16:19:20.717Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/e3fbb502fca3469e51ced4f1e1326364c338be91edc5db5a8ddd26b303fa/pywavelets-1.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8f8b1cc2df012401cb837ee6fa2f59607c7b4fe0ff409d9a4f6906daf40dc86", size = 4437654, upload-time = "2025-08-04T16:19:22.359Z" }, + { url = "https://files.pythonhosted.org/packages/92/44/c9b25084048d9324881a19b88e0969a4141bcfdc1d218f1b4b680b7af1c1/pywavelets-1.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db43969c7a8fbb17693ecfd14f21616edc3b29f0e47a49b32fa4127c01312a67", size = 4496435, upload-time = "2025-08-04T16:19:23.842Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b6/b27ec18c72b1dee3314e297af39c5f8136d43cc130dd93cb6c178ca820e5/pywavelets-1.9.0-cp313-cp313-win32.whl", hash = "sha256:9e7d60819d87dcd6c68a2d1bc1d37deb1f4d96607799ab6a25633ea484dcda41", size = 4132709, upload-time = "2025-08-04T16:19:25.415Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/78ef3f9fb36cdb16ee82371d22c3a7c89eeb79ec8c9daef6222060da6c79/pywavelets-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:0d70da9d7858c869e24dc254f16a61dc09d8a224cad85a10c393b2eccddeb126", size = 4213377, upload-time = "2025-08-04T16:19:26.875Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cd/ca0d9db0ff29e3843f6af60c2f5eb588794e05ca8eeb872a595867b1f3f5/pywavelets-1.9.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dc85f44c38d76a184a1aa2cb038f802c3740428c9bb877525f4be83a223b134", size = 4354336, upload-time = "2025-08-04T16:19:28.745Z" }, + { url = "https://files.pythonhosted.org/packages/82/d6/70afefcc1139f37d02018a3b1dba3b8fc87601bb7707d9616b7f7a76e269/pywavelets-1.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7acf6f950c6deaecd210fbff44421f234a8ca81eb6f4da945228e498361afa9d", size = 4335721, upload-time = "2025-08-04T16:19:30.371Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3a/713f731b9ed6df0c36269c8fb62be8bb28eb343b9e26b13d6abda37bce38/pywavelets-1.9.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:144d4fc15c98da56654d0dca2d391b812b8d04127b194a37ad4a497f8e887141", size = 4418702, upload-time = "2025-08-04T16:19:31.743Z" }, + { url = "https://files.pythonhosted.org/packages/44/e8/f801eb4b5f7a316ba20054948c5d6b27b879c77fab2674942e779974bd86/pywavelets-1.9.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1aa3729585408a979d655736f74b995b511c86b9be1544f95d4a3142f8f4b8b5", size = 4470023, upload-time = "2025-08-04T16:19:32.963Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/44b002cb16f2a392f2082308dd470b3f033fa4925d3efa7c46f790ce895a/pywavelets-1.9.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e0e24ad6b8eb399c49606dd1fcdcbf9749ad7f6d638be3fe6f59c1f3098821e2", size = 4426498, upload-time = "2025-08-04T16:19:34.151Z" }, + { url = "https://files.pythonhosted.org/packages/91/fe/2b70276ede7878c5fe8356ca07574db5da63e222ce39a463e84bfad135e8/pywavelets-1.9.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3830e6657236b53a3aae20c735cccead942bb97c54bbca9e7d07bae01645fe9c", size = 4477528, upload-time = "2025-08-04T16:19:35.932Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ed/d58b540c15e36508cfeded7b0d39493e811b0dce18d9d4e6787fb2e89685/pywavelets-1.9.0-cp313-cp313t-win32.whl", hash = "sha256:81bb65facfbd7b50dec50450516e72cdc51376ecfdd46f2e945bb89d39bfb783", size = 4186493, upload-time = "2025-08-04T16:19:37.198Z" }, + { url = "https://files.pythonhosted.org/packages/84/b2/12a849650d618a86bbe4d8876c7e20a7afe59a8cad6f49c57eca9af26dfa/pywavelets-1.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:47d52cf35e2afded8cfe1133663f6f67106a3220b77645476ae660ad34922cb4", size = 4274821, upload-time = "2025-08-04T16:19:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1f/18c82122547c9eec2232d800b02ada1fbd30ce2136137b5738acca9d653e/pywavelets-1.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:53043d2f3f4e55a576f51ac594fe33181e1d096d958e01524db5070eb3825306", size = 4314440, upload-time = "2025-08-04T16:19:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e1/1c92ac6b538ef5388caf1a74af61cf6af16ea6d14115bb53357469cb38d6/pywavelets-1.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bc36b42b1b125fd9cb56e7956b22f8d0f83c1093f49c77fc042135e588c799", size = 4290162, upload-time = "2025-08-04T16:19:41.322Z" }, + { url = "https://files.pythonhosted.org/packages/96/d3/d856a2cac8069c20144598fa30a43ca40b5df2e633230848a9a942faf04a/pywavelets-1.9.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08076eb9a182ddc6054ac86868fb71df6267c341635036dc63d20bdbacd9ad7e", size = 4437162, upload-time = "2025-08-04T16:19:42.556Z" }, + { url = "https://files.pythonhosted.org/packages/c9/54/777e0495acd4fb008791e84889be33d6e7fc8af095b441d939390b7d2491/pywavelets-1.9.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ee1ee7d80f88c64b8ec3b5021dd1e94545cc97f0cd479fb51aa7b10f6def08e", size = 4498169, upload-time = "2025-08-04T16:19:43.791Z" }, + { url = "https://files.pythonhosted.org/packages/76/68/81b97f4d18491a18fbe17e06e2eee80a591ce445942f7b6f522de07813c5/pywavelets-1.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3226b6f62838a6ccd7782cb7449ee5d8b9d61999506c1d9b03b2baf41b01b6fd", size = 4443318, upload-time = "2025-08-04T16:19:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/92/74/5147f2f0436f7aa131cb1bc13dba32ef5f3862748ae1c7366b4cde380362/pywavelets-1.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fb7f4b11d18e2db6dd8deee7b3ce8343d45f195f3f278c2af6e3724b1b93a24", size = 4503294, upload-time = "2025-08-04T16:19:46.632Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d4/af998cc71e869919e0ab45471bd43e91d055ac7bc3ce6f56cc792c9b6bc8/pywavelets-1.9.0-cp314-cp314-win32.whl", hash = "sha256:9902d9fc9812588ab2dce359a1307d8e7f002b53a835640e2c9388fe62a82fd4", size = 4144478, upload-time = "2025-08-04T16:19:47.974Z" }, + { url = "https://files.pythonhosted.org/packages/7d/66/1d071eae5cc3e3ad0e45334462f8ce526a79767ccb759eb851aa5b78a73a/pywavelets-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:7e57792bde40e331d6cc65458e5970fd814dba18cfc4e9add9d051e901a7b7c7", size = 4227186, upload-time = "2025-08-04T16:19:49.57Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1f/da0c03ac99bd9d20409c0acf6417806d4cf333d70621da9f535dd0cf27fa/pywavelets-1.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b47c72fb4b76d665c4c598a5b621b505944e5b761bf03df9d169029aafcb652f", size = 4354391, upload-time = "2025-08-04T16:19:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/de9e225d8cc307fbb4fda88aefa79442775d5e27c58ee4d3c8a8580ceba6/pywavelets-1.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:969e369899e7eab546ea5d77074e4125082e6f9dad71966499bf5dee3758be55", size = 4335810, upload-time = "2025-08-04T16:19:52.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/3b/336761359d07cd44a4233ca854704ff2a9e78d285879ccc82d254b9daa57/pywavelets-1.9.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8aeffd4f35036c1fade972a61454de5709a7a8fc9a7d177eefe3ac34d76962e5", size = 4422220, upload-time = "2025-08-04T16:19:54.068Z" }, + { url = "https://files.pythonhosted.org/packages/98/61/76ccc7ada127f14f65eda40e37407b344fd3713acfca7a94d7f0f67fe57d/pywavelets-1.9.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f63f400fcd4e7007529bd06a5886009760da35cd7e76bb6adb5a5fbee4ffeb8c", size = 4470156, upload-time = "2025-08-04T16:19:55.379Z" }, + { url = "https://files.pythonhosted.org/packages/e0/de/142ca27ee729cf64113c2560748fcf2bd45b899ff282d6f6f3c0e7f177bb/pywavelets-1.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a63bcb6b5759a7eb187aeb5e8cd316b7adab7de1f4b5a0446c9a6bcebdfc22fb", size = 4430167, upload-time = "2025-08-04T16:19:56.566Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5e/90b39adff710d698c00ba9c3125e2bec99dad7c5f1a3ba37c73a78a6689f/pywavelets-1.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9950eb7c8b942e9bfa53d87c7e45a420dcddbd835c4c5f1aca045a3f775c6113", size = 4477378, upload-time = "2025-08-04T16:19:58.162Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1a/89f5f4ebcb9d34d9b7b2ac0a868c8b6d8c78d699a36f54407a060cea0566/pywavelets-1.9.0-cp314-cp314t-win32.whl", hash = "sha256:097f157e07858a1eb370e0d9c1bd11185acdece5cca10756d6c3c7b35b52771a", size = 4209132, upload-time = "2025-08-04T16:20:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/68/d2/a8065103f5e2e613b916489e6c85af6402a1ec64f346d1429e2d32cb8d03/pywavelets-1.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3b6ff6ba4f625d8c955f68c2c39b0a913776d406ab31ee4057f34ad4019fb33b", size = 4306793, upload-time = "2025-08-04T16:20:02.934Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -792,6 +3316,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] +[[package]] +name = "rdkit" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/37/0875c5af275031b57e3da0e103a85ff2d3fdbacfde5ab459f9bfed4d774e/rdkit-2025.9.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:4b068562a0fc9a17db77e76b77e10957c56fa69f0a0b3a25bd6e35e924237e59", size = 31766217, upload-time = "2025-10-06T09:04:43.675Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1b/ceebffc8d123594992f90f453dc666c9730a74841692c862996d335c25b0/rdkit-2025.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58218214f799100f6cb3b12e7bf3abdf3e17c1b5959d42a6c797b0f98f501bab", size = 29182056, upload-time = "2025-10-06T09:04:47.686Z" }, + { url = "https://files.pythonhosted.org/packages/23/8e/f5ffc57520212bcd0937f175e550faf9c1bdab9e6c3b86401e734c01cc06/rdkit-2025.9.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b25c37c452ec41a5b5a2fce9aef9f637b569cfea464c3cfa95e81477123cd08f", size = 34646881, upload-time = "2025-10-06T09:04:52.322Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/83ad99664bb500ee4864964c2b4099bdfb621b58fbc9ae265766e224fbd6/rdkit-2025.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:cf7f05cea1e1c11662490e9bd5f95d44591dddc4c8f6a792d0eca6394edc606b", size = 36167555, upload-time = "2025-10-06T09:04:56.036Z" }, + { url = "https://files.pythonhosted.org/packages/dd/79/ed349426406a832fe99eb1c206e629e2194a1024cc22a5e8e7db8207b7d0/rdkit-2025.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:a22d77f9b6b3c00d25001d323fb3febde7e96534f6e7cee8b84967220e729eea", size = 23578781, upload-time = "2025-10-06T09:05:00.134Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/b04ca14b404852700f273767070f5cd5b7500d9ca2257e41ed68de6c3955/rdkit-2025.9.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:42f3c4225aa17a53681e38fcbb53e44fa02a7937abe58fcb7605368f396008f3", size = 31765114, upload-time = "2025-10-06T09:05:04.204Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2e/220f55ba68b2745c9f15fe3db94a1013ebd41e087534757a9b1dbaff393a/rdkit-2025.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b6a14105beafa319d781854cc24acd6341cd600cd02e1659f93ac130b9d2787c", size = 29181058, upload-time = "2025-10-06T09:05:07.723Z" }, + { url = "https://files.pythonhosted.org/packages/88/b5/9501e4bc30d787b8266e5edd8268bec71972af1a7f717c7b837fdc1dd98a/rdkit-2025.9.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a6fded29ef405c09e48e2fd62c1680948c98f7d34f11ca209fc7690f44cd0f4e", size = 34645507, upload-time = "2025-10-06T09:05:11.493Z" }, + { url = "https://files.pythonhosted.org/packages/bb/bb/c562dc38db3e23c4a22459552c4642c3e6a59dc50ff42ba917c2d7095110/rdkit-2025.9.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9c6923647f4041df8c39b1aff147b36545e66573f331ce929418176849caebfe", size = 36166073, upload-time = "2025-10-06T09:05:15.796Z" }, + { url = "https://files.pythonhosted.org/packages/d4/01/3ba5d12ce9941767b2c4ab0fa672e2606b1d36b2b7f0798bce846930708b/rdkit-2025.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:5ee6b3f2880a0d62930855ac3d56a21c518d952e9b660f60e2f49d0183e36b9f", size = 23577803, upload-time = "2025-10-06T09:05:19.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/94/225792e12046b492e5c37671480e58d2bff14035c0d18122fa2b7abca155/rdkit-2025.9.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fa71303ae06d5d7aea6e76a5321a02f83ed4057ca10566269502b511a6ba8c48", size = 31767003, upload-time = "2025-10-06T09:05:22.734Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/b06b28b43d70a83cfd35a1ede3b549d2b2bf9547e898bb95aa84e9ed10e8/rdkit-2025.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:02c8d361b0e7d0adda46687a82777a34be0b913cbbdacbedb88b7c066262de43", size = 29201272, upload-time = "2025-10-06T09:05:26.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/80/b9f990de716344e7c09dafaadc1c7397d15e78c02ecb984a2408ca2b3970/rdkit-2025.9.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:3fe0a66acd4065cc379f0d46c9b7c89b3822642a44683311bd269093edbab967", size = 34676950, upload-time = "2025-10-06T09:05:29.88Z" }, + { url = "https://files.pythonhosted.org/packages/fe/76/e1acd4a03f31cf323bd4899062ae954df029d06b1f8794bb1a03dbbf9f5c/rdkit-2025.9.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:1d6c0f30f206067648f567e62e294d98a55bf40c550684f3da54ea061305f5b5", size = 36174898, upload-time = "2025-10-06T09:05:34.096Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0f/39b8fc050830146a011ed86b7c5c738b30fd22fb0532e91984ceef6d00a8/rdkit-2025.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:5393ef7c40c7552d54f569057a4914159b0cd1a634f110e5a884659550f1ff0b", size = 24057571, upload-time = "2025-10-06T09:05:37.368Z" }, +] + [[package]] name = "requests" version = "2.32.5" @@ -804,7 +3354,64 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "python_full_version < '3.14' and platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/c7/ee630b29e04a672ecfc9b63227c87fd7a37eb67c1bf30fe95376437f897c/ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a", size = 147269, upload-time = "2025-10-22T17:54:02.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/73/bb1bc2529f852e7bf64a2dec885e89ff9f5cc7bbf6c9340eed30ff2c69c5/ruamel.yaml-0.18.16-py3-none-any.whl", hash = "sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba", size = 119858, upload-time = "2025-10-22T17:53:59.012Z" }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/e9/39ec4d4b3f91188fad1842748f67d4e749c77c37e353c4e545052ee8e893/ruamel.yaml.clib-0.2.14.tar.gz", hash = "sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e", size = 225394, upload-time = "2025-09-22T19:51:23.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/42/ccfb34a25289afbbc42017e4d3d4288e61d35b2e00cfc6b92974a6a1f94b/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27", size = 271775, upload-time = "2025-09-23T14:24:12.771Z" }, + { url = "https://files.pythonhosted.org/packages/82/73/e628a92e80197ff6a79ab81ec3fa00d4cc082d58ab78d3337b7ba7043301/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052", size = 138842, upload-time = "2025-09-22T19:50:49.156Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c5/346c7094344a60419764b4b1334d9e0285031c961176ff88ffb652405b0c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a", size = 647404, upload-time = "2025-09-22T19:50:52.921Z" }, + { url = "https://files.pythonhosted.org/packages/df/99/65080c863eb06d4498de3d6c86f3e90595e02e159fd8529f1565f56cfe2c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29", size = 753141, upload-time = "2025-09-22T19:50:50.294Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e3/0de85f3e3333f8e29e4b10244374a202a87665d1131798946ee22cf05c7c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4", size = 703477, upload-time = "2025-09-22T19:50:51.508Z" }, + { url = "https://files.pythonhosted.org/packages/d9/25/0d2f09d8833c7fd77ab8efeff213093c16856479a9d293180a0d89f6bed9/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9", size = 741157, upload-time = "2025-09-23T18:42:50.408Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8c/959f10c2e2153cbdab834c46e6954b6dd9e3b109c8f8c0a3cf1618310985/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259", size = 745859, upload-time = "2025-09-22T19:50:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6b/e580a7c18b485e1a5f30a32cda96b20364b0ba649d9d2baaf72f8bd21f83/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023", size = 770200, upload-time = "2025-09-22T19:50:55.718Z" }, + { url = "https://files.pythonhosted.org/packages/ef/44/3455eebc761dc8e8fdced90f2b0a3fa61e32ba38b50de4130e2d57db0f21/ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl", hash = "sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54", size = 98829, upload-time = "2025-09-22T19:50:58.895Z" }, + { url = "https://files.pythonhosted.org/packages/76/ab/5121f7f3b651db93de546f8c982c241397aad0a4765d793aca1dac5eadee/ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68", size = 115570, upload-time = "2025-09-22T19:50:57.981Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ae/e3811f05415594025e96000349d3400978adaed88d8f98d494352d9761ee/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32", size = 269205, upload-time = "2025-09-23T14:24:15.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/06/7d51f4688d6d72bb72fa74254e1593c4f5ebd0036be5b41fe39315b275e9/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85", size = 137417, upload-time = "2025-09-22T19:50:59.82Z" }, + { url = "https://files.pythonhosted.org/packages/5a/08/b4499234a420ef42960eeb05585df5cc7eb25ccb8c980490b079e6367050/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e", size = 642558, upload-time = "2025-09-22T19:51:03.388Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ba/1975a27dedf1c4c33306ee67c948121be8710b19387aada29e2f139c43ee/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb", size = 744087, upload-time = "2025-09-22T19:51:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/20/15/8a19a13d27f3bd09fa18813add8380a29115a47b553845f08802959acbce/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d", size = 699709, upload-time = "2025-09-22T19:51:02.075Z" }, + { url = "https://files.pythonhosted.org/packages/19/ee/8d6146a079ad21e534b5083c9ee4a4c8bec42f79cf87594b60978286b39a/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59", size = 708926, upload-time = "2025-09-23T18:42:51.707Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/426b714abdc222392e68f3b8ad323930d05a214a27c7e7a0f06c69126401/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca", size = 740202, upload-time = "2025-09-22T19:51:04.673Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ac/3c5c2b27a183f4fda8a57c82211721c016bcb689a4a175865f7646db9f94/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6", size = 765196, upload-time = "2025-09-22T19:51:05.916Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/06f56a71fd55021c993ed6e848c9b2e5e9cfce180a42179f0ddd28253f7c/ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl", hash = "sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2", size = 98635, upload-time = "2025-09-22T19:51:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/51/79/76aba16a1689b50528224b182f71097ece338e7a4ab55e84c2e73443b78a/ruamel.yaml.clib-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78", size = 115238, upload-time = "2025-09-22T19:51:07.081Z" }, + { url = "https://files.pythonhosted.org/packages/21/e2/a59ff65c26aaf21a24eb38df777cb9af5d87ba8fc8107c163c2da9d1e85e/ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7df6f6e9d0e33c7b1d435defb185095386c469109de723d514142632a7b9d07f", size = 271441, upload-time = "2025-09-23T14:24:16.498Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fa/3234f913fe9a6525a7b97c6dad1f51e72b917e6872e051a5e2ffd8b16fbb/ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83", size = 137970, upload-time = "2025-09-22T19:51:09.472Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ec/4edbf17ac2c87fa0845dd366ef8d5852b96eb58fcd65fc1ecf5fe27b4641/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27", size = 739639, upload-time = "2025-09-22T19:51:10.566Z" }, + { url = "https://files.pythonhosted.org/packages/15/18/b0e1fafe59051de9e79cdd431863b03593ecfa8341c110affad7c8121efc/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640", size = 764456, upload-time = "2025-09-22T19:51:11.736Z" }, ] [[package]] @@ -833,6 +3440,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/73/4de6579bac8e979fca0a77e54dec1f1e011a0d268165eb8a9bc0982a6564/ruff-0.14.3-py3-none-win_arm64.whl", hash = "sha256:26eb477ede6d399d898791d01961e16b86f02bc2486d0d1a7a9bb2379d055dc1", size = 12590017, upload-time = "2025-10-31T00:26:24.52Z" }, ] +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imageio" }, + { name = "lazy-loader" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "scipy" }, + { name = "tifffile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://urob.github.io/numpy-mkl" } +dependencies = [ + { name = "mkl" }, + { name = "mkl-service" }, + { name = "numpy" }, +] +wheels = [ + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.2/scipy-1.16.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82cdf597dce708a7e5ad21f8f7e2382f18c750866c7fea84156ffc5100eeca85" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.2/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:69892facb9b4201079fd6c73c646d6e0ab2376e64e1213e3de62941def53e2fa" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.2/scipy-1.16.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6296838860731f71ca6bd44e18a6b6a649b405b52eb48ba05bb994db06d8520c" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.2/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:f2ad7f0b53030e48b7003bd1d596abffd53251e2513bb5371674a590d3486ffe" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.2/scipy-1.16.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92756f22310ece2e27df010f1dc30ae5c473bafb7f9f5d5e9e04a532e4399bfd" }, + { url = "https://github.com/urob/numpy-mkl/releases/download/0.1.2/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:90af63e42b02812edd3eae50236de9953d439b2cc4b153aecaec586df9d944fb" }, +] + +[[package]] +name = "scmrepo" +version = "3.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp-retry" }, + { name = "asyncssh" }, + { name = "dulwich" }, + { name = "fsspec", extra = ["tqdm"] }, + { name = "funcy" }, + { name = "gitpython" }, + { name = "pathspec" }, + { name = "pygit2" }, + { name = "pygtrie" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/a7/d9e2dfad90dd9ce3429156a100684ef61e8444e98164726d52bd3ed77ce0/scmrepo-3.5.2.tar.gz", hash = "sha256:c951d98cd36aead02a69a75926455a163d435c6f996c76b92be5f0c717551f28", size = 96642, upload-time = "2025-08-06T14:46:32.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/2e/10b7fe92ddc69e5aae177775a3c8ed890bdd6cb40c2aa04e0a982937edd1/scmrepo-3.5.2-py3-none-any.whl", hash = "sha256:6e4660572b76512d0e013ca9806692188c736e8c9c76f833e3674fc21a558788", size = 73868, upload-time = "2025-08-06T14:46:31.635Z" }, +] + [[package]] name = "selectolax" version = "0.4.0" @@ -877,6 +3552,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/96/e2f3ddd77dbfabf2b22dff676a16b0c87da71785756fa53b48b2217e1e4a/selectolax-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:22f2ce15e8e79acb66b576518609efe5d34e32200cb0818b48c7ebd89f59d4e6", size = 1863193, upload-time = "2025-09-28T17:52:37.052Z" }, ] +[[package]] +name = "semver" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, +] + +[[package]] +name = "sentry-sdk" +version = "2.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/18/09875b4323b03ca9025bae7e6539797b27e4fc032998a466b4b9c3d24653/sentry_sdk-2.43.0.tar.gz", hash = "sha256:52ed6e251c5d2c084224d73efee56b007ef5c2d408a4a071270e82131d336e20", size = 368953, upload-time = "2025-10-29T11:26:08.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/31/8228fa962f7fd8814d634e4ebece8780e2cdcfbdf0cd2e14d4a6861a7cd5/sentry_sdk-2.43.0-py2.py3-none-any.whl", hash = "sha256:4aacafcf1756ef066d359ae35030881917160ba7f6fc3ae11e0e58b09edc2d5d", size = 400997, upload-time = "2025-10-29T11:26:05.77Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -886,6 +3583,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "shortuuid" +version = "1.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/e2/bcf761f3bff95856203f9559baf3741c416071dd200c0fc19fad7f078f86/shortuuid-1.0.13.tar.gz", hash = "sha256:3bb9cf07f606260584b1df46399c0b87dd84773e7b25912b7e391e30797c5e72", size = 9662, upload-time = "2024-03-11T20:11:06.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/44/21d6bf170bf40b41396480d8d49ad640bca3f2b02139cd52aa1e272830a5/shortuuid-1.0.13-py3-none-any.whl", hash = "sha256:a482a497300b49b4953e15108a7913244e1bb0d41f9d332f5e9925dba33a3c5a", size = 10529, upload-time = "2024-03-11T20:11:04.807Z" }, +] + +[[package]] +name = "shtab" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/3e/837067b970c1d2ffa936c72f384a63fdec4e186b74da781e921354a94024/shtab-1.7.2.tar.gz", hash = "sha256:8c16673ade76a2d42417f03e57acf239bfb5968e842204c17990cae357d07d6f", size = 45751, upload-time = "2025-04-12T20:28:03.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/03/3271b7bb470fbab4adf5bd30b0d32143909d96f3608d815b447357f47f2b/shtab-1.7.2-py3-none-any.whl", hash = "sha256:858a5805f6c137bb0cda4f282d27d08fd44ca487ab4a6a36d2a400263cd0b5c1", size = 14214, upload-time = "2025-04-12T20:28:01.82Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -895,10 +3619,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + [[package]] name = "snraware" version = "0.1.0" source = { editable = "." } +dependencies = [ + { name = "cffi" }, + { name = "click" }, + { name = "dvc", extra = ["azure"] }, + { name = "einops" }, + { name = "h5py" }, + { name = "hydra-colorlog" }, + { name = "hydra-core" }, + { name = "imageio" }, + { name = "lightning" }, + { name = "matplotlib" }, + { name = "mkl-fft" }, + { name = "mkl-service" }, + { name = "nibabel" }, + { name = "numpy" }, + { name = "omegaconf" }, + { name = "opencv-python" }, + { name = "pandas" }, + { name = "pathlib" }, + { name = "piq" }, + { name = "prettytable" }, + { name = "pytest" }, + { name = "pytorch-wavelets" }, + { name = "pywavelets" }, + { name = "pyyaml" }, + { name = "scikit-image" }, + { name = "scipy" }, + { name = "setuptools" }, + { name = "torch" }, + { name = "torch-interpol" }, + { name = "torchinfo" }, + { name = "torchmetrics" }, + { name = "torchvision" }, + { name = "typing-extensions" }, + { name = "wandb", extra = ["media"] }, +] [package.dev-dependencies] dev = [ @@ -920,6 +3689,43 @@ dev = [ ] [package.metadata] +requires-dist = [ + { name = "cffi" }, + { name = "click" }, + { name = "dvc", extras = ["azure"] }, + { name = "einops" }, + { name = "h5py" }, + { name = "hydra-colorlog" }, + { name = "hydra-core" }, + { name = "imageio" }, + { name = "lightning" }, + { name = "matplotlib" }, + { name = "mkl-fft", specifier = ">=2.0.0" }, + { name = "mkl-service", specifier = ">=2.4.2", index = "https://urob.github.io/numpy-mkl" }, + { name = "nibabel" }, + { name = "numpy", specifier = ">=2.2.6", index = "https://urob.github.io/numpy-mkl" }, + { name = "omegaconf" }, + { name = "opencv-python" }, + { name = "pandas" }, + { name = "pathlib" }, + { name = "piq" }, + { name = "prettytable" }, + { name = "pytest", specifier = ">=8.4.2" }, + { name = "pytorch-wavelets" }, + { name = "pywavelets" }, + { name = "pyyaml" }, + { name = "scikit-image" }, + { name = "scipy", index = "https://urob.github.io/numpy-mkl" }, + { name = "setuptools" }, + { name = "torch", specifier = ">=2.8.0" }, + { name = "torch-interpol" }, + { name = "torchinfo" }, + { name = "torchmetrics", specifier = "==1.7.3" }, + { name = "torchvision" }, + { name = "typing-extensions" }, + { name = "wandb" }, + { name = "wandb", extras = ["media"] }, +] [package.metadata.requires-dev] dev = [ @@ -940,6 +3746,280 @@ dev = [ { name = "setuptools" }, ] +[[package]] +name = "soundfile" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" }, + { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" }, + { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" }, + { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, +] + +[[package]] +name = "sqltrie" +version = "0.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "orjson", marker = "implementation_name == 'cpython'" }, + { name = "pygtrie" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/e6/f3832264bcd98b9e71c93c579ab6b39eb1db659cab305e59f8f7c1adc777/sqltrie-0.11.2.tar.gz", hash = "sha256:4df47089b3abfe347bcf81044e633b8c7737ebda4ce1fec8b636a85954ac36da", size = 23551, upload-time = "2025-02-19T15:11:35.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/a7/96dd20ed6c4008ca57aa14bd89588eff1dfc163f45067cf715df290dc211/sqltrie-0.11.2-py3-none-any.whl", hash = "sha256:4afb1390bbe8a6900a53709b76213a436fbaf352de0b99ba9b0d395d4a0ca6b6", size = 17140, upload-time = "2025-02-19T15:11:34.044Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tbb" +version = "2022.3.0" +source = { registry = "https://urob.github.io/numpy-mkl" } +dependencies = [ + { name = "tcmlib" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/9e/b7f1f7af53580e4e8cf39cf51b14c8e295d767b3ae9d78b5007d6058cfc8/tbb-2022.3.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:a7e122cb98a6f8823940341aa323dcdfffa77e36712a22a1ae3a76430fa9f412" }, + { url = "https://files.pythonhosted.org/packages/19/e7/8a3ae90c12186033dfd38a7292b2caef3ed10813162ab4713149191d70ef/tbb-2022.3.0-py3-none-win_amd64.whl", hash = "sha256:bb893f5772855ff2fd21867cb5c2806cb7d2960a178e660b342bc6a706a6ebf4" }, +] + +[[package]] +name = "tcmlib" +version = "1.4.1" +source = { registry = "https://urob.github.io/numpy-mkl" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/a4/38e8b5a27b66ab286168ba6c449771ed71d71ec76524e7f12401474a5151/tcmlib-1.4.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:0d5bd98db48d31bec7fedba5c23599bf9ae43c7016d4c3946d25242d320cee89" }, + { url = "https://files.pythonhosted.org/packages/eb/4d/2b1d55dba475f602197e284b9a5e5460139a9c5ef2bfb63c8d2a1fafc163/tcmlib-1.4.1-py2.py3-none-win_amd64.whl", hash = "sha256:5202a1fd8541182fbd4ef72848784e4bf94340152d7ddff33d401d30930fa53c" }, +] + +[[package]] +name = "tifffile" +version = "2025.10.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/b5/0d8f3d395f07d25ec4cafcdfc8cab234b2cc6bf2465e9d7660633983fe8f/tifffile-2025.10.16.tar.gz", hash = "sha256:425179ec7837ac0e07bc95d2ea5bea9b179ce854967c12ba07fc3f093e58efc1", size = 371848, upload-time = "2025-10-16T22:56:09.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/5e/56c751afab61336cf0e7aa671b134255a30f15f59cd9e04f59c598a37ff5/tifffile-2025.10.16-py3-none-any.whl", hash = "sha256:41463d979c1c262b0a5cdef2a7f95f0388a072ad82d899458b154a48609d759c", size = 231162, upload-time = "2025-10-16T22:56:07.214Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "torch" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d3/3985739f3b8e88675127bf70f82b3a48ae083e39cda56305dbd90398fec0/torch-2.9.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e5f7af1dc4c0a7c4a260c2534f41ddaf209714f7c89145e644c44712fbd6b642", size = 104107898, upload-time = "2025-10-15T15:46:20.883Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4b/f4bb2e6c25d0272f798cd6d7a04ed315da76cec68c602d87040c7847287f/torch-2.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:01cff95ecd9a212ea2f141db28acccdceb6a4c54f64e6c51091146f5e2a772c6", size = 899738273, upload-time = "2025-10-15T15:50:04.188Z" }, + { url = "https://files.pythonhosted.org/packages/66/11/c1c5ba6691cda6279087c35bd626536e4fd29521fe740abf5008377a9a02/torch-2.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:4582b162f541651f0cb184d3e291c05c2f556c7117c64a9873e2ee158d40062b", size = 109280887, upload-time = "2025-10-15T15:46:26.228Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5f/b85bd8c05312d71de9402bf5868d217c38827cfd09d8f8514e5be128a52b/torch-2.9.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:33f58e9a102a91259af289d50525c30323b5c9ae1d31322b6447c0814da68695", size = 74478983, upload-time = "2025-10-15T15:46:39.406Z" }, + { url = "https://files.pythonhosted.org/packages/c2/1c/90eb13833cdf4969ea9707586d7b57095c3b6e2b223a7256bf111689bcb8/torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c30a17fc83eeab346913e237c64b15b5ba6407fff812f6c541e322e19bc9ea0e", size = 104111330, upload-time = "2025-10-15T15:46:35.238Z" }, + { url = "https://files.pythonhosted.org/packages/0e/21/2254c54b8d523592c25ef4434769aa23e29b1e6bf5f4c0ad9e27bf442927/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8f25033b8667b57857dfd01458fbf2a9e6a6df1f8def23aef0dc46292f6aa642", size = 899750243, upload-time = "2025-10-15T15:48:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/b7/a5/5cb94fa4fd1e78223455c23c200f30f6dc10c6d4a2bcc8f6e7f2a2588370/torch-2.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:d037f1b4ffd25013be4a7bf3651a0a910c68554956c7b2c92ebe87c76475dece", size = 109284513, upload-time = "2025-10-15T15:46:45.061Z" }, + { url = "https://files.pythonhosted.org/packages/66/e8/fc414d8656250ee46120b44836ffbb3266343db424b3e18ca79ebbf69d4f/torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e4e5b5cba837a2a8d1a497ba9a58dae46fa392593eaa13b871c42f71847503a5", size = 74830362, upload-time = "2025-10-15T15:46:48.983Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5f/9474c98fc5ae0cd04b9466035428cd360e6611a86b8352a0fc2fa504acdc/torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:64693568f5dc4dbd5f880a478b1cea0201cc6b510d91d1bc54fea86ac5d1a637", size = 104144940, upload-time = "2025-10-15T15:47:29.076Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5a/8e0c1cf57830172c109d4bd6be2708cabeaf550983eee7029291322447a0/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:f8ed31ddd7d10bfb3fbe0b9fe01b1243577f13d75e6f4a0839a283915ce3791e", size = 899744054, upload-time = "2025-10-15T15:48:29.864Z" }, + { url = "https://files.pythonhosted.org/packages/6d/28/82c28b30fcb4b7c9cdd995763d18bbb830d6521356712faebbad92ffa61d/torch-2.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:eff527d4e4846e6f70d2afd8058b73825761203d66576a7e04ea2ecfebcb4ab8", size = 109517546, upload-time = "2025-10-15T15:47:33.395Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a91f96ec74347fa5fd24453fa514bc61c61ecc79196fa760b012a1873d96/torch-2.9.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:f8877779cf56d1ce431a7636703bdb13307f5960bb1af49716d8b179225e0e6a", size = 74480732, upload-time = "2025-10-15T15:47:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/9f70af34b334a7e0ef496ceec96b7ec767bd778ea35385ce6f77557534d1/torch-2.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7e614fae699838038d888729f82b687c03413c5989ce2a9481f9a7e7a396e0bb", size = 74433037, upload-time = "2025-10-15T15:47:41.894Z" }, + { url = "https://files.pythonhosted.org/packages/b7/84/37cf88625901934c97109e583ecc21777d21c6f54cda97a7e5bbad1ee2f2/torch-2.9.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:dfb5b8cd310ba3436c7e14e8b7833ef658cf3045e50d2bdaed23c8fc517065eb", size = 104116482, upload-time = "2025-10-15T15:47:46.266Z" }, + { url = "https://files.pythonhosted.org/packages/56/8e/ca8b17866943a8d4f4664d402ea84210aa274588b4c5d89918f5caa24eec/torch-2.9.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b3d29524993a478e46f5d598b249cd824b7ed98d7fba538bd9c4cde6c803948f", size = 899746916, upload-time = "2025-10-15T15:50:40.294Z" }, + { url = "https://files.pythonhosted.org/packages/43/65/3b17c0fbbdab6501c5b320a52a648628d0d44e7379f64e27d9eef701b6bf/torch-2.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:71c7578984f5ec0eb645eb4816ac8435fcf3e3e2ae1901bcd2f519a9cafb5125", size = 109275151, upload-time = "2025-10-15T15:49:20.715Z" }, + { url = "https://files.pythonhosted.org/packages/83/36/74f8c051f785500396e42f93542422422dfd874a174f21f8d955d36e5d64/torch-2.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:71d9309aee457bbe0b164bce2111cd911c4ed4e847e65d5077dbbcd3aba6befc", size = 74823353, upload-time = "2025-10-15T15:49:16.59Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/dc3b4e2f9ba98ae27238f0153ca098bf9340b2dafcc67fde645d496dfc2a/torch-2.9.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c08fb654d783899e204a32cca758a7ce8a45b2d78eeb89517cc937088316f78e", size = 104140340, upload-time = "2025-10-15T15:50:19.67Z" }, + { url = "https://files.pythonhosted.org/packages/c0/8d/b00657f8141ac16af7bb6cda2e67de18499a3263b78d516b9a93fcbc98e3/torch-2.9.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ec8feb0099b2daa5728fbc7abb0b05730fd97e0f359ff8bda09865aaa7bd7d4b", size = 899731750, upload-time = "2025-10-15T15:49:36.673Z" }, + { url = "https://files.pythonhosted.org/packages/fc/29/bd361e0cbb2c79ce6450f42643aaf6919956f89923a50571b0ebfe92d142/torch-2.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:695ba920f234ad4170c9c50e28d56c848432f8f530e6bc7f88fcb15ddf338e75", size = 109503850, upload-time = "2025-10-15T15:50:24.118Z" }, +] + +[[package]] +name = "torch-interpol" +version = "0.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/36/f2bcf9ba3de378532b007390356ca2bdd5649c5ab6d72595bf422d202c73/torch-interpol-0.2.6.tar.gz", hash = "sha256:a3789aa83a1bd0eed2b05c0741a222b675e7c41bf9797ec246c167c1efeee577", size = 48454, upload-time = "2024-09-13T08:04:39.145Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/5c/a9c3ad572cb11191cfd15dae019f108df26b1cdb1556c7baa4cf68065524/torch_interpol-0.2.6-py3-none-any.whl", hash = "sha256:c583bf4790c4a1b083c9e28501e55c3cf93e5d974b95f09ef28f3378e73bec1b", size = 37201, upload-time = "2024-09-13T08:04:37.653Z" }, +] + +[[package]] +name = "torchinfo" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/d9/2b811d1c0812e9ef23e6cf2dbe022becbe6c5ab065e33fd80ee05c0cd996/torchinfo-1.8.0.tar.gz", hash = "sha256:72e94b0e9a3e64dc583a8e5b7940b8938a1ac0f033f795457f27e6f4e7afa2e9", size = 25880, upload-time = "2023-05-14T19:23:26.377Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/25/973bd6128381951b23cdcd8a9870c6dcfc5606cb864df8eabd82e529f9c1/torchinfo-1.8.0-py3-none-any.whl", hash = "sha256:2e911c2918603f945c26ff21a3a838d12709223dc4ccf243407bce8b6e897b46", size = 23377, upload-time = "2023-05-14T19:23:24.141Z" }, +] + +[[package]] +name = "torchmetrics" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/22/8b16c4ec34d93ee15024924cbbe84fbd235bb3e1df2cc8f48c865c1528e7/torchmetrics-1.7.3.tar.gz", hash = "sha256:08450a19cdb67ba1608aac0b213e5dc73033e11b60ad4719696ebcede591621e", size = 566545, upload-time = "2025-06-13T15:39:37.498Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/f2/bed7da46003c26ed44fc7fa3ecc98a84216f0d4758e5e6a3693754d490d9/torchmetrics-1.7.3-py3-none-any.whl", hash = "sha256:7b6fd43e92f0a1071c8bcb029637f252b0630699140a93ed8817ce7afe9db34e", size = 962639, upload-time = "2025-06-13T15:39:35.69Z" }, +] + +[[package]] +name = "torchvision" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/ef/81e4e69e02e2c4650b30e8c11c8974f946682a30e0ab7e9803a831beff76/torchvision-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c61d40bcd2e2451e932902a702ad495ba1ec6f279e90b1e15cef2bb55dc911e2", size = 1891726, upload-time = "2025-10-15T15:51:16.977Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e3809b3302caea9a12c13f3adebe4fef127188438e719fd6c8dc93db1da6/torchvision-0.24.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b0531d1483fc322d7da0d83be52f0df860a75114ab87dbeeb9de765feaeda843", size = 2419495, upload-time = "2025-10-15T15:51:11.885Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e6/7324ead6793075a8c75c56abeed1236d1750de16a5613cfe2ddad164a92a/torchvision-0.24.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:26b9dd9c083f8e5f7ac827de6d5b88c615d9c582dc87666770fbdf16887e4c25", size = 8050480, upload-time = "2025-10-15T15:51:24.012Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ad/3c56fcd2a0d6e8afa80e115b5ade4302232ec99655220a51d05709819523/torchvision-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:060b7c50ed4b3fb0316b08e2e31bfd874ec2f63ef5ae02f81e54341ca4e88703", size = 4292225, upload-time = "2025-10-15T15:51:27.699Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b5/b2008e4b77a8d6aada828dd0f6a438d8f94befa23fdd2d62fa0ac6e60113/torchvision-0.24.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84d79cfc6457310107ce4d712de7a3d388b24484bc9aeded4a76d8f8e3a2813d", size = 1891722, upload-time = "2025-10-15T15:51:28.854Z" }, + { url = "https://files.pythonhosted.org/packages/8f/02/e2f6b0ff93ca4db5751ac9c5be43f13d5e53d9e9412324f464dca1775027/torchvision-0.24.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:fec12a269cf80f6b0b71471c8d498cd3bdd9d8e892c425bf39fecb604852c3b0", size = 2371478, upload-time = "2025-10-15T15:51:37.842Z" }, + { url = "https://files.pythonhosted.org/packages/77/85/42e5fc4f716ec7b73cf1f32eeb5c77961be4d4054b26cd6a5ff97f20c966/torchvision-0.24.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:7323a9be5e3da695605753f501cdc87824888c5655d27735cdeaa9986b45884c", size = 8050200, upload-time = "2025-10-15T15:51:46.276Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/48cb0b6b26276d2120b1e0dbc877579a748eae02b4091a7522ce54f6d5e1/torchvision-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:08cad8b204196e945f0b2d73adee952d433db1c03645851d52b22a45f1015b13", size = 4309939, upload-time = "2025-10-15T15:51:39.002Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/3dd10830b047eeb46ae6b465474258d7b4fbb7d8872dca69bd42449f5c82/torchvision-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ab956a6e588623353e0f20d4b03eb1656cb4a3c75ca4dd8b4e32e01bc43271a", size = 2028355, upload-time = "2025-10-15T15:51:22.384Z" }, + { url = "https://files.pythonhosted.org/packages/f7/cf/2d7e43409089ce7070f5336161f9216d58653ee1cb26bcb5d6c84cc2de36/torchvision-0.24.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:b1b3db80609c32a088554e8e94b4fc31f1033fe5bb4ac0673ec49c3eb03fb4da", size = 2374466, upload-time = "2025-10-15T15:51:35.382Z" }, + { url = "https://files.pythonhosted.org/packages/e9/30/8f7c328fd7e0a9665da4b6b56b1c627665c18470bfe62f3729ad3eda9aec/torchvision-0.24.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:e6635f100d455c80b43f297df4b8585a76c6a2e114802f6567ddd28d7b5479b0", size = 8217068, upload-time = "2025-10-15T15:51:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/55/a2/b6f9e40e2904574c80b3bb872c66af20bbd642053e7c8e1b9e99ab396535/torchvision-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4ce158bbdc3a9086034bced0b5212888bd5b251fee6d08a9eff151d30b4b228a", size = 4273912, upload-time = "2025-10-15T15:51:33.866Z" }, + { url = "https://files.pythonhosted.org/packages/1b/24/790a39645cc8c71bf442d54a76da9bda5caeb2a44c5f7e02498649cd99d4/torchvision-0.24.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4bdfc85a5ed706421555f32cdc5e3ddb6d40bf65ef03a274ce3c176393e2904b", size = 2028335, upload-time = "2025-10-15T15:51:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d7/69479a066ea773653e88eda99031e38681e9094046f87cb957af5036db0e/torchvision-0.24.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:73576a9c4a593223fbae85a64e8bbd77049abd1101893ecf3c5e981284fd58b4", size = 2371609, upload-time = "2025-10-15T15:51:29.859Z" }, + { url = "https://files.pythonhosted.org/packages/46/64/3c7fdb3771ec992b9445a1f7a969466b23ce2cdb14e09303b3db351a0655/torchvision-0.24.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:dd565b1b06666ff399d0801d4d1824fa570c0167a179ca700a5be232527b3c62", size = 8214918, upload-time = "2025-10-15T15:51:41.465Z" }, + { url = "https://files.pythonhosted.org/packages/58/51/abc416bc34d574ad479af738e413d9ebf93027ee92d0f4ae38f966b818f7/torchvision-0.24.0-cp314-cp314-win_amd64.whl", hash = "sha256:eb45d12ac48d757738788fd3fb8e88e647d6b2ab2424134ca87556efc72d81b5", size = 4257776, upload-time = "2025-10-15T15:51:42.642Z" }, + { url = "https://files.pythonhosted.org/packages/08/f7/261d1353c611820541ecd43046b89da3f1ae998dc786e4288b890a009883/torchvision-0.24.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:68120e7e03c31900e499a10bb7fdd63cfd67f0054c9fa108e7e27f9cd372f315", size = 2028359, upload-time = "2025-10-15T15:51:32.119Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fd/615d8a86db1578345de7fa1edaf476fbcf4f057bf7e4fd898306b620c487/torchvision-0.24.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:64e54494043eecf9f57a9881c6fdea49c62282782e737c002ae8b1639e6ea80e", size = 2374469, upload-time = "2025-10-15T15:51:40.19Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/bac11e8fdbf00d6c398246ff2781370aa72c99f2ac685c01ce79354c9a32/torchvision-0.24.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:75ef9546323b321a451239d886f0cb528f7e98bb294da47a3200effd4e572064", size = 8217060, upload-time = "2025-10-15T15:51:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/47/6f/9fba8abc468c904570699eceeb51588f9622172b8fffa4ab11bcf15598c2/torchvision-0.24.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2efb617667950814fc8bb9437e5893861b3616e214285be33cbc364a3f42c599", size = 4358490, upload-time = "2025-10-15T15:51:43.884Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "triton" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/3a/e991574f3102147b642e49637e0281e9bb7c4ba254edb2bab78247c85e01/triton-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9e71db82261c4ffa3921cd050cd5faa18322d2d405c30eb56084afaff3b0833", size = 170476535, upload-time = "2025-10-13T16:38:05.18Z" }, + { url = "https://files.pythonhosted.org/packages/6c/29/10728de8a6e932e517c10773486b8e99f85d1b1d9dd87d9a9616e1fef4a1/triton-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e6bb9aa5519c084a333acdba443789e50012a4b851cd486c54f0b8dc2a8d3a12", size = 170487289, upload-time = "2025-10-13T16:38:11.662Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/db80e48b9220c9bce872b0f616ad0446cdf554a40b85c7865cbca99ab3c2/triton-3.5.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c83f2343e1a220a716c7b3ab9fccfcbe3ad4020d189549200e2d2e8d5868bed9", size = 170577179, upload-time = "2025-10-13T16:38:17.865Z" }, + { url = "https://files.pythonhosted.org/packages/ff/60/1810655d1d856c9a4fcc90ee8966d85f552d98c53a6589f95ab2cbe27bb8/triton-3.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da0fa67ccd76c3dcfb0bffe1b1c57c685136a6bd33d141c24d9655d4185b1289", size = 170487949, upload-time = "2025-10-13T16:38:24.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/b7/1dec8433ac604c061173d0589d99217fe7bf90a70bdc375e745d044b8aad/triton-3.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:317fe477ea8fd4524a6a8c499fb0a36984a56d0b75bf9c9cb6133a1c56d5a6e7", size = 170580176, upload-time = "2025-10-13T16:38:31.14Z" }, +] + +[[package]] +name = "typer" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/28/7c85c8032b91dbe79725b6f17d2fffc595dff06a35c7a30a37bef73a1ab4/typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37", size = 106492, upload-time = "2025-10-20T17:03:49.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a", size = 47028, upload-time = "2025-10-20T17:03:47.617Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -949,6 +4029,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "umf" +version = "1.0.2" +source = { registry = "https://urob.github.io/numpy-mkl" } +dependencies = [ + { name = "tcmlib" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/8e/4a90b6aa955268988e7491f502b7ac2bd65cb954b4979bfcc892cf019b50/umf-1.0.2-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:ef4d144a2007a73a1a22ee575ee4f5a1894a206532c6f6d77b4cf548643502fb" }, + { url = "https://files.pythonhosted.org/packages/4c/8c/dfbcedb97b56b2e87a9fe61e43e70b09abb439ed90e06e08d4365fc60c5f/umf-1.0.2-py2.py3-none-win_amd64.whl", hash = "sha256:695b6be076a7bd2037b0cdbe5ee025f9ae74934cc78bdf25df8b52e8f746e457" }, +] + [[package]] name = "urllib3" version = "2.5.0" @@ -958,6 +4071,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] +[[package]] +name = "vine" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, +] + +[[package]] +name = "voluptuous" +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/af/a54ce0fb6f1d867e0b9f0efe5f082a691f51ccf705188fca67a3ecefd7f4/voluptuous-0.15.2.tar.gz", hash = "sha256:6ffcab32c4d3230b4d2af3a577c87e1908a714a11f6f95570456b1849b0279aa", size = 51651, upload-time = "2024-07-02T19:10:00.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/a8/8f9cc6749331186e6a513bfe3745454f81d25f6e34c6024f88f80c71ed28/voluptuous-0.15.2-py3-none-any.whl", hash = "sha256:016348bc7788a9af9520b1764ebd4de0df41fe2138ebe9e06fa036bf86a65566", size = 31349, upload-time = "2024-07-02T19:09:58.125Z" }, +] + +[[package]] +name = "wandb" +version = "0.22.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "gitpython" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sentry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/d1/6b70f365ed86bd69debba8ad55dec8606fc21006e7ca703a5a091bd3b719/wandb-0.22.3.tar.gz", hash = "sha256:04468a8ab2769a46f5e384c9c4ada5da0dced005ca689a8424e4b8b5cb2a0291", size = 44337368, upload-time = "2025-10-28T23:59:10.275Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/02/87fb60f587ec249f784a40bd91c30de1b2b24d691ee72675d5b66c3d0728/wandb-0.22.3-py3-none-macosx_12_0_arm64.whl", hash = "sha256:81b3b6e405f38342b0a080898b7d00c5b9375432f5ba358942a09e65cdcfe781", size = 18758047, upload-time = "2025-10-28T23:58:46.56Z" }, + { url = "https://files.pythonhosted.org/packages/26/88/64081740ef2b2efc7fbcb2139a07a849e42bcb09ae0c56ae50c41bd0ad63/wandb-0.22.3-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:d29c16817cca6401b4919069ec7570c781eacb67dc0b1ff2e0096a9a59581720", size = 19798011, upload-time = "2025-10-28T23:58:49.718Z" }, + { url = "https://files.pythonhosted.org/packages/19/72/c4f922b33dbb84d1c81ee045ff8791dd14e26d79e1e9bbafff964b7043e2/wandb-0.22.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb955d73a4ba55df9adc61fafbabef5556784d33fc39c7b5c8165d2694ddeb3b", size = 18542713, upload-time = "2025-10-28T23:58:51.927Z" }, + { url = "https://files.pythonhosted.org/packages/ad/98/3ce5f6e2086d91b0c51b38ae7ff591109e7da2bb25fe1a12eec0cdbaa494/wandb-0.22.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f3ebe41a26506117a098fdfd2706ed0e50b37899bfbefe3a0628fcbd70c69d", size = 19984910, upload-time = "2025-10-28T23:58:54.641Z" }, + { url = "https://files.pythonhosted.org/packages/5e/57/e68cb38427b60490d6ddf1b992e6c7f36be83be1079d291ce87a8d347f48/wandb-0.22.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2973462bed5d4a653b1a97cf9fc350673bb200fb356a2f4eba34beae9b87e0aa", size = 18581776, upload-time = "2025-10-28T23:58:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/66/6d/543f907ce0c6b6da13628b23d19ca7282c559fd73eb47b04977b9a61d0c6/wandb-0.22.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c5c2bd18f95c1639863c527da0a5818ac6b0e5194f9c691426b265908ddd8b2c", size = 20078800, upload-time = "2025-10-28T23:58:59.217Z" }, + { url = "https://files.pythonhosted.org/packages/da/91/1decaf1a6ac2017481c782e0fad7f90bc9ae4057f3d76d478cb6527f3dd3/wandb-0.22.3-py3-none-win32.whl", hash = "sha256:09ca1edfe0fd6dc30447d368acddb825668e60ee705c98594a6bbfd30d34d47e", size = 19160297, upload-time = "2025-10-28T23:59:01.536Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ba/3b092634279994b0c79fe05220532822be09f3a353ae95c54e7142769db8/wandb-0.22.3-py3-none-win_amd64.whl", hash = "sha256:55403bf93872c9978433d101324f51e43e78c70c809bf6d06ca7b2760e39f497", size = 19160300, upload-time = "2025-10-28T23:59:04.06Z" }, + { url = "https://files.pythonhosted.org/packages/7f/80/4662fce9eebcc8c71f5083e9152ccaf7d43d4ca9c446e1422f9aa784a51c/wandb-0.22.3-py3-none-win_arm64.whl", hash = "sha256:49f66b05882abfa53816cc8d01b3c2435a89c5a090176802fa6928b5979d34d9", size = 17461959, upload-time = "2025-10-28T23:59:07.059Z" }, +] + +[package.optional-dependencies] +media = [ + { name = "bokeh" }, + { name = "imageio" }, + { name = "moviepy" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "plotly" }, + { name = "rdkit" }, + { name = "soundfile" }, +] + [[package]] name = "watchdog" version = "6.0.0" @@ -993,3 +4165,127 @@ sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e476 wheels = [ { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, ] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "xyzservices" +version = "2025.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/2b/58b3db8814e39277dd6c34aa206f7d0231ff0406284e18e03d4920a4bc78/xyzservices-2025.10.0.tar.gz", hash = "sha256:c6b7648276c98e8222fbec84d9c763128cf3653705017a4d6c4c3652480ee144", size = 1135110, upload-time = "2025-10-30T14:46:36.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/8f/447cc9cb57456d786204af0f450ffb920039104c5eff6626337c9f403bd1/xyzservices-2025.10.0-py3-none-any.whl", hash = "sha256:cfd6423367c7bc717ed5824d4dd7de2c91486886c1c193db9d8f0fa7fd43bc1b", size = 92737, upload-time = "2025-10-30T14:46:34.923Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + +[[package]] +name = "zc-lockfile" +version = "4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/9a/2fef89272d98b799e4daa50201c5582ec76bdd4e92a1a7e3deb74c52b7fa/zc_lockfile-4.0.tar.gz", hash = "sha256:d3ab0f53974296a806db3219b9191ba0e6d5cbbd1daa2e0d17208cb9b29d2102", size = 10956, upload-time = "2025-09-18T07:32:34.412Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/7f/3a614b65bc4b181578b1d50a78663ee02d5d2d3b859712f3d3597c8afe6f/zc_lockfile-4.0-py3-none-any.whl", hash = "sha256:aa3aa295257bebaa09ea9ad5cb288bf9f98f88de6932f96b6659f62715d83581", size = 9143, upload-time = "2025-09-18T07:32:33.517Z" }, +]