#!/usr/bin/env python3 """ Setup script for the Image Tagger application. This script checks and installs all required dependencies. """ # Python 3.12+ compatibility patch for pkgutil.ImpImporter import sys if sys.version_info >= (3, 12): import pkgutil import importlib.machinery # Add ImpImporter as a compatibility shim for older packages if not hasattr(pkgutil, 'ImpImporter'): class ImpImporter: def __init__(self, path=None): self.path = path def find_module(self, fullname, path=None): return None pkgutil.ImpImporter = ImpImporter import os import sys import subprocess import platform from pathlib import Path import re import urllib.request import shutil import tempfile import time import webbrowser # Define the required packages # Added setuptools and setuptools-distutils to fix distutils missing error SETUPTOOLS_PACKAGES = [ "setuptools>=58.0.0", "setuptools-distutils>=0.3.0", "wheel>=0.38.0", ] REQUIRED_PACKAGES = [ "streamlit>=1.21.0", "pillow>=9.0.0", # Important: Pin NumPy to 1.24.x for PyTorch compatibility # NumPy 2.x is not compatible with current PyTorch/torchvision builds "numpy==1.24.3", # Required for building Flash Attention "ninja>=1.10.0", "packaging>=20.0", # Required for the essence generator "matplotlib>=3.5.0", "tqdm>=4.62.0", "scipy>=1.7.0", ] # Packages to install after PyTorch POST_TORCH_PACKAGES = [ "einops>=0.6.1", # Required for Flash Attention ] CUDA_PACKAGES = { # CUDA version: torch version "11.8": "torch==2.0.1+cu118 torchvision==0.15.2+cu118 --index-url https://download.pytorch.org/whl/cu118", "11.7": "torch==2.0.1+cu117 torchvision==0.15.2+cu117 --index-url https://download.pytorch.org/whl/cu117", "11.6": "torch==2.0.1+cu116 torchvision==0.15.2+cu116 --index-url https://download.pytorch.org/whl/cu116", "cpu": "torch==2.0.1+cpu torchvision==0.15.2+cpu --index-url https://download.pytorch.org/whl/cpu" } # ONNX and acceleration packages ONNX_PACKAGES = [ "onnx>=1.14.0", "onnxruntime>=1.15.0", "onnxruntime-gpu>=1.15.0;platform_system!='Darwin'", # Skip GPU version on macOS ] # TensorRT packages (for NVIDIA GPUs only) TENSORRT_PACKAGES = [ "nvidia-tensorrt>=8.6.1;platform_system=='Linux'", # Linux only "tensorrt>=8.6.1;platform_system=='Windows'", # Windows only "nvidia-cuda-nvrtc-cu11>=11.8.89;platform_system!='Darwin'", "nvidia-cuda-runtime-cu11>=11.8.89;platform_system!='Darwin'", "nvidia-cudnn-cu11>=8.7.0;platform_system!='Darwin'", ] # Colors for terminal output class Colors: HEADER = '\033[95m' BLUE = '\033[94m' GREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' def print_colored(text, color): """Print text in color""" if sys.platform == "win32": # Just print the text without color on Windows print(text) else: print(f"{color}{text}{Colors.ENDC}") def check_python_version(): """Check if Python version is 3.8 or higher, recommend 3.11.9 specifically""" print_colored("Checking Python version...", Colors.BLUE) version = sys.version_info if version.major < 3 or (version.major == 3 and version.minor < 8): print_colored("Error: Python 3.8 or higher is required. You have " + sys.version, Colors.FAIL) print_colored("Please install a newer Python version and try again.", Colors.FAIL) return False print_colored(f"[OK] Python {version.major}.{version.minor}.{version.micro} detected", Colors.GREEN) # Recommend Python 3.11.9 specifically if version.major == 3 and (version.minor != 11 or version.micro != 9): recommended = False warning_color = Colors.WARNING # Extra warning for Python 3.12+ due to known compatibility issues if version.major == 3 and version.minor >= 12: print_colored("WARNING: Python 3.12+ has known compatibility issues with this application.", Colors.FAIL) print_colored("The application has been tested and works reliably with Python 3.11.9.", Colors.FAIL) warning_color = Colors.FAIL recommended = True elif version.major == 3 and version.minor == 11 and version.micro != 9: print_colored("Note: This application has been tested with Python 3.11.9 specifically.", Colors.WARNING) recommended = True else: print_colored("Note: This application is recommended to use Python 3.11.9.", Colors.WARNING) recommended = True if recommended: print_colored("Download Python 3.11.9:", warning_color) if sys.platform == "win32": print_colored(" https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe", warning_color) elif sys.platform == "darwin": # macOS print_colored(" https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg", warning_color) else: # Linux print_colored(" https://www.python.org/ftp/python/3.11.9/Python-3.11.9.tgz", warning_color) print_colored(" Or use your distribution's package manager", warning_color) if version.major == 3 and version.minor >= 12: # For Python 3.12+, we'll still continue but with a confirmation print_colored("\nDo you want to continue with the current Python version? (y/n)", Colors.BLUE) response = input().strip().lower() if response != 'y' and response != 'yes': print_colored("Setup aborted. Please install Python 3.11.9 and try again.", Colors.FAIL) return False print_colored("Continuing with current Python version. Some features may not work correctly.", Colors.WARNING) else: print_colored("[PERFECT] Python 3.11.9 detected - this is the recommended version!", Colors.GREEN) return True def create_virtual_env(): """Create a virtual environment if one doesn't exist""" print_colored("\nChecking for virtual environment...", Colors.BLUE) venv_path = Path("venv") if venv_path.exists(): print_colored("[OK] Virtual environment already exists", Colors.GREEN) return True print_colored("Creating a new virtual environment...", Colors.BLUE) try: subprocess.run([sys.executable, "-m", "venv", "venv"], check=True) print_colored("[OK] Virtual environment created successfully", Colors.GREEN) return True except subprocess.CalledProcessError: print_colored("Error: Failed to create virtual environment", Colors.FAIL) return False def get_venv_python(): """Get path to Python in the virtual environment""" if sys.platform == "win32": return os.path.join("venv", "Scripts", "python.exe") else: return os.path.join("venv", "bin", "python") def get_venv_pip(): """Get path to pip in the virtual environment""" if sys.platform == "win32": return os.path.join("venv", "Scripts", "pip.exe") else: return os.path.join("venv", "bin", "pip") def check_cuda(): """Check CUDA availability and version""" print_colored("\nChecking for CUDA...", Colors.BLUE) # Check if nvidia-smi is available cuda_available = False cuda_version = None try: if sys.platform == "win32": # Windows: Check for nvidia-smi process = subprocess.run(["where", "nvidia-smi"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if process.returncode == 0: # Run nvidia-smi to get version nvidia_smi = subprocess.run(["nvidia-smi"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if nvidia_smi.returncode == 0: cuda_available = True # Extract CUDA Version from nvidia-smi output match = re.search(r"CUDA Version: (\d+\.\d+)", nvidia_smi.stdout) if match: cuda_version = match.group(1) else: # Linux/Mac: Check for nvidia-smi process = subprocess.run(["which", "nvidia-smi"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if process.returncode == 0: # Run nvidia-smi to get version nvidia_smi = subprocess.run(["nvidia-smi"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if nvidia_smi.returncode == 0: cuda_available = True # Extract CUDA Version from nvidia-smi output match = re.search(r"CUDA Version: (\d+\.\d+)", nvidia_smi.stdout) if match: cuda_version = match.group(1) except Exception as e: print_colored(f"Error checking CUDA: {str(e)}", Colors.WARNING) if cuda_available and cuda_version: print_colored(f"[OK] CUDA {cuda_version} detected", Colors.GREEN) # Return the closest supported CUDA version for supported_version in CUDA_PACKAGES.keys(): if supported_version != "cpu" and float(supported_version) <= float(cuda_version): return supported_version print_colored("No CUDA detected, will use CPU-only version", Colors.WARNING) return "cpu" def check_numpy_version(): """Check if NumPy is installed and if it's a compatible version""" print_colored("\nChecking for existing NumPy installation...", Colors.BLUE) pip_path = get_venv_pip() # Check if NumPy is installed try: result = subprocess.run([pip_path, "show", "numpy"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if result.returncode == 0: # Extract version from output version_match = re.search(r"Version: ([\d\.]+)", result.stdout) if version_match: current_version = version_match.group(1) print_colored(f"NumPy {current_version} is currently installed", Colors.BLUE) # Check if it's NumPy 2.x if current_version.startswith("2."): print_colored(f"Warning: NumPy {current_version} is not compatible with PyTorch.", Colors.WARNING) print_colored("NumPy will be downgraded to 1.24.3 for compatibility.", Colors.WARNING) # Uninstall NumPy print_colored("Uninstalling incompatible NumPy version...", Colors.BLUE) subprocess.run([pip_path, "uninstall", "-y", "numpy"], check=True) print_colored("[OK] Successfully uninstalled NumPy", Colors.GREEN) return False # If it's NumPy 1.24.x, we're good if current_version.startswith("1.24."): print_colored(f"[OK] NumPy {current_version} is compatible with PyTorch", Colors.GREEN) return True # For any other 1.x version, warn but continue print_colored(f"Note: NumPy will be updated to 1.24.3 for optimal compatibility", Colors.BLUE) return False except Exception as e: print_colored(f"Error checking NumPy: {str(e)}", Colors.WARNING) print_colored("NumPy is not installed", Colors.BLUE) return False def install_packages(cuda_version): """Install required packages using pip""" print_colored("\nInstalling required packages...", Colors.BLUE) pip_path = get_venv_pip() # First, upgrade pip try: subprocess.run([pip_path, "install", "--upgrade", "pip"], check=True) print_colored("[OK] Pip upgraded successfully", Colors.GREEN) except subprocess.CalledProcessError: print_colored("Warning: Failed to upgrade pip", Colors.WARNING) # Install setuptools packages first to ensure distutils is available print_colored("\nInstalling setuptools and distutils...", Colors.BLUE) for package in SETUPTOOLS_PACKAGES: try: print_colored(f"Installing {package}...", Colors.BLUE) subprocess.run([pip_path, "install", package], check=True) print_colored(f"[OK] Installed {package}", Colors.GREEN) except subprocess.CalledProcessError as e: print_colored(f"Warning: Issue installing {package}: {e}", Colors.WARNING) print_colored("Continuing installation process...", Colors.BLUE) # Check NumPy version and install/upgrade if needed numpy_compatible = check_numpy_version() # Install base packages (except torch) for package in REQUIRED_PACKAGES: if numpy_compatible and package.startswith("numpy"): # Skip NumPy if already at compatible version print_colored(f"Skipping {package} (already installed at compatible version)", Colors.GREEN) continue try: print_colored(f"Installing {package}...", Colors.BLUE) subprocess.run([pip_path, "install", package], check=True) print_colored(f"[OK] Installed {package}", Colors.GREEN) except subprocess.CalledProcessError as e: print_colored(f"Error installing {package}: {e}", Colors.FAIL) return False # Install PyTorch with appropriate CUDA version print_colored(f"\nInstalling PyTorch {'with CUDA support' if cuda_version != 'cpu' else '(CPU version)'}...", Colors.BLUE) torch_command = CUDA_PACKAGES[cuda_version].split() try: subprocess.run([pip_path, "install"] + torch_command, check=True) print_colored("[OK] PyTorch installed successfully", Colors.GREEN) except subprocess.CalledProcessError as e: print_colored(f"Error installing PyTorch: {e}", Colors.FAIL) print_colored("You may need to manually install PyTorch from https://pytorch.org/", Colors.WARNING) return False # Install any packages that need to come after PyTorch for package in POST_TORCH_PACKAGES: try: subprocess.run([pip_path, "install", package], check=True) print_colored(f"[OK] Installed {package}", Colors.GREEN) except subprocess.CalledProcessError as e: print_colored(f"Error installing {package}: {e}", Colors.FAIL) return False return True def install_acceleration_packages(cuda_version, install_tensorrt=False): """Install ONNX Runtime and TensorRT packages if CUDA is available""" print_colored("\nInstalling ONNX Runtime and acceleration packages...", Colors.BLUE) pip_path = get_venv_pip() # Choose either onnxruntime or onnxruntime-gpu, not both if cuda_version != "cpu": onnx_package = "onnxruntime-gpu>=1.15.0" else: onnx_package = "onnxruntime>=1.15.0" try: print_colored(f"Installing ONNX...", Colors.BLUE) subprocess.run([pip_path, "install", "onnx>=1.14.0"], check=True) print_colored(f"Installing {onnx_package}...", Colors.BLUE) subprocess.run([pip_path, "install", onnx_package], check=True) print_colored(f"[OK] ONNX packages installed", Colors.GREEN) except subprocess.CalledProcessError as e: print_colored(f"Warning: Issue installing ONNX packages: {e}", Colors.WARNING) # Install TensorRT packages only if explicitly requested if cuda_version != "cpu" and install_tensorrt: print_colored("\nAttempting to install TensorRT packages...", Colors.BLUE) print_colored("Note: TensorRT installation might require manual steps depending on your system", Colors.WARNING) for package in TENSORRT_PACKAGES: try: print_colored(f"Installing {package.split(';')[0]}...", Colors.BLUE) subprocess.run([pip_path, "install", package], check=True) print_colored(f"[OK] Installed {package.split(';')[0]}", Colors.GREEN) except subprocess.CalledProcessError as e: print_colored(f"Warning: Issue installing {package.split(';')[0]}: {e}", Colors.WARNING) print_colored("TensorRT may need to be installed manually.", Colors.WARNING) else: print_colored("Skipping TensorRT installation (no CUDA detected)", Colors.BLUE) return True def run_application(): """Run the application after setup""" print_colored("\nLaunching the application...", Colors.BLUE) # Check if app.py exists app_path = "app.py" if not os.path.exists(app_path): print_colored(f"Error: {app_path} not found. Can't launch application.", Colors.FAIL) return False # Get the path to streamlit in the virtual environment if sys.platform == "win32": streamlit_path = os.path.join("venv", "Scripts", "streamlit.exe") else: streamlit_path = os.path.join("venv", "bin", "streamlit") if not os.path.exists(streamlit_path): print_colored(f"Error: Streamlit not found at {streamlit_path}", Colors.FAIL) return False # Launch application try: print_colored("\nStarting Image Tagger Application...", Colors.GREEN) # Open browser first time.sleep(2) # Give it a moment before starting the app # Run streamlit in a subprocess command = [streamlit_path, "run", app_path] subprocess.Popen(command) print_colored("[OK] Application launched successfully", Colors.GREEN) return True except Exception as e: print_colored(f"Error launching application: {e}", Colors.FAIL) return False def main(): """Main setup function""" print_colored("=" * 60, Colors.HEADER) print_colored(" Image Tagger - Setup Script", Colors.HEADER) print_colored(" (Recommended: Python 3.11.9)", Colors.HEADER) print_colored("=" * 60, Colors.HEADER) # Check Python version if not check_python_version(): return False # Create virtual environment if not create_virtual_env(): return False # Check CUDA version cuda_version = check_cuda() # Install packages if not install_packages(cuda_version): return False # Ask if the user wants TensorRT (only if CUDA is available) install_tensorrt = False if cuda_version != "cpu": print_colored("\nTensorRT packages add 2-3GB to the environment size but can improve performance.", Colors.BLUE) print_colored("Would you like to install TensorRT support? (y/n)", Colors.BLUE) response = input().strip().lower() install_tensorrt = (response == 'y' or response == 'yes') # Install acceleration packages with the user's preference if not install_acceleration_packages(cuda_version, install_tensorrt): print_colored("Warning: Some acceleration packages could not be installed", Colors.WARNING) print_colored("The application will still work, but performance may be reduced", Colors.WARNING) print_colored("\n" + "=" * 60, Colors.HEADER) print_colored(" Setup completed successfully!", Colors.GREEN) print_colored("=" * 60, Colors.HEADER) print_colored("\nTo run the application, use:", Colors.BLUE) if sys.platform == "win32": print_colored(" run_app.bat or python run_app.py", Colors.BOLD) else: print_colored(" ./run_app.py", Colors.BOLD) # Ask if the user wants to run the application now print_colored("\nWould you like to run the application now? (y/n)", Colors.BLUE) response = input().strip().lower() if response == 'y' or response == 'yes': run_application() else: print_colored("\nYou can run the application later using the commands above or running the batch scripts.", Colors.BLUE) return True if __name__ == "__main__": success = main() if not success: sys.exit(1)