Files
FoodClassifier/data_management/utils/image_loader.py
T

153 lines
4.0 KiB
Python

"""
图片加载工具
用于从URL加载图片并转换为PIL/CTkImage格式
优化版本:使用连接池、线程池和缓存
"""
import io
import requests
from PIL import Image
from typing import Optional
import customtkinter as ctk
from concurrent.futures import ThreadPoolExecutor
from functools import lru_cache
import threading
# ======================== 全局资源管理 ========================
# HTTP会话(连接池)
_session = None
_session_lock = threading.Lock()
def get_session() -> requests.Session:
"""获取全局HTTP会话(单例模式)"""
global _session
if _session is None:
with _session_lock:
if _session is None:
_session = requests.Session()
# 配置连接池
adapter = requests.adapters.HTTPAdapter(
pool_connections=10, # 连接池大小
pool_maxsize=20, # 最大连接数
max_retries=2, # 重试次数
pool_block=False
)
_session.mount('http://', adapter)
_session.mount('https://', adapter)
return _session
# 线程池(用于图片加载)
_thread_pool = None
_thread_pool_lock = threading.Lock()
def get_thread_pool() -> ThreadPoolExecutor:
"""获取全局线程池(单例模式)"""
global _thread_pool
if _thread_pool is None:
with _thread_pool_lock:
if _thread_pool is None:
# 限制最多同时加载10张图片
_thread_pool = ThreadPoolExecutor(
max_workers=10,
thread_name_prefix="ImageLoader"
)
return _thread_pool
# ======================== 图片加载函数 ========================
def load_image_from_url(
url: str,
size: tuple = (150, 150),
timeout: int = 5 # 降低超时时间,避免长时间阻塞
) -> Optional[ctk.CTkImage]:
"""
从URL加载图片(使用连接池)
Args:
url: 图片URL
size: 目标尺寸 (width, height)
timeout: 超时时间(秒)
Returns:
CTkImage对象,失败返回None
"""
try:
# 使用全局session(连接复用)
session = get_session()
# 下载图片
response = session.get(url, timeout=timeout)
response.raise_for_status()
# 转换为PIL Image
image_data = io.BytesIO(response.content)
pil_image = Image.open(image_data)
# 转换为RGB(防止RGBA等格式问题)
if pil_image.mode != 'RGB':
pil_image = pil_image.convert('RGB')
# 创建CTkImage
ctk_image = ctk.CTkImage(
light_image=pil_image,
dark_image=pil_image,
size=size
)
return ctk_image
except requests.exceptions.Timeout:
print(f"加载图片超时: {url}")
return None
except requests.exceptions.RequestException as e:
print(f"网络请求失败 {url}: {e}")
return None
except Exception as e:
print(f"加载图片失败 {url}: {e}")
return None
@lru_cache(maxsize=100)
def create_placeholder_image(
size: tuple = (150, 150),
text: str = "加载失败"
) -> ctk.CTkImage:
"""
创建占位图片(带缓存)
Args:
size: 图片尺寸
text: 显示文字
Returns:
CTkImage对象
"""
# 创建灰色背景图片
pil_image = Image.new('RGB', size, color=(200, 200, 200))
ctk_image = ctk.CTkImage(
light_image=pil_image,
dark_image=pil_image,
size=size
)
return ctk_image
def cleanup_resources():
"""清理全局资源(程序退出时调用)"""
global _session, _thread_pool
# 关闭线程池
if _thread_pool is not None:
_thread_pool.shutdown(wait=False)
_thread_pool = None
# 关闭HTTP会话
if _session is not None:
_session.close()
_session = None