diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4538f23..01d9539 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -2,10 +2,12 @@ name: Check file size on: pull_request: branches: [main] - - # to run this workflow manually from the Actions tab workflow_dispatch: +permissions: + contents: read + pull-requests: write + jobs: sync-to-hub: runs-on: ubuntu-latest @@ -13,4 +15,4 @@ jobs: - name: Check large files uses: ActionsDesk/lfs-warning@v2.0 with: - filesizelimit: 10485760 # this is 10MB \ No newline at end of file + filesizelimit: 10485760 # this is 10MB diff --git a/.gitignore b/.gitignore index 9be9146..150f2ee 100644 --- a/.gitignore +++ b/.gitignore @@ -182,4 +182,7 @@ EDA/ model_outputs/ # VS CODE -.vscode/ \ No newline at end of file +.vscode/ + +# Test images +test_images/ \ No newline at end of file diff --git a/README.md b/README.md index a4a4887..4b02612 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ This repository contains a Gradio web application for eye disease detection usin - Support for **multiple model architectures** (MobileNetV4, LeViT, EfficientViT, GENet, RegNetX) - **Custom model loading** from saved model checkpoints - **Visualization** of prediction probabilities +- **Attention heatmap visualization** using GradCAM to show which regions the model focuses on - **Dockerized deployment** option ## Supported Eye Conditions @@ -85,7 +86,21 @@ The system can detect the following eye conditions: 2. (Optional) Specify the path to your trained model file (.pth) 3. Select the model architecture (MobileNetV4, LeViT, EfficientViT, GENet, RegNetX) 4. Click "Analyze Image" to get the prediction -5. View the results and probability distribution +5. View the results including: + - Probability distribution across all disease classes + - Attention heatmap showing which regions the model focused on for its prediction + +### Understanding the Attention Heatmap + +The attention heatmap is generated using GradCAM (Gradient-weighted Class Activation Mapping), which visualizes the regions of the fundus image that the model considers most important for making its prediction: + +- **Red/Yellow areas**: Regions the model focuses on most strongly +- **Blue/Green areas**: Regions with less influence on the prediction + +This visualization helps in: +- Understanding the model's decision-making process +- Validating that the model is looking at clinically relevant features +- Building trust in the AI's predictions by making them interpretable ## Model Training diff --git a/gradio-inference.py b/gradio-inference.py index ccdc815..b157fd8 100644 --- a/gradio-inference.py +++ b/gradio-inference.py @@ -4,14 +4,19 @@ import os import numpy as np +import cv2 import torch import torch.nn as nn +import torch.nn.functional as F import gradio as gr from PIL import Image import logging +from pytorch_grad_cam import GradCAM +from pytorch_grad_cam.utils.image import show_cam_on_image + from main import get_transform logging.basicConfig(level=logging.INFO) @@ -80,49 +85,161 @@ def load_model(model_type: str = "efficientvit") -> nn.Module: return model -def predict_image(image: np.ndarray, model_type: str) -> dict: +def get_target_layers(model, model_type): + """ + Get the target layers for GradCAM based on model type. + + Args: + model: The model + model_type: Type of model + + Returns: + target_layers: List of layers to use for GradCAM """ - Predict eye disease from an uploaded image. + try: + if model_type == "mobilenetv4": + # For MobileNetV4, use the last convolutional layer in features + return [model.features[-1]] + elif model_type == "levit": + # For LeViT (transformer), use the last block + return [model.blocks[-1]] + elif model_type == "efficientvit": + # For EfficientViT, use the last stage + return [model.stages[-1]] + elif model_type == "gernet": + # For GENet, use the last stage + return [model.stages[-1]] + elif model_type == "regnetx": + # For RegNetX, use the last trunk layer + return [model.trunk[-1]] + else: + # Default: try to get the last feature layer + if hasattr(model, 'features'): + return [model.features[-1]] + elif hasattr(model, 'stages'): + return [model.stages[-1]] + elif hasattr(model, 'blocks'): + return [model.blocks[-1]] + else: + raise ValueError(f"Cannot determine target layer for model type: {model_type}") + except Exception as e: + logging.warning(f"Error getting target layer: {e}. Using fallback.") + # Fallback: try to get any reasonable last conv layer + for module in reversed(list(model.modules())): + if isinstance(module, nn.Conv2d): + return [module] + raise ValueError("Could not find suitable target layer for GradCAM") + + +def apply_heatmap_on_image(img, cam, alpha=0.4): + """ + Apply CAM heatmap overlay on the original image. + + Args: + img: Original image (PIL Image or numpy array) + cam: Class activation map (grayscale, values 0-1) + alpha: Overlay transparency (not used with show_cam_on_image, kept for compatibility) + + Returns: + Heatmap overlay image as numpy array + """ + # Convert PIL to numpy if needed + if isinstance(img, Image.Image): + img = np.array(img) + + # Normalize image to 0-1 range for show_cam_on_image + img_float = img.astype(np.float32) / 255.0 + + # Resize CAM to match image size + h, w = img.shape[:2] + cam_resized = cv2.resize(cam, (w, h)) + + # Use pytorch_grad_cam utility to overlay + # This function expects img in 0-1 range and cam in 0-1 range + overlay = show_cam_on_image(img_float, cam_resized, use_rgb=True) + + return overlay + + +def predict_image(image: np.ndarray, model_type: str) -> tuple[dict, np.ndarray]: + """ + Predict eye disease from an uploaded image and generate attention heatmap. Args: image: Input image from Gradio - model_path: Path to the model state dict model_type: Type of model architecture Returns: - Dictionary of class probabilities + Tuple of (Dictionary of class probabilities, Heatmap overlay image) """ try: - logging.info("Starting prediction...") + + # Handle None image + if image is None: + logging.warning("No image provided.") + return {cls: 0.0 for cls in CLASSES}, None + # Load model model = load_model(model_type) + model.to(device) # Preprocess image logging.info("Preprocessing image...") - if image is None: - logging.warning("No image provided.") - return {cls: 0.0 for cls in CLASSES} transform = get_transform() - if image is None: - return {cls: 0.0 for cls in CLASSES} - # Convert numpy array to PIL Image - img = Image.fromarray(image).convert("RGB") - img_tensor = transform(img).unsqueeze(0).to(device) + # Convert numpy array to PIL Image and keep original for heatmap + img_pil = Image.fromarray(image).convert("RGB") + img_tensor = transform(img_pil).unsqueeze(0).to(device) logging.info("Image preprocessed successfully.") - # Make prediction - with torch.no_grad(): - outputs = model(img_tensor) - probabilities = torch.nn.functional.softmax(outputs, dim=1)[0].cpu().numpy() - - # Return probabilities for each class - return {cls: float(prob) for cls, prob in zip(CLASSES, probabilities)} + # Get target layers for GradCAM + try: + target_layers = get_target_layers(model, model_type) + logging.info(f"Using target layers: {target_layers}") + + # Initialize GradCAM from pytorch_grad_cam library + cam_extractor = GradCAM(model=model, target_layers=target_layers) + + # Generate CAM - the library handles forward and backward passes + grayscale_cam = cam_extractor(input_tensor=img_tensor, targets=None) + + # Get the CAM for the first image in batch + cam = grayscale_cam[0, :] + + # Get model prediction + with torch.no_grad(): + outputs = model(img_tensor) + + # Generate heatmap overlay + heatmap_overlay = apply_heatmap_on_image(img_pil, cam) + + # Clean up + del cam_extractor + + except Exception as e: + logging.error(f"Error generating heatmap: {e}") + import traceback + traceback.print_exc() + # Fallback: just do prediction without heatmap + with torch.no_grad(): + outputs = model(img_tensor) + heatmap_overlay = np.array(img_pil) # Return original image + + # Get probabilities + probabilities = F.softmax(outputs, dim=1)[0].cpu().detach().numpy() + + # Return probabilities and heatmap + result_dict = {cls: float(prob) for cls, prob in zip(CLASSES, probabilities)} + + logging.info("Prediction completed successfully.") + return result_dict, heatmap_overlay except Exception as e: logging.error(f"Error during prediction: {e}") - return {cls: 0.0 for cls in CLASSES} + import traceback + traceback.print_exc() + return {cls: 0.0 for cls in CLASSES}, None def main(): @@ -158,12 +275,13 @@ def main(): with gr.Column(): output_chart = gr.Label(label="Prediction") + output_heatmap = gr.Image(label="Attention Heatmap") # Process the image when the button is clicked submit_btn.click( fn=predict_image, inputs=[input_image, model_type], - outputs=output_chart, + outputs=[output_chart, output_heatmap], ) # Examples section @@ -171,7 +289,7 @@ def main(): gr.Examples( examples=[], # Add example paths here inputs=input_image, - outputs=[output_chart], + outputs=[output_chart, output_heatmap], fn=predict_image, cache_examples=True, ) @@ -187,7 +305,15 @@ def main(): - Enter the path to your trained model file (.pth) - Select the model architecture that was used for training 3. **Analyze**: Click the "Analyze Image" button to get results - 4. **Interpret results**: The system will show the detected condition and probability distribution + 4. **Interpret results**: The system will show the detected condition, probability distribution, and an attention heatmap + + ## Attention Heatmap: + + The attention heatmap visualizes which regions of the fundus image the model is focusing on when making its prediction. + - **Red/Yellow areas**: Regions the model considers most important for the diagnosis + - **Blue/Green areas**: Regions with less influence on the prediction + + This helps in understanding and validating the model's decision-making process. ## Model Information: diff --git a/pyproject.toml b/pyproject.toml index 8279b23..3027b4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,11 @@ readme = "README.md" requires-python = ">=3.12.9" dependencies = [ "gradio>=5.29.0", + "grad-cam>=1.5.0", "matplotlib>=3.10.3", + "opencv-python>=4.8.0", "pandas>=2.2.3", + "pillow>=10.0.0", "scikit-learn>=1.6.1", "seaborn>=0.13.2", "timm>=1.0.15", diff --git a/requirements.txt b/requirements.txt index babf184..8c02fe9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,287 @@ -gradio>=5.29.0, -matplotlib>=3.10.3, -pandas>=2.2.3, -scikit-learn>=1.6.1, -seaborn>=0.13.2, -timm>=1.0.15, -torch>=2.7.0, -torchaudio>=2.7.0, -torchvision>=0.22.0, -tqdm>=4.67.1, \ No newline at end of file +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml -o requirements.txt +aiofiles==24.1.0 + # via gradio +annotated-types==0.7.0 + # via pydantic +anyio==4.11.0 + # via + # gradio + # httpx + # starlette +brotli==1.1.0 + # via gradio +certifi==2025.10.5 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.4.4 + # via requests +click==8.3.0 + # via + # typer + # uvicorn +contourpy==1.3.3 + # via matplotlib +cycler==0.12.1 + # via matplotlib +fastapi==0.119.1 + # via gradio +ffmpy==0.6.4 + # via gradio +filelock==3.20.0 + # via + # huggingface-hub + # torch +fonttools==4.60.1 + # via matplotlib +fsspec==2025.9.0 + # via + # gradio-client + # huggingface-hub + # torch +grad-cam==1.5.5 + # via eyediseasedetection (pyproject.toml) +gradio==5.49.1 + # via eyediseasedetection (pyproject.toml) +gradio-client==1.13.3 + # via gradio +groovy==0.1.2 + # via gradio +h11==0.16.0 + # via + # httpcore + # uvicorn +hf-xet==1.1.10 + # via huggingface-hub +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via + # gradio + # gradio-client + # safehttpx +huggingface-hub==0.36.0 + # via + # gradio + # gradio-client + # timm +idna==3.11 + # via + # anyio + # httpx + # requests +jinja2==3.1.6 + # via + # gradio + # torch +joblib==1.5.2 + # via scikit-learn +kiwisolver==1.4.9 + # via matplotlib +markdown-it-py==4.0.0 + # via rich +markupsafe==3.0.3 + # via + # gradio + # jinja2 +matplotlib==3.10.7 + # via + # eyediseasedetection (pyproject.toml) + # grad-cam + # seaborn +mdurl==0.1.2 + # via markdown-it-py +mpmath==1.3.0 + # via sympy +networkx==3.5 + # via torch +numpy==2.2.6 + # via + # contourpy + # grad-cam + # gradio + # matplotlib + # opencv-python + # pandas + # scikit-learn + # scipy + # seaborn + # torchvision +nvidia-cublas-cu12==12.8.4.1 + # via + # nvidia-cudnn-cu12 + # nvidia-cusolver-cu12 + # torch +nvidia-cuda-cupti-cu12==12.8.90 + # via torch +nvidia-cuda-nvrtc-cu12==12.8.93 + # via torch +nvidia-cuda-runtime-cu12==12.8.90 + # via torch +nvidia-cudnn-cu12==9.10.2.21 + # via torch +nvidia-cufft-cu12==11.3.3.83 + # via torch +nvidia-cufile-cu12==1.13.1.3 + # via torch +nvidia-curand-cu12==10.3.9.90 + # via torch +nvidia-cusolver-cu12==11.7.3.90 + # via torch +nvidia-cusparse-cu12==12.5.8.93 + # via + # nvidia-cusolver-cu12 + # torch +nvidia-cusparselt-cu12==0.7.1 + # via torch +nvidia-nccl-cu12==2.27.5 + # via torch +nvidia-nvjitlink-cu12==12.8.93 + # via + # nvidia-cufft-cu12 + # nvidia-cusolver-cu12 + # nvidia-cusparse-cu12 + # torch +nvidia-nvshmem-cu12==3.3.20 + # via torch +nvidia-nvtx-cu12==12.8.90 + # via torch +opencv-python==4.12.0.88 + # via + # eyediseasedetection (pyproject.toml) + # grad-cam +orjson==3.11.3 + # via gradio +packaging==25.0 + # via + # gradio + # gradio-client + # huggingface-hub + # matplotlib +pandas==2.3.3 + # via + # eyediseasedetection (pyproject.toml) + # gradio + # seaborn +pillow==11.3.0 + # via + # eyediseasedetection (pyproject.toml) + # grad-cam + # gradio + # matplotlib + # torchvision +pydantic==2.11.10 + # via + # fastapi + # gradio +pydantic-core==2.33.2 + # via pydantic +pydub==0.25.1 + # via gradio +pygments==2.19.2 + # via rich +pyparsing==3.2.5 + # via matplotlib +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +python-multipart==0.0.20 + # via gradio +pytz==2025.2 + # via pandas +pyyaml==6.0.3 + # via + # gradio + # huggingface-hub + # timm +requests==2.32.5 + # via huggingface-hub +rich==14.2.0 + # via typer +ruff==0.14.1 + # via gradio +safehttpx==0.1.6 + # via gradio +safetensors==0.6.2 + # via timm +scikit-learn==1.7.2 + # via + # eyediseasedetection (pyproject.toml) + # grad-cam +scipy==1.16.2 + # via scikit-learn +seaborn==0.13.2 + # via eyediseasedetection (pyproject.toml) +semantic-version==2.10.0 + # via gradio +setuptools==80.9.0 + # via torch +shellingham==1.5.4 + # via typer +six==1.17.0 + # via python-dateutil +sniffio==1.3.1 + # via anyio +starlette==0.48.0 + # via + # fastapi + # gradio +sympy==1.14.0 + # via torch +threadpoolctl==3.6.0 + # via scikit-learn +timm==1.0.20 + # via eyediseasedetection (pyproject.toml) +tomlkit==0.13.3 + # via gradio +torch==2.9.0+cu128 + # via + # eyediseasedetection (pyproject.toml) + # grad-cam + # timm + # torchaudio + # torchvision +torchaudio==2.9.0 + # via eyediseasedetection (pyproject.toml) +torchvision==0.24.0+cu128 + # via + # eyediseasedetection (pyproject.toml) + # grad-cam + # timm +tqdm==4.67.1 + # via + # eyediseasedetection (pyproject.toml) + # grad-cam + # huggingface-hub +triton==3.5.0 + # via torch +ttach==0.0.3 + # via grad-cam +typer==0.20.0 + # via gradio +typing-extensions==4.15.0 + # via + # anyio + # fastapi + # gradio + # gradio-client + # huggingface-hub + # pydantic + # pydantic-core + # starlette + # torch + # typer + # typing-inspection +typing-inspection==0.4.2 + # via pydantic +tzdata==2025.2 + # via pandas +urllib3==2.5.0 + # via requests +uvicorn==0.38.0 + # via gradio +websockets==15.0.1 + # via gradio-client diff --git a/uv.lock b/uv.lock index 426c7e0..d3c64cb 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12.9" resolution-markers = [ "(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform == 'win32')", @@ -202,9 +202,12 @@ name = "eyediseasedetection" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "grad-cam" }, { name = "gradio" }, { name = "matplotlib" }, + { name = "opencv-python" }, { name = "pandas" }, + { name = "pillow" }, { name = "scikit-learn" }, { name = "seaborn" }, { name = "timm" }, @@ -219,9 +222,12 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "grad-cam", specifier = ">=1.5.0" }, { name = "gradio", specifier = ">=5.29.0" }, { name = "matplotlib", specifier = ">=3.10.3" }, + { name = "opencv-python", specifier = ">=4.8.0" }, { name = "pandas", specifier = ">=2.2.3" }, + { name = "pillow", specifier = ">=10.0.0" }, { name = "scikit-learn", specifier = ">=1.6.1" }, { name = "seaborn", specifier = ">=0.13.2" }, { name = "timm", specifier = ">=1.0.15" }, @@ -299,6 +305,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/4b/e0cfc1a6f17e990f3e64b7d941ddc4acdc7b19d6edd51abf495f32b1a9e4/fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711", size = 194435, upload-time = "2025-03-31T15:27:07.028Z" }, ] +[[package]] +name = "grad-cam" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "scikit-learn" }, + { name = "torch", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torch", version = "2.7.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "torchvision", version = "0.22.0", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "torchvision", version = "0.22.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torchvision", version = "0.22.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or sys_platform == 'win32'" }, + { name = "tqdm" }, + { name = "ttach" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/b3/e8b060e69d4de4b4d8a86868762dbc1ecaa58affa538a8af201a38a408ef/grad-cam-1.5.5.tar.gz", hash = "sha256:690c433d226d35c89c9eb170462db204909cb06b39c7381e6880a49b6fc37015", size = 7783293, upload-time = "2025-04-07T05:13:54.984Z" } + [[package]] name = "gradio" version = "5.29.0" @@ -795,6 +821,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/cd/0e8c51b2ae3a58f054f2e7fe91b82d201abfb30167f2431e9bd92d532f42/nvidia_nvtx_cu12-12.8.55-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dd0780f1a55c21d8e06a743de5bd95653de630decfff40621dbde78cc307102", size = 89896, upload-time = "2025-01-23T17:50:44.487Z" }, ] +[[package]] +name = "opencv-python" +version = "4.12.0.88" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/71/25c98e634b6bdeca4727c7f6d6927b056080668c5008ad3c8fc9e7f8f6ec/opencv-python-4.12.0.88.tar.gz", hash = "sha256:8b738389cede219405f6f3880b851efa3415ccd674752219377353f017d2994d", size = 95373294, upload-time = "2025-07-07T09:20:52.389Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/68/3da40142e7c21e9b1d4e7ddd6c58738feb013203e6e4b803d62cdd9eb96b/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:f9a1f08883257b95a5764bf517a32d75aec325319c8ed0f89739a57fae9e92a5", size = 37877727, upload-time = "2025-07-07T09:13:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/33/7c/042abe49f58d6ee7e1028eefc3334d98ca69b030e3b567fe245a2b28ea6f/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:812eb116ad2b4de43ee116fcd8991c3a687f099ada0b04e68f64899c09448e81", size = 57326471, upload-time = "2025-07-07T09:13:41.26Z" }, + { url = "https://files.pythonhosted.org/packages/62/3a/440bd64736cf8116f01f3b7f9f2e111afb2e02beb2ccc08a6458114a6b5d/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:51fd981c7df6af3e8f70b1556696b05224c4e6b6777bdd2a46b3d4fb09de1a92", size = 45887139, upload-time = "2025-07-07T09:13:50.761Z" }, + { url = "https://files.pythonhosted.org/packages/68/1f/795e7f4aa2eacc59afa4fb61a2e35e510d06414dd5a802b51a012d691b37/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:092c16da4c5a163a818f120c22c5e4a2f96e0db4f24e659c701f1fe629a690f9", size = 67041680, upload-time = "2025-07-07T09:14:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/02/96/213fea371d3cb2f1d537612a105792aa0a6659fb2665b22cad709a75bd94/opencv_python-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:ff554d3f725b39878ac6a2e1fa232ec509c36130927afc18a1719ebf4fbf4357", size = 30284131, upload-time = "2025-07-07T09:14:08.819Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/eb88edc2e2b11cd2dd2e56f1c80b5784d11d6e6b7f04a1145df64df40065/opencv_python-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:d98edb20aa932fd8ebd276a72627dad9dc097695b3d435a4257557bbb49a79d2", size = 39000307, upload-time = "2025-07-07T09:14:16.641Z" }, +] + [[package]] name = "orjson" version = "3.10.18" @@ -1388,13 +1431,13 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6bba7dca5d9a729f1e8e9befb98055498e551efaf5ed034824c168b560afc1ac" }, { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7c0f08d1c44a02abad389373dddfce75904b969a410be2f4e5109483dd3dc0ce" }, { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:1704e5dd66c9221e4e8b6ae2d80cbf54e129571e643f5fa9ca78cc6d2096403a" }, - { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:633f35e8b1b1f640ef5f8a98dbd84f19b548222ce7ba8f017fe47ce6badc106a" }, { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:d2f69f909da5dc52113ec66a851d62079f3d52c83184cf64beebdf12ca2f705c" }, { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp313-cp313-win_amd64.whl", hash = "sha256:58c749f52ddc9098155c77d6c74153bb13d8978fd6e1063b5d7b41d4644f5af5" }, - { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fa05ac6ebed4777de7a5eff398c1f17b697c02422516748ce66a8151873e5a0e" }, { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:78e13c26c38ae92d6841cf9ce760d7e9d52bca3e3183de371812e84274b054dc" }, { url = "https://download.pytorch.org/whl/cu128/torch-2.7.0%2Bcu128-cp313-cp313t-win_amd64.whl", hash = "sha256:3559e98be824c2b12ab807319cd61c6174d73a524c9961317de8e8a44133c5c5" }, ] @@ -1507,6 +1550,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/93/f28a696fa750b9b608baa236f8225dd3290e5aff27433b06143adc025961/triton-3.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce4700fc14032af1e049005ae94ba908e71cd6c2df682239aed08e49bc71b742", size = 156580729, upload-time = "2025-04-09T20:27:55.424Z" }, ] +[[package]] +name = "ttach" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/5d/4c49e0eca4206bc25eff4ba89cee51b781466e2e3aad2f1057fd5d2634be/ttach-0.0.3.tar.gz", hash = "sha256:120c4dd881feb0e9c8dd63b154f2655891c3e20689b68a94d162bfd5557bcb48", size = 9600, upload-time = "2020-07-09T14:44:09.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/a3/ee48a184a185c1897c582c72240c2c8a0d0aeb5f8051a71d4e4cd930c52d/ttach-0.0.3-py3-none-any.whl", hash = "sha256:7000bb4334f856b0c79a341df386c92f1c76faf091043cc3cd7f541d2149faf8", size = 9839, upload-time = "2020-07-09T14:44:08.006Z" }, +] + [[package]] name = "typer" version = "0.15.3"