diff --git a/Calculate_Process/Calculate.py b/Calculate_Process/Calculate.py index e80953f..8a479c7 100644 --- a/Calculate_Process/Calculate.py +++ b/Calculate_Process/Calculate.py @@ -22,11 +22,11 @@ class Calculate(): DataFrame = pd.DataFrame( { "loss" : "{:.2f}".format(Loss), - "precision" : "{:.2f}".format(Precision * 100), - "recall" : "{:.2f}".format(Recall * 100), - "accuracy" : "{:.2f}".format(Accuracy * 100), - "f1" : "{:.2f}".format(F1 * 100), - "AUC" : "{:.2f}".format(AUC * 100) + "precision" : "{:.2f}".format(Precision), + "recall" : "{:.2f}".format(Recall), + "accuracy" : "{:.2f}".format(Accuracy), + "f1" : "{:.2f}".format(F1), + "AUC" : "{:.2f}".format(AUC) }, index = [0] ) self.History.append(DataFrame) @@ -40,7 +40,7 @@ class Calculate(): F1_Mean = np.mean(self.F1_Record) AUC_Mean = np.mean(self.AUC_Record) - Mean_DataFram = self.Construction_To_DataFrame(Loss_Mean, Accuracy_Mean, Precision_Mean, Recall_Mean, F1_Mean, AUC_Mean) + Mean_DataFram = self.Construction_To_DataFrame(Loss_Mean, Accuracy_Mean * 100, Precision_Mean * 100, Recall_Mean * 100, F1_Mean * 100, AUC_Mean * 100) return Mean_DataFram diff --git a/Calculate_Process/__pycache__/Calculate.cpython-311.pyc b/Calculate_Process/__pycache__/Calculate.cpython-311.pyc index 099f699..20f6370 100644 Binary files a/Calculate_Process/__pycache__/Calculate.cpython-311.pyc and b/Calculate_Process/__pycache__/Calculate.cpython-311.pyc differ diff --git a/Calculate_Process/__pycache__/Calculate.cpython-312.pyc b/Calculate_Process/__pycache__/Calculate.cpython-312.pyc new file mode 100644 index 0000000..0e9fc34 Binary files /dev/null and b/Calculate_Process/__pycache__/Calculate.cpython-312.pyc differ diff --git a/Calculate_Process/__pycache__/__init__.cpython-311.pyc b/Calculate_Process/__pycache__/__init__.cpython-311.pyc index 3406166..ad07743 100644 Binary files a/Calculate_Process/__pycache__/__init__.cpython-311.pyc and b/Calculate_Process/__pycache__/__init__.cpython-311.pyc differ diff --git a/Calculate_Process/__pycache__/__init__.cpython-312.pyc b/Calculate_Process/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..0069b7a Binary files /dev/null and b/Calculate_Process/__pycache__/__init__.cpython-312.pyc differ diff --git a/Image_Process/Image_Generator.py b/Image_Process/Image_Generator.py index 61b57a1..282468e 100644 --- a/Image_Process/Image_Generator.py +++ b/Image_Process/Image_Generator.py @@ -1,39 +1,51 @@ -from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image +from model_data_processing.processing import make_label_list from _validation.ValidationTheEnterData import validation_the_enter_data from Load_process.file_processing import Process_File -from Load_process.LoadData import Load_Data_Prepare -from torchvision import transforms +from Load_process.LoadData import Load_Data_Prepare, Load_Data_Tools from Training_Tools.PreProcess import Training_Precesses -import numpy as np -from PIL import Image -import torch -import cv2 + +from torchvision import transforms class Image_generator(): '''製作資料強化''' - def __init__(self, Generator_Root, Labels, Image_Size) -> None: + def __init__(self, Training_Root, Generator_Root, Labels, Image_Size, Class_Count) -> None: self._validation = validation_the_enter_data() self.stop = 0 self.Labels = Labels + self.Training_Root = Training_Root self.Generator_Root = Generator_Root self.Image_Size = Image_Size - self.Class_Count = 904 + self.Class_Count = Class_Count pass - def Processing_Main(self, Training_Dict_Data_Root): + def Processing_Main(self): data_size = 2712 + File = Process_File() + Prepare = Load_Data_Prepare() + Load_Tool = Load_Data_Tools() - # 製作標準資料增強 - ''' - 這裡我想要做的是依照paper上的資料強化IMAGE DATA COLLECTION AND IMPLEMENTATION OF DEEP LEARNING-BASED MODEL IN DETECTING MONKEYPOX DISEASE USING MODIFIED VGG16 - 產生出資料強化後的影像 - ''' - for i in range(1, 5, 1): - print("\nAugmentation one Generator image") - data_size = self.get_processing_Augmentation(Training_Dict_Data_Root, i, data_size) - self.stop += data_size + if not File.Judge_File_Exist(self.Generator_Root): # 檔案若不存在 + # 確定我要多少個List + Prepare.Set_Data_Content([], len(self.Labels)) - print() + # 製作讀檔字典並回傳檔案路徑 + Prepare.Set_Label_List(self.Labels) + Prepare.Set_Data_Dictionary(Prepare.Get_Label_List(), Prepare.Get_Data_Content(), len(self.Labels)) + Original_Dict_Data_Root = Prepare.Get_Data_Dict() + get_all_original_image_data = Load_Tool.get_data_root(self.Training_Root, Original_Dict_Data_Root, Prepare.Get_Label_List()) + + # 儲存資料強化後資料 + # 製作標準資料增強 + ''' + 這裡我想要做的是依照paper上的資料強化IMAGE DATA COLLECTION AND IMPLEMENTATION OF DEEP LEARNING-BASED MODEL IN DETECTING MONKEYPOX DISEASE USING MODIFIED VGG16 + 產生出資料強化後的影像 + ''' + for i in range(1, 5, 1): + print(f"\nAugmentation {i} Generator image") + data_size = self.get_processing_Augmentation(get_all_original_image_data, i, data_size) + self.stop += data_size + else: # 若檔案存在 + print("standard data and myself data are exist\n") def get_processing_Augmentation(self, original_image_root : dict, Augment_choose, data_size): Prepaer = Load_Data_Prepare() @@ -51,7 +63,6 @@ class Image_generator(): strardand = 要使用哪種Image Augmentation ''' File = Process_File() - image_processing = Read_image_and_Process_image(self.Image_Size) tool = Training_Precesses(self.Image_Size) Classes = [] Transform = self.Generator_Content(stardand) @@ -60,15 +71,15 @@ class Image_generator(): Image_Roots = self.get_data_roots[label] save_root = File.Make_Save_Root(label, save_roots) # 合併路徑 - Classes = image_processing.make_label_list(len(Image_Roots), "1") - Training_Dataset = tool.Setting_DataSet(Image_Roots, Classes) + Classes = make_label_list(len(Image_Roots), "1") + Training_Dataset = tool.Setting_DataSet(Image_Roots, Classes, "Generator") Training_DataLoader = tool.Dataloader_Sampler(Training_Dataset, 1, False) if File.JudgeRoot_MakeDir(save_root): # 判斷要存的資料夾存不存在,不存在則創立 print("The file is exist.This Script is not creating new fold.") for i in range(1, int(self.Class_Count / len(Image_Roots)) + 1, 1): - for batch_idx, (images, labels) in enumerate(Training_DataLoader): + for batch_idx, (images, labels, File_Name, File_Classes) in enumerate(Training_DataLoader): for j, img in enumerate(images): # if i == self.stop: # break @@ -78,7 +89,6 @@ class Image_generator(): # 轉換為 NumPy 陣列並從 BGR 轉為 RGB img_np = img.numpy().transpose(1, 2, 0) # 轉回 HWC 格式 - img_np = cv2.cvtColor(img_np, cv2.COLOR_BGR2RGB) # BGR 轉 RGB img_pil = transforms.ToPILImage()(img_np) File.Save_PIL_File("image_" + label + str(data_size) + ".png", save_root, img_pil) # 存檔 diff --git a/Image_Process/Image_Mask_Ground_Truth_Processing.py b/Image_Process/Image_Mask_Ground_Truth_Processing.py new file mode 100644 index 0000000..1cefe51 --- /dev/null +++ b/Image_Process/Image_Mask_Ground_Truth_Processing.py @@ -0,0 +1,262 @@ +import xml.etree.ElementTree as ET +import cv2 +import os +import numpy as np +from typing import List, Dict, Optional, Tuple +from utils.Stomach_Config import Loading_Config + +class XMLAnnotationProcessor: + """ + XML標註檔案處理器 + 專門處理包含bounding box資訊的XML檔案,並在對應圖片上繪製邊界框 + """ + + def __init__(self, dataset_root: str): + """ + 初始化XML處理器 + + Args: + dataset_root: 圖片資料集根目錄 + output_folder: 輸出資料夾 + """ + self.dataset_root = dataset_root + self.box_color = (0, 255, 0) # 綠色邊界框 + self.text_color = (0, 255, 0) # 綠色文字 + self.box_thickness = 2 + self.font_scale = 0.5 + self.font = cv2.FONT_HERSHEY_SIMPLEX + + def _ensure_output_folder(self, Save_Root: str) -> None: + """確保輸出資料夾存在""" + if not os.path.exists(Save_Root): + os.makedirs(Save_Root) + + def parse_xml(self, xml_file_path: str, Label: str) -> Optional[Dict]: + """ + 解析XML檔案並提取所有相關資訊 + + Args: + xml_file_path: XML檔案路徑 + + Returns: + Dict: 包含檔案資訊和bounding box的字典,解析失敗時返回None + """ + try: + tree = ET.parse(xml_file_path) + root = tree.getroot() + + # 提取基本資訊 + filename_element = root.find('filename') + + if filename_element is None: + print(f"找不到path元素在 {xml_file_path}") + return None + + filename = filename_element.text if filename_element is not None else "Unknown" + Original_Image_Data_Root = os.path.join(self.dataset_root, Label) + Original_Image_Data_Root = os.path.join(Original_Image_Data_Root, filename) + + # 提取圖片尺寸 + size_element = root.find('size') + width = int(size_element.find('width').text) if size_element is not None else 0 + height = int(size_element.find('height').text) if size_element is not None else 0 + depth = int(size_element.find('depth').text) if size_element is not None else 3 + + # 提取所有bounding box + bounding_boxes = [] + objects = root.findall('object') + + for obj in objects: + bndbox = obj.find('bndbox') + if bndbox is not None: + bbox_info = { + 'name': obj.find('name').text if obj.find('name') is not None else "Unknown", + 'pose': obj.find('pose').text if obj.find('pose') is not None else "Unspecified", + 'truncated': int(obj.find('truncated').text) if obj.find('truncated') is not None else 0, + 'difficult': int(obj.find('difficult').text) if obj.find('difficult') is not None else 0, + 'xmin': int(bndbox.find('xmin').text), + 'ymin': int(bndbox.find('ymin').text), + 'xmax': int(bndbox.find('xmax').text), + 'ymax': int(bndbox.find('ymax').text) + } + bounding_boxes.append(bbox_info) + + return { + 'filename': filename, + 'image_path': Original_Image_Data_Root, + 'width': width, + 'height': height, + 'depth': depth, + 'bounding_boxes': bounding_boxes + } + + except Exception as e: + print(f"解析XML檔案 {xml_file_path} 時發生錯誤: {str(e)}") + return None + + def load_image(self, image_path: str) -> Optional[np.ndarray]: + """ + 載入圖片檔案 + + Args: + image_path: 圖片檔案路徑 + + Returns: + np.ndarray: 圖片陣列,載入失敗時返回None + """ + if not os.path.exists(image_path): + print(f"圖片檔案不存在: {image_path}") + return None + + image = cv2.imread(image_path) + if image is None: + print(f"無法讀取圖片: {image_path}") + return None + + return image + + def draw_bounding_boxes(self, image: np.ndarray, bounding_boxes: List[Dict]) -> np.ndarray: + """ + 創建遮罩圖片:bounding box內保持原圖,外部為黑色 + + Args: + image: 圖片陣列 + bounding_boxes: bounding box資訊列表 + + Returns: + np.ndarray: 處理後的遮罩圖片陣列 + """ + # 創建黑色背景圖片 + height, width = image.shape[:2] + result_image = np.zeros((height, width, 3), dtype=np.uint8) + + for i, bbox in enumerate(bounding_boxes): + xmin, ymin = bbox['xmin'], bbox['ymin'] + xmax, ymax = bbox['xmax'], bbox['ymax'] + object_name = bbox['name'] + + # 確保座標在圖片範圍內 + xmin = max(0, min(xmin, width-1)) + ymin = max(0, min(ymin, height-1)) + xmax = max(0, min(xmax, width-1)) + ymax = max(0, min(ymax, height-1)) + + # 將bounding box範圍內的原圖複製到結果圖像中 + result_image[ymin:ymax, xmin:xmax] = image[ymin:ymax, xmin:xmax] + + print(f"Object {i+1}: {object_name} - 座標: ({xmin}, {ymin}, {xmax}, {ymax})") + + return result_image + + def save_annotated_image(self, image: np.ndarray, original_filename: str, Annotation_Root : str, Label : str) -> str: + """ + 儲存標註後的圖片 + + Args: + image: 標註後的圖片陣列 + original_filename: 原始檔案名稱 + + Returns: + str: 儲存的檔案路徑 + """ + output_filename = f"annotated_{original_filename}" + output_path = os.path.join(Annotation_Root, Label) + Save_Image_Roots = os.path.join(output_path, output_filename) + # 確保輸出資料夾存在 + self._ensure_output_folder(output_path) + + cv2.imwrite(Save_Image_Roots, image) + print(f"已儲存標註圖片至: {Save_Image_Roots}") + return Save_Image_Roots + + def process_single_xml(self, xml_file_path: str, Annotation_Root : str, Label : str) -> Optional[Tuple[np.ndarray, str]]: + """ + 處理單一XML檔案 + + Args: + xml_file_path: XML檔案路徑 + + Returns: + Tuple[np.ndarray, str]: (標註後的圖片, 輸出路徑),處理失敗時返回None + """ + # 解析XML + xml_data = self.parse_xml(xml_file_path, Label) + if xml_data is None: + return None + + # 載入圖片 + image = self.load_image(xml_data['image_path']) + if image is None: + return None + + # 繪製bounding box + annotated_image = self.draw_bounding_boxes(image, xml_data['bounding_boxes']) + + # 儲存結果 + output_path = self.save_annotated_image(annotated_image, xml_data['filename'], Annotation_Root, Label) + + return annotated_image, output_path + + def process_multiple_xml(self, xml_folder_path: str, Annotation_Root : str, Label : str) -> List[Tuple[str, bool]]: + """ + 批量處理多個XML檔案 + + Args: + xml_folder_path: 包含XML檔案的資料夾路徑 + + Returns: + List[Tuple[str, bool]]: [(檔案名稱, 處理成功與否), ...] + """ + if not os.path.exists(xml_folder_path): + print(f"XML資料夾不存在: {xml_folder_path}") + return [] + + xml_files = [f for f in os.listdir(xml_folder_path) if f.endswith('.xml')] + + if not xml_files: + print(f"在 {xml_folder_path} 中找不到XML檔案") + return [] + + print(f"找到 {len(xml_files)} 個XML檔案") + for xml_file in xml_files: + try: + Read_XML_File = os.path.join(xml_folder_path, xml_file) + self.process_single_xml(Read_XML_File, Annotation_Root, Label) + print(f"\n處理檔案: {xml_file}") + except Exception as e: + print(f"處理 {xml_file} 時發生錯誤: {str(e)}") + return + + def get_bounding_boxes_info(self, xml_file_path: str) -> Optional[Dict]: + """ + 僅提取XML中的bounding box資訊,不進行圖片處理 + + Args: + xml_file_path: XML檔案路徑 + + Returns: + Dict: 包含檔案資訊和bounding box座標的字典 + """ + return self.parse_xml(xml_file_path) + + def set_drawing_style(self, box_color: Tuple[int, int, int] = None, + text_color: Tuple[int, int, int] = None, + box_thickness: int = None, + font_scale: float = None) -> None: + """ + 設定繪圖樣式 + + Args: + box_color: 邊界框顏色 (B, G, R) + text_color: 文字顏色 (B, G, R) + box_thickness: 邊界框粗細 + font_scale: 字體大小 + """ + if box_color is not None: + self.box_color = box_color + if text_color is not None: + self.text_color = text_color + if box_thickness is not None: + self.box_thickness = box_thickness + if font_scale is not None: + self.font_scale = font_scale diff --git a/Image_Process/__pycache__/Image_Generator.cpython-311.pyc b/Image_Process/__pycache__/Image_Generator.cpython-311.pyc index aed1412..6e9158b 100644 Binary files a/Image_Process/__pycache__/Image_Generator.cpython-311.pyc and b/Image_Process/__pycache__/Image_Generator.cpython-311.pyc differ diff --git a/Image_Process/__pycache__/Image_Generator.cpython-312.pyc b/Image_Process/__pycache__/Image_Generator.cpython-312.pyc new file mode 100644 index 0000000..cb08e7b Binary files /dev/null and b/Image_Process/__pycache__/Image_Generator.cpython-312.pyc differ diff --git a/Image_Process/__pycache__/Image_Mask_Ground_Truth_Processing.cpython-311.pyc b/Image_Process/__pycache__/Image_Mask_Ground_Truth_Processing.cpython-311.pyc new file mode 100644 index 0000000..76c939d Binary files /dev/null and b/Image_Process/__pycache__/Image_Mask_Ground_Truth_Processing.cpython-311.pyc differ diff --git a/Image_Process/__pycache__/image_enhancement.cpython-311.pyc b/Image_Process/__pycache__/image_enhancement.cpython-311.pyc new file mode 100644 index 0000000..f2d9cab Binary files /dev/null and b/Image_Process/__pycache__/image_enhancement.cpython-311.pyc differ diff --git a/Image_Process/__pycache__/image_enhancement.cpython-312.pyc b/Image_Process/__pycache__/image_enhancement.cpython-312.pyc new file mode 100644 index 0000000..e58aa36 Binary files /dev/null and b/Image_Process/__pycache__/image_enhancement.cpython-312.pyc differ diff --git a/Image_Process/image_enhancement.py b/Image_Process/image_enhancement.py index ce3cc4e..35a2ea6 100644 --- a/Image_Process/image_enhancement.py +++ b/Image_Process/image_enhancement.py @@ -1,87 +1,309 @@ import cv2 import numpy as np +import torch +from PIL import Image +import torchvision +import functools +import inspect -def shapen(image): # 銳化處理 - sigma = 100 - blur_img = cv2.GaussianBlur(image, (0, 0), sigma) - usm = cv2.addWeighted(image, 1.5, blur_img, -0.5, 0) - - return usm - -def increase_contrast(image): # 增加資料對比度 - output = image # 建立 output 變數 - alpha = 2 - beta = 10 - cv2.convertScaleAbs(image, output, alpha, beta) # 套用 convertScaleAbs - - return output - -def adaptive_histogram_equalization(image): - ycrcb = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB) - channels = cv2.split(ycrcb) - clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) - clahe.apply(channels[0], channels[0]) - - ycrcb = cv2.merge(channels) - Change_image = cv2.cvtColor(ycrcb, cv2.COLOR_YCR_CB2BGR) +# 套用裝飾器到現有函數 +def unsharp_mask(image, kernel_size=(5, 5), sigma=1.0, amount=1.0, threshold=0): + """使用OpenCV實現的Unsharp Mask銳化處理 + 參數: + image: PIL.Image對象(RGB格式) + kernel_size: 高斯模糊的核大小,必須是奇數 + sigma: 高斯模糊的標準差 + amount: 銳化程度,值越大效果越強 + threshold: 邊緣檢測閾值,僅在邊緣處進行銳化 + 返回: + 銳化後的PIL.Image對象 + """ + # 轉換PIL圖像為numpy數組 + numpy_img = np.array(image, dtype=np.uint8) - return Change_image + # 對原圖進行高斯模糊 + blurred = cv2.GaussianBlur(numpy_img, kernel_size, sigma) + + # 計算銳化後的圖像 + sharpened = cv2.addWeighted(numpy_img, 1 + amount, blurred, -amount, 0) + + # 如果設置了threshold,只在邊緣處應用銳化 + if threshold > 0: + low_contrast_mask = np.absolute(numpy_img - blurred) < threshold + np.copyto(sharpened, numpy_img, where=low_contrast_mask) + + # 確保像素值在有效範圍內 + sharpened = np.clip(sharpened, 0, 255).astype(np.uint8) + + # 轉回PIL圖像 + return Image.fromarray(sharpened) -def Remove_Background(image, Matrix_Size): - skinCrCbHist = np.zeros((256,256), dtype= np.uint8) - cv2.ellipse(skinCrCbHist, (113,155),(23,25), 43, 0, 360, (255, 255, 255), -1) #繪製橢圓弧線 +def histogram_equalization(image): + """GPU加速的一般直方圖等化 + 參數: + image: PIL.Image對象(RGB格式) + 返回: + 直方圖等化後的PIL.Image對象 + """ + # 轉換為numpy數組並轉為PyTorch張量 + numpy_img = np.array(image) + tensor_img = torch.from_numpy(numpy_img).float().to('cuda') + + # 分離通道並進行直方圖等化 + result = torch.zeros_like(tensor_img) + for i in range(3): # 對RGB三個通道分別處理 + channel = tensor_img[..., i] + + # 計算直方圖 + hist = torch.histc(channel, bins=256, min=0, max=255) + + # 計算累積分布函數(CDF) + cdf = torch.cumsum(hist, dim=0) + cdf_normalized = ((cdf - cdf.min()) * 255) / (cdf.max() - cdf.min()) + + # 應用直方圖等化 + result[..., i] = cdf_normalized[channel.long()] + + # 轉回CPU和numpy數組 + result = torch.clamp(result, 0, 255).byte() + result_np = result.cpu().numpy() + return Image.fromarray(result_np) - img_ycrcb = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB) - y,cr,cb = cv2.split(img_ycrcb) #拆分出Y,Cr,Cb值 +def Contrast_Limited_Adaptive_Histogram_Equalization(image, clip_limit=3.0, tile_size=(8, 8)): + """使用OpenCV實現的對比度限制自適應直方圖均衡化(CLAHE) + + 參數: + image: PIL.Image對象(RGB格式) + clip_limit: 剪切限制,用於限制對比度增強的程度,較大的值會產生更強的對比度 + tile_size: 圖像分塊大小的元組(height, width),較小的值會產生更局部的增強效果 + + 返回: + CLAHE處理後的PIL.Image對象 + """ + # 將PIL圖像轉換為OpenCV格式(BGR) + numpy_img = np.array(image) + bgr_img = cv2.cvtColor(numpy_img, cv2.COLOR_RGB2BGR) + + # 轉換到LAB色彩空間 + lab_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2LAB) + + # 創建CLAHE對象 + clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=tile_size) + + # 分離LAB通道 + l, a, b = cv2.split(lab_img) + + # 對L通道應用CLAHE + l_clahe = clahe.apply(l) + + # 合併處理後的L通道與原始的a和b通道 + lab_output = cv2.merge([l_clahe, a, b]) + + # 將LAB轉回BGR,然後轉換為RGB + bgr_output = cv2.cvtColor(lab_output, cv2.COLOR_LAB2BGR) + rgb_output = cv2.cvtColor(bgr_output, cv2.COLOR_BGR2RGB) + + # 轉換為PIL圖像並返回 + return Image.fromarray(rgb_output) - skin = np.zeros(cr.shape, dtype = np.uint8) #掩膜 - (x,y) = cr.shape +def adaptive_histogram_equalization_without_limit(image, tile_size=(8, 8)): + """使用OpenCV實現的自適應直方圖均衡化(AHE) + + 參數: + image: PIL.Image對象(RGB格式) + tile_size: 圖像分塊大小的元組(height, width),較小的值會產生更局部的增強效果 + + 返回: + AHE處理後的PIL.Image對象 + """ + # 將PIL圖像轉換為OpenCV格式(BGR) + numpy_img = np.array(image) + bgr_img = cv2.cvtColor(numpy_img, cv2.COLOR_RGB2BGR) + + # 轉換到LAB色彩空間 + lab_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2LAB) + + # 分離LAB通道 + l, a, b = cv2.split(lab_img) + + # 創建AHE對象(不設置clip limit) + clahe = cv2.createCLAHE(clipLimit=None, tileGridSize=tile_size) + + # 對L通道應用AHE + l_ahe = clahe.apply(l) + + # 合併處理後的L通道與原始的a和b通道 + lab_output = cv2.merge([l_ahe, a, b]) + + # 將LAB轉回BGR,然後轉換為RGB + bgr_output = cv2.cvtColor(lab_output, cv2.COLOR_LAB2BGR) + rgb_output = cv2.cvtColor(bgr_output, cv2.COLOR_BGR2RGB) + + # 轉換為PIL圖像並返回 + return Image.fromarray(rgb_output) - # 依序取出圖片中每個像素 - for i in range(x): - for j in range(y): - if skinCrCbHist [cr[i][j], cb[i][j]] > 0: #若不在橢圓區間中 - skin[i][j] = 255 - # 如果該像素的灰階度大於 200,調整該像素的透明度 - # 使用 255 - gray[y, x] 可以將一些邊緣的像素變成半透明,避免太過鋸齒的邊緣 - # img_change = cv2.cvtColor(img_change, cv2.COLOR_BGRA2BGR) - img = cv2.bitwise_and(image, image, mask = skin) - img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) +def laplacian_sharpen(image): + """ + GPU加速的拉普拉斯銳化處理函數 + 參數: + image: PIL.Image對象(RGB格式) + 返回: + 銳化後的PIL.Image對象 + """ + # 轉換為numpy數組並轉為PyTorch張量 + numpy_img = np.array(image) + tensor_img = torch.from_numpy(numpy_img).float().to('cuda') + + # 創建拉普拉斯算子 + laplacian_kernel = torch.tensor([ + [0, 1, 0], + [1, -4, 1], + [0, 1, 0] + ], dtype=torch.float32, device='cuda').unsqueeze(0).unsqueeze(0) + + # 對每個通道進行處理 + result = torch.zeros_like(tensor_img) + for i in range(3): # RGB三個通道 + channel = tensor_img[..., i] + # 添加批次和通道維度 + channel = channel.unsqueeze(0).unsqueeze(0) + # 應用拉普拉斯算子 + laplacian = torch.nn.functional.conv2d(channel, laplacian_kernel, padding=1) + # 移除批次和通道維度 + laplacian = laplacian.squeeze() + # 銳化處理:原圖 - 拉普拉斯 + result[..., i] = channel.squeeze() - laplacian + + # 確保像素值在合理範圍內 + result = torch.clamp(result, 0, 255).byte() + + # 轉回CPU和numpy數組 + result_np = result.cpu().numpy() + return Image.fromarray(result_np) - h = image.shape[0] # 取得圖片高度 - w = image.shape[1] # 取得圖片寬度 +def adjust_hsv(image, v_adjustment=0): + """調整圖像的HSV色彩空間中的H和V通道 + + 參數: + image: PIL.Image對象(RGB格式) + v_adjustment: V通道的調整值,範圍建議在[-255, 255]之間 + + 返回: + HSV調整後的PIL.Image對象 + """ + # 將PIL圖像轉換為OpenCV格式(BGR) + numpy_img = np.array(image) + bgr_img = cv2.cvtColor(numpy_img, cv2.COLOR_RGB2BGR) + + # 轉換到HSV色彩空間 + hsv_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2HSV) + + # 調整V通道 + hsv_img[..., 2] = np.clip(hsv_img[..., 2] + v_adjustment, 0, 255) + + # 將HSV轉回BGR,然後轉換為RGB + bgr_output = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2BGR) + rgb_output = cv2.cvtColor(bgr_output, cv2.COLOR_BGR2RGB) + + # 轉換為PIL圖像並返回 + return Image.fromarray(rgb_output) - for x in range(w): - for y in range(h): - if img_gray[y, x] == 0: - # if x == 0 and y == 0: # 當X Y都在左上角時 - # image[y, x] = Add(1, Matrix_Size, image[y, x]) / Matrix_Size - # if x == w - 1 and y == 0: # 當X Y都在右上角時 - # image[y, x] = Add(w - Matrix_Size, w, image[y, x]) / Matrix_Size - # if x == 0 and y == h - 1: # 當X Y都在左下角時 - # image[y, x] = (image[y - 1, x] + image[y - 1, x + 1] + image[y, x + 1]) / 3 - # if x == w - 1 and y == h - 1: # 當X Y都在右下角時 - # image[y, x] = (image[y, x - 1] + image[y - 1, x - 1] + image[y - 1, x]) / 3 +def gamma_correction(image, gamma=1.0): + """對圖像進行伽馬校正 + + 參數: + image: PIL.Image對象(RGB格式) + gamma: 伽馬值,gamma > 1 時圖像變暗,gamma < 1 時圖像變亮,gamma = 1 時保持不變 + + 返回: + 伽馬校正後的PIL.Image對象 + """ + # 將PIL圖像轉換為numpy數組 + numpy_img = np.array(image) + + # 將像素值歸一化到[0, 1]範圍 + normalized = numpy_img.astype(float) / 255.0 + + # 應用伽馬校正 + corrected = np.power(normalized, gamma) + + # 將值縮放回[0, 255]範圍 + output = np.clip(corrected * 255.0, 0, 255).astype(np.uint8) + + # 轉換回PIL圖像並返回 + return Image.fromarray(output) - # if (x > 0 and x < w - 1) and y == 0: # 當上面的X Y從左到右 - # image[y, x] = (image[y, x - 1] + image[y + 1, x - 1] + image[y + 1, x] + image[y, x + 1] + image[y + 1, x + 1]) / 5 - # if (x > 0 and x < w - 1) and y == h - 1: # 當下面的X Y從左到右 - # image[y, x] = (image[y, x - 1] + image[y - 1, x - 1] + image[y - 1, x] + image[y, x + 1] + image[y - 1, x + 1]) / 5 - # if x == 0 and (y > 0 and y < h - 1): # 當左邊的X Y從上到下 - # image[y, x] = (image[y - 1, x] + image[y - 1, x + 1] + image[y, x + 1] + image[y + 1, x + 1] + image[y + 1, x]) / 5 - # if x == w - 1 and (y > 0 and y < h - 1): # 當右邊X Y從上到下 - # image[y, x] = (image[y - 1, x] + image[y - 1, x - 1] + image[y, x - 1] + image[y + 1, x - 1] + image[y + 1, x]) / 5 +def Hight_Light(image, Threshold): + image = np.array(image) - if (x >= 1 and x < w - 1) and (y >= 1 and y < h - 1): # 當y >= 2 且 X >= 2 - image[y, x] = Add(x, y, image, Matrix_Size) / Matrix_Size - # BGRA_image[y, x, 3] = 255 - gray[y, x] - return image + gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) + # 使用閾值檢測高光點(白色液滴) + _, thresh = cv2.threshold(gray, Threshold, 255, cv2.THRESH_BINARY) + # 使用形態學操作(膨脹)來擴大遮罩區域 + kernel = np.ones((5, 5), np.uint8) + dilated = cv2.dilate(thresh, kernel, iterations=1) + # 使用 inpaint 修復高光點 + image_inpaint = cv2.inpaint(image, dilated, 3, cv2.INPAINT_TELEA) + return Image.fromarray(image_inpaint) -def Add(width_Center, Height_Center, image, Mask_Size): - total = 0 - for i in range(Mask_Size): - for j in range(Mask_Size): - total += image[width_Center - ((Mask_Size - 1) / 2) + j, Height_Center - ((Mask_Size - 1) / 2) + i] +def median_filter(image: Image.Image, kernel_size: int = 3): + """ + 中值濾波(Median Filter)實現 + + 參數: + image: PIL.Image對象(RGB格式) + kernel_size: 濾波核大小,必須是奇數 + + 返回: + 濾波後的PIL.Image對象 + """ + # 確保kernel_size是奇數 + if kernel_size % 2 == 0: + kernel_size += 1 + + # 轉換PIL圖像為numpy數組 + numpy_img = np.array(image, dtype=np.uint8) + + # 對每個通道應用中值濾波 + result = np.zeros_like(numpy_img) + for i in range(3): # 對RGB三個通道分別處理 + result[:, :, i] = cv2.medianBlur(numpy_img[:, :, i], kernel_size) + + # 確保像素值在有效範圍內 + result = np.clip(result, 0, 255).astype(np.uint8) + + # 轉回PIL圖像 + return Image.fromarray(result) - return total \ No newline at end of file +def mean_filter(image: Image.Image, kernel_size: int = 3): + """ + 均質濾波(Mean Filter)實現 + + 參數: + image: PIL.Image對象(RGB格式) + kernel_size: 濾波核大小,必須是奇數 + + 返回: + 濾波後的PIL.Image對象 + """ + # 確保kernel_size是奇數 + if kernel_size % 2 == 0: + kernel_size += 1 + + # 轉換PIL圖像為numpy數組 + numpy_img = np.array(image, dtype=np.uint8) + + # 創建均質濾波核(所有元素都是1/(kernel_size*kernel_size)) + kernel = np.ones((kernel_size, kernel_size), np.float32) / (kernel_size * kernel_size) + + # 對每個通道應用均質濾波 + result = np.zeros_like(numpy_img) + for i in range(3): # 對RGB三個通道分別處理 + result[:, :, i] = cv2.filter2D(numpy_img[:, :, i], -1, kernel) + + # 確保像素值在有效範圍內 + result = np.clip(result, 0, 255).astype(np.uint8) + + # 轉回PIL圖像 + return Image.fromarray(result) \ No newline at end of file diff --git a/Image_Process/load_and_ImageGenerator.py b/Image_Process/load_and_ImageGenerator.py deleted file mode 100644 index f879a4e..0000000 --- a/Image_Process/load_and_ImageGenerator.py +++ /dev/null @@ -1,58 +0,0 @@ -from Load_process.LoadData import Loding_Data_Root -from Image_Process.Image_Generator import Image_generator -from Load_process.file_processing import Process_File -from model_data_processing.processing_for_cut_image import Cut_Indepentend_Data -from Load_process.Loading_Tools import Load_Data_Prepare, Load_Data_Tools - -class Load_ImageGenerator(): - ''' - 這是一個拿來進行資料強化的物件,最主要結合了學姊給的資料強化與我自行設定的資料強化。 -藉由此物件先將資料讀取出來,並將資料分別進行資料強化,利用資料強化來迷部資料的不平衡 -這只是其中一個實驗 - -Parmeter - standard_root: 做跟學姊給的資料強化同一種的資料強化 - myself_root: 資料強化的內容參數是我自己設定的 - IndependentDataRoot: 要存回去的資料夾路徑 - Herpeslabels: 皰疹的類別 - MonKeyPoxlabels: 猴痘的類別(猴痘、水痘、正常) - herpes_data: 合併herpes Dataset的資料成一個List - MonkeyPox_data: 合併MonkeyPox DataSet 的資料成一個List - ''' - def __init__(self, Training_Root,Test_Root, Generator_Root, Labels, Image_Size) -> None: - self.Training_Root = Training_Root - self.TestRoot = Test_Root - self.GeneratoRoot = Generator_Root - self.Labels = Labels - self.Image_Size = Image_Size - pass - - def process_main(self, Data_Length : int): - File = Process_File() - Prepare = Load_Data_Prepare() - load = Loding_Data_Root(self.Labels, self.Training_Root, self.GeneratoRoot) - Indepentend = Cut_Indepentend_Data(self.Training_Root, self.Labels) - Load_Tool = Load_Data_Tools() - Generator = Image_generator(self.GeneratoRoot, self.Labels, self.Image_Size) - - # 將測試資料獨立出來 - test_size = 0.2 - Indepentend.IndependentData_main(self.TestRoot, test_size) - - if not File.Judge_File_Exist(self.GeneratoRoot): # 檔案若不存在 - # 確定我要多少個List - Prepare.Set_Data_Content([], Data_Length) - - # 製作讀檔字典並回傳檔案路徑 - Prepare.Set_Label_List(self.Labels) - Prepare.Set_Data_Dictionary(Prepare.Get_Label_List(), Prepare.Get_Data_Content(), Data_Length) - Original_Dict_Data_Root = Prepare.Get_Data_Dict() - get_all_original_image_data = Load_Tool.get_data_root(self.Training_Root, Original_Dict_Data_Root, Prepare.Get_Label_List()) - - # 儲存資料強化後資料 - Generator.Processing_Main(get_all_original_image_data) # 執行資料強化 - else: # 若檔案存在 - print("standard data and myself data are exist\n") - - # 執行讀檔 - return load.process_main() \ No newline at end of file diff --git a/Load_process/LoadData.py b/Load_process/LoadData.py index 9d57b25..3acfd46 100644 --- a/Load_process/LoadData.py +++ b/Load_process/LoadData.py @@ -11,15 +11,16 @@ class Loding_Data_Root(Process_File): super().__init__() pass - def process_main(self): + def process_main(self, status): '''處理讀Training、Image Generator檔資料''' Merge = merge() get_Image_Data = self.get_Image_data_roots(self.Train_Root) - Get_ImageGenerator_Image_Data = self.get_Image_data_roots(self.Generator_Root) - # Get_Total_Image_Data_Root = Merge.merge_dict_to_dict(get_Image_Data, Get_ImageGenerator_Image_Data) - # Get_Total_Image_Data_Root = Merge.merge_data_main(get_Image_Data, 0, len(self.Label_List)) + if status: + Get_ImageGenerator_Image_Data = self.get_Image_data_roots(self.Generator_Root) + Get_Total_Image_Data_Root = Merge.merge_dict_to_dict(get_Image_Data, Get_ImageGenerator_Image_Data) + return Get_Total_Image_Data_Root return get_Image_Data diff --git a/Load_process/Load_Indepentend.py b/Load_process/Load_Indepentend.py index 5cdc158..9971c35 100644 --- a/Load_process/Load_Indepentend.py +++ b/Load_process/Load_Indepentend.py @@ -1,11 +1,10 @@ -from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image from merge_class.merge import merge -from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image from Load_process.LoadData import Load_Data_Prepare, Load_Data_Tools -from model_data_processing.processing import Balance_Process +from model_data_processing.processing import make_label_list +from utils.Stomach_Config import Loading_Config class Load_Indepentend_Data(): - def __init__(self, Labels, OneHot_Encording): + def __init__(self, OneHot_Encording): ''' 影像切割物件 label有2類,會將其轉成one-hot-encoding的形式 @@ -13,33 +12,33 @@ class Load_Indepentend_Data(): [1, 0] = NPC_positive ''' self.merge = merge() - self.Labels = Labels self.OneHot_Encording = OneHot_Encording pass - def process_main(self, Test_data_root): - self.test, self.test_label = self.get_Independent_image(Test_data_root) + def process_main(self, Test_data_root, Test_mask_root): + self.test, self.test_label, self.test_mask = self.get_Independent_image(Test_data_root, Test_mask_root) print("\ntest_labels有" + str(len(self.test_label)) + "筆資料\n") - # self.validation, self.validation_label = self.get_Independent_image(Validation_data_root) - # print("validation_labels有 " + str(len(self.validation_label)) + " 筆資料\n") - - def get_Independent_image(self, independent_DataRoot): - image_processing = Read_image_and_Process_image(123) - - classify_image = [] + def get_Independent_image(self, independent_DataRoot, independent_MaskRoot): Total_Size_List = [] - Total_Dict_Data_Root = self.Get_Independent_data_Root(independent_DataRoot) # 讀取測試資料集的資料 + Total_Dict_Data_Root = self.Get_Independent_data_Root(independent_DataRoot, Loading_Config["Training_Labels"], len(Loading_Config["Training_Labels"])) # 讀取測試資料集的資料 + Total_Dict_Mask_Root = self.Get_Independent_data_Root(independent_MaskRoot, Loading_Config["XML_Loading_Label"], len(Loading_Config["XML_Loading_Label"])) # 讀取測試資料集的mask資料 + # 將測試資料字典轉成列表,並且將其排序 Total_List_Data_Root = [] - for Label in self.Labels: + for Label in Loading_Config["Training_Labels"]: Total_List_Data_Root.append(Total_Dict_Data_Root[Label]) + + # 將測試資料字典轉成列表,並且將其排序 + Total_List_Mask_Data_Root = [] + for Label in Loading_Config["XML_Loading_Label"]: + Total_List_Mask_Data_Root.append(Total_Dict_Mask_Root[Label]) - test_label, Classify_Label = [], [] + classify_image, Classify_Label = [], [] i = 0 # 計算classify_image的counter,且計算總共有幾筆資料 for test_title in Total_List_Data_Root: # 藉由讀取所有路徑來進行讀檔 - test_label = image_processing.make_label_list(len(test_title), self.OneHot_Encording[i]) # 製作對應圖片數量的label出來+ - print(self.Labels[i] + " 有 " + str(len(test_label)) + " 筆資料 ") + test_label = make_label_list(len(test_title), self.OneHot_Encording[i]) # 製作對應圖片數量的label出來+ + print(Loading_Config["Training_Labels"][i] + " 有 " + str(len(test_label)) + " 筆資料 ") Total_Size_List.append(len(test_label)) @@ -47,28 +46,28 @@ class Load_Indepentend_Data(): Classify_Label.append(test_label) i += 1 - test = self.merge.merge_data_main(classify_image, 0, len(self.Labels)) - test_label = self.merge.merge_data_main(Classify_Label, 0, len(self.Labels)) + classify_Mask_image = [] + i = 0 # 計算classify_image的counter,且計算總共有幾筆資料 + for test_title in Total_List_Mask_Data_Root: # 藉由讀取所有路徑來進行讀檔 + print(Loading_Config["XML_Loading_Label"][i] + " 有 " + str(len(test_title)) + " 筆資料 ") - # test = [] - # test = image_processing.Data_Augmentation_Image(original_test_root) - # test, test_label = image_processing.image_data_processing(test, original_test_label) + classify_Mask_image.append(test_title) + i += 1 - # Balance_Data = list(zip(test, test_label)) - # test, test_label = Balance_Process(Balance_Data, Total_Size_List) # 打亂並取出指定資料筆數的資料 - # test = image_processing.normalization(test) - - - return test, test_label + test = self.merge.merge_data_main(classify_image, 0, len(Loading_Config["Training_Labels"])) + test_label = self.merge.merge_data_main(Classify_Label, 0, len(Loading_Config["Training_Labels"])) + test_Mask = self.merge.merge_data_main(classify_Mask_image, 0, len(Loading_Config["XML_Loading_Label"])) + + return test, test_label, test_Mask - def Get_Independent_data_Root(self, load_data_root): + def Get_Independent_data_Root(self, load_data_root, Dictory_Keys, Length): Prepare = Load_Data_Prepare() Load_Tool = Load_Data_Tools() - Prepare.Set_Data_Content([], len(self.Labels)) - Prepare.Set_Data_Dictionary(self.Labels, Prepare.Get_Data_Content(), 2) + Prepare.Set_Data_Content([], Length) + Prepare.Set_Data_Dictionary(Dictory_Keys, Prepare.Get_Data_Content(), Length) Get_Data_Dict_Content = Prepare.Get_Data_Dict() - Total_Data_Roots = Load_Tool.get_data_root(load_data_root, Get_Data_Dict_Content, self.Labels) + Total_Data_Roots = Load_Tool.get_data_root(load_data_root, Get_Data_Dict_Content, Dictory_Keys) - return Total_Data_Roots \ No newline at end of file + return Total_Data_Roots diff --git a/Load_process/__pycache__/LoadData.cpython-311.pyc b/Load_process/__pycache__/LoadData.cpython-311.pyc index 5f87c98..9b87432 100644 Binary files a/Load_process/__pycache__/LoadData.cpython-311.pyc and b/Load_process/__pycache__/LoadData.cpython-311.pyc differ diff --git a/Load_process/__pycache__/LoadData.cpython-312.pyc b/Load_process/__pycache__/LoadData.cpython-312.pyc new file mode 100644 index 0000000..5759b87 Binary files /dev/null and b/Load_process/__pycache__/LoadData.cpython-312.pyc differ diff --git a/Load_process/__pycache__/Load_Indepentend.cpython-311.pyc b/Load_process/__pycache__/Load_Indepentend.cpython-311.pyc index 2d7ab71..efad45c 100644 Binary files a/Load_process/__pycache__/Load_Indepentend.cpython-311.pyc and b/Load_process/__pycache__/Load_Indepentend.cpython-311.pyc differ diff --git a/Load_process/__pycache__/Load_Indepentend.cpython-312.pyc b/Load_process/__pycache__/Load_Indepentend.cpython-312.pyc new file mode 100644 index 0000000..be032eb Binary files /dev/null and b/Load_process/__pycache__/Load_Indepentend.cpython-312.pyc differ diff --git a/Load_process/__pycache__/Loading_Tools.cpython-311.pyc b/Load_process/__pycache__/Loading_Tools.cpython-311.pyc index 2cbc0d0..1ecff0d 100644 Binary files a/Load_process/__pycache__/Loading_Tools.cpython-311.pyc and b/Load_process/__pycache__/Loading_Tools.cpython-311.pyc differ diff --git a/Load_process/__pycache__/Loading_Tools.cpython-312.pyc b/Load_process/__pycache__/Loading_Tools.cpython-312.pyc new file mode 100644 index 0000000..720496a Binary files /dev/null and b/Load_process/__pycache__/Loading_Tools.cpython-312.pyc differ diff --git a/Load_process/__pycache__/file_processing.cpython-311.pyc b/Load_process/__pycache__/file_processing.cpython-311.pyc index 923bee5..7fb6a36 100644 Binary files a/Load_process/__pycache__/file_processing.cpython-311.pyc and b/Load_process/__pycache__/file_processing.cpython-311.pyc differ diff --git a/Load_process/__pycache__/file_processing.cpython-312.pyc b/Load_process/__pycache__/file_processing.cpython-312.pyc new file mode 100644 index 0000000..5339628 Binary files /dev/null and b/Load_process/__pycache__/file_processing.cpython-312.pyc differ diff --git a/Load_process/file_processing.py b/Load_process/file_processing.py index c5c4de7..d09a826 100644 --- a/Load_process/file_processing.py +++ b/Load_process/file_processing.py @@ -35,16 +35,14 @@ class Process_File(): save_root = self.Make_Save_Root(FileName, save_root) np.save(save_root, image) - def Save_CSV_File(self, file_name, data): # 儲存訓練結果 - Save_Root = '../Result/Training_Result/save_the_train_result(' + str(datetime.date.today()) + ")" + def Save_CSV_File(self, Save_Root, file_name, data): # 儲存訓練結果 self.JudgeRoot_MakeDir(Save_Root) modelfiles = self.Make_Save_Root(file_name + ".csv", Save_Root) # 將檔案名稱及路徑字串合併成完整路徑 data.to_csv(modelfiles, mode = "a") - def Save_TXT_File(self, content, File_Name): - model_dir = '../Result/save_the_train_result(' + str(datetime.date.today()) + ")" # 儲存的檔案路徑,由save_the_train_result + 當天日期 - self.JudgeRoot_MakeDir(model_dir) - modelfiles = self.Make_Save_Root(File_Name + ".txt", model_dir) # 將檔案名稱及路徑字串合併成完整路徑 + def Save_TXT_File(self, content, Save_Root, File_Name): + self.JudgeRoot_MakeDir(Save_Root) + modelfiles = self.Make_Save_Root(f"{File_Name}.txt", Save_Root) # 將檔案名稱及路徑字串合併成完整路徑 with open(modelfiles, mode = 'a') as file: file.write(content) diff --git a/Model_Loss/CIOU_Loss.py b/Model_Loss/CIOU_Loss.py new file mode 100644 index 0000000..76db8a6 --- /dev/null +++ b/Model_Loss/CIOU_Loss.py @@ -0,0 +1,315 @@ +import torch +import torch.nn as nn +import math + +class CIOULoss(nn.Module): + """ + Complete Intersection over Union (CIOU) Loss + 適用於目標檢測中的邊界框回歸任務 + + CIOU Loss 考慮了三個幾何因子: + 1. 重疊面積 (Overlap area) + 2. 中心點距離 (Central point distance) + 3. 長寬比一致性 (Aspect ratio consistency) + """ + + def __init__(self, eps=1e-7): + super(CIOULoss, self).__init__() + self.eps = eps + + def forward(self, pred_boxes, target_boxes): + """ + 計算 CIOU Loss + + Args: + pred_boxes: 預測邊界框 [N, 4] (x1, y1, x2, y2) 或 [N, 4] (cx, cy, w, h) 或分割掩碼 [B, 1, H, W] + target_boxes: 真實邊界框 [N, 4] (x1, y1, x2, y2) 或 [N, 4] (cx, cy, w, h) 或分割掩碼 [B, 1, H, W] + + Returns: + CIOU loss value + """ + # 檢查輸入是否為分割掩碼格式 + if len(pred_boxes.shape) == 4 and pred_boxes.shape[1] == 1: + # 將分割掩碼轉換為邊界框格式 + pred_boxes = self._mask_to_boxes(pred_boxes) + target_boxes = self._mask_to_boxes(target_boxes) + + # 如果無法從掩碼中提取有效的邊界框,則返回一個小的損失值 + if pred_boxes is None or target_boxes is None: + return torch.tensor(0.01, device=pred_boxes.device if pred_boxes is not None else target_boxes.device) + + # 確保輸入為浮點數 + pred_boxes = pred_boxes.float() + target_boxes = target_boxes.float() + + # 檢查邊界框維度是否正確 + if pred_boxes.dim() == 1: + # 如果是單個邊界框,擴展為批次大小為1的張量 + pred_boxes = pred_boxes.unsqueeze(0) + if target_boxes.dim() == 1: + target_boxes = target_boxes.unsqueeze(0) + + # 確保邊界框有4個坐標 + if pred_boxes.shape[1] != 4 or target_boxes.shape[1] != 4: + # 如果坐標數量不正確,返回一個小的損失值 + return torch.tensor(0.01, device=pred_boxes.device) + + # 如果輸入是 (cx, cy, w, h) 格式,轉換為 (x1, y1, x2, y2) + if self._is_center_format(pred_boxes, target_boxes): + pred_boxes = self._center_to_corner(pred_boxes) + target_boxes = self._center_to_corner(target_boxes) + + # 計算交集區域 + intersection = self._calculate_intersection(pred_boxes, target_boxes) + + # 計算各自的面積 + pred_area = (pred_boxes[:, 2] - pred_boxes[:, 0]) * (pred_boxes[:, 3] - pred_boxes[:, 1]) + target_area = (target_boxes[:, 2] - target_boxes[:, 0]) * (target_boxes[:, 3] - target_boxes[:, 1]) + + # 計算聯集面積 + union = pred_area + target_area - intersection + self.eps + + # 計算 IoU + iou = intersection / union + + # 計算最小外接矩形 + enclose_x1 = torch.min(pred_boxes[:, 0], target_boxes[:, 0]) + enclose_y1 = torch.min(pred_boxes[:, 1], target_boxes[:, 1]) + enclose_x2 = torch.max(pred_boxes[:, 2], target_boxes[:, 2]) + enclose_y2 = torch.max(pred_boxes[:, 3], target_boxes[:, 3]) + + # 計算最小外接矩形的對角線距離平方 + enclose_diagonal_sq = (enclose_x2 - enclose_x1) ** 2 + (enclose_y2 - enclose_y1) ** 2 + self.eps + + # 計算兩個邊界框中心點之間的距離平方 + pred_center_x = (pred_boxes[:, 0] + pred_boxes[:, 2]) / 2 + pred_center_y = (pred_boxes[:, 1] + pred_boxes[:, 3]) / 2 + target_center_x = (target_boxes[:, 0] + target_boxes[:, 2]) / 2 + target_center_y = (target_boxes[:, 1] + target_boxes[:, 3]) / 2 + + center_distance_sq = (pred_center_x - target_center_x) ** 2 + (pred_center_y - target_center_y) ** 2 + + # 計算長寬比一致性項 + pred_w = pred_boxes[:, 2] - pred_boxes[:, 0] + pred_h = pred_boxes[:, 3] - pred_boxes[:, 1] + target_w = target_boxes[:, 2] - target_boxes[:, 0] + target_h = target_boxes[:, 3] - target_boxes[:, 1] + + # 避免除零 + pred_w = torch.clamp(pred_w, min=self.eps) + pred_h = torch.clamp(pred_h, min=self.eps) + target_w = torch.clamp(target_w, min=self.eps) + target_h = torch.clamp(target_h, min=self.eps) + + v = (4 / (math.pi ** 2)) * torch.pow(torch.atan(target_w / target_h) - torch.atan(pred_w / pred_h), 2) + + # 計算 alpha 參數 + with torch.no_grad(): + alpha = v / (1 - iou + v + self.eps) + + # 計算 CIOU + ciou = iou - (center_distance_sq / enclose_diagonal_sq) - alpha * v + + # 返回 CIOU Loss (1 - CIOU) + ciou_loss = 1 - ciou + + return ciou_loss.mean() + + def _is_center_format(self, pred_boxes, target_boxes): + """ + 判斷輸入格式是否為中心點格式 (cx, cy, w, h) + 簡單的啟發式判斷:如果第三、四列的值都是正數且相對較小,可能是寬高 + """ + # 這裡使用簡單的判斷邏輯,實際使用時可能需要更精確的判斷 + return False # 預設假設輸入為 (x1, y1, x2, y2) 格式 + + def _center_to_corner(self, boxes): + """ + 將中心點格式 (cx, cy, w, h) 轉換為角點格式 (x1, y1, x2, y2) + """ + cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3] + x1 = cx - w / 2 + y1 = cy - h / 2 + x2 = cx + w / 2 + y2 = cy + h / 2 + return torch.stack([x1, y1, x2, y2], dim=1) + + def _mask_to_boxes(self, masks): + """ + 將分割掩碼轉換為邊界框格式 [N, 4] (x1, y1, x2, y2) + + Args: + masks: 分割掩碼 [B, 1, H, W] + + Returns: + boxes: 邊界框 [B, 4] (x1, y1, x2, y2) + """ + batch_size = masks.size(0) + device = masks.device + + # 將掩碼轉換為二值掩碼 + binary_masks = (torch.sigmoid(masks) > 0.5).float() + + # 初始化邊界框張量 + boxes = torch.zeros(batch_size, 4, device=device) + + # 對每個批次處理 + for b in range(batch_size): + mask = binary_masks[b, 0] # [H, W] + + # 找出非零元素的索引 + non_zero_indices = torch.nonzero(mask, as_tuple=True) + + # 如果掩碼中沒有非零元素,則使用默認的小邊界框 + if len(non_zero_indices[0]) == 0: + # 返回一個默認的小邊界框 + boxes[b] = torch.tensor([0, 0, 1, 1], device=device) + continue + + # 計算邊界框坐標 + y_min = torch.min(non_zero_indices[0]) + y_max = torch.max(non_zero_indices[0]) + x_min = torch.min(non_zero_indices[1]) + x_max = torch.max(non_zero_indices[1]) + + # 存儲邊界框 [x1, y1, x2, y2] + boxes[b] = torch.tensor([x_min, y_min, x_max, y_max], device=device) + + return boxes + + def _calculate_intersection(self, pred_boxes, target_boxes): + """ + 計算兩個邊界框的交集面積 + """ + x1 = torch.max(pred_boxes[:, 0], target_boxes[:, 0]) + y1 = torch.max(pred_boxes[:, 1], target_boxes[:, 1]) + x2 = torch.min(pred_boxes[:, 2], target_boxes[:, 2]) + y2 = torch.min(pred_boxes[:, 3], target_boxes[:, 3]) + + # 計算交集的寬度和高度 + intersection_w = torch.clamp(x2 - x1, min=0) + intersection_h = torch.clamp(y2 - y1, min=0) + + return intersection_w * intersection_h + + +class DIoULoss(nn.Module): + """ + Distance Intersection over Union (DIoU) Loss + CIOU 的簡化版本,只考慮重疊面積和中心點距離 + """ + + def __init__(self, eps=1e-7): + super(DIoULoss, self).__init__() + self.eps = eps + + def forward(self, pred_boxes, target_boxes): + # 確保輸入為浮點數 + pred_boxes = pred_boxes.float() + target_boxes = target_boxes.float() + + # 計算交集區域 + intersection = self._calculate_intersection(pred_boxes, target_boxes) + + # 計算各自的面積 + pred_area = (pred_boxes[:, 2] - pred_boxes[:, 0]) * (pred_boxes[:, 3] - pred_boxes[:, 1]) + target_area = (target_boxes[:, 2] - target_boxes[:, 0]) * (target_boxes[:, 3] - target_boxes[:, 1]) + + # 計算聯集面積 + union = pred_area + target_area - intersection + self.eps + + # 計算 IoU + iou = intersection / union + + # 計算最小外接矩形的對角線距離平方 + enclose_x1 = torch.min(pred_boxes[:, 0], target_boxes[:, 0]) + enclose_y1 = torch.min(pred_boxes[:, 1], target_boxes[:, 1]) + enclose_x2 = torch.max(pred_boxes[:, 2], target_boxes[:, 2]) + enclose_y2 = torch.max(pred_boxes[:, 3], target_boxes[:, 3]) + + enclose_diagonal_sq = (enclose_x2 - enclose_x1) ** 2 + (enclose_y2 - enclose_y1) ** 2 + self.eps + + # 計算中心點距離平方 + pred_center_x = (pred_boxes[:, 0] + pred_boxes[:, 2]) / 2 + pred_center_y = (pred_boxes[:, 1] + pred_boxes[:, 3]) / 2 + target_center_x = (target_boxes[:, 0] + target_boxes[:, 2]) / 2 + target_center_y = (target_boxes[:, 1] + target_boxes[:, 3]) / 2 + + center_distance_sq = (pred_center_x - target_center_x) ** 2 + (pred_center_y - target_center_y) ** 2 + + # 計算 DIoU + diou = iou - (center_distance_sq / enclose_diagonal_sq) + + # 返回 DIoU Loss + diou_loss = 1 - diou + + return diou_loss.mean() + + def _calculate_intersection(self, pred_boxes, target_boxes): + """計算交集面積""" + x1 = torch.max(pred_boxes[:, 0], target_boxes[:, 0]) + y1 = torch.max(pred_boxes[:, 1], target_boxes[:, 1]) + x2 = torch.min(pred_boxes[:, 2], target_boxes[:, 2]) + y2 = torch.min(pred_boxes[:, 3], target_boxes[:, 3]) + + intersection_w = torch.clamp(x2 - x1, min=0) + intersection_h = torch.clamp(y2 - y1, min=0) + + return intersection_w * intersection_h + + +class GIoULoss(nn.Module): + """ + Generalized Intersection over Union (GIoU) Loss + IoU 的泛化版本,考慮了最小外接矩形 + """ + + def __init__(self, eps=1e-7): + super(GIoULoss, self).__init__() + self.eps = eps + + def forward(self, pred_boxes, target_boxes): + # 確保輸入為浮點數 + pred_boxes = pred_boxes.float() + target_boxes = target_boxes.float() + + # 計算交集 + intersection = self._calculate_intersection(pred_boxes, target_boxes) + + # 計算各自面積 + pred_area = (pred_boxes[:, 2] - pred_boxes[:, 0]) * (pred_boxes[:, 3] - pred_boxes[:, 1]) + target_area = (target_boxes[:, 2] - target_boxes[:, 0]) * (target_boxes[:, 3] - target_boxes[:, 1]) + + # 計算聯集 + union = pred_area + target_area - intersection + self.eps + + # 計算 IoU + iou = intersection / union + + # 計算最小外接矩形面積 + enclose_x1 = torch.min(pred_boxes[:, 0], target_boxes[:, 0]) + enclose_y1 = torch.min(pred_boxes[:, 1], target_boxes[:, 1]) + enclose_x2 = torch.max(pred_boxes[:, 2], target_boxes[:, 2]) + enclose_y2 = torch.max(pred_boxes[:, 3], target_boxes[:, 3]) + + enclose_area = (enclose_x2 - enclose_x1) * (enclose_y2 - enclose_y1) + self.eps + + # 計算 GIoU + giou = iou - (enclose_area - union) / enclose_area + + # 返回 GIoU Loss + giou_loss = 1 - giou + + return giou_loss.mean() + + def _calculate_intersection(self, pred_boxes, target_boxes): + """計算交集面積""" + x1 = torch.max(pred_boxes[:, 0], target_boxes[:, 0]) + y1 = torch.max(pred_boxes[:, 1], target_boxes[:, 1]) + x2 = torch.min(pred_boxes[:, 2], target_boxes[:, 2]) + y2 = torch.min(pred_boxes[:, 3], target_boxes[:, 3]) + + intersection_w = torch.clamp(x2 - x1, min=0) + intersection_h = torch.clamp(y2 - y1, min=0) + + return intersection_w * intersection_h \ No newline at end of file diff --git a/Model_Loss/Loss.py b/Model_Loss/Loss.py index a9c340b..b507f7d 100644 --- a/Model_Loss/Loss.py +++ b/Model_Loss/Loss.py @@ -8,10 +8,20 @@ class Entropy_Loss(nn.Module): super(Entropy_Loss, self).__init__() def forward(self, outputs, labels): - # 範例: 使用均方誤差作為損失計算 - # outputs = torch.argmax(outputs, 1) + # 转换为张量 outputs_New = torch.as_tensor(outputs, dtype=torch.float32) labels_New = torch.as_tensor(labels, dtype=torch.float32) - - loss = functional.cross_entropy(outputs_New, labels_New) + + # 检查输出和标签的维度是否匹配 + if outputs_New.shape[1] != labels_New.shape[1]: + # 如果维度不匹配,使用交叉熵损失函数 + # 对于交叉熵损失,标签需要是类别索引而不是one-hot编码 + # 将one-hot编码转换为类别索引 + _, labels_indices = torch.max(labels_New, dim=1) + loss = functional.cross_entropy(outputs_New, labels_indices) + else: + # 如果维度匹配,始终使用binary_cross_entropy_with_logits + # 它会自动应用sigmoid函数,避免输入值超出[0,1]范围 + loss = functional.binary_cross_entropy_with_logits(outputs_New, labels_New) + return torch.as_tensor(loss, dtype = torch.float32) \ No newline at end of file diff --git a/Model_Loss/Perceptual_Loss.py b/Model_Loss/Perceptual_Loss.py new file mode 100644 index 0000000..d2ac6dd --- /dev/null +++ b/Model_Loss/Perceptual_Loss.py @@ -0,0 +1,116 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from torchvision import models, transforms + +class VGGPerceptualLoss(nn.Module): + """ + 基於VGG19的感知損失函數 + 使用預訓練的VGG19網絡提取特徵,計算特徵空間中的損失 + """ + def __init__(self, feature_layers=[2, 7, 12, 21, 30], use_normalization=True): + super(VGGPerceptualLoss, self).__init__() + + # 載入預訓練的VGG19模型 + vgg = models.vgg19(pretrained=True).features + + # 凍結VGG參數 + for param in vgg.parameters(): + param.requires_grad = False + + # 將模型移到與輸入相同的設備上(在forward中處理) + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + + # 選擇要使用的特徵層 + self.feature_layers = feature_layers + self.vgg_layers = nn.ModuleList() + + # 分割VGG網絡到指定層 + layer_idx = 0 + current_layer = 0 + + for i, layer in enumerate(vgg): + if layer_idx < len(feature_layers) and i <= feature_layers[layer_idx]: + self.vgg_layers.append(layer) + if i == feature_layers[layer_idx]: + layer_idx += 1 + else: + break + + # 是否使用ImageNet標準化 + self.use_normalization = use_normalization + if use_normalization: + self.normalize = transforms.Normalize( + mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225] + ) + + # 損失權重 + self.weights = [1.0, 1.0, 1.0, 1.0, 1.0] # 可以調整不同層的權重 + + def extract_features(self, x): + """ + 提取VGG特徵 + """ + # 確保輸入在[0,1]範圍內 + if x.min() < 0 or x.max() > 1: + x = torch.clamp(x, 0, 1) + + # 標準化 + if self.use_normalization: + # 確保normalize在與輸入相同的設備上 + if hasattr(self, 'normalize') and not isinstance(self.normalize, torch.nn.Module): + self.normalize = transforms.Normalize( + mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225] + ).to(x.device) + x = self.normalize(x) + + features = [] + layer_idx = 0 + + # 確保所有VGG層都在與輸入相同的設備上 + device = x.device + for i, layer in enumerate(self.vgg_layers): + layer = layer.to(device) # 確保層在正確的設備上 + x = layer(x) + + # 檢查是否到達目標特徵層 + if layer_idx < len(self.feature_layers) and i == self.feature_layers[layer_idx]: + features.append(x) + layer_idx += 1 + + return features + + def forward(self, pred, target): + """ + 計算感知損失 + pred: 預測圖像 [B, C, H, W] + target: 目標圖像 [B, C, H, W] + """ + # 確保模型在與輸入相同的設備上 + device = pred.device + self.vgg_layers = nn.ModuleList([layer.to(device) for layer in self.vgg_layers]) + + # 確保輸入尺寸匹配 + if pred.shape != target.shape: + pred = F.interpolate(pred, size=target.shape[2:], mode='bilinear', align_corners=False) + + # 如果是單通道,轉換為三通道 + if pred.shape[1] == 1: + pred = pred.repeat(1, 3, 1, 1) + if target.shape[1] == 1: + target = target.repeat(1, 3, 1, 1) + + # 提取特徵 + pred_features = self.extract_features(pred) + target_features = self.extract_features(target) + + # 計算特徵損失 + perceptual_loss = 0 + for i, (pred_feat, target_feat) in enumerate(zip(pred_features, target_features)): + # 使用MSE計算特徵差異 + feat_loss = F.mse_loss(pred_feat, target_feat) + perceptual_loss += self.weights[i] * feat_loss + + return perceptual_loss diff --git a/Model_Loss/Segmentation_Loss.py b/Model_Loss/Segmentation_Loss.py new file mode 100644 index 0000000..230c33a --- /dev/null +++ b/Model_Loss/Segmentation_Loss.py @@ -0,0 +1,22 @@ +from multiprocessing import Value +import pstats +import torch +import torch.nn as nn +import torch.nn.functional as F +import torchvision.models as models +from torchvision import transforms +from Model_Loss.CIOU_Loss import CIOULoss +from Model_Loss.Perceptual_Loss import VGGPerceptualLoss + +class Segmentation_Loss(nn.Module): + def __init__(self) -> None: + super(Segmentation_Loss, self).__init__() + self.Perceptual_Loss = VGGPerceptualLoss() + self.CIOU = CIOULoss() + pass + + def forward(self, Output_Result, GroundTruth_Result): + Perceptual_Loss = self.Perceptual_Loss(Output_Result, GroundTruth_Result) + CIOU_Loss = self.CIOU(Output_Result, GroundTruth_Result) + + return Perceptual_Loss + CIOU_Loss diff --git a/Model_Loss/__pycache__/CIOU_Loss.cpython-311.pyc b/Model_Loss/__pycache__/CIOU_Loss.cpython-311.pyc new file mode 100644 index 0000000..bdf4037 Binary files /dev/null and b/Model_Loss/__pycache__/CIOU_Loss.cpython-311.pyc differ diff --git a/Model_Loss/__pycache__/Loss.cpython-311.pyc b/Model_Loss/__pycache__/Loss.cpython-311.pyc index 1e8d4c4..1c98ff2 100644 Binary files a/Model_Loss/__pycache__/Loss.cpython-311.pyc and b/Model_Loss/__pycache__/Loss.cpython-311.pyc differ diff --git a/Model_Loss/__pycache__/Loss.cpython-312.pyc b/Model_Loss/__pycache__/Loss.cpython-312.pyc new file mode 100644 index 0000000..2d6a51a Binary files /dev/null and b/Model_Loss/__pycache__/Loss.cpython-312.pyc differ diff --git a/Model_Loss/__pycache__/Perceptual_Loss.cpython-311.pyc b/Model_Loss/__pycache__/Perceptual_Loss.cpython-311.pyc new file mode 100644 index 0000000..e6e719a Binary files /dev/null and b/Model_Loss/__pycache__/Perceptual_Loss.cpython-311.pyc differ diff --git a/Model_Loss/__pycache__/Segmentation_Loss.cpython-311.pyc b/Model_Loss/__pycache__/Segmentation_Loss.cpython-311.pyc new file mode 100644 index 0000000..f17d384 Binary files /dev/null and b/Model_Loss/__pycache__/Segmentation_Loss.cpython-311.pyc differ diff --git a/Model_Loss/__pycache__/binary_cross_entropy.cpython-311.pyc b/Model_Loss/__pycache__/binary_cross_entropy.cpython-311.pyc new file mode 100644 index 0000000..80f46ff Binary files /dev/null and b/Model_Loss/__pycache__/binary_cross_entropy.cpython-311.pyc differ diff --git a/Model_Loss/binary_cross_entropy.py b/Model_Loss/binary_cross_entropy.py new file mode 100644 index 0000000..f497f0c --- /dev/null +++ b/Model_Loss/binary_cross_entropy.py @@ -0,0 +1,145 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class BinaryCrossEntropy(nn.Module): + """ + 基本的二元交叉熵損失函數 + """ + def __init__(self, reduction='mean'): + """ + 初始化 + + Args: + reduction (str): 'mean', 'sum' 或 'none',指定如何減少損失 + """ + super(BinaryCrossEntropy, self).__init__() + + def forward(self, predictions, targets): + """ + 計算二元交叉熵損失 + + Args: + predictions (torch.Tensor): 模型的預測輸出,形狀為 [batch_size, ...] + targets (torch.Tensor): 目標標籤,形狀與 predictions 相同 + + Returns: + torch.Tensor: 計算得到的損失值 + """ + # 確保輸入是張量 + predictions = torch.as_tensor(predictions, dtype=torch.float32) + targets = torch.as_tensor(targets, dtype=torch.float32) + + return F.binary_cross_entropy_with_logits(predictions, targets) + + # # 檢查輸出和標籤的維度是否匹配 + # if predictions.shape[1] != targets.shape[1]: + # # 如果維度不匹配,使用交叉熵損失函數 + # # 對於交叉熵損失,標籤需要是類別索引而不是one-hot編碼 + # # 將one-hot編碼轉換為類別索引 + # _, targets_indices = torch.max(targets, dim=1) + # return F.cross_entropy(predictions, targets_indices, reduction=self.reduction) + # else: + # # 如果維度匹配,使用二元交叉熵損失函數 + # # 使用 PyTorch 內建的 binary_cross_entropy_with_logits 函數 + # # 它會自動應用 sigmoid 函數,避免輸入值超出 [0,1] 範圍 + # return F.binary_cross_entropy_with_logits(predictions, targets, reduction=self.reduction) + + +class WeightedBinaryCrossEntropy(nn.Module): + """ + 帶權重的二元交叉熵損失函數 + """ + def __init__(self, pos_weight=1.0, neg_weight=1.0, reduction='mean'): + """ + 初始化 + + Args: + pos_weight (float): 正樣本的權重 + neg_weight (float): 負樣本的權重 + reduction (str): 'mean', 'sum' 或 'none',指定如何減少損失 + """ + super(WeightedBinaryCrossEntropy, self).__init__() + self.pos_weight = pos_weight + self.neg_weight = neg_weight + self.reduction = reduction + + def forward(self, predictions, targets): + """ + 計算帶權重的二元交叉熵損失 + + Args: + predictions (torch.Tensor): 模型的預測輸出,形狀為 [batch_size, ...] + targets (torch.Tensor): 目標標籤,形狀與 predictions 相同 + + Returns: + torch.Tensor: 計算得到的損失值 + """ + # 確保輸入是張量 + predictions = torch.as_tensor(predictions, dtype=torch.float32) + targets = torch.as_tensor(targets, dtype=torch.float32) + + # 使用 sigmoid 確保預測值在 [0,1] 範圍內 + predictions = torch.sigmoid(predictions) + + # 計算帶權重的二元交叉熵損失 + loss = -self.pos_weight * targets * torch.log(predictions + 1e-7) - \ + self.neg_weight * (1 - targets) * torch.log(1 - predictions + 1e-7) + + # 根據 reduction 方式返回損失 + if self.reduction == 'mean': + return loss.mean() + elif self.reduction == 'sum': + return loss.sum() + else: # 'none' + return loss + + +class LabelSmoothingBCE(nn.Module): + """ + 帶標籤平滑的二元交叉熵損失函數 + """ + def __init__(self, smoothing=0.1, reduction='mean'): + """ + 初始化 + + Args: + smoothing (float): 標籤平滑係數,範圍 [0, 1] + reduction (str): 'mean', 'sum' 或 'none',指定如何減少損失 + """ + super(LabelSmoothingBCE, self).__init__() + self.smoothing = smoothing + self.reduction = reduction + + def forward(self, predictions, targets): + """ + 計算帶標籤平滑的二元交叉熵損失 + + Args: + predictions (torch.Tensor): 模型的預測輸出,形狀為 [batch_size, ...] + targets (torch.Tensor): 目標標籤,形狀與 predictions 相同 + + Returns: + torch.Tensor: 計算得到的損失值 + """ + # 確保輸入是張量 + predictions = torch.as_tensor(predictions, dtype=torch.float32) + targets = torch.as_tensor(targets, dtype=torch.float32) + + # 應用標籤平滑 + targets = targets * (1 - self.smoothing) + 0.5 * self.smoothing + + # 使用 sigmoid 確保預測值在 [0,1] 範圍內 + predictions = torch.sigmoid(predictions) + + # 計算二元交叉熵損失 + loss = -targets * torch.log(predictions + 1e-7) - (1 - targets) * torch.log(1 - predictions + 1e-7) + + # 根據 reduction 方式返回損失 + if self.reduction == 'mean': + return loss.mean() + elif self.reduction == 'sum': + return loss.sum() + else: # 'none' + return loss \ No newline at end of file diff --git a/README.md b/README.md index 24be781..e8fd911 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,116 @@ -main.py: 主程式檔 +# 胃試鏡疾病判斷系統 + +## 項目概述 + +本項目是一個基於深度學習的胃試鏡疾病自動診斷系統,主要用於檢測和分類胃部疾病,特別是胃癌(CA)。系統採用了分割和分類的兩階段方法,首先對胃試鏡圖像進行分割以識別可疑區域,然後對這些區域進行分類以確定是否存在疾病。 + +* 資料集類別: 3分類(胃癌、非胃癌但有病、正常資料) +* 基礎模型: Xception + +## 主要功能 + +- **圖像預處理**:包括直方圖均衡化、自適應直方圖均衡化、銳化、HSV調整、伽馬校正等多種圖像增強方法 +- **數據增強**:通過圖像生成器擴充訓練數據集 +- **疾病分割**:使用GastroSegNet模型對胃試鏡圖像中的可疑區域進行分割 +- **疾病分類**:使用修改版Xception模型對分割後的區域進行分類,分為正常(Normal)、胃癌(CA)和待確認(Have_Question)三類 +- **結果可視化**:提供混淆矩陣、訓練曲線等可視化工具 + +## 系統架構 + +系統採用了兩階段的處理流程: + +1. **分割階段**:使用GastroSegNet模型對輸入圖像進行分割,識別可疑區域 +2. **分類階段**: + - 第一組分類器:區分正常(Normal)和其他(Others) + - 第二組分類器:區分胃癌(CA)和待確認(Have_Question) + +## 環境要求 + +- Python 3.8+ +- PyTorch 1.8+ +- CUDA(推薦用於加速訓練) +- 其他依賴庫:torchvision, numpy, opencv-python, scikit-image, pandas等 + +## 使用方法 + +### 數據準備 + +1. 將訓練數據放置在`../Dataset/Training`目錄下 +2. 將測試數據放置在`../Dataset/Testing`目錄下 +3. 標註數據(XML格式)放置在`../Label_Image`目錄下 + +### 訓練模型 + +```bash +uv run main.py +``` + +訓練過程將自動執行以下步驟: +1. 數據預處理和增強 +2. 訓練分割模型(GastroSegNet) +3. 使用分割模型處理圖像 +4. 訓練分類模型(修改版Xception) + +### 結果查看 + +訓練結果將保存在`../Result`目錄下,包括: +- 訓練曲線圖:`../Result/Training_Image` +- 混淆矩陣:`../Result/Matrix_Image` +- 訓練結果數據:`../Result/Training_Result` +- 最佳模型:`../Result/save_the_best_model` + +## 項目結構 + +- `main.py`:程序入口點 +- `experiments/`:實驗相關代碼 + - `experiment.py`:實驗主流程 + - `Training/`:訓練相關代碼 + - `Models/`:模型定義 +- `Image_Process/`:圖像處理相關代碼 +- `Model_Loss/`:損失函數定義 +- `Training_Tools/`:訓練工具 +- `utils/`:工具函數和配置 + +## 配置說明 + +系統配置在`utils/Stomach_Config.py`中定義,主要包括: + +- `Image_Enhance`:圖像增強方法 +- `Loading_Config`:數據加載配置 +- `Training_Config`:訓練參數配置 +- `Model_Config`:模型參數配置 +- `Save_Result_File_Config`:結果保存路徑配置 + +## 模型說明 + +### 分割模型(GastroSegNet) + +用於識別胃試鏡圖像中的可疑區域,輸出分割掩碼。 + +### 分類模型(修改版Xception) + +基於Xception架構,針對胃試鏡圖像分類任務進行了修改,主要用於區分正常、胃癌和待確認三類。 +* 主執行檔: main.py ## load_process ### 負責讀取影像檔案、分割獨立資料(測試、驗證)、讀取獨立資料、一般檔案的操作 -File_Process : 檔案操作的主程式,包含開檔、創立檔案、判斷檔案是否存在等都是他負責的範圍。是一般物件也是LoadData的父物件 -LoadData : 讀檔主程式,一切讀檔動作由他開始。繼承File_Process(子物件) -Cutting_Indepentend_Image : 讀取獨立資料(testing、Validation)的物件 +* File_Process : 檔案操作的主程式,包含開檔、創立檔案、判斷檔案是否存在等都是他負責的範圍。是一般物件也是LoadData的父物件 +* LoadData : 讀檔主程式,一切讀檔動作由他開始。繼承File_Process(子物件) +* Cutting_Indepentend_Image : 讀取獨立資料(testing、Validation)的物件 + +## Calculate_Process +### 計算模型的評估指標的內容 +* Calculate: 計算模型的評估指標的平均跟標準差,並將結果儲存到檔案中 ## Image_Process ### 負責進行資料擴增、影像處理等的操作 -* Generator_Content : 負責建立基礎Generator項目,為Image_Generator的父類別 -* Image_Generator : 負責製造資料擴增的資料,並將資料存到檔案中。繼承Generator_Content(子物件) -* image_enhancement : 負責進行影像處理並將資料回傳 +* Image_Generator : 負責製造資料擴增的資料,並將資料存到檔案中。 +* image_enhancement : 負責進行影像處理將資料強化。 +* + +## all_models_tools +### 模型的調控細節,如early stop、降低學習率和儲存最佳模型 +* all_model_tools: call back的方法 ## Model_Tools ### 負責進行模型的基礎架構,包含Convolution、Dense、以及其他模型的配件 @@ -42,11 +142,13 @@ Cutting_Indepentend_Image : 讀取獨立資料(testing、Validation)的物件 ### 負責驗證程式碼內的資料型態或輸入錯誤等問題 * Validation : 驗證程式碼錯誤 -## Draw +## draw_tools ### 負責畫圖的工具 -* Draw_Tools : 畫出混淆矩陣、走勢圖的工具 +* draw : 畫出混淆矩陣、走勢圖的工具 * Grad_CAM : 畫出模型可視化的熱力圖的工具 ## Experiment ### 執行實驗的主程式 -* Experiment : 負責執行讀檔、設定模型Compile的細節、執行訓練、驗證結果等功能 +* Experiment : 負責執行讀檔、設定模型與實驗的細節、執行訓練、驗證結果等功能 +* Model_All_Step : 執行模型的訓練流程設定與細節參數設定 +* pytorch_Model: 設定模型的架構 diff --git a/Training_Tools/PreProcess.py b/Training_Tools/PreProcess.py index a01d880..a342d3a 100644 --- a/Training_Tools/PreProcess.py +++ b/Training_Tools/PreProcess.py @@ -7,32 +7,68 @@ import numpy as np import cv2 class ListDataset(Dataset): - def __init__(self, data_list, labels_list, transform): + def __init__(self, data_list, labels_list, Mask_List, transform): self.data = data_list self.labels = labels_list + self.Mask_Truth_List = Mask_List self.transform = transform + self.roots = [] def __len__(self): return len(self.data) def __getitem__(self, idx): Image_Root = self.data[idx] + + Mask_Ground_Truth = None + if self.Mask_Truth_List is not None: + mask_path = self.Mask_Truth_List[idx] + if mask_path is not None: # 確保掩碼路徑不為None + try: + Mask_Ground_Truth = Image.open(mask_path).convert("RGB") + # 先不轉換為 tensor,等待 transform 處理完後再轉換 + except Exception as e: + print(e) + + Split_Roots = Image_Root.split("/") + # Split_Roots = Split_Roots[-1].split("\\") + File_Name = Split_Roots[-1] + classes = Split_Roots[-2] + try: - with open(Image_Root, 'rb') as file: - Images = Image.open(file).convert("RGB") - # Image = cv2.imread(Image_Root, cv2.IMREAD_COLOR) # 讀檔(彩色) - # Image = cv2.cvtColor(Image, cv2.COLOR_BGR2RGB) + Images = Image.open(Image_Root).convert("RGB") except Exception as e: - print(e) + assert e is not None, f"Error loading image {Image_Root}: {e}" - if self.transform is not "Generator": + if self.transform != "Generator": Images = self.transform(Images) + if self.Mask_Truth_List is not None and Mask_Ground_Truth is not None and not isinstance(Mask_Ground_Truth, torch.Tensor): + Mask_Ground_Truth = self.transform(Mask_Ground_Truth) - Images = torch.tensor(np.array(Images)) + # 確保 Images 是 tensor + if not isinstance(Images, torch.Tensor): + Images = torch.tensor(np.array(Images)) + + # 確保 Mask_Ground_Truth 是 tensor + if self.Mask_Truth_List is not None and Mask_Ground_Truth is not None and not isinstance(Mask_Ground_Truth, torch.Tensor): + Mask_Ground_Truth = torch.tensor(np.array(Mask_Ground_Truth)) + label = self.labels[idx] + if self.Mask_Truth_List is not None: + # 如果掩碼為None,創建一個與圖像相同大小的空掩碼 + if Mask_Ground_Truth is None: + if isinstance(Images, torch.Tensor): + # 創建與圖像相同大小的空掩碼張量 + Mask_Ground_Truth = torch.zeros_like(Images) + else: + # 如果圖像不是張量,創建一個空的PIL圖像 + Mask_Ground_Truth = Image.new('RGB', Images.size, (0, 0, 0)) + if self.transform != "Generator": + Mask_Ground_Truth = self.transform(Mask_Ground_Truth) + return Images, Mask_Ground_Truth, label, File_Name, classes # print(f"Dataset_Data: \n{sample}\n") - return Images, label + return Images, label, File_Name, classes class Training_Precesses: def __init__(self, ImageSize): @@ -43,27 +79,19 @@ class Training_Precesses: def Dataloader_Sampler(self, SubDataSet, Batch_Size, Sampler=True): if Sampler: - # Data_Loader = DataLoader( - # dataset=SubDataSet, - # batch_size=Batch_Size, - # num_workers=0, - # pin_memory=True, - # sampler=self.Setting_RandomSampler_Content(SubDataSet) - # ) Data_Loader = DataLoader( dataset=SubDataSet, batch_size=Batch_Size, num_workers=0, pin_memory=True, - sampler=self.Setting_RandomSampler_Content(SubDataSet) + sampler=self.Setting_WeightedRandomSampler_Content(SubDataSet) ) else: Data_Loader = DataLoader( dataset=SubDataSet, batch_size=Batch_Size, num_workers=0, - pin_memory=True, - shuffle=True + pin_memory=True ) return Data_Loader @@ -84,6 +112,16 @@ class Training_Precesses: if labels.ndim > 1: # If one-hot encoded labels = np.argmax(labels, axis=1) + # 確保標籤是整數類型 + try: + # 嘗試將標籤轉換為整數 + labels = labels.astype(np.int64) + except ValueError: + # 如果標籤是字符串,先將其映射到整數 + unique_labels = np.unique(labels) + label_to_idx = {label: idx for idx, label in enumerate(unique_labels)} + labels = np.array([label_to_idx[label] for label in labels]) + # Count occurrences of each class class_counts = np.bincount(labels) class_weights = 1.0 / class_counts # Inverse frequency as weight @@ -98,20 +136,28 @@ class Training_Precesses: def Setting_RandomSampler_Content(self, Dataset): return RandomSampler(Dataset, generator = self.generator) - def Setting_DataSet(self, Datas, Labels, transform = None): + def Setting_DataSet(self, Datas, Labels, Mask_List, transform = None): # 資料預處理 if transform == None: transform = transforms.Compose([ - transforms.Resize((256, 256)) + transforms.Resize((self.ImageSize, self.ImageSize)) ]) elif transform == "Transform": - transform = transforms.Compose([ - transforms.Resize((256, 256)), + transform = transforms.Compose([ + transforms.Resize((self.ImageSize, self.ImageSize)), transforms.ToTensor() ]) elif transform == "Generator": transform = "Generator" # Create Dataset - list_dataset = ListDataset(Datas, Labels , transform) - return list_dataset \ No newline at end of file + list_dataset = ListDataset(Datas, Labels, Mask_List, transform) + return list_dataset + + def Setting_SubsetRandomSampler_Content(self, SubDataSet): + # Calculate subset indices (example: using a fraction of the dataset) + dataset_size = len(SubDataSet) + subset_size = int(0.8 * dataset_size) # Use 80% of the dataset as an example + subset_indices = torch.randperm(dataset_size, generator=self.generator)[:subset_size] + + return SubsetRandomSampler(subset_indices, generator=self.generator) \ No newline at end of file diff --git a/Training_Tools/Tools.py b/Training_Tools/Tools.py index b39e469..7f30087 100644 --- a/Training_Tools/Tools.py +++ b/Training_Tools/Tools.py @@ -5,39 +5,9 @@ import torch class Tool: def __init__(self) -> None: - self.__ICG_Training_Root = "" - self.__Normal_Training_Root = "" - self.__Comprehensive_Training_Root = "" - - self.__ICG_Test_Data_Root = "" - self.__Normal_Test_Data_Root = "" - self.__Comprehensive_Testing_Root = "" - - self.__ICG_ImageGenerator_Data_Root = "" - self.__Normal_ImageGenerator_Data_Root = "" - self.__Comprehensive_Generator_Root = "" - - self.__Labels = [] self.__OneHot_Encording = [] pass - def Set_Labels(self): - self.__Labels = ["stomach_cancer_Crop", "Normal_Crop", "Have_Question_Crop"] - # self.__Labels = ["NPC_negative", "NPC_positive"] - - def Set_Save_Roots(self): - self.__ICG_Training_Root = "../Dataset/Training" - self.__Normal_Training_Root = "../Dataset/Training/CA" - self.__Comprehensive_Training_Root = "../Dataset/Training/Mixed" - - self.__ICG_Test_Data_Root = "../Dataset/Testing" - self.__Normal_Test_Data_Root = "../Dataset/Training/Normal_TestData" - self.__Comprehensive_Testing_Root = "../Dataset/Training/Comprehensive_TestData" - - self.__ICG_ImageGenerator_Data_Root = "../Dataset/ImageGenerator" - self.__Normal_ImageGenerator_Data_Root = "../Dataset/Training/Normal_ImageGenerator" - self.__Comprehensive_Generator_Root = "../Dataset/Training/Comprehensive_ImageGenerator" - def Set_OneHotEncording(self, content): Counter = [] for i in range(len(content)): @@ -46,35 +16,6 @@ class Tool: Counter = torch.tensor(Counter) self.__OneHot_Encording = functional.one_hot(Counter, len(content)) pass - - def Get_Data_Label(self): - ''' - 取得所需資料的Labels - ''' - return self.__Labels - - def Get_Save_Roots(self, choose): - '''回傳結果為Train, test, validation - choose = 1 => 取白光 Label - else => 取濾光 Label - - 若choose != 1 || choose != 2 => 會回傳四個結果 - ''' - if choose == 1: - return self.__ICG_Training_Root, self.__ICG_Test_Data_Root - if choose == 2: - return self.__Normal_Training_Root, self.__Normal_Test_Data_Root - else: - return self.__Comprehensive_Training_Root, self.__Comprehensive_Testing_Root - - def Get_Generator_Save_Roots(self, choose): - '''回傳結果為Train, test, validation''' - if choose == 1: - return self.__ICG_ImageGenerator_Data_Root - if choose == 2: - return self.__Normal_ImageGenerator_Data_Root - else: - return self.__Comprehensive_Generator_Root def Get_OneHot_Encording_Label(self): return self.__OneHot_Encording \ No newline at end of file diff --git a/Training_Tools/__pycache__/PreProcess.cpython-311.pyc b/Training_Tools/__pycache__/PreProcess.cpython-311.pyc index c4a7a1a..07e2dd7 100644 Binary files a/Training_Tools/__pycache__/PreProcess.cpython-311.pyc and b/Training_Tools/__pycache__/PreProcess.cpython-311.pyc differ diff --git a/Training_Tools/__pycache__/PreProcess.cpython-312.pyc b/Training_Tools/__pycache__/PreProcess.cpython-312.pyc new file mode 100644 index 0000000..3dacc79 Binary files /dev/null and b/Training_Tools/__pycache__/PreProcess.cpython-312.pyc differ diff --git a/Training_Tools/__pycache__/Tools.cpython-311.pyc b/Training_Tools/__pycache__/Tools.cpython-311.pyc index 7ba88ba..1f871c4 100644 Binary files a/Training_Tools/__pycache__/Tools.cpython-311.pyc and b/Training_Tools/__pycache__/Tools.cpython-311.pyc differ diff --git a/Training_Tools/__pycache__/Tools.cpython-312.pyc b/Training_Tools/__pycache__/Tools.cpython-312.pyc new file mode 100644 index 0000000..d47147f Binary files /dev/null and b/Training_Tools/__pycache__/Tools.cpython-312.pyc differ diff --git a/Training_Tools/__pycache__/__init__.cpython-311.pyc b/Training_Tools/__pycache__/__init__.cpython-311.pyc index 62f65d9..f90ae1f 100644 Binary files a/Training_Tools/__pycache__/__init__.cpython-311.pyc and b/Training_Tools/__pycache__/__init__.cpython-311.pyc differ diff --git a/Training_Tools/__pycache__/__init__.cpython-312.pyc b/Training_Tools/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..c489d7f Binary files /dev/null and b/Training_Tools/__pycache__/__init__.cpython-312.pyc differ diff --git a/__pycache__/Processing_image.cpython-311.pyc b/__pycache__/Processing_image.cpython-311.pyc new file mode 100644 index 0000000..5f3b3a6 Binary files /dev/null and b/__pycache__/Processing_image.cpython-311.pyc differ diff --git a/__pycache__/claculate_output_data.cpython-311.pyc b/__pycache__/claculate_output_data.cpython-311.pyc new file mode 100644 index 0000000..f463f61 Binary files /dev/null and b/__pycache__/claculate_output_data.cpython-311.pyc differ diff --git a/__pycache__/generate_training_data_with_segmentation.cpython-311.pyc b/__pycache__/generate_training_data_with_segmentation.cpython-311.pyc new file mode 100644 index 0000000..8e8743a Binary files /dev/null and b/__pycache__/generate_training_data_with_segmentation.cpython-311.pyc differ diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000..1d1dbac Binary files /dev/null and b/__pycache__/main.cpython-311.pyc differ diff --git a/__pycache__/test_binary_cross_entropy.cpython-311.pyc b/__pycache__/test_binary_cross_entropy.cpython-311.pyc new file mode 100644 index 0000000..5eb1754 Binary files /dev/null and b/__pycache__/test_binary_cross_entropy.cpython-311.pyc differ diff --git a/__pycache__/test_bounding_box.cpython-311.pyc b/__pycache__/test_bounding_box.cpython-311.pyc new file mode 100644 index 0000000..c56c146 Binary files /dev/null and b/__pycache__/test_bounding_box.cpython-311.pyc differ diff --git a/__pycache__/test_main.cpython-311.pyc b/__pycache__/test_main.cpython-311.pyc new file mode 100644 index 0000000..06c08d8 Binary files /dev/null and b/__pycache__/test_main.cpython-311.pyc differ diff --git a/__pycache__/test_mask_loading.cpython-311.pyc b/__pycache__/test_mask_loading.cpython-311.pyc new file mode 100644 index 0000000..39f1e2b Binary files /dev/null and b/__pycache__/test_mask_loading.cpython-311.pyc differ diff --git a/__pycache__/test_model_branch.cpython-311.pyc b/__pycache__/test_model_branch.cpython-311.pyc new file mode 100644 index 0000000..c2c1594 Binary files /dev/null and b/__pycache__/test_model_branch.cpython-311.pyc differ diff --git a/__pycache__/test_processing_main.cpython-311.pyc b/__pycache__/test_processing_main.cpython-311.pyc new file mode 100644 index 0000000..e17904a Binary files /dev/null and b/__pycache__/test_processing_main.cpython-311.pyc differ diff --git a/__pycache__/test_segmentation.cpython-311.pyc b/__pycache__/test_segmentation.cpython-311.pyc new file mode 100644 index 0000000..c57440f Binary files /dev/null and b/__pycache__/test_segmentation.cpython-311.pyc differ diff --git a/__pycache__/testing_Labels_Accuracy.cpython-311.pyc b/__pycache__/testing_Labels_Accuracy.cpython-311.pyc new file mode 100644 index 0000000..f7fa948 Binary files /dev/null and b/__pycache__/testing_Labels_Accuracy.cpython-311.pyc differ diff --git a/_validation/__pycache__/ValidationTheEnterData.cpython-311.pyc b/_validation/__pycache__/ValidationTheEnterData.cpython-311.pyc index f3a8bd9..5e9ba22 100644 Binary files a/_validation/__pycache__/ValidationTheEnterData.cpython-311.pyc and b/_validation/__pycache__/ValidationTheEnterData.cpython-311.pyc differ diff --git a/_validation/__pycache__/ValidationTheEnterData.cpython-312.pyc b/_validation/__pycache__/ValidationTheEnterData.cpython-312.pyc new file mode 100644 index 0000000..69ee064 Binary files /dev/null and b/_validation/__pycache__/ValidationTheEnterData.cpython-312.pyc differ diff --git a/a00.xml b/a00.xml new file mode 100644 index 0000000..c24b249 --- /dev/null +++ b/a00.xml @@ -0,0 +1,26 @@ + + Processing_Image + a00.jpg + D:\Programing\stomach_cancer\Processing_Image\a00.jpg + + Unknown + + + 1074 + 1074 + 3 + + 0 + + Have_Question + Unspecified + 0 + 0 + + 263 + 740 + 333 + 814 + + + diff --git a/all_models_tools/__pycache__/__init__.cpython-311.pyc b/all_models_tools/__pycache__/__init__.cpython-311.pyc index e397ef7..8221143 100644 Binary files a/all_models_tools/__pycache__/__init__.cpython-311.pyc and b/all_models_tools/__pycache__/__init__.cpython-311.pyc differ diff --git a/all_models_tools/__pycache__/__init__.cpython-312.pyc b/all_models_tools/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..2748b92 Binary files /dev/null and b/all_models_tools/__pycache__/__init__.cpython-312.pyc differ diff --git a/all_models_tools/__pycache__/all_model_tools.cpython-311.pyc b/all_models_tools/__pycache__/all_model_tools.cpython-311.pyc index f1d5835..6b6689e 100644 Binary files a/all_models_tools/__pycache__/all_model_tools.cpython-311.pyc and b/all_models_tools/__pycache__/all_model_tools.cpython-311.pyc differ diff --git a/all_models_tools/__pycache__/all_model_tools.cpython-312.pyc b/all_models_tools/__pycache__/all_model_tools.cpython-312.pyc new file mode 100644 index 0000000..703017a Binary files /dev/null and b/all_models_tools/__pycache__/all_model_tools.cpython-312.pyc differ diff --git a/all_models_tools/all_model_tools.py b/all_models_tools/all_model_tools.py index 7d5b8b3..5290b2f 100644 --- a/all_models_tools/all_model_tools.py +++ b/all_models_tools/all_model_tools.py @@ -2,19 +2,6 @@ from Load_process.file_processing import Process_File import datetime import torch -# def attention_block(input): -# channel = input.shape[-1] - -# GAP = GlobalAveragePooling2D()(input) - -# block = Dense(units = channel // 16, activation = "relu")(GAP) -# block = Dense(units = channel, activation = "sigmoid")(block) -# block = Reshape((1, 1, channel))(block) - -# block = Multiply()([input, block]) - -# return block - class EarlyStopping: def __init__(self, patience=74, verbose=False, delta=0): self.patience = patience @@ -45,12 +32,11 @@ class EarlyStopping: print(f"Validation loss decreased ({self.best_loss:.6f} --> {val_loss:.6f}). Saving model to {save_path}") -def call_back(model_name, index, optimizer): +def call_back(Save_Root, index, optimizer): File = Process_File() - model_dir = '../Result/save_the_best_model/' + model_name - File.JudgeRoot_MakeDir(model_dir) - modelfiles = File.Make_Save_Root('best_model( ' + str(datetime.date.today()) + " )-" + index + ".pt", model_dir) + File.JudgeRoot_MakeDir(Save_Root) + modelfiles = File.Make_Save_Root('best_model( ' + str(datetime.date.today()) + " )-" + index + ".pt", Save_Root) # model_mckp = ModelCheckpoint(modelfiles, monitor='val_loss', save_best_only=True, save_weights_only = True, mode='auto') @@ -60,7 +46,6 @@ def call_back(model_name, index, optimizer): optimizer, factor = 0.94, # 學習率降低的量。 new_lr = lr * factor patience = 2, # 沒有改進的時期數,之後學習率將降低 - verbose = 0, min_lr = 0 # 學習率下限 ) diff --git a/all_models_tools/pre_train_model_construction.py b/all_models_tools/pre_train_model_construction.py deleted file mode 100644 index 8b72d80..0000000 --- a/all_models_tools/pre_train_model_construction.py +++ /dev/null @@ -1,116 +0,0 @@ -from all_models_tools.all_model_tools import attention_block -from keras.activations import softmax, sigmoid -from keras.applications import VGG16,VGG19, ResNet50, ResNet50V2, ResNet101, ResNet101V2, ResNet152, ResNet152V2, InceptionV3, InceptionResNetV2, MobileNet, MobileNetV2, DenseNet121, NASNetLarge, Xception -from keras.layers import GlobalAveragePooling2D, Dense, Flatten -from keras import regularizers -from keras.layers import Add -from application.Xception_indepentment import Xception_indepentment - -def Original_VGG19_Model(): - vgg19 = VGG19(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(vgg19.output) - dense = Dense(units = 4096, activation = "relu")(GAP) - dense = Dense(units = 4096, activation = "relu")(dense) - output = Dense(units = 2, activation = "softmax")(dense) - - return vgg19.input, output - -def Original_ResNet50_model(): - xception = ResNet50(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(xception.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return xception.input, dense - -def Original_NASNetLarge_model(): - nasnetlarge = NASNetLarge(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(nasnetlarge.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return nasnetlarge.input, dense - -def Original_DenseNet121_model(): - Densenet201 = DenseNet121(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(Densenet201.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return Densenet201.input, dense - -def Original_Xception_model(): - xception = Xception(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(xception.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return xception.input, dense - -def Original_VGG16_Model(): - vgg16 = VGG16(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - flatten = Flatten()(vgg16.output) - dense = Dense(units = 4096, activation = "relu")(flatten) - dense = Dense(units = 4096, activation = "relu")(dense) - output = Dense(units = 2, activation = "softmax")(dense) - - return vgg16.input, output - -def Original_ResNet50v2_model(): - resnet50v2 = ResNet50V2(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(resnet50v2.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return resnet50v2.input, dense - -def Original_ResNet101_model(): - resnet101 = ResNet101(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(resnet101.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return resnet101.input, dense - -def Original_ResNet101V2_model(): - resnet101v2 = ResNet101V2(include_top = False, weights = "imagenet", input_shape = (512, 512, 3)) - GAP = GlobalAveragePooling2D()(resnet101v2.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return resnet101v2.input, dense - -def Original_ResNet152_model(): - resnet152 = ResNet152(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(resnet152.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return resnet152.input, dense - -def Original_ResNet152V2_model(): - resnet152v2 = ResNet152V2(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(resnet152v2.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return resnet152v2.input, dense - -def Original_InceptionV3_model(): - inceptionv3 = InceptionV3(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(inceptionv3.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return inceptionv3.input, dense - -def Original_InceptionResNetV2_model(): - inceptionResnetv2 = InceptionResNetV2(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(inceptionResnetv2.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return inceptionResnetv2.input, dense - -def Original_MobileNet_model(): - mobilenet = MobileNet(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(mobilenet.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return mobilenet.input, dense - -def Original_MobileNetV2_model(): - mobilenetv2 = MobileNetV2(include_top = False, weights = "imagenet", input_shape = (200, 200, 3)) - GAP = GlobalAveragePooling2D()(mobilenetv2.output) - dense = Dense(units = 2, activation = "softmax")(GAP) - - return mobilenetv2.input, dense \ No newline at end of file diff --git a/annotation_files.txt b/annotation_files.txt new file mode 100644 index 0000000..f929d5e Binary files /dev/null and b/annotation_files.txt differ diff --git a/annotation_question_files.txt b/annotation_question_files.txt new file mode 100644 index 0000000..c26511b Binary files /dev/null and b/annotation_question_files.txt differ diff --git a/draw_tools/Grad_cam.py b/draw_tools/Grad_cam.py index ccdf7eb..2b1d5cc 100644 --- a/draw_tools/Grad_cam.py +++ b/draw_tools/Grad_cam.py @@ -23,7 +23,7 @@ class GradCAM: def Processing_Main(self, Test_Dataloader, File_Path): File = Process_File() - for batch_idx, (images, labels) in enumerate(Test_Dataloader): + for batch_idx, (images, labels, File_Name, File_Classes) in enumerate(Test_Dataloader): # Move data to device images = images.to(self.device, dtype=torch.float32) # [64, C, H, W] labels = labels.to(self.device, dtype=torch.float32) # [64, num_classes] @@ -36,14 +36,18 @@ class GradCAM: # Process each image in the batch for i in range(images.size(0)): # Loop over batch size (64) - class_idx = label_classes[i] heatmap = heatmaps[i] # Extract heatmap for this image - overlaid_image = self.overlay_heatmap(heatmap, images[i]) + overlaid_image = self.overlay_heatmap(heatmap, images[i], alpha=0.5) # Create file path based on class - path = f"{File_Path}/class_{class_idx}" + path = f"{File_Path}/{File_Classes[i]}" File.JudgeRoot_MakeDir(path) - File.Save_CV2_File(f"batch_{batch_idx}_img_{i}.png", path, overlaid_image) + # Save overlaid image + File.Save_CV2_File(f"batch_{batch_idx}_{File_Name[i]}", path, overlaid_image) + # # Save raw heatmap separately + # heatmap_resized = cv2.resize(heatmap, (images[i].shape[2], images[i].shape[1]), interpolation=cv2.INTER_CUBIC) + # heatmap_colored = (plt.cm.viridis(heatmap_resized)[:, :, :3] * 255).astype(np.uint8) + # File.Save_CV2_File(f"batch_{batch_idx}_img_{i}_heatmap.png", path, heatmap_colored) def save_activations(self, module, input, output): self.activations = output.detach() # [64, C, H', W'] @@ -86,19 +90,29 @@ class GradCAM: grad_cam = torch.sum(weights * activations, dim=1)[0] # [64, H', W'] -> [H', W'] grad_cam = F.relu(grad_cam) # Apply ReLU grad_cam = grad_cam / (grad_cam.max() + 1e-8) # Normalize to [0, 1] - return grad_cam.cpu().numpy() + + # Apply Gaussian smoothing to reduce artifacts + grad_cam_np = grad_cam.cpu().numpy() + grad_cam_np = cv2.GaussianBlur(grad_cam_np, (5, 5), 0) + # Re-normalize after blur + grad_cam_np = grad_cam_np / (grad_cam_np.max() + 1e-8) + + return grad_cam_np def overlay_heatmap(self, heatmap, image, alpha=0.5): - # Resize heatmap to match input image spatial dimensions + # Resize heatmap to match input image spatial dimensions using INTER_CUBIC for smoother results heatmap = np.uint8(255 * heatmap) # Scale to [0, 255] - heatmap = Image.fromarray(heatmap).resize((image.shape[1], image.shape[2]), Image.BILINEAR) - heatmap = np.array(heatmap) - heatmap = plt.cm.jet(heatmap)[:, :, :3] # Apply colormap (jet) + heatmap = cv2.resize(heatmap, (image.shape[2], image.shape[1]), interpolation=cv2.INTER_CUBIC) + # Use viridis colormap for better interpretability + heatmap = plt.cm.viridis(heatmap)[:, :, :3] # Apply viridis colormap # Convert image tensor to numpy and denormalize (assuming ImageNet stats) image_np = image.detach().cpu().permute(1, 2, 0).numpy() # [H, W, C] + # Ensure image is in [0, 1] range (if not already) + if image_np.max() > 1.0: + image_np = (image_np - image_np.min()) / (image_np.max() - image_np.min()) - # Overlay - overlay = alpha * heatmap + (1 - alpha) * image_np / 255.0 + # Overlay heatmap on the image + overlay = alpha * heatmap + (1 - alpha) * image_np overlay = np.clip(overlay, 0, 1) * 255 return overlay.astype(np.uint8) # Return uint8 for cv2 \ No newline at end of file diff --git a/draw_tools/Saliency_Map.py b/draw_tools/Saliency_Map.py new file mode 100644 index 0000000..c8a1e60 --- /dev/null +++ b/draw_tools/Saliency_Map.py @@ -0,0 +1,195 @@ +import torch +import torch.nn as nn +import numpy as np +import cv2 +import matplotlib.pyplot as plt +from Load_process.file_processing import Process_File + +class SaliencyMap: + def __init__(self, model): + self.model = model + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + self.model.to(self.device) + self.model.eval() # 設置為評估模式 + + def Processing_Main(self, Test_Dataloader, File_Path): + """處理測試數據集並生成顯著性圖""" + File = Process_File() + + for batch_idx, (images, labels, File_Name, File_Classes) in enumerate(Test_Dataloader): + # 將數據移至設備 + images = images.to(self.device, dtype=torch.float32) + labels = labels.to(self.device, dtype=torch.float32) + + # 獲取真實類別索引 + label_classes = torch.argmax(labels, dim=1).cpu().numpy() + + # 為批次中的每個圖像生成顯著性圖 + for i in range(images.size(0)): + # 獲取單個圖像和類別 + image = images[i:i+1] # 保持批次維度 + target_class = label_classes[i] + + # 生成顯著性圖 + saliency_map = self.generate_saliency(image, target_class) + + # 將顯著性圖疊加到原始圖像上 + overlaid_image = self.overlay_saliency(saliency_map, image[0]) + + # 創建保存路徑 + path = f"{File_Path}/{File_Classes[i]}" + File.JudgeRoot_MakeDir(path) + + # 保存結果 + File.Save_CV2_File(f"saliency_{batch_idx}_{File_Name[i]}", path, overlaid_image) + + def generate_saliency(self, image, target_class): + """生成單個圖像的顯著性圖""" + # 確保需要梯度 + image.requires_grad_(True) + + # 前向傳播 + output = self.model(image) + + # 清除之前的梯度 + self.model.zero_grad() + + # 創建one-hot編碼的目標 + one_hot = torch.zeros_like(output) + one_hot[0, target_class] = 1 + + # 反向傳播 + output.backward(gradient=one_hot) + + # 獲取梯度 + gradients = image.grad.data + + # 計算顯著性圖 (取絕對值並在通道維度上取最大值) + saliency, _ = torch.max(gradients.abs(), dim=1) + + # 轉換為numpy並歸一化 + saliency_np = saliency.cpu().numpy()[0] + saliency_np = self._normalize(saliency_np) + + # 應用平滑處理以減少噪聲 + saliency_np = cv2.GaussianBlur(saliency_np, (5, 5), 0) + saliency_np = self._normalize(saliency_np) # 再次歸一化 + + return saliency_np + + def _normalize(self, x): + """將數組歸一化到[0,1]範圍""" + # 添加小的epsilon以避免除以零 + return (x - x.min()) / (x.max() - x.min() + 1e-8) + + def overlay_saliency(self, saliency, image, alpha=0.5): + """將顯著性圖疊加到原始圖像上""" + # 將顯著性圖縮放到[0,255]範圍 + saliency_uint8 = np.uint8(255 * saliency) + + # 應用顏色映射 + heatmap = cv2.applyColorMap(saliency_uint8, cv2.COLORMAP_JET) + + # 將圖像張量轉換為numpy數組 + image_np = image.detach().cpu().permute(1, 2, 0).numpy() + + # 確保圖像在[0,1]範圍內 + if image_np.max() > 1.0: + image_np = (image_np - image_np.min()) / (image_np.max() - image_np.min()) + + # 將圖像轉換為uint8 + image_uint8 = np.uint8(255 * image_np) + + # 如果圖像是單通道的,轉換為3通道 + if len(image_uint8.shape) == 2 or image_uint8.shape[2] == 1: + image_uint8 = cv2.cvtColor(image_uint8, cv2.COLOR_GRAY2BGR) + + # 疊加顯著性圖和原始圖像 + overlaid = cv2.addWeighted(heatmap, alpha, image_uint8, 1-alpha, 0) + + return overlaid + + def generate_smooth_saliency(self, image, target_class, n_samples=20, noise_level=0.1): + """使用SmoothGrad技術生成更平滑的顯著性圖""" + # 獲取輸入圖像的標準差 + stdev = noise_level * (torch.max(image) - torch.min(image)).item() + + # 累積梯度 + accumulated_gradients = None + + # 生成多個帶噪聲的樣本並計算梯度 + for _ in range(n_samples): + # 添加高斯噪聲 + noisy_image = image + torch.randn_like(image) * stdev + noisy_image.requires_grad_(True) + + # 前向傳播 + output = self.model(noisy_image) + + # 反向傳播 + self.model.zero_grad() + one_hot = torch.zeros_like(output) + one_hot[0, target_class] = 1 + output.backward(gradient=one_hot) + + # 獲取梯度 + gradients = noisy_image.grad.data + + # 累積梯度 + if accumulated_gradients is None: + accumulated_gradients = gradients + else: + accumulated_gradients += gradients + + # 計算平均梯度 + avg_gradients = accumulated_gradients / n_samples + + # 計算顯著性圖 + saliency, _ = torch.max(avg_gradients.abs(), dim=1) + + # 轉換為numpy並歸一化 + saliency_np = saliency.cpu().numpy()[0] + saliency_np = self._normalize(saliency_np) + + return saliency_np + + def generate_guided_saliency(self, image, target_class): + """使用Guided Backpropagation生成顯著性圖""" + # 保存原始ReLU反向傳播函數 + relu_backward_functions = {} + for module in self.model.modules(): + if isinstance(module, nn.ReLU): + relu_backward_functions[module] = module.backward + module.backward = self._guided_relu_backward + + # 生成顯著性圖 + image.requires_grad_(True) + output = self.model(image) + + self.model.zero_grad() + one_hot = torch.zeros_like(output) + one_hot[0, target_class] = 1 + output.backward(gradient=one_hot) + + # 獲取梯度 + gradients = image.grad.data + + # 恢復原始ReLU反向傳播函數 + for module in relu_backward_functions: + module.backward = relu_backward_functions[module] + + # 計算顯著性圖 (只保留正梯度) + saliency = torch.clamp(gradients, min=0) + saliency, _ = torch.max(saliency, dim=1) + + # 轉換為numpy並歸一化 + saliency_np = saliency.cpu().numpy()[0] + saliency_np = self._normalize(saliency_np) + + return saliency_np + + def _guided_relu_backward(self, grad_output): + """Guided ReLU的反向傳播函數""" + # 只允許正梯度流過 + positive_grad_output = torch.clamp(grad_output, min=0) + return positive_grad_output \ No newline at end of file diff --git a/draw_tools/__pycache__/Grad_cam.cpython-311.pyc b/draw_tools/__pycache__/Grad_cam.cpython-311.pyc index 88a53ae..ef5b8ce 100644 Binary files a/draw_tools/__pycache__/Grad_cam.cpython-311.pyc and b/draw_tools/__pycache__/Grad_cam.cpython-311.pyc differ diff --git a/draw_tools/__pycache__/Grad_cam.cpython-312.pyc b/draw_tools/__pycache__/Grad_cam.cpython-312.pyc new file mode 100644 index 0000000..aacbd93 Binary files /dev/null and b/draw_tools/__pycache__/Grad_cam.cpython-312.pyc differ diff --git a/draw_tools/__pycache__/Saliency_Map.cpython-311.pyc b/draw_tools/__pycache__/Saliency_Map.cpython-311.pyc new file mode 100644 index 0000000..d53aee3 Binary files /dev/null and b/draw_tools/__pycache__/Saliency_Map.cpython-311.pyc differ diff --git a/draw_tools/__pycache__/__init__.cpython-311.pyc b/draw_tools/__pycache__/__init__.cpython-311.pyc index 2095704..18f9ce6 100644 Binary files a/draw_tools/__pycache__/__init__.cpython-311.pyc and b/draw_tools/__pycache__/__init__.cpython-311.pyc differ diff --git a/draw_tools/__pycache__/__init__.cpython-312.pyc b/draw_tools/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..92c5c5b Binary files /dev/null and b/draw_tools/__pycache__/__init__.cpython-312.pyc differ diff --git a/draw_tools/__pycache__/draw.cpython-311.pyc b/draw_tools/__pycache__/draw.cpython-311.pyc index c84a100..2369b00 100644 Binary files a/draw_tools/__pycache__/draw.cpython-311.pyc and b/draw_tools/__pycache__/draw.cpython-311.pyc differ diff --git a/draw_tools/__pycache__/draw.cpython-312.pyc b/draw_tools/__pycache__/draw.cpython-312.pyc new file mode 100644 index 0000000..abbd447 Binary files /dev/null and b/draw_tools/__pycache__/draw.cpython-312.pyc differ diff --git a/draw_tools/draw.py b/draw_tools/draw.py index 9c0ef61..06853e3 100644 --- a/draw_tools/draw.py +++ b/draw_tools/draw.py @@ -5,33 +5,51 @@ import matplotlib.figure as figure import matplotlib.backends.backend_agg as agg from Load_process.file_processing import Process_File -def plot_history(Epochs, Losses, Accuracys, file_name, model_name): +def plot_history(Losses, Accuracys, Save_Root, File_Name): File = Process_File() plt.figure(figsize=(16,4)) plt.subplot(1,2,1) - plt.plot(range(1, Epochs + 1), Losses[0]) - plt.plot(range(1, Epochs + 1), Losses[1]) + + # 修正維度不匹配問題 + train_losses = Losses[0] + val_losses = Losses[1] + + # 分別繪製訓練損失和驗證損失 + train_epochs = range(1, len(train_losses) + 1) + plt.plot(train_epochs, train_losses, label='Train') + + val_epochs = range(1, len(val_losses) + 1) + plt.plot(val_epochs, val_losses, label='Validation') + plt.ylabel('Losses') plt.xlabel('epoch') - plt.legend(['Train','Validation'], loc='upper left') + plt.legend(loc='upper left') plt.title('Model Loss') - plt.subplot(1,2,2) - plt.plot(range(1, Epochs + 1), Accuracys[0]) - plt.plot(range(1, Epochs + 1), Accuracys[1]) - plt.ylabel('Accuracies') - plt.xlabel('epoch') - plt.legend(['Train','Validation'], loc='upper left') - plt.title('Model Accuracy') + if Accuracys is not None: + plt.subplot(1,2,2) + train_acc = Accuracys[0] + val_acc = Accuracys[1] + + # 分別繪製訓練準確率和驗證準確率 + train_epochs_acc = range(1, len(train_acc) + 1) + plt.plot(train_epochs_acc, train_acc, label='Train') + + val_epochs_acc = range(1, len(val_acc) + 1) + plt.plot(val_epochs_acc, val_acc, label='Validation') + + plt.ylabel('Accuracies') + plt.xlabel('epoch') + plt.legend(loc='upper left') + plt.title('Model Accuracy') - model_dir = '../Result/Training_Image/save_the_train_image( ' + str(datetime.date.today()) + " )" - File.JudgeRoot_MakeDir(model_dir) - modelfiles = File.Make_Save_Root(str(model_name) + " " + str(file_name) + ".png", model_dir) + File.JudgeRoot_MakeDir(Save_Root) + modelfiles = File.Make_Save_Root(f"{str(File_Name)}.png", Save_Root) plt.savefig(modelfiles) plt.close("all") # 關閉圖表 -def draw_heatmap(matrix, model_name, index): # 二分類以上混淆矩陣做法 +def draw_heatmap(matrix, Save_Root, File_Name, index): # 二分類以上混淆矩陣做法 File = Process_File() # 创建热图 @@ -40,20 +58,19 @@ def draw_heatmap(matrix, model_name, index): # 二分類以上混淆矩陣做法 Ax = fig.add_subplot(111) sns.heatmap(matrix, square = True, annot = True, fmt = 'd', linecolor = 'white', cmap = "Purples", ax = Ax)#画热力图,cmap表示设定的颜色集 - model_dir = '../Result/Matrix_Image/model_matrix_image ( ' + str(datetime.date.today()) + " )" - File.JudgeRoot_MakeDir(model_dir) - modelfiles = File.Make_Save_Root(str(model_name) + "-" + str(index) + ".png", model_dir) + File.JudgeRoot_MakeDir(Save_Root) + modelfiles = File.Make_Save_Root(f"{File_Name}-{str(index)}.png", Save_Root) # confusion.figure.savefig(modelfiles) # 设置图像参数 - Ax.set_title(str(model_name) + " confusion matrix") + Ax.set_title(f"{File_Name} confusion matrix") Ax.set_xlabel("X-Predict label of the model") Ax.set_ylabel("Y-True label of the model") # 保存图像到文件中 canvas.print_figure(modelfiles) -def Confusion_Matrix_of_Two_Classification(Model_Name, Matrix, index): +def Confusion_Matrix_of_Two_Classification(Matrix, Save_Root, File_Name, index): File = Process_File() fx = sns.heatmap(Matrix, annot=True, cmap='turbo') @@ -63,13 +80,20 @@ def Confusion_Matrix_of_Two_Classification(Model_Name, Matrix, index): fx.set_xlabel('answer Values ') fx.set_ylabel('Predicted Values') - # labels the boxes - fx.xaxis.set_ticklabels(['False','True']) - fx.yaxis.set_ticklabels(['False','True']) + # 根据矩阵维度动态设置标签 + n_classes = Matrix.shape[0] + # 如果是2类问题,使用False/True标签 + if n_classes == 2: + labels = ['False', 'True'] + else: + # 对于多类问题,使用数字标签 + labels = [str(i) for i in range(n_classes)] + + fx.xaxis.set_ticklabels(labels) + fx.yaxis.set_ticklabels(labels) - model_dir = '../Result/model_matrix_image ( ' + str(datetime.date.today()) + " )" - File.JudgeRoot_MakeDir(model_dir) - modelfiles = File.Make_Save_Root(str(Model_Name) + "-" + str(index) + ".png", model_dir) + File.JudgeRoot_MakeDir(Save_Root) + modelfiles = File.Make_Save_Root(f"{File_Name}-{str(index)}.png", Save_Root) plt.savefig(modelfiles) plt.close("all") # 關閉圖表 diff --git a/experiments/Model_All_Step.py b/experiments/Model_All_Step.py deleted file mode 100644 index 2e217f0..0000000 --- a/experiments/Model_All_Step.py +++ /dev/null @@ -1,210 +0,0 @@ -from tqdm import tqdm -from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score -from torchmetrics.functional import auroc -from torch.nn import functional - -from all_models_tools.all_model_tools import call_back -from Model_Loss.Loss import Entropy_Loss -from merge_class.merge import merge -from draw_tools.Grad_cam import GradCAM - -import time -import torch.optim as optim -import numpy as np -import torch -import pandas as pd -import datetime - - -class All_Step: - def __init__(self, Model, Epoch, Number_Of_Classes, Model_Name, Experiment_Name): - self.Model = Model - self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - - self.Epoch = Epoch - self.Number_Of_Classes = Number_Of_Classes - - self.Model_Name = Model_Name - self.Experiment_Name = Experiment_Name - - def Training_Step(self, train_subset, val_subset, train_loader, val_loader, model_name, fold, TargetLayer): - # Reinitialize model and optimizer for each fold - # self.Model = self.Model.__class__(self.Number_Of_Classes).to(self.device) # Reinitialize model - Optimizer = optim.SGD(self.Model.parameters(), lr=0.045, momentum=0.9, weight_decay=0.01) - model_path, early_stopping, scheduler = call_back(model_name, f"_fold{fold}", Optimizer) - - criterion = Entropy_Loss() # Custom loss function - Merge_Function = merge() - - # Lists to store metrics for this fold - train_losses = [] - val_losses = [] - train_accuracies = [] - val_accuracies = [] - epoch = 0 - - # Epoch loop - for epoch in range(self.Epoch): - self.Model.train() # Start training - running_loss = 0.0 - all_train_preds = [] - all_train_labels = [] - processed_samples = 0 - - # Calculate epoch start time - start_time = time.time() - total_samples = len(train_subset) # Total samples in subset, not DataLoader - total_Validation_samples = len(val_subset) - - # Progress bar for training batches - epoch_iterator = tqdm(train_loader, desc=f"Fold {fold + 1}/5, Epoch [{epoch + 1}/{self.Epoch}]") - - for inputs, labels in epoch_iterator: - inputs, labels = inputs.to(self.device), labels.to(self.device) # Already tensors from DataLoader - - Optimizer.zero_grad() - outputs = self.Model(inputs) - loss = criterion(outputs, labels) - loss.backward() - Optimizer.step() - running_loss += loss.item() - - # Collect training predictions and labels - Output_Values, Output_Indexs = torch.max(outputs, dim=1) - True_Indexs = np.argmax(labels.cpu().numpy(), axis=1) - - all_train_preds.append(Output_Indexs.cpu().numpy()) - all_train_labels.append(True_Indexs) - - processed_samples += inputs.size(0) # Use size(0) for batch size - - # Calculate progress and timing - progress = (processed_samples / total_samples) * 100 - elapsed_time = time.time() - start_time - iterations_per_second = processed_samples / elapsed_time if elapsed_time > 0 else 0 - eta = (total_samples - processed_samples) / iterations_per_second if iterations_per_second > 0 else 0 - time_str = f"{int(elapsed_time//60):02d}:{int(elapsed_time%60):02d}<{int(eta//60):02d}:{int(eta%60):02d}" - - # Calculate batch accuracy(正確label數量 / 該batch總共的label數量) - batch_accuracy = (Output_Indexs.cpu().numpy() == True_Indexs).mean() - - # Update progress bar - epoch_iterator.set_postfix_str( - f"{processed_samples}/{total_samples} [{time_str}, {iterations_per_second:.2f}it/s, " - f"acc={batch_accuracy:.3f}, loss={loss.item():.3f}]" - ) - - epoch_iterator.close() - # Merge predictions and labels - all_train_preds = Merge_Function.merge_data_main(all_train_preds, 0, len(all_train_preds)) - all_train_labels = Merge_Function.merge_data_main(all_train_labels, 0, len(all_train_labels)) - - Training_Loss = running_loss / len(train_loader) - train_accuracy = accuracy_score(all_train_labels, all_train_preds) - - train_losses.append(Training_Loss) - train_accuracies.append(train_accuracy) - - # Validation step - self.Model.eval() - val_loss = 0.0 - all_val_preds = [] - all_val_labels = [] - - start_Validation_time = time.time() - epoch_iterator = tqdm(val_loader, desc=f"\tValidation-Fold {fold + 1}/5, Epoch [{epoch + 1}/{self.Epoch}]") - with torch.no_grad(): - for inputs, labels in epoch_iterator: - inputs, labels = inputs.to(self.device), labels.to(self.device) - outputs = self.Model(inputs) - loss = criterion(outputs, labels) - val_loss += loss.item() - - # Collect validation predictions and labels - Output_Values, Output_Indexs = torch.max(outputs, dim=1) - True_Indexs = np.argmax(labels.cpu().numpy(), axis=1) - - all_val_preds.append(Output_Indexs.cpu().numpy()) - all_val_labels.append(True_Indexs) - - processed_samples += inputs.size(0) # Use size(0) for batch size - - # Calculate progress and timing - progress = (processed_samples / total_Validation_samples) * 100 - elapsed_time = time.time() - start_Validation_time - iterations_per_second = processed_samples / elapsed_time if elapsed_time > 0 else 0 - eta = (total_Validation_samples - processed_samples) / iterations_per_second if iterations_per_second > 0 else 0 - time_str = f"{int(elapsed_time//60):02d}:{int(elapsed_time%60):02d}<{int(eta//60):02d}:{int(eta%60):02d}" - - # Calculate batch accuracy - batch_accuracy = (Output_Indexs.cpu().numpy() == True_Indexs).mean() - - # Update progress bar - epoch_iterator.set_postfix_str( - f"{processed_samples}/{total_Validation_samples} [{time_str}, {iterations_per_second:.2f}it/s, " - f"acc={batch_accuracy:.3f}, loss={loss.item():.3f}]" - ) - - epoch_iterator.close() - print("\n") - - # Merge predictions and labels - all_val_preds = Merge_Function.merge_data_main(all_val_preds, 0, len(all_val_preds)) - all_val_labels = Merge_Function.merge_data_main(all_val_labels, 0, len(all_val_labels)) - - val_loss /= len(val_loader) - val_accuracy = accuracy_score(all_val_labels, all_val_preds) - - val_losses.append(val_loss) - val_accuracies.append(val_accuracy) - - print(f"Traini Loss: {Training_Loss:.4f}, Accuracy: {train_accuracy:0.2f}, Validation Loss: {val_loss:.4f}, Accuracy: {val_accuracy:0.2f}\n") - - if epoch % 10 == 0: - Grad = GradCAM(self.Model, TargetLayer) - Grad.Processing_Main(val_loader, f"../Result/GradCAM_Image/Validation/GradCAM_Image({str(datetime.date.today())})/fold-{str(fold)}/") - - # Early stopping - early_stopping(val_loss, self.Model, model_path) - if early_stopping.early_stop: - print(f"Early stopping triggered in Fold {fold + 1} at epoch {epoch + 1}") - break - - # Learning rate adjustment - scheduler.step(val_loss) - - Total_Epoch = epoch + 1 - return self.Model, model_path, train_losses, val_losses, train_accuracies, val_accuracies, Total_Epoch - - def Evaluate_Model(self, cnn_model, Test_Dataloader): - # (Unchanged Evaluate_Model method) - cnn_model.eval() - True_Label, Predict_Label = [], [] - True_Label_OneHot, Predict_Label_OneHot = [], [] - loss = 0.0 - - with torch.no_grad(): - for images, labels in Test_Dataloader: - images, labels = torch.as_tensor(images).to(self.device), torch.as_tensor(labels).to(self.device) - outputs = cnn_model(images) - Output_Values, Output_Indexs = torch.max(outputs, 1) - True_Indexs = np.argmax(labels.cpu().numpy(), 1) - - True_Label.append(Output_Indexs.cpu().numpy()) - Predict_Label.append(True_Indexs) - - Predict_Label_OneHot.append(torch.tensor(functional.one_hot(Output_Indexs, self.Number_Of_Classes), dtype=torch.float32).cpu().numpy()[0]) - True_Label_OneHot.append(torch.tensor(labels, dtype=torch.int).cpu().numpy()[0]) - - loss /= len(Test_Dataloader) - - True_Label_OneHot = torch.as_tensor(True_Label_OneHot, dtype=torch.int) - Predict_Label_OneHot = torch.as_tensor(Predict_Label_OneHot, dtype=torch.float32) - - accuracy = accuracy_score(True_Label, Predict_Label) - precision = precision_score(True_Label, Predict_Label, average="macro") - recall = recall_score(True_Label, Predict_Label, average="macro") - AUC = auroc(Predict_Label_OneHot, True_Label_OneHot, num_labels=self.Number_Of_Classes, task="multilabel", average="macro") - f1 = f1_score(True_Label, Predict_Label, average="macro") - - return True_Label, Predict_Label, loss, accuracy, precision, recall, AUC, f1 \ No newline at end of file diff --git a/experiments/Models/GastroSegNet_Model.py b/experiments/Models/GastroSegNet_Model.py new file mode 100644 index 0000000..2160fa9 --- /dev/null +++ b/experiments/Models/GastroSegNet_Model.py @@ -0,0 +1,91 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +class Encode_Block(nn.Module): + """基本的卷積塊:Conv2d + BatchNorm + ReLU""" + def __init__(self, in_channels, out_channels): + super(Encode_Block, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1), + nn.BatchNorm2d(out_channels), + nn.ReLU(inplace=True), + nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1), + nn.BatchNorm2d(out_channels), + nn.ReLU(inplace=True) + ) + + def forward(self, x): + return self.conv(x) + +class GastroSegNet(nn.Module): + """簡單的U-Net實現""" + def __init__(self, in_channels=3, out_channels=3, features=[32, 64, 128, 256]): + super(GastroSegNet, self).__init__() + + # 編碼器(下採樣路徑) + self.encoder = nn.ModuleList() + self.pool = nn.MaxPool2d(kernel_size=2, stride=2) + + # 第一層 + self.encoder.append(Encode_Block(in_channels, features[0])) + + # 其他編碼層 + for i in range(1, len(features)): + self.encoder.append(Encode_Block(features[i-1], features[i])) + + # 瓶頸層(最底層) + self.bottleneck = Encode_Block(features[-1], features[-1] * 2) + + # 解碼器(上採樣路徑) + self.decoder = nn.ModuleList() + self.upconv = nn.ModuleList() + + # 創建上採樣和解碼層 + for i in range(len(features)): + self.upconv.append( + nn.ConvTranspose2d(features[-1-i] * 2, features[-1-i], kernel_size=2, stride=2) + ) + self.decoder.append( + Encode_Block(features[-1-i] * 2, features[-1-i]) + ) + + # 最終輸出層 + self.final_conv = nn.Conv2d(features[0], out_channels, kernel_size=1) + + def forward(self, x): + # 存儲跳躍連接 + skip_connections = [] + + # 編碼器路徑 + for encoder_layer in self.encoder: + x = encoder_layer(x) + skip_connections.append(x) + x = self.pool(x) + + # 瓶頸層 + x = self.bottleneck(x) + + # 反轉跳躍連接列表 + skip_connections = skip_connections[::-1] + + # 解碼器路徑 + for i, (upconv, decoder) in enumerate(zip(self.upconv, self.decoder)): + # 上採樣 + x = upconv(x) + + # 獲取對應的跳躍連接 + skip = skip_connections[i] + + # 如果尺寸不匹配,調整大小 + if x.shape != skip.shape: + x = F.interpolate(x, size=skip.shape[2:], mode='bilinear', align_corners=False) + + # 連接跳躍連接 + x = torch.cat([skip, x], dim=1) + + # 通過解碼塊 + x = decoder(x) + + # 最終輸出 + return self.final_conv(x) \ No newline at end of file diff --git a/experiments/Models/Xception_Model_Modification.py b/experiments/Models/Xception_Model_Modification.py new file mode 100644 index 0000000..f956f88 --- /dev/null +++ b/experiments/Models/Xception_Model_Modification.py @@ -0,0 +1,148 @@ +import torch.nn as nn +import torch.nn.functional as F +import torch + +class SeparableConv2d(nn.Module): + def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=True): + super(SeparableConv2d, self).__init__() + self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size=kernel_size, stride=stride, + padding=padding, groups=in_channels, bias=bias) + self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, + padding=0, bias=bias) + + def forward(self, x): + x = self.depthwise(x) + x = self.pointwise(x) + return x + +class EntryFlow(nn.Module): + def __init__(self, in_channels=3): + super(EntryFlow, self).__init__() + self.conv1 = nn.Conv2d(in_channels, 32, 3, stride=2, padding=1, bias=False, dilation = 2) + self.bn1 = nn.BatchNorm2d(32) + self.conv2 = nn.Conv2d(32, 64, 3, padding=1, bias=False, dilation = 2) + self.bn2 = nn.BatchNorm2d(64) + + self.conv3_residual = nn.Sequential( + SeparableConv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128), + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128), + nn.MaxPool2d(3, stride=2, padding=1) + ) + self.conv3_shortcut = nn.Conv2d(64, 128, 1, stride=2, bias=False) + self.bn3 = nn.BatchNorm2d(128) + + self.conv4_residual = nn.Sequential( + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256), + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256), + nn.MaxPool2d(3, stride=2, padding=1) + ) + self.conv4_shortcut = nn.Conv2d(128, 256, 1, stride=2, bias=False) + self.bn4 = nn.BatchNorm2d(256) + + self.conv5_residual = nn.Sequential( + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(256, 728, 3, padding=1), + nn.BatchNorm2d(728), + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(728, 728, 3, padding=1), + nn.BatchNorm2d(728), + nn.MaxPool2d(3, stride=2, padding=1) + ) + self.conv5_shortcut = nn.Conv2d(256, 728, 1, stride=2, bias=False) + self.bn5 = nn.BatchNorm2d(728) + + def forward(self, x): + x = F.relu(self.bn1(self.conv1(x))) + x = F.relu(self.bn2(self.conv2(x))) + + residual = self.conv3_residual(x) + shortcut = self.conv3_shortcut(x) + x = F.relu(self.bn3(residual + shortcut)) + + residual = self.conv4_residual(x) + shortcut = self.conv4_shortcut(x) + x = F.relu(self.bn4(residual + shortcut)) + + residual = self.conv5_residual(x) + shortcut = self.conv5_shortcut(x) + x = F.relu(self.bn5(residual + shortcut)) + return x + +class MiddleFlow(nn.Module): + def __init__(self): + super(MiddleFlow, self).__init__() + self.conv_residual = nn.Sequential( + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(728, 728, 3, padding=1), + nn.BatchNorm2d(728), + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(728, 728, 3, padding=1), + nn.BatchNorm2d(728), + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(728, 728, 3, padding=1), + nn.BatchNorm2d(728) + ) + + def forward(self, x): + return self.conv_residual(x) + x + +class ExitFlow(nn.Module): + def __init__(self, num_classes=2): + super(ExitFlow, self).__init__() + self.conv1_residual = nn.Sequential( + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(728, 1024, 3, padding=1), + nn.BatchNorm2d(1024), + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(1024, 1024, 3, padding=1), + nn.BatchNorm2d(1024), + nn.MaxPool2d(3, stride=2, padding=1) + ) + self.conv1_shortcut = nn.Conv2d(728, 1024, 1, stride=2, bias=False) + self.bn1 = nn.BatchNorm2d(1024) + + self.conv2 = nn.Sequential( + SeparableConv2d(1024, 1536, 3, padding=1), + nn.BatchNorm2d(1536), + nn.ReLU(inplace=False), # 修改這裡 + SeparableConv2d(1536, 2048, 3, padding=1), + nn.BatchNorm2d(2048), + nn.ReLU(inplace=False) # 修改這裡 + ) + self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) + self.Hidden = nn.Linear(2048, 1025) + self.fc = nn.Linear(1025, num_classes) + + def forward(self, x): + residual = self.conv1_residual(x) + shortcut = self.conv1_shortcut(x) + x = F.relu(self.bn1(residual + shortcut)) + + x = self.conv2(x) + x = self.avgpool(x) + x = x.view(x.size(0), -1) + x = self.Hidden(x) + x = self.fc(x) + return x + +class Xception(nn.Module): + def __init__(self, num_classes=2): + super(Xception, self).__init__() + self.entry_flow = EntryFlow(in_channels=3) # 默认输入通道为3 + self.middle_flow = nn.Sequential(*[MiddleFlow() for _ in range(8)]) + self.exit_flow = ExitFlow(num_classes) + + def forward(self, x): + # 正常的前向傳播 + x = self.entry_flow(x) + x = self.middle_flow(x) + x = self.exit_flow(x) + + return x \ No newline at end of file diff --git a/experiments/Models/__init__.py b/experiments/Models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/experiments/Models/__pycache__/GastroSegNet_Model.cpython-311.pyc b/experiments/Models/__pycache__/GastroSegNet_Model.cpython-311.pyc new file mode 100644 index 0000000..60dce15 Binary files /dev/null and b/experiments/Models/__pycache__/GastroSegNet_Model.cpython-311.pyc differ diff --git a/experiments/Models/__pycache__/Xception_Model_Modification.cpython-311.pyc b/experiments/Models/__pycache__/Xception_Model_Modification.cpython-311.pyc new file mode 100644 index 0000000..186c9c7 Binary files /dev/null and b/experiments/Models/__pycache__/Xception_Model_Modification.cpython-311.pyc differ diff --git a/experiments/Models/__pycache__/__init__.cpython-311.pyc b/experiments/Models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1c5e11f Binary files /dev/null and b/experiments/Models/__pycache__/__init__.cpython-311.pyc differ diff --git a/experiments/Models/__pycache__/pytorch_Model.cpython-311.pyc b/experiments/Models/__pycache__/pytorch_Model.cpython-311.pyc new file mode 100644 index 0000000..f1c8741 Binary files /dev/null and b/experiments/Models/__pycache__/pytorch_Model.cpython-311.pyc differ diff --git a/experiments/Models/pytorch_Model.py b/experiments/Models/pytorch_Model.py new file mode 100644 index 0000000..be79e6d --- /dev/null +++ b/experiments/Models/pytorch_Model.py @@ -0,0 +1,30 @@ +import torch.nn as nn +import timm + +from utils.Stomach_Config import Model_Config + +class ModifiedXception(nn.Module): + def __init__(self, num_classes): + super(ModifiedXception, self).__init__() + + # 加載 Xception 預訓練模型,去掉最後一層 (fc 層) + self.base_model = timm.create_model(Model_Config["Model Name"], pretrained=True) + self.base_model.fc = nn.Identity() # 移除原來的 fully connected 層 + + # 新增全局平均池化層、隱藏層和輸出層 + self.global_avg_pool = nn.AdaptiveAvgPool1d(Model_Config["GPA Output Nodes"]) # 全局平均池化 + self.hidden_layer = nn.Linear(Model_Config["GPA Output Nodes"], Model_Config["Linear Hidden Nodes"]) # 隱藏層,輸入大小取決於 Xception 的輸出大小 + self.output_layer = nn.Linear(Model_Config["Linear Hidden Nodes"], Model_Config["Output Linear Nodes"]) # 輸出層,依據分類數目設定 + + # 激活函數與 dropout + self.relu = nn.ReLU() + self.softmax = nn.Softmax(1) + self.dropout = nn.Dropout(Model_Config["Dropout Rate"]) + + def forward(self, x): + x = self.base_model(x) # Xception 主體 + x = self.global_avg_pool(x) # 全局平均池化 + x = self.relu(self.hidden_layer(x)) # 隱藏層 + ReLU + x = self.dropout(x) # Dropout + x = self.output_layer(x) # 輸出層 + return x \ No newline at end of file diff --git a/experiments/Training/Identification_Block_Training.py b/experiments/Training/Identification_Block_Training.py new file mode 100644 index 0000000..6e467db --- /dev/null +++ b/experiments/Training/Identification_Block_Training.py @@ -0,0 +1,461 @@ +from tqdm import tqdm +from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score +from torchmetrics.functional import auroc +from torch.nn import functional +from torch import nn +import torch +from sklearn.model_selection import KFold +from torchinfo import summary +from sklearn.metrics import confusion_matrix + +from all_models_tools.all_model_tools import call_back +from Model_Loss.Loss import Entropy_Loss +from Model_Loss.binary_cross_entropy import BinaryCrossEntropy +from merge_class.merge import merge +from draw_tools.Saliency_Map import SaliencyMap +from utils.Stomach_Config import Training_Config, Loading_Config, Save_Result_File_Config +from experiments.Models.Xception_Model_Modification import Xception +from Load_process.LoadData import Loding_Data_Root +from Training_Tools.PreProcess import Training_Precesses +from Calculate_Process.Calculate import Calculate +from Load_process.file_processing import Process_File +from draw_tools.draw import plot_history, draw_heatmap +from Load_process.file_processing import Process_File + +import time +import torch.optim as optim +import numpy as np +import torch +import pandas as pd +import datetime +import argparse +import os + +class Identification_Block_Training_Step(Loding_Data_Root, Training_Precesses): + def __init__(self, Experiment_Name, Best_Model_Save_Root): + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + + self.Model = self.Construct_Identification_Model_CUDA() # 模型變數 + self.train_subset = None # Training Dataset 的子集 + self.val_subset = None # Validation Dataset 的子集 + self.train_loader = None # Training DataLoader 的讀檔器 + self.val_loader = None # Validation DataLoader 的讀檔器 + self.Mask = None # 遮罩變數,接收GastroSegNet產出來的Mask + self.Grad = None # 梯度變數,後面用來執行Grad CAM + + self.model_name = Training_Config["Model_Name"] # 取名,使用哪個模型(可能是預處理模型/自己設計的模型) + self.Epoch = Training_Config["Epoch"] # 訓練該模型的次數 + self.train_batch_size = Training_Config["Train_Batch_Size"] # 訓練模型的Batch Size + self.Experiment_Name = Experiment_Name + self.Number_Of_Classes = Loading_Config["Identification_Label_Length"] + self.Best_Model_Save_Root = Best_Model_Save_Root + + # 初始化多個繼承物件 + Training_Precesses.__init__(self, Training_Config["Image_Size"]) + Loding_Data_Root.__init__(self, Loading_Config["Training_Labels"], Loading_Config["Train_Data_Root"], Loading_Config["Test_Data_Root"]) + + pass + + def Processing_Main(self, training_dataset, Test_Dataloader=None): + # Lists to store metrics across all folds + all_fold_train_losses = [] + all_fold_val_losses = [] + all_fold_train_accuracies = [] + all_fold_val_accuracies = [] + Calculate_Process = Calculate() + File = Process_File() + Calculate_Tool = [Calculate() for i in range(3)] + Best_Model_Path = None + Best_Validation_Loss = 100000000 + + # K-Fold loop + kf = KFold(n_splits=5, shuffle=True, random_state=42) + for fold, (train_idx, val_idx) in enumerate(kf.split(range(len(training_dataset)))): # K-Fold 交叉驗證迴圈 + print(f"\nStarting Fold {fold + 1}/5") + + # Create training and validation subsets for this fold + self.train_subset = torch.utils.data.Subset(training_dataset, train_idx) + self.val_subset = torch.utils.data.Subset(training_dataset, val_idx) + + # Wrap subsets in DataLoaders (use same batch size as original) + self.train_loader = self.Dataloader_Sampler(self.train_subset , self.train_batch_size, True) + self.val_loader = self.Dataloader_Sampler(self.val_subset, self.train_batch_size, True) + + # 模型訓練與驗證 + model_path, train_losses, Validation_losses, train_accuracies, Validation_accuracies, Total_Epoch, best_val_loss = self.Training_And_Validation(fold) + + # Store fold results + all_fold_train_losses.append(train_losses) + all_fold_val_losses.append(Validation_losses) + all_fold_train_accuracies.append(train_accuracies) + all_fold_val_accuracies.append(Validation_accuracies) + + # 确保张量在CPU上,以便可以转换为NumPy数组 + if torch.is_tensor(train_losses): + train_losses = train_losses.cpu().detach().numpy() + if torch.is_tensor(Validation_losses): + Validation_losses = Validation_losses.cpu().detach().numpy() + if torch.is_tensor(train_accuracies): + train_accuracies = train_accuracies.cpu().detach().numpy() + if torch.is_tensor(Validation_accuracies): + Validation_accuracies = Validation_accuracies.cpu().detach().numpy() + + Losses = [train_losses, Validation_losses] + Accuracies = [train_accuracies, Validation_accuracies] + plot_history(Losses, Accuracies, f"{Save_Result_File_Config['Identification_Plot_Image']}/{self.Experiment_Name}", f"train-{str(fold)}") # 將訓練結果化成圖,並將化出來的圖丟出去儲存 + + # 驗證結果 + True_Label, Predict_Label, loss, accuracy, precision, recall, AUC, f1 = self.Evaluate_Model(self.Model, Test_Dataloader, fold) + + # 紀錄該次訓練結果 + Calculate_Process.Append_numbers(loss, accuracy, precision, recall, AUC, f1) + self.record_matrix_image(True_Label, Predict_Label, fold) + print(self.record_everyTime_test_result(loss, accuracy, precision, recall, AUC, f1, fold, self.Experiment_Name)) # 紀錄當前訓練完之後的預測結果,並輸出成csv檔 + + # 使用識別模型進行各類別評估 + Calculate_Tool = self.Evaluate_Per_Class_Metrics(self.Model, Test_Dataloader, Loading_Config["Training_Labels"], Calculate_Tool) + + if best_val_loss < Best_Validation_Loss: + Best_Validation_Loss = best_val_loss + Best_Model_Path = model_path + + Calculate_Process.Calculate_Mean() + Calculate_Process.Calculate_Std() + + File.Save_CSV_File(f"../Result/Experiment_Result/{self.Experiment_Name}/Total/{str(datetime.date.today())}", f"Total_Training_Result-{fold}", Calculate_Process.Output_Style()) + + for Calculate_Every_Class in Calculate_Tool: + Calculate_Every_Class.Calculate_Mean() + Calculate_Every_Class.Calculate_Std() + + # Aggregate results across folds + avg_train_losses = np.mean([losses[-1] for losses in all_fold_train_losses]) + avg_val_losses = np.mean([losses[-1] for losses in all_fold_val_losses]) + avg_train_accuracies = np.mean([acc[-1] for acc in all_fold_train_accuracies]) + avg_val_accuracies = np.mean([acc[-1] for acc in all_fold_val_accuracies]) + + print(f"\nCross-Validation Results:") + print(f"Avg Train Loss: {avg_train_losses:.4f}, Avg Val Loss: {avg_val_losses:.4f}") + print(f"Avg Train Acc: {avg_train_accuracies:.4f}, Avg Val Acc: {avg_val_accuracies:.4f}") + + File.Save_TXT_File(content = f"\nCross-Validation Results:\nAvg Train Loss: {avg_train_losses:.4f}, Avg Val Loss: {avg_val_losses:.4f}\nAvg Train Acc: {avg_train_accuracies:.4f}, Avg Val Acc: {avg_val_accuracies:.4f}\n", Save_Root = Save_Result_File_Config["Identification_Average_Result"], File_Name = "Training_Average_Result") + + # 返回最後一個fold的模型路徑和平均指標 + return Best_Model_Path, Calculate_Process, Calculate_Tool + + def Training_And_Validation(self, Fold): + # Reinitialize model and optimizer for each fold + # self.Model = self.Model.__class__(self.Number_Of_Classes).to(self.device) # Reinitialize model + self.Model = self.Construct_Identification_Model_CUDA() # 模型初始化 + Optimizer = optim.SGD(self.Model.parameters(), lr=0.045, momentum=0.9, weight_decay = Training_Config["weight_decay"]) + model_path, early_stopping, scheduler = call_back(self.Best_Model_Save_Root, f"fold{Fold}", Optimizer) + + # Lists to store metrics for this fold + train_losses = [] + Validation_losses = [] + train_accuracies = [] + Validation_accuracies = [] + + # Epoch loop + for epoch in range(self.Epoch): + self.Model.train() # Start training + Training_Loss = 0.0 + All_Predict_List, All_Label_List = [], [] + + # Progress bar for training batches + epoch_iterator = tqdm(self.train_loader, desc=f"Fold {Fold + 1}/5, Epoch [{epoch + 1}/{self.Epoch}]") + Start_Time = time.time() + + for inputs, labels, File_Name, File_Classes in epoch_iterator: + Optimizer.zero_grad() # 清零梯度,防止梯度累積 + Total_Losses, Training_Loss, All_Predict_List, All_Label_List, Predict_Indexs, Truth_Indexs = self.Model_Branch(inputs, labels, All_Predict_List, All_Label_List, Training_Loss) + Total_Losses.backward() + + Optimizer.step() + self.Calculate_Progress_And_Timing(inputs, Predict_Indexs, Truth_Indexs, self.train_subset, Total_Losses, epoch_iterator, Start_Time) + + train_losses, train_accuracies, Training_Loss, Train_accuracy = self.Calculate_Average_Scores(self.train_loader, Training_Loss, All_Predict_List, All_Label_List, train_losses, train_accuracies) + + # Validation step + self.Model.eval() + val_loss = 0.0 + all_val_preds = [] + all_val_labels = [] + + start_Validation_time = time.time() + epoch_iterator = tqdm(self.val_loader, desc=f"\tValidation-Fold {Fold + 1}/5, Epoch [{epoch + 1}/{self.Epoch}]") + with torch.no_grad(): + for inputs, labels, File_Name, File_Classes in epoch_iterator: + Validation_Total_Loss, val_loss, all_val_preds, all_val_labels, Predict_Indexs, Truth_Indexs = self.Model_Branch(inputs, labels, all_val_preds, all_val_labels, val_loss) + self.Calculate_Progress_And_Timing(inputs, Predict_Indexs, Truth_Indexs, self.val_subset, Validation_Total_Loss, epoch_iterator, start_Validation_time) + + Validation_losses, Validation_accuracies, val_loss, val_accuracy = self.Calculate_Average_Scores(self.val_loader, val_loss, all_val_preds, all_val_labels, Validation_losses, Validation_accuracies) + print(f"Traini Loss: {Training_Loss:.4f}, Accuracy: {Train_accuracy:0.2f}, Validation Loss: {val_loss:.4f}, Accuracy: {val_accuracy:0.2f}\n") + + # if epoch % 5 == 0: + # # Grad = GradCAM(self.Model, TargetLayer) + # # Grad.Processing_Main(val_loader, f"../Result/GradCAM_Image/Validation/GradCAM_Image({str(datetime.date.today())})/{self.Experiment_Name}/fold-{str(fold)}/") + + # # 創建SaliencyMap實例 + # saliency_map = SaliencyMap(self.Model) + # # 處理測試數據集 + # saliency_map.Processing_Main(self.val_loader, f"../Result/Saliency_Image/Validation/Saliency_Image({str(datetime.date.today())})/{self.Experiment_Name}/fold-{str(Fold)}/") + + # Early stopping + early_stopping(val_loss, self.Model, model_path) + best_val_loss = early_stopping.best_loss + if early_stopping.early_stop: + print(f"Early stopping triggered in Fold {Fold + 1} at epoch {epoch + 1}") + break + + # Learning rate adjustment + scheduler.step(val_loss) + + Total_Epoch = epoch + 1 + # 確保返回模型路徑 + return model_path, train_losses, Validation_losses, train_accuracies, Validation_accuracies, Total_Epoch, best_val_loss + + def Construct_Identification_Model_CUDA(self): + # 从Model_Config中获取输出节点数量 + Model = Xception(num_classes=Loading_Config["Identification_Label_Length"]) + + # 注释掉summary调用,避免Mask参数问题 + # 直接打印模型结构 + print(f"Model structure: {Model}") + + # 打印模型参数 + for name, parameters in Model.named_parameters(): + print(f"Layer Name: {name}, Parameters: {parameters.size()}") + + return self.Convert_Model_To_CUDA(Model) + + def Convert_Model_To_CUDA(self, model): + if torch.cuda.device_count() > 1: + model = nn.DataParallel(model) + + model = model.to(self.device) + + return model + + def Model_Branch(self, Input_Images, Labels, All_Predict_List : list, All_Label_List : list, running_loss): + # 直接將張量移到設備上,不需要重新創建 + Input_Images.requires_grad = False + Input_Images, Labels = Input_Images.to(self.device), Labels.to(self.device) + + # 確保輸入圖像在有效範圍內 + Input_Images = torch.clamp(Input_Images, 0.0, 1.0) + + Predict = self.Model(Input_Images) + + # Collect training predictions and labels + Output_Values, Output_Indexs = torch.max(Predict, dim=1) + True_Indexs = np.argmax(Labels.cpu().numpy(), axis=1) + + All_Predict_List.append(Output_Indexs.cpu().numpy()) + All_Label_List.append(True_Indexs) + + # 不需要對Predict進行clamp,因為我們已經修改了損失函數使用binary_cross_entropy_with_logits + Losses = self.Losses(Predict, Labels) + running_loss += Losses.item() + + return Losses, running_loss, All_Predict_List, All_Label_List, Output_Indexs, True_Indexs + + def Losses(self, Predicts, Labels): + criterion = BinaryCrossEntropy() + Loss = criterion(Predicts, Labels) + return Loss + + def Evaluate_Model(self, cnn_model, Test_Dataloader, index, identification_model_path=None): + # 載入識別模型權重(如果提供了路徑) + if identification_model_path is not None: + cnn_model.load_state_dict(torch.load(identification_model_path)) + else: + assert identification_model_path is None, "No identification model path provided for evaluation." + + # 評估模型 + cnn_model.eval() + True_Label, Predict_Label = [], [] + True_Label_OneHot, Predict_Label_OneHot = [], [] + loss = 0.0 + + with torch.no_grad(): + for images, labels, File_Name, File_Classes in Test_Dataloader: + Total_Loss, Running_Loss, Predict_Label, True_Label, Output_Indexs, Truth_Index = self.Model_Branch(images, labels, Predict_Label, True_Label, 0) + + Predict_Label_OneHot.append(torch.tensor(functional.one_hot(Output_Indexs, self.Number_Of_Classes), dtype=torch.float32).cpu().numpy()[0]) + True_Label_OneHot.append(torch.tensor(labels, dtype=torch.int).cpu().numpy()[0]) + + loss /= len(Test_Dataloader) + + True_Label_OneHot = torch.as_tensor(True_Label_OneHot, dtype=torch.int) + Predict_Label_OneHot = torch.as_tensor(Predict_Label_OneHot, dtype=torch.float32) + + accuracy = accuracy_score(True_Label, Predict_Label) + precision = precision_score(True_Label, Predict_Label, average="macro") + recall = recall_score(True_Label, Predict_Label, average="macro") + AUC = auroc(Predict_Label_OneHot, True_Label_OneHot, num_labels=self.Number_Of_Classes, task="multilabel", average="macro") + f1 = f1_score(True_Label, Predict_Label, average="macro") + + # 計算混淆矩陣 + matrix = confusion_matrix(True_Label, Predict_Label) + draw_heatmap(matrix, f"{Save_Result_File_Config['Identification_Marix_Image']}/{self.Experiment_Name}/Identification_Test_Marix_Image", f"confusion_matrix", index) # 呼叫畫出confusion matrix的function + + return True_Label, Predict_Label, loss, accuracy, precision, recall, AUC, f1 + + def Evaluate_Per_Class_Metrics(self, cnn_model, Test_Dataloader, Labels, Calculate_Tool, identification_model_path=None): + """ + Evaluate the model on the test dataloader and compute binary classification metrics for each class. + + Parameters: + - cnn_model: The trained model to evaluate. + - Test_Dataloader: DataLoader for the test dataset. + - Labels: List of class names for better readability. + - Calculate_Tool: Tool for recording metrics. + - identification_model_path: Path to the trained model weights (optional). + + Returns: + - Calculate_Tool: Updated with binary classification metrics for each class. + """ + # 載入識別模型權重(如果提供了路徑) + if identification_model_path is not None: + cnn_model.load_state_dict(torch.load(identification_model_path)) + + # 测试GPU是否可用 + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + print(f"使用设备: {device}") + + # 设置为评估模式 + cnn_model.eval() + + all_results = [] + all_labels = [] + # 使用PyTorch的预测方式 + with torch.no_grad(): # 不计算梯度 + for inputs, labels, _, _ in Test_Dataloader: + inputs = inputs.to(device) + labels = labels.to(device) + + outputs = cnn_model(inputs) + _, predicted = torch.max(outputs, 1) + all_results.append(predicted.cpu().numpy()) + all_labels.append(np.argmax(labels.cpu().numpy(), axis=1)) + + # 将所有批次的结果合并为一个数组 + Predict = np.concatenate(all_results) + y_test = np.concatenate(all_labels) + + print(f"预测结果: {Predict}\n") + # 计算整体评估指标 + accuracy = accuracy_score(y_test, Predict) + + # 打印整体准确率 + print(f"整体准确率 (Accuracy): {accuracy:.4f}") + + # 假设只有两个类别:0和1 + # 计算类别0的二分类评估指标(将类别0视为正类,类别1视为负类) + print(f"类别 {Labels[0]} 的二分类评估指标:") + y_binary_0 = (y_test == 0).astype(int) + predict_binary_0 = (Predict == 0).astype(int) + + # 计算二分类指标 + binary_accuracy_0 = accuracy_score(y_binary_0, predict_binary_0) + binary_precision_0 = precision_score(y_binary_0, predict_binary_0, zero_division=0) + binary_recall_0 = recall_score(y_binary_0, predict_binary_0, zero_division=0) + binary_f1_0 = f1_score(y_binary_0, predict_binary_0, zero_division=0) + + # 打印二分类指标 + print(f" 准确率 (Accuracy): {binary_accuracy_0:.4f}") + print(f" 精确率 (Precision): {binary_precision_0:.4f}") + print(f" 召回率 (Recall): {binary_recall_0:.4f}") + print(f" F1值: {binary_f1_0:.4f}\n") + + # 记录类别0的指标 + Calculate_Tool[0].Append_numbers(0, binary_accuracy_0, binary_precision_0, binary_recall_0, 0, binary_f1_0) + + # 计算类别1的二分类评估指标(将类别1视为正类,类别0视为负类) + print(f"类别 {Labels[1]} 的二分类评估指标:") + y_binary_1 = (y_test == 1).astype(int) + predict_binary_1 = (Predict == 1).astype(int) + + # 计算二分类指标 + binary_accuracy_1 = accuracy_score(y_binary_1, predict_binary_1) + binary_precision_1 = precision_score(y_binary_1, predict_binary_1, zero_division=0) + binary_recall_1 = recall_score(y_binary_1, predict_binary_1, zero_division=0) + binary_f1_1 = f1_score(y_binary_1, predict_binary_1, zero_division=0) + + # 打印二分类指标 + print(f" 准确率 (Accuracy): {binary_accuracy_1:.4f}") + print(f" 精确率 (Precision): {binary_precision_1:.4f}") + print(f" 召回率 (Recall): {binary_recall_1:.4f}") + print(f" F1值: {binary_f1_1:.4f}\n") + + # 记录类别1的指标 + Calculate_Tool[1].Append_numbers(0, binary_accuracy_1, binary_precision_1, binary_recall_1, 0, binary_f1_1) + + return Calculate_Tool + + def Calculate_Progress_And_Timing(self, inputs, Predict_Labels, Truth_Labels, Subset, loss, epoch_iterator, Start_Time): + # Calculate progress and timing + total_samples = len(Subset) + processed_samples = 0 + + processed_samples += inputs.size(0) # Use size(0) for batch size + + # Calculate progress and timing + elapsed_time = time.time() - Start_Time + iterations_per_second = processed_samples / elapsed_time if elapsed_time > 0 else 0 + eta = (total_samples - processed_samples) / iterations_per_second if iterations_per_second > 0 else 0 + time_str = f"{int(elapsed_time//60):02d}:{int(elapsed_time%60):02d}<{int(eta//60):02d}:{int(eta%60):02d}" + + # Calculate batch metrics using PSNR/SSIM loss + # 检查loss是否为张量,如果是则调用item(),否则直接使用浮点数值 + batch_loss = loss.item() if torch.is_tensor(loss) else loss + # Calculate batch accuracy + batch_accuracy = (Predict_Labels.cpu().numpy() == Truth_Labels).mean() + + # Update progress bar + epoch_iterator.set_postfix_str( + f"{processed_samples}/{total_samples} [{time_str}, {iterations_per_second:.2f}it/s, " + f"acc={batch_accuracy:.3f}, loss={batch_loss:.3f}]" + ) + + return epoch_iterator + + def Calculate_Average_Scores(self, Data_Loader, Running_Losses, All_Predict_Labels, All_Truth_Labels, Losses, Accuracies): + Merge_Function = merge() + + All_Predicts = Merge_Function.merge_data_main(All_Predict_Labels, 0, len(All_Predict_Labels)) + All_Truths = Merge_Function.merge_data_main(All_Truth_Labels, 0, len(All_Truth_Labels)) + + Running_Losses /= len(Data_Loader) + Accuracy = accuracy_score(All_Truths, All_Predicts) + + Losses.append(Running_Losses) + Accuracies.append(Accuracy) + + return Losses, Accuracies, Running_Losses, Accuracy + + def record_matrix_image(self, True_Labels, Predict_Labels, index): + '''劃出混淆矩陣(熱力圖)''' + # 計算混淆矩陣 + matrix = confusion_matrix(True_Labels, Predict_Labels) + # Confusion_Matrix_of_Two_Classification(matrix, Save_Result_File_Config["Identification_Marix_Image"], Experiment_Name, index) # 呼叫畫出confusion matrix的function + draw_heatmap(matrix, Save_Result_File_Config["Identification_Marix_Image"], self.Experiment_Name, index) # 呼叫畫出confusion matrix的function + + def record_everyTime_test_result(self, loss, accuracy, precision, recall, auc, f, indexs, model_name): + '''記錄我單次的訓練結果並將它輸出到檔案中''' + File = Process_File() + + Dataframe = pd.DataFrame( + { + "model_name" : str(model_name), + "loss" : "{:.2f}".format(loss), + "precision" : "{:.2f}%".format(precision * 100), + "recall" : "{:.2f}%".format(recall * 100), + "accuracy" : "{:.2f}%".format(accuracy * 100), + "f" : "{:.2f}%".format(f * 100), + "AUC" : "{:.2f}%".format(auc * 100) + }, index = [indexs]) + File.Save_CSV_File(Save_Result_File_Config["Identification_Every_Fold_Training_Result"], "train_result", Dataframe) + + return Dataframe \ No newline at end of file diff --git a/experiments/Training/Segmentation_Block_Training.py b/experiments/Training/Segmentation_Block_Training.py new file mode 100644 index 0000000..80a8d9e --- /dev/null +++ b/experiments/Training/Segmentation_Block_Training.py @@ -0,0 +1,467 @@ +from tqdm import tqdm +from torch import nn +from sklearn.model_selection import KFold +from skimage import measure + +from all_models_tools.all_model_tools import call_back +from utils.Stomach_Config import Training_Config, Loading_Config, Save_Result_File_Config +from Load_process.LoadData import Loding_Data_Root +from Training_Tools.PreProcess import Training_Precesses +from ..Models.GastroSegNet_Model import GastroSegNet +from Model_Loss.Segmentation_Loss import Segmentation_Loss +from draw_tools.draw import plot_history + +import time +import torch.optim as optim +import torch +import torch.nn.functional as F +import numpy as np +import cv2 +import os + +class Segmentation_Block_Training_Step(Loding_Data_Root, Training_Precesses): + def __init__(self, Best_Model_Save_Root): + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 設置裝置,若有GPU則使用GPU,否則使用CPU + + self.Model = self.Construct_Segment_Model_CUDA() # 模型變數 + self.train_subset = None # Training Dataset 的子集 + self.val_subset = None # Validation Dataset 的子集 + self.train_loader = None # Training DataLoader 的讀檔器 + self.val_loader = None # Validation DataLoader 的讀檔器 + self.Mask = None # 遮罩變數,接收RD Net產出來的Mask + self.Grad = None # 梯度變數,後面用來執行Grad CAM + + self.model_name = Training_Config["Mask_Experiment_Name"] # 取名,使用哪個模型(可能是預處理模型/自己設計的模型) + self.epoch = Training_Config["Epoch"] # 訓練該模型的次數 + self.train_batch_size = Training_Config["Train_Batch_Size"] # 訓練模型的Batch Size + self.Experiment_Name = Training_Config["Mask_Experiment_Name"] # 取名,使用哪個模型(可能是預處理模型/自己設計的模型) + self.Best_Model_Save_Root = Best_Model_Save_Root + + # 初始化繼承物件 + Training_Precesses.__init__(self, Training_Config["Image_Size"]) + Loding_Data_Root.__init__(self, Loading_Config["Training_Labels"], Loading_Config["Train_Data_Root"], Loading_Config["Test_Data_Root"]) + + def Processing_Main(self, training_dataset, return_processed_images=False, test_dataloader=None): + Best_Model_Path = None + Best_Validation_Loss = 100000000 + # K-Fold loop + kf = KFold(n_splits=5, shuffle=True, random_state=42) + Training_Data_Lader = self.Dataloader_Sampler(training_dataset, self.train_batch_size, True) + + for fold, (train_idx, val_idx) in enumerate(kf.split(range(len(training_dataset)))): # K-Fold 交叉驗證迴圈 + print(f"\nStarting Fold {fold + 1}/5") + + # Create training and validation subsets for this fold + self.train_subset = torch.utils.data.Subset(training_dataset, train_idx) + self.val_subset = torch.utils.data.Subset(training_dataset, val_idx) + + # Wrap subsets in DataLoaders (use same batch size as original) + self.train_loader = self.Dataloader_Sampler(self.train_subset , self.train_batch_size, True) + self.val_loader = self.Dataloader_Sampler(self.val_subset, self.train_batch_size, True) + + # 模型訓練與驗證 + model_path, Training_Losses, Validation_Losses, Total_Epoch, best_val_loss = self.Training_And_Validation(fold) + Losses = [Training_Losses, Validation_Losses] + + if best_val_loss < Best_Validation_Loss: + Best_Validation_Loss = best_val_loss + Best_Model_Path = model_path + + # 將訓練結果化成圖,並將化出來的圖丟出去儲存 + plot_history(Losses, None, f"{Save_Result_File_Config['Segument_Plot_Image']}/{self.Experiment_Name}", f"train-{str(fold)}") # 將訓練結果化成圖,並將化出來的圖丟出去儲存 + + # 如果需要返回處理後的圖像(用於後續識別訓練) + if return_processed_images is not None: + # 載入最佳模型 + self.Model = self.Construct_Segment_Model_CUDA() + self.Model.load_state_dict(torch.load(Best_Model_Path)) + self.Model.eval() + + # 處理測試數據 + with torch.no_grad(): + for Input_Images, Mask_Ground_Truth_Image, Labels, File_Name, File_Classes in Training_Data_Lader: + # 使用Model_Branch處理圖像並獲取分割結果,同時傳遞文件名以保存邊界框圖像 + self.Model_Branch(Input_Images, Mask_Ground_Truth_Image, 0.0, Save_Result_File_Config["Segument_Bounding_Box_Image"], return_processed_image=True, file_names=File_Name, Classes=File_Classes) + + avg_test_loss = self.evaluate_on_test(Best_Model_Path, test_dataloader) + + return Best_Model_Path, avg_test_loss + + return Best_Model_Path + + def Training_And_Validation(self, Fold): + self.Model = self.Construct_Segment_Model_CUDA() # 模型初始化 + Optimizer = optim.SGD(self.Model.parameters(), lr=0.045, momentum=0.9, weight_decay=0.01) # 優化器初始化 + model_path, early_stopping, scheduler = call_back(f"{self.Best_Model_Save_Root}/{self.Experiment_Name}", f"fold{Fold}", Optimizer) # 防止過擬合細節函數初始化 + epoch = 0 + Training_Losses, Validation_Losses = [], [] + Training_Running_Losses, Validation_Running_Losses = 0.0, 0.0 + + # Epoch loop + for epoch in range(self.epoch): + self.Model.train() # Start training + + # Progress bar for training batches + epoch_iterator = tqdm(self.train_loader, desc=f"Fold {Fold + 1}/5, Epoch [{epoch + 1}/{self.epoch}]") + Start_Time = time.time() + + for Input_Images, Mask_Ground_Truth_Image, Labels, File_Name, File_Classes in epoch_iterator: + Optimizer.zero_grad() # 清零梯度,防止梯度累積 + + Training_Total_Losses, Training_Running_Losses = self.Model_Branch(Input_Images, Mask_Ground_Truth_Image, Training_Running_Losses) + Training_Total_Losses.backward() + + Optimizer.step() + epoch_iterator = self.Calculate_Progress_And_Timing(Input_Images, self.train_subset, Training_Total_Losses, epoch_iterator, Start_Time) + + Training_Losses, Training_Running_Losses = self.Calculate_Average_Scores(self.train_loader, Training_Running_Losses, Training_Losses) + + # Validation step + self.Model.eval() + epoch_iterator_Validation = tqdm(self.val_loader, desc=f"\tValidation-Fold {Fold + 1}/5, Epoch [{epoch + 1}/{self.epoch}]") + with torch.no_grad(): + for Input_Images, Mask_Ground_Truth_Image, Labels, File_Name, File_Classes in epoch_iterator_Validation: + Validation_Total_Losses, Validation_Running_Losses = self.Model_Branch(Input_Images, Mask_Ground_Truth_Image, Validation_Running_Losses) + + # 添加Start_Time參數 + Start_Time = time.time() + epoch_iterator_Validation = self.Calculate_Progress_And_Timing(Input_Images, self.val_subset, Validation_Total_Losses, epoch_iterator_Validation, Start_Time) + + Validation_Losses, Validation_Running_Losses = self.Calculate_Average_Scores(self.val_loader, Validation_Running_Losses, Validation_Losses) + print(f"Traini Loss: {Training_Running_Losses:.4f}, Validation Loss: {Validation_Running_Losses:.4f}\n") + + # Early stopping + early_stopping(Validation_Running_Losses, self.Model, model_path) + if early_stopping.early_stop: + print(f"Early stopping triggered in Fold {Fold + 1} at epoch {epoch + 1}") + break + + # Scheduler step + scheduler.step(Validation_Running_Losses) + + Total_Epoch = epoch + 1 + best_val_loss = early_stopping.best_loss + return model_path, Training_Losses, Validation_Losses, Total_Epoch, best_val_loss + + def Construct_Segment_Model_CUDA(self): + GaSeg = GastroSegNet() + + # 添加輸出模型摘要的功能 + print("\n==== GastroSegNet 模型摘要 ====\n") + print(f"輸入通道數: {GaSeg.encoder[0].conv[0].in_channels}") + print(f"輸出通道數: {GaSeg.final_conv.out_channels}") + + # 計算總參數量 + total_params = sum(p.numel() for p in GaSeg.parameters() if p.requires_grad) + print(f"可訓練參數總量: {total_params:,}") + + # 顯示模型結構 + print("\n模型結構:") + print(f"- 編碼器層數: {len(GaSeg.encoder)}") + print(f"- 解碼器層數: {len(GaSeg.decoder)}") + + print("\n特徵通道配置:") + features_str = ", ".join([str(GaSeg.encoder[i].conv[0].out_channels) for i in range(len(GaSeg.encoder))]) + print(f" - 編碼器特徵通道: {features_str}") + print(f" - 瓶頸層特徵通道: {GaSeg.bottleneck.conv[0].out_channels}") + + print("\n==== 摘要結束 ====\n") + + return self.Convert_Model_To_CUDA(GaSeg) + + def Convert_Model_To_CUDA(self, model): + if torch.cuda.device_count() > 1: + model = nn.DataParallel(model) + + model = model.to(self.device) + + return model + + def Model_Branch(self, Input_Images, Mask_Ground_Truth_Image, running_loss, Save_Dir = None, return_processed_image=False, file_names=None, Classes=None): + # 直接將張量移到設備上,不需要重新創建 + Input_Images.requires_grad = False + Input_Images = Input_Images.to(self.device) + Segmentation_Output = self.Model(Input_Images) + + # 如果需要返回處理後的圖像(用於推理階段) + if return_processed_image: + # 調整模型產出影像大小 + Segmentation_Output = self.Compare_Image_And_Resize_It(Segmentation_Output, Input_Images) + # 處理分割輸出,選擇候選框並將框外像素變黑,同時保存邊界框圖像 + return self.process_segmentation_output(Input_Images, Segmentation_Output, Save_Dir, save_bbox_images=True, file_names=file_names, Classes=Classes) + + Mask_Ground_Truth_Image = Mask_Ground_Truth_Image.to(self.device) + # 調整模型產出影像大小 + Segmentation_Output = self.Compare_Image_And_Resize_It(Segmentation_Output, Mask_Ground_Truth_Image) + + Losses = self.Losses(Segmentation_Output, Mask_Ground_Truth_Image) + # 計算損失(不需要手動設置requires_grad,因為損失計算會自動處理梯度) + running_loss += Losses.item() + return Losses, running_loss + + def Compare_Image_And_Resize_It(self, Image, Target): # 調整兩張影像大小到一樣 + # 檢查Target的維度 + if Target.dim() < 3: + # 如果Target是2D張量,將其擴展為4D張量 [batch_size, channels, height, width] + Target = Target.unsqueeze(0).unsqueeze(0) + elif Target.dim() == 3: + # 如果Target是3D張量,將其擴展為4D張量 [batch_size, channels, height, width] + Target = Target.unsqueeze(0) + + # 獲取目標尺寸 + target_height = Target.size(-2) # 使用倒數第二維作為高度 + target_width = Target.size(-1) # 使用倒數第一維作為寬度 + + # 調整Image大小 + Image = torch.nn.functional.interpolate(Image, size=(target_height, target_width), mode='nearest') + + # 動態調整通道維度 + if Image.size(1) != Target.size(1) and Target.dim() >= 3: + conv = torch.nn.Conv2d(Image.size(1), Target.size(1), kernel_size=1).to(self.device) + Image = conv(Image) + + return Image + + def Losses(self, Segmentation_Output_Image, Segmentation_Mask_GroundTruth_Image): + criterion = Segmentation_Loss() + Loss = criterion(Segmentation_Output_Image, Segmentation_Mask_GroundTruth_Image) + return Loss + + def Record_Average_Losses(self, DataLoader): + loss = 0.0 + losses = [] + + # Calculate average validation loss + loss /= len(DataLoader) + losses.append(loss) + + return losses + + def Calculate_Progress_And_Timing(self, inputs, Subset, loss, epoch_iterator, Start_Time): + # Calculate progress and timing + total_samples = len(Subset) + processed_samples = 0 + + processed_samples += inputs.size(0) # Use size(0) for batch size + + # Calculate progress and timing + elapsed_time = time.time() - Start_Time + iterations_per_second = processed_samples / elapsed_time if elapsed_time > 0 else 0 + eta = (total_samples - processed_samples) / iterations_per_second if iterations_per_second > 0 else 0 + time_str = f"{int(elapsed_time//60):02d}:{int(elapsed_time%60):02d}<{int(eta//60):02d}:{int(eta%60):02d}" + + # Calculate batch metrics using PSNR/SSIM loss + batch_loss = loss.item() + + # Update progress bar with PSNR/SSIM loss + epoch_iterator.set_postfix_str( + f"{processed_samples}/{total_samples} [{time_str}, {iterations_per_second:.2f}it/s, " + f"loss={batch_loss:.3f}]" + ) + + return epoch_iterator + + def Calculate_Average_Scores(self, Data_Loader, Running_Losses, Losses): + Running_Losses /= len(Data_Loader) + Losses.append(Running_Losses) + + return Losses, Running_Losses + + def process_segmentation_output(self, input_images, segmentation_output, bbox_save_dir = None, save_bbox_images=True, file_names=None, Classes = None): + """處理分割輸出,選擇候選框並將框外像素變黑,同時保存邊界框圖像 + + Args: + input_images: 原始輸入圖像 [B, C, H, W] + segmentation_output: 分割模型輸出 [B, 1, H, W] + save_bbox_images: 是否保存邊界框圖像 + file_names: 圖像文件名列表,用於保存邊界框圖像 + + Returns: + processed_images: 處理後的圖像,框外像素變黑 [B, 3, H, W] (始終確保輸出是3通道) + """ + + # 將輸出轉換為二值掩碼 (閾值為0.5) + Batch_Size_Of_Image = segmentation_output.size(0) + binary_masks = (torch.sigmoid(segmentation_output) > 0.5).float() + + # 創建一個輸出張量,確保始終是3通道 + # 獲取批次大小、高度和寬度 + Batch_Size_Of_Image, _, height, width = input_images.size() + + # 創建3通道的輸出張量,不管輸入是幾個通道,輸出都是3通道 + processed_images = torch.zeros(Batch_Size_Of_Image, 3, height, width, device=input_images.device) + + # 對批次中的每張圖像進行處理 + for Batch_Size in range(Batch_Size_Of_Image): + # 創建保存邊界框圖像的目錄 + new_bbox_save_dir = bbox_save_dir + new_bbox_save_dir = os.path.join(new_bbox_save_dir, Classes[Batch_Size]) + if save_bbox_images and not os.path.exists(new_bbox_save_dir): + os.makedirs(new_bbox_save_dir, exist_ok=True) + print(f"創建邊界框圖像保存目錄: {new_bbox_save_dir}") + + # 獲取當前圖像的二值掩碼並轉換為numpy數組 + mask = binary_masks[Batch_Size, 0].cpu().numpy().astype(np.uint8) + + # 使用連通區域分析找出所有候選區域 + labeled_mask, num_labels = measure.label(mask, return_num=True, connectivity=2) + + if num_labels > 0: + # 計算每個區域的面積 + regions = measure.regionprops(labeled_mask) + + # 根據面積排序區域(從大到小) + regions.sort(key=lambda x: x.area, reverse=True) + + # 選擇最大的區域作為最終掩碼 + if len(regions) > 0: + # 創建一個新的掩碼,只包含最大的區域 + final_mask = np.zeros_like(mask) + for coords in regions[0].coords: + final_mask[coords[0], coords[1]] = 1 + + # 獲取最大區域的邊界框 + bbox = regions[0].bbox # (min_row, min_col, max_row, max_col) + + # 將最終掩碼轉換回PyTorch張量 + final_mask_tensor = torch.from_numpy(final_mask).float().to(self.device) + + # 確保掩碼與輸入圖像的尺寸匹配 + if final_mask_tensor.shape != input_images[Batch_Size, 0].shape: + # 調整掩碼大小以匹配輸入圖像 + final_mask_tensor = torch.nn.functional.interpolate( + final_mask_tensor.unsqueeze(0).unsqueeze(0), + size=input_images[Batch_Size, 0].shape, + mode='nearest' + ).squeeze(0).squeeze(0) + + # 將掩碼應用到原始圖像上(保留框內像素,將框外像素變黑) + # 處理不同通道數的輸入圖像 + if input_images.size(1) == 1: # 單通道輸入 + # 將單通道複製到3個通道 + for Channel in range(3): + processed_images[Batch_Size, Channel] = input_images[Batch_Size, 0] * final_mask_tensor + elif input_images.size(1) == 3: # 三通道輸入 + # 直接複製三個通道 + for Channel in range(3): + processed_images[Batch_Size, Channel] = input_images[Batch_Size, Channel] * final_mask_tensor + else: # 其他通道數(不太可能,但為了健壯性) + # 取前三個通道或複製第一個通道 + for Channel in range(3): + if Channel < input_images.size(1): + processed_images[Batch_Size, Channel] = input_images[Batch_Size, Channel] * final_mask_tensor + else: + processed_images[Batch_Size, Channel] = input_images[Batch_Size, 0] * final_mask_tensor + + # 保存帶有邊界框的圖像 + if save_bbox_images: + # 將輸入圖像轉換為numpy數組並調整為適合顯示的格式 + img_tensor = input_images[Batch_Size].clone().detach().cpu() + img_np = img_tensor.permute(1, 2, 0).numpy() + + # 將圖像從[0,1]範圍轉換為[0,255]範圍 + img_np = (img_np * 255).astype(np.uint8) + + # 確保圖像是連續的內存塊 + img_np = np.ascontiguousarray(img_np) + + # 如果圖像是單通道的,轉換為三通道 + if img_np.shape[2] == 1: + img_np = cv2.cvtColor(img_np, cv2.COLOR_GRAY2BGR) + elif img_np.shape[2] == 3: + # 確保是BGR格式(OpenCV默認格式) + img_np = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR) + + # 繪製邊界框 + min_row, min_col, max_row, max_col = bbox + cv2.rectangle(img_np, (min_col, min_row), (max_col, max_row), (0, 255, 0), 2) + + # 生成保存文件名 + if file_names is not None and Batch_Size < len(file_names): + # 使用提供的文件名 + file_name = os.path.basename(file_names[Batch_Size]) + save_path = os.path.join(new_bbox_save_dir, f"bbox_{file_name}.png") + else: + # 使用索引作為文件名 + save_path = os.path.join(new_bbox_save_dir, file_names) + + # 保存圖像 (已經是BGR格式,直接保存) + cv2.imwrite(save_path, img_np) + print(f"已保存邊界框圖像: {save_path}") + else: + # 如果沒有找到區域,則保留原始圖像,但確保是3通道 + if input_images.size(1) == 1: # 單通道輸入 + # 將單通道複製到3個通道 + for Channel in range(3): + processed_images[Batch_Size, Channel] = input_images[Batch_Size, 0] + elif input_images.size(1) == 3: # 三通道輸入 + # 直接複製三個通道 + processed_images[Batch_Size] = input_images[Batch_Size] + else: # 其他通道數 + # 取前三個通道或複製第一個通道 + for Channel in range(3): + if Channel < input_images.size(1): + processed_images[Batch_Size, Channel] = input_images[Batch_Size, Channel] + else: + processed_images[Batch_Size, Channel] = input_images[Batch_Size, 0] + else: + # 如果沒有找到任何區域,則保留原始圖像,但確保是3通道 + if input_images.size(1) == 1: # 單通道輸入 + # 將單通道複製到3個通道 + for Channel in range(3): + processed_images[Batch_Size, Channel] = input_images[Batch_Size, 0] + elif input_images.size(1) == 3: # 三通道輸入 + # 直接複製三個通道 + for Channel in range(3): + processed_images[Batch_Size, Channel] = input_images[Batch_Size, Channel] + else: # 其他通道數 + # 取前三個通道或複製第一個通道 + for Channel in range(3): + if Channel < input_images.size(1): + processed_images[Batch_Size, Channel] = input_images[Batch_Size, Channel] + else: + processed_images[Batch_Size, Channel] = input_images[Batch_Size, 0] + + # 在沒有候選區域的情況下也保存處理後的圖像 + if save_bbox_images: + # 轉換為可保存的numpy格式(與上方保存邊界框圖像一致的流程) + img_tensor = processed_images[Batch_Size].clone().detach().cpu() + img_np = img_tensor.permute(1, 2, 0).numpy() + img_np = (img_np * 255).astype(np.uint8) + img_np = np.ascontiguousarray(img_np) + + # 保證三通道BGR格式 + if img_np.shape[2] == 1: + img_np = cv2.cvtColor(img_np, cv2.COLOR_GRAY2BGR) + elif img_np.shape[2] == 3: + img_np = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR) + + # 生成保存文件名(使用提供的檔名或索引),並標記為無邊界框 + if file_names is not None and isinstance(file_names, (list, tuple)) and Batch_Size < len(file_names): + file_name = os.path.basename(file_names[Batch_Size]) + save_path = os.path.join(new_bbox_save_dir, f"no_bbox_{file_name}.png") + else: + base_name = file_names if isinstance(file_names, str) and len(file_names) > 0 else f"no_bbox_{Batch_Size}.png" + save_path = os.path.join(new_bbox_save_dir, base_name) + + cv2.imwrite(save_path, img_np) + print(f"已保存無邊界框圖像: {save_path}") + + def evaluate_on_test(self, model_path, test_dataloader): + if test_dataloader is None: + raise ValueError("Test dataloader is required for evaluation.") + + self.Model = self.Construct_Segment_Model_CUDA() + self.Model.load_state_dict(torch.load(model_path)) + self.Model.eval() + Test_Losses = [] + + test_loss = 0.0 + with torch.no_grad(): + for Input_Images, Mask_Ground_Truth_Image, _, _, _ in test_dataloader: + losses, test_loss = self.Model_Branch(Input_Images, Mask_Ground_Truth_Image, test_loss) + + losses, test_loss = self.Calculate_Average_Scores(test_dataloader, test_loss, Test_Losses) + + print(f"Average Test Loss: {test_loss}") + return test_loss \ No newline at end of file diff --git a/experiments/Training/__init__.py b/experiments/Training/__init__.py new file mode 100644 index 0000000..13fa926 --- /dev/null +++ b/experiments/Training/__init__.py @@ -0,0 +1,2 @@ +from ..Models.GastroSegNet_Model import GastroSegNet +from ..Models.Xception_Model_Modification import Xception \ No newline at end of file diff --git a/experiments/Training/__pycache__/Identification_Block_Training.cpython-311.pyc b/experiments/Training/__pycache__/Identification_Block_Training.cpython-311.pyc new file mode 100644 index 0000000..10e85ce Binary files /dev/null and b/experiments/Training/__pycache__/Identification_Block_Training.cpython-311.pyc differ diff --git a/experiments/Training/__pycache__/Segmentation_Block_Training.cpython-311.pyc b/experiments/Training/__pycache__/Segmentation_Block_Training.cpython-311.pyc new file mode 100644 index 0000000..87d59ce Binary files /dev/null and b/experiments/Training/__pycache__/Segmentation_Block_Training.cpython-311.pyc differ diff --git a/experiments/Training/__pycache__/__init__.cpython-311.pyc b/experiments/Training/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..b01b9c5 Binary files /dev/null and b/experiments/Training/__pycache__/__init__.cpython-311.pyc differ diff --git a/experiments/__pycache__/Model_All_Step.cpython-311.pyc b/experiments/__pycache__/Model_All_Step.cpython-311.pyc index 2f0ba02..44c7c71 100644 Binary files a/experiments/__pycache__/Model_All_Step.cpython-311.pyc and b/experiments/__pycache__/Model_All_Step.cpython-311.pyc differ diff --git a/experiments/__pycache__/Model_All_Step.cpython-312.pyc b/experiments/__pycache__/Model_All_Step.cpython-312.pyc new file mode 100644 index 0000000..6dabc5e Binary files /dev/null and b/experiments/__pycache__/Model_All_Step.cpython-312.pyc differ diff --git a/experiments/__pycache__/__init__.cpython-311.pyc b/experiments/__pycache__/__init__.cpython-311.pyc index d2d2096..82a2d91 100644 Binary files a/experiments/__pycache__/__init__.cpython-311.pyc and b/experiments/__pycache__/__init__.cpython-311.pyc differ diff --git a/experiments/__pycache__/__init__.cpython-312.pyc b/experiments/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..c5515cf Binary files /dev/null and b/experiments/__pycache__/__init__.cpython-312.pyc differ diff --git a/experiments/__pycache__/__init__.cpython-313.pyc b/experiments/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..5f5b3a8 Binary files /dev/null and b/experiments/__pycache__/__init__.cpython-313.pyc differ diff --git a/experiments/__pycache__/experiment.cpython-311.pyc b/experiments/__pycache__/experiment.cpython-311.pyc index 722d63f..ab0e9ed 100644 Binary files a/experiments/__pycache__/experiment.cpython-311.pyc and b/experiments/__pycache__/experiment.cpython-311.pyc differ diff --git a/experiments/__pycache__/experiment.cpython-312.pyc b/experiments/__pycache__/experiment.cpython-312.pyc new file mode 100644 index 0000000..82deb95 Binary files /dev/null and b/experiments/__pycache__/experiment.cpython-312.pyc differ diff --git a/experiments/__pycache__/experiment.cpython-313.pyc b/experiments/__pycache__/experiment.cpython-313.pyc new file mode 100644 index 0000000..a970e78 Binary files /dev/null and b/experiments/__pycache__/experiment.cpython-313.pyc differ diff --git a/experiments/__pycache__/pytorch_Model.cpython-311.pyc b/experiments/__pycache__/pytorch_Model.cpython-311.pyc index e3c4de1..6214541 100644 Binary files a/experiments/__pycache__/pytorch_Model.cpython-311.pyc and b/experiments/__pycache__/pytorch_Model.cpython-311.pyc differ diff --git a/experiments/__pycache__/pytorch_Model.cpython-312.pyc b/experiments/__pycache__/pytorch_Model.cpython-312.pyc new file mode 100644 index 0000000..0845c42 Binary files /dev/null and b/experiments/__pycache__/pytorch_Model.cpython-312.pyc differ diff --git a/experiments/experiment.py b/experiments/experiment.py index 878d080..d3319d8 100644 --- a/experiments/experiment.py +++ b/experiments/experiment.py @@ -1,26 +1,23 @@ -from torchinfo import summary -from sklearn.model_selection import KFold -from sklearn.metrics import confusion_matrix - from Training_Tools.PreProcess import Training_Precesses -from experiments.pytorch_Model import ModifiedXception -from experiments.Model_All_Step import All_Step from Load_process.Load_Indepentend import Load_Indepentend_Data from _validation.ValidationTheEnterData import validation_the_enter_data -from Load_process.file_processing import Process_File -from draw_tools.Grad_cam import GradCAM -from draw_tools.draw import plot_history, draw_heatmap -from Calculate_Process.Calculate import Calculate +from Load_process.LoadData import Loding_Data_Root +from utils.Stomach_Config import Training_Config, Loading_Config, Save_Result_File_Config +from experiments.Training.Identification_Block_Training import Identification_Block_Training_Step +from experiments.Training.Segmentation_Block_Training import Segmentation_Block_Training_Step +from Load_process.LoadData import Load_Data_Prepare +from merge_class.merge import merge +from model_data_processing.processing import make_label_list +from sklearn.metrics import accuracy_score import numpy as np import torch import torch.nn as nn import time import pandas as pd -import datetime class experiments(): - def __init__(self, Image_Size, Model_Name, Experiment_Name, Epoch, Train_Batch_Size, tools, Number_Of_Classes, status): + def __init__(self, Xception_Training_Data, Xception_Training_Label, Xception_Training_Mask_Data, tools, Number_Of_Classes, status): ''' # 實驗物件 @@ -48,155 +45,404 @@ class experiments(): * record_matrix_image: 劃出混淆矩陣(熱力圖) * record_everyTime_test_result: 記錄我單次的訓練結果並將它輸出到檔案中 ''' + self.model_name = Training_Config["Model_Name"] # 取名,告訴我我是用哪個模型(可能是預處理模型/自己設計的模型) + self.epoch = Training_Config["Epoch"] + self.train_batch_size = Training_Config["Train_Batch_Size"] + self.Image_Size = Training_Config["Image_Size"] - self.Topic_Tool = tools - - self.validation_obj = validation_the_enter_data() # 呼叫驗證物件 - self.cut_image = Load_Indepentend_Data(self.Topic_Tool.Get_Data_Label(), self.Topic_Tool.Get_OneHot_Encording_Label()) # 呼叫切割影像物件 - - self.model_name = Model_Name # 取名,告訴我我是用哪個模型(可能是預處理模型/自己設計的模型) - self.experiment_name = Experiment_Name - self.epoch = Epoch - self.train_batch_size = Train_Batch_Size self.Number_Of_Classes = Number_Of_Classes - self.Image_Size = Image_Size - - self.Grad = "" + self.Xception_Training_Data = Xception_Training_Data + self.Xception_Training_Label = Xception_Training_Label + self.Xception_Training_Mask_Data = Xception_Training_Mask_Data + self.Topic_Tool = tools self.Status = status + self.Grad = None + self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + self.validation_obj = validation_the_enter_data() # 呼叫驗證物件 + self.cut_image = Load_Indepentend_Data(self.Topic_Tool.Get_OneHot_Encording_Label()) # 呼叫切割影像物件 pass - def processing_main(self, Training_Data, Training_Label): - Train, Test = self.Topic_Tool.Get_Save_Roots(self.Status) - Calculate_Process = Calculate() - - print(f"Training Data Content: {Training_Data[0]}") + def processing_main(self): + print(f"Testing Data Prepring!!!!") start = time.time() - self.cut_image.process_main(Test) # 呼叫處理test Data與Validation Data的function + self.cut_image.process_main(Loading_Config["Test_Data_Root"], Loading_Config["Annotation_Testing_Root"]) # 呼叫處理test Data與Validation Data的function end = time.time() print("讀取testing與validation資料(154)執行時間:%f 秒\n" % (end - start)) # 將處理好的test Data 與 Validation Data 丟給這個物件的變數 - self.test, self.test_label = self.cut_image.test, self.cut_image.test_label - # self.test = self.test.permute(0, 3, 1, 2) - # Training_Data = Training_Data.permute(0, 3, 1, 2) + print("Testing Data is Prepared finish!!!!") PreProcess = Training_Precesses(self.Image_Size) - File = Process_File() - # print(f"Dataset_Data: \n{self.test}\nLabel: \n{self.test_label}\n") - Testing_Dataset = PreProcess.Setting_DataSet(self.test, self.test_label, "Transform") - self.Test_Dataloader = PreProcess.Dataloader_Sampler(Testing_Dataset, 1, False) - # for images, labels in self.Test_Dataloader: - # print(images.shape) - - # Lists to store metrics across all folds - all_fold_train_losses = [] - all_fold_val_losses = [] - all_fold_train_accuracies = [] - all_fold_val_accuracies = [] - - # Define K-fold cross-validator - K_Fold = KFold(n_splits = 5, shuffle = True, random_state = 42) - # Get the underlying dataset from PreProcess_Classes_Data - training_dataset = PreProcess.Setting_DataSet(Training_Data, Training_Label, "Transform") - # K-Fold loop - for fold, (train_idx, val_idx) in enumerate(K_Fold.split(training_dataset)): - - cnn_model = self.construct_model() # 呼叫讀取模型的function - print(summary(cnn_model, input_size=(int(self.train_batch_size / 2), 3, self.Image_Size, self.Image_Size))) - for name, parameters in cnn_model.named_parameters(): - print(f"Layer Name: {name}, Parameters: {parameters.size()}") - - TargetLayer = cnn_model.base_model.conv4.pointwise - Grad = GradCAM(cnn_model, TargetLayer) - - step = All_Step(cnn_model, self.epoch, self.Number_Of_Classes, self.model_name, self.experiment_name) - print("\n\n\n讀取訓練資料執行時間:%f 秒\n\n" % (end - start)) - print(f"\nStarting Fold {fold + 1}/5") - - # Create training and validation subsets for this fold - train_subset = torch.utils.data.Subset(training_dataset, train_idx) - val_subset = torch.utils.data.Subset(training_dataset, val_idx) - - # print(f"Dataset_Data: \n{train_subset.dataset.data}\nLabel: \n{train_subset.dataset.labels}\n") - - # Wrap subsets in DataLoaders (use same batch size as original) - train_loader = PreProcess.Dataloader_Sampler(train_subset , self.train_batch_size, False) - val_loader = PreProcess.Dataloader_Sampler(val_subset, self.train_batch_size, False) - - cnn_model, model_path, train_losses, val_losses, train_accuracies, val_accuracies, Total_Epoch = step.Training_Step(train_subset, val_subset, train_loader, val_loader, self.model_name, fold, TargetLayer) - - # Store fold results - all_fold_train_losses.append(train_losses) - all_fold_val_losses.append(val_losses) - all_fold_train_accuracies.append(train_accuracies) - all_fold_val_accuracies.append(val_accuracies) - - Losses = [train_losses, val_losses] - Accuracies = [train_accuracies, val_accuracies] - plot_history(Total_Epoch, Losses, Accuracies, "train" + str(fold), self.experiment_name) # 將訓練結果化成圖,並將化出來的圖丟出去儲存 - - cnn_model.load_state_dict(torch.load(model_path)) - True_Label, Predict_Label, loss, accuracy, precision, recall, AUC, f1 = step.Evaluate_Model(cnn_model, self.Test_Dataloader) - - Grad.Processing_Main(self.Test_Dataloader, f"../Result/GradCAM_Image/Testing/GradCAM_Image({str(datetime.date.today())})/fold-{str(fold)}") - Calculate_Process.Append_numbers(loss, accuracy, precision, recall, AUC, f1) - - self.record_matrix_image(True_Label, Predict_Label, self.experiment_name, fold) - print(self.record_everyTime_test_result(loss, accuracy, precision, recall, AUC, f1, fold, self.experiment_name)) # 紀錄當前訓練完之後的預測結果,並輸出成csv檔 + # 分類正常跟其他資料集的測試資料 + Normal_And_Other_Test_Data = self.cut_image.test.copy() + normal_vs_others_Test_labels = [] - # Aggregate results across folds - avg_train_losses = np.mean([losses[-1] for losses in all_fold_train_losses]) - avg_val_losses = np.mean([losses[-1] for losses in all_fold_val_losses]) - avg_train_accuracies = np.mean([acc[-1] for acc in all_fold_train_accuracies]) - avg_val_accuracies = np.mean([acc[-1] for acc in all_fold_val_accuracies]) + # 將標籤轉換為二分類:Normal(1) vs Others(0) + for label in self.cut_image.test_label: + if np.argmax(label) == 1: # Normal_Crop + # Normal類別標籤為[0, 1] + normal_vs_others_Test_labels.append(np.array([0, 1])) + else: + # 其他類別標籤為[1, 0] + normal_vs_others_Test_labels.append(np.array([1, 0])) + + # 創建Normal vs Others的測試數據集 + normal_vs_others_test_dataset = PreProcess.Setting_DataSet( + Normal_And_Other_Test_Data, + normal_vs_others_Test_labels, + None, + "Transform" + ) + normal_vs_others_test_dataloader = PreProcess.Dataloader_Sampler(normal_vs_others_test_dataset, 1, False) - print(f"\nCross-Validation Results:") - print(f"Avg Train Loss: {avg_train_losses:.4f}, Avg Val Loss: {avg_val_losses:.4f}") - print(f"Avg Train Acc: {avg_train_accuracies:.4f}, Avg Val Acc: {avg_val_accuracies:.4f}") + # ========================================================================================================================================================================================= - Calculate_Process.Calculate_Mean() - Calculate_Process.Calculate_Std() + # 分類分割模型的測試資料 + # 使用CA資料和Have_Question資料訓練分割模型 + ca_have_question_test_data = [] + ca_have_question_test_labels = [] + + # 篩選CA和Have_Question資料 + for i, label in enumerate(self.cut_image.test_label): + # 檢查是否為CA或Have_Question類別 + if np.argmax(label) == 0 or np.argmax(label) == 2: # stomach_cancer_Crop或Have_Question_Crop + ca_have_question_test_data.append(self.cut_image.test[i]) + ca_have_question_test_labels.append(self.cut_image.test_label[i]) - print(Calculate_Process.Output_Style()) + print(f"CA and Have_Question Test Data Count: {len(ca_have_question_test_data)}") + print(f"CA and Have_Question Test Mask Count: {len(self.cut_image.test_mask)}") + + # 創建CA和Have_Question的訓練數據集 + segumentation_test_dataset = PreProcess.Setting_DataSet( + ca_have_question_test_data, + ca_have_question_test_labels, + self.cut_image.test_mask, + "Transform" + ) + Segumentation_test_dataloader = PreProcess.Dataloader_Sampler(segumentation_test_dataset, 1, False) - File.Save_TXT_File(content = f"\nCross-Validation Results:\nAvg Train Loss: {avg_train_losses:.4f}, Avg Val Loss: {avg_val_losses:.4f}\nAvg Train Acc: {avg_train_accuracies:.4f}, Avg Val Acc: {avg_val_accuracies:.4f}\n", File_Name = "Training_Average_Result") + # ========================================================================================================================================================================================= - pass + # 非胃癌有病與胃癌資料的分類測試資料 + # 準備CA vs Have_Question的訓練數據 + ca_vs_have_question_test_data = [] + ca_vs_have_question_test_labels = [] + + # 篩選CA和Have_Question資料 + for i, label in enumerate(self.cut_image.test_label): + if np.argmax(label) == 0: # stomach_cancer_Crop + ca_vs_have_question_test_data.append(self.cut_image.test[i]) + ca_vs_have_question_test_labels.append(np.array([1, 0])) # CA類別標籤為[1, 0] + elif np.argmax(label) == 2: # Have_Question_Crop + ca_vs_have_question_test_data.append(self.cut_image.test[i]) + ca_vs_have_question_test_labels.append(np.array([0, 1])) # Have_Question類別標籤為[0, 1] + + # 創建CA vs Have_Question的測試數據集 + ca_vs_have_question_test_dataset = PreProcess.Setting_DataSet( + ca_vs_have_question_test_data, + ca_vs_have_question_test_labels, + None, + "Transform" + ) + ca_vs_have_question_test_dataloader = PreProcess.Dataloader_Sampler(ca_vs_have_question_test_dataset, 1, False) - def construct_model(self): - '''決定我這次訓練要用哪個model''' - cnn_model = ModifiedXception(self.Number_Of_Classes) + # ========================================================================================================================================================================================= - if torch.cuda.device_count() > 1: - cnn_model = nn.DataParallel(cnn_model) + # 建立最終測試資料(不含遮罩) + Testing_Data, Testing_Label = self.cut_image.test.copy(), self.cut_image.test_label.copy() + Test_Dataset = PreProcess.Setting_DataSet( + Testing_Data, + Testing_Label, + None, + "Transform" + ) + self.Test_Dataloader = PreProcess.Dataloader_Sampler(Test_Dataset, 1, False) - cnn_model = cnn_model.to(self.device) - return cnn_model + # ========================================================================================================================================================================================= + + print("Training is started!!\n") + + # 創建訓練步驟物件 + identification_Normal_step = Identification_Block_Training_Step(Training_Config["Normal_Experiment_Name"], Save_Result_File_Config["Normal_Identification_Best_Model"]) + identification_CA_step = Identification_Block_Training_Step(Training_Config["CA_Experiment_Name"], Save_Result_File_Config["CA_Identification_Best_Model"]) + segmentation_step = Segmentation_Block_Training_Step(Save_Result_File_Config["Segmentation_Best_Model"]) + + print("\n=== 第一階段:訓練正常資料分類模型 ===\n") + # 第一組:訓練Normal資料和其他資料的分類模型 + print("\n--- Normal vs Others分類模型 ---\n") + + # 準備Normal vs Others的訓練數據 + # 分類testing的資料 + normal_vs_others_data = self.Xception_Training_Data.copy() + normal_vs_others_labels = [] + + # 將標籤轉換為二分類:Normal(1) vs Others(0) + for label in self.Xception_Training_Label: + if np.argmax(label) == 1: # Normal_Crop + # Normal類別標籤為[0, 1] + normal_vs_others_labels.append(np.array([0, 1])) + else: + # 其他類別標籤為[1, 0] + normal_vs_others_labels.append(np.array([1, 0])) + + # 創建Normal vs Others的訓練數據集 + normal_vs_others_dataset = PreProcess.Setting_DataSet( + normal_vs_others_data, + normal_vs_others_labels, + None, + "Transform" + ) + + # 訓練Normal vs Others分類模型 + Best_Normal_Model_Path, Normal_Calculate_Process, Normal_Calculate_Tool = identification_Normal_step.Processing_Main(normal_vs_others_dataset, normal_vs_others_test_dataloader) + + + # 訓練流程:先訓練分割模型,再訓練分類模型 + print("\n=== 第二階段:訓練分割模型 ===\n") + # 使用CA資料和Have_Question資料訓練分割模型 + ca_have_question_data = [] + ca_have_question_labels = [] + + # 篩選CA和Have_Question資料 + for i, label in enumerate(self.Xception_Training_Label): + # 檢查是否為CA或Have_Question類別 + if np.argmax(label) == 0 or np.argmax(label) == 2: # stomach_cancer_Crop或Have_Question_Crop + ca_have_question_data.append(self.Xception_Training_Data[i]) + ca_have_question_labels.append(self.Xception_Training_Label[i]) + + # 創建CA和Have_Question的訓練數據集 + ca_have_question_dataset = PreProcess.Setting_DataSet( + ca_have_question_data, + ca_have_question_labels, + self.Xception_Training_Mask_Data, + "Transform" + ) + + # 執行分割模型訓練,並獲取處理後的圖像 + segmentation_best_model_path, avg_test_loss = segmentation_step.Processing_Main( + ca_have_question_dataset, + return_processed_images=True, + test_dataloader=Segumentation_test_dataloader + ) + + print(f"分割模型訓練完成,模型路徑: {segmentation_best_model_path}") + + # 將處理後的圖像保存起來,用於後續分析或可視化 + # 這裡可以添加保存處理後圖像的代碼,例如使用torchvision.utils.save_image + + print("\n=== 第三階段:訓練CA資料分類模型 ===\n") + # 第二組:訓練CA資料和Have_Question資料的分類模型 + print("\n--- 訓練CA vs Have_Question分類模型 ---\n") + + Load = Loding_Data_Root(Loading_Config["XML_Loading_Label"], Save_Result_File_Config["Segument_Bounding_Box_Image"], None) + CA_Laod_Data_Dict = Load.process_main(False) + + Total_Size_List = [] + Train_Size = 0 + + print("前處理後資料集總數") + for label in Loading_Config["XML_Loading_Label"]: + Train_Size += len(CA_Laod_Data_Dict[label]) + Total_Size_List.append(len(CA_Laod_Data_Dict[label])) + print(f"Labels: {label}, 總數為: {len(CA_Laod_Data_Dict[label])}") + + print("總共有 " + str(Train_Size) + " 筆資料") + + # 做出跟資料相同數量的Label + Classes = [] + Encording_Label = np.array([[1, 0], [0, 1]]) + i = 0 + for encording in Encording_Label: + Classes.append(make_label_list(Total_Size_List[i], encording)) + i += 1 + + # 將資料做成Dict的資料型態 + Prepare = Load_Data_Prepare() + Merge = merge() + Label_Length = len(Loading_Config["XML_Loading_Label"]) + + Prepare.Set_Final_Dict_Data(Loading_Config["XML_Loading_Label"], CA_Laod_Data_Dict, Classes, Label_Length) + Final_Dict_Data = Prepare.Get_Final_Data_Dict() + keys = list(Final_Dict_Data.keys()) + + Training_Data = Merge.merge_all_image_data(Final_Dict_Data[keys[0]], Final_Dict_Data[keys[1]]) # 將訓練資料合併成一個list + for i in range(2, Label_Length): + Training_Data = Merge.merge_all_image_data(Training_Data, Final_Dict_Data[keys[i]]) # 將訓練資料合併成一個list + + Training_Label = Merge.merge_all_image_data(Final_Dict_Data[keys[Label_Length]], Final_Dict_Data[keys[Label_Length + 1]]) #將訓練資料的label合併成一個label的list + for i in range(Label_Length + 2, 2 * Label_Length): + Training_Label = Merge.merge_all_image_data(Training_Label, Final_Dict_Data[keys[i]]) # 將訓練資料合併成一個list + + # 創建CA vs Have_Question的訓練數據集 + ca_vs_have_question_dataset = PreProcess.Setting_DataSet( + Training_Data, + Training_Label, + None, + "Transform" + ) + + # 訓練CA vs Have_Question分類模型 + Best_CA_Model_Path, CA_Calculate_Process, CA_Calculate_Tool = identification_CA_step.Processing_Main(ca_vs_have_question_dataset, ca_vs_have_question_test_dataloader) + + # 顯示訓練完成的指標平均值 + print("\n=== Normal and another的指標平均值 ===\n") + print(f"Normal and another identification result is \n {Normal_Calculate_Process.Output_Style()}\n") + + print("\n=== Normal and another各類別的指標平均值 ===\n") + for Calculate_Every_Class in Normal_Calculate_Tool: + print(f"\nNormal and another identification result is \n {Calculate_Every_Class.Output_Style()}\n") + print("\n\n") + + # 顯示訓練完成的指標平均值 + print("\n=== CA and Have Question的指標平均值 ===\n") + print(f"CA and Have Question identification result is \n {CA_Calculate_Process.Output_Style()}\n") + + print("\n=== CA and Have Question各類別的指標平均值 ===\n") + for Calculate_Every_Class in CA_Calculate_Tool: + print(f"\nCA vs Have_Question identification result is \n {Calculate_Every_Class.Output_Style()}\n") + print("\n") + + # return Best_Normal_Model_Path, Best_CA_Model_Path, segmentation_best_model_path - def record_matrix_image(self, True_Labels, Predict_Labels, model_name, index): - '''劃出混淆矩陣(熱力圖)''' - # 計算混淆矩陣 - matrix = confusion_matrix(True_Labels, Predict_Labels) - draw_heatmap(matrix, model_name, index) # 呼叫畫出confusion matrix的function - - def record_everyTime_test_result(self, loss, accuracy, precision, recall, auc, f, indexs, model_name): - '''記錄我單次的訓練結果並將它輸出到檔案中''' - File = Process_File() + def test_workflow(self, identification_Normal_step, identification_CA_step, segmentation_step, normal_vs_others_model_path, ca_vs_have_question_model_path, segmentation_model_path): + """測試流程: + 1. 先用Normal vs Others模型辨識是Normal資料還是其他資料 + 2. 若辨識為其他資料,或辨識為Normal但F1 score不到50%,進入下一階段 + 3. 將進入下一階段的資料丟到分割模型產生mask並選擇候選框,將框外像素變黑 + 4. 再丟到CA vs Have_Question模型中辨識為CA或Have_Question + """ + print("\n=== 開始測試流程 ===\n") - Dataframe = pd.DataFrame( - { - "model_name" : str(model_name), - "loss" : "{:.2f}".format(loss), - "precision" : "{:.2f}%".format(precision * 100), - "recall" : "{:.2f}%".format(recall * 100), - "accuracy" : "{:.2f}%".format(accuracy * 100), - "f" : "{:.2f}%".format(f * 100), - "AUC" : "{:.2f}%".format(auc * 100) - }, index = [indexs]) - File.Save_CSV_File("train_result", Dataframe) + identification_Normal_step = Identification_Block_Training_Step(Training_Config["Normal_Experiment_Name"], Save_Result_File_Config["Normal_Identification_Best_Model"]) + identification_CA_step = Identification_Block_Training_Step(Training_Config["CA_Experiment_Name"], Save_Result_File_Config["CA_Identification_Best_Model"]) + segmentation_step = Segmentation_Block_Training_Step(Save_Result_File_Config["Segmentation_Best_Model"]) + + # 準備測試結果記錄 + results = [] + PreProcess = Training_Precesses(self.Image_Size) + + # 第一階段:使用Normal vs Others模型進行辨識 + print("\n--- 第一階段:Normal vs Others辨識 ---\n") + + # 載入Normal vs Others模型 + identification_Normal_step.Model.load_state_dict(torch.load(normal_vs_others_model_path)) + identification_Normal_step.Model.eval() + + # 記錄需要進入第二階段的樣本 + second_stage_samples = [] + second_stage_prepare = [] + second_save_classes = [] + second_load_Classes = [] + + with torch.no_grad(): + for i, (images, labels, file_names, file_classes) in enumerate(self.Test_Dataloader): + # 將數據移到設備上 + images = images.to(identification_Normal_step.device) + + # 進行預測 + outputs = identification_Normal_step.Model(images) + Output_Values, predicted = torch.max(outputs, dim=1) + labels = np.argmax(labels.cpu().numpy(), axis=1) + + # 計算F1 score (這裡簡化為判斷是否需要進入第二階段) + # 如果預測為Others(0)或預測為Normal(1)但置信度不高,進入第二階段 + if predicted.item() == 0 or outputs[0][1].item() < 0.5: # Others或Normal但置信度低 + second_stage_samples.append((images, labels, file_names, file_classes)) + second_stage_indices.append(i) + print(f"樣本 {file_names[0]} 需要進入第二階段 (預測={predicted.item()}, 置信度={outputs[0][predicted.item()].item():.4f})") + second_save_classes.append(file_classes[0]) - return Dataframe \ No newline at end of file + # Labels = torch.argmax(labels, dim=1) + Normal_Accuracy = accuracy_score(labels, predicted.cpu().numpy()) + print(f"Normal vs Others 辨識準確率: {Normal_Accuracy:.2f}") + + second_stage_prepare.append((images, labels, file_names, file_classes)) + + for Classes in second_save_classes: + if Classes not in second_load_Classes: + second_load_Classes.append(Classes) + + # 第二階段:使用分割模型產生mask,再使用CA vs Have_Question模型進行辨識 + if second_stage_samples: + print(f"\n--- 第二階段:分割模型產生mask並進行CA vs Have_Question辨識 ---\n") + print(f"共有 {len(second_stage_samples)} 個樣本進入第二階段") + + # 載入分割模型 + segmentation_step.Model.load_state_dict(torch.load(segmentation_model_path)) + segmentation_step.Model.eval() + + # 載入CA vs Have_Question模型 + identification_CA_step.Model.load_state_dict(torch.load(ca_vs_have_question_model_path)) + identification_CA_step.Model.eval() + + with torch.no_grad(): + for i, (images, labels, file_names, file_classes) in enumerate(second_stage_samples): + # 將數據移到設備上 + images = images.to(segmentation_step.device) + + # 產生mask並處理圖像 + segmentation_step.Model_Branch(Input_Images = images, Mask_Ground_Truth_Image = None, running_loss = 0, Save_Dir = Save_Result_File_Config["Segument_Test_Bounding_Box_Image"], return_processed_image=True, file_names=f"bbox_image_{file_names}.png", Classes = second_save_classes) + + Load = Loding_Data_Root(second_load_Classes, Save_Result_File_Config["Segument_Test_Bounding_Box_Image"], None) + CA_Test_Laod_Data_Dict = Load.process_main(False) + + Total_Size_List = [] + Train_Size = 0 + + print("前處理後資料集總數") + for labels in second_load_Classes: + Train_Size += len(CA_Test_Laod_Data_Dict[labels]) + Total_Size_List.append(len(CA_Test_Laod_Data_Dict[labels])) + print(f"Labels: {labels}, 總數為: {len(CA_Test_Laod_Data_Dict[labels])}") + + print("總共有 " + str(Train_Size) + " 筆資料") + + # 做出跟資料相同數量的Label + Classes = [] + Encording_Label = np.array([[1, 0], [0, 1]]) + i = 0 + for encording in Encording_Label: + Classes.append(make_label_list(Total_Size_List[i], encording)) + i += 1 + + # 將資料做成Dict的資料型態 + Prepare = Load_Data_Prepare() + Merge = merge() + Label_Length = len(second_load_Classes) + + Prepare.Set_Final_Dict_Data(second_load_Classes, CA_Test_Laod_Data_Dict, Classes, Label_Length) + Final_Dict_Data = Prepare.Get_Final_Data_Dict() + keys = list(Final_Dict_Data.keys()) + + Testing_Data = Merge.merge_all_image_data(Final_Dict_Data[keys[0]], Final_Dict_Data[keys[1]]) # 將訓練資料合併成一個list + for i in range(2, Label_Length): + Testing_Data = Merge.merge_all_image_data(Testing_Data, Final_Dict_Data[keys[i]]) # 將訓練資料合併成一個list + + Testing_Label = Merge.merge_all_image_data(Final_Dict_Data[keys[Label_Length]], Final_Dict_Data[keys[Label_Length + 1]]) #將訓練資料的label合併成一個label的list + for i in range(Label_Length + 2, 2 * Label_Length): + Testing_Label = Merge.merge_all_image_data(Testing_Label, Final_Dict_Data[keys[i]]) # 將訓練資料合併成一個list + + # 創建CA vs Have_Question的訓練數據集 + ca_vs_have_question_test_dataset = PreProcess.Setting_DataSet( + Testing_Data, + Testing_Label, + None, + "Transform" + ) + + Evalution_DataLoad = PreProcess.Dataloader_Sampler(ca_vs_have_question_test_dataset, 1, False) + + Running_Loss = 0.0 + Losses, Accuracies = [], [] + All_Predict_List, All_Label_List = [], [] + # 使用處理後的圖像進行CA vs Have_Question模型辨識 + with torch.no_grad(): + for i, (inputs, labels, File_Name, File_Classes) in enumerate(Evalution_DataLoad): + Evalution_Total_Loss, Running_Loss, All_Predict_List, All_Label_List, Predict_Indexs, Truth_Indexs = identification_CA_step.Model_Branch(inputs, labels, All_Predict_List, All_Label_List, Running_Loss) + + Losses, Accuracies, val_loss, val_accuracy = identification_CA_step.Calculate_Average_Scores(Evalution_DataLoad, Running_Loss, All_Predict_List, All_Label_List, Losses, Accuracies) + print(f"CA vs Have Question 辨識準確率: {Accuracies:0.2f}\n") diff --git a/experiments/pytorch_Model.py b/experiments/pytorch_Model.py deleted file mode 100644 index e66b7ec..0000000 --- a/experiments/pytorch_Model.py +++ /dev/null @@ -1,81 +0,0 @@ -import torch.nn as nn -import timm - - -# class ModifiedXception(nn.Module): -# def __init__(self, num_classes): -# super(ModifiedXception, self).__init__() - -# # Load Xception pre-trained model (full model, not just features) -# self.base_model = timm.create_model( -# 'xception', -# pretrained=True -# ) - -# # Replace the default global pooling with AdaptiveAvgPool2d -# self.base_model.global_pool = nn.AdaptiveAvgPool2d(output_size=1) # Output size of 1x1 spatially - -# # Replace the final fully connected layer with Identity to get features -# self.base_model.fc = nn.Identity() # Output will be 2048 (Xception's default feature size) - -# # Custom head: Linear from 2048 to 1370, additional 1370 layer, then to num_classes -# self.custom_head = nn.Sequential( -# nn.Linear(2048, 1025), # From Xception’s 2048 features to 1370 -# nn.ReLU(), # Activation -# nn.Dropout(0.6), # Dropout for regularization -# nn.Linear(1025, num_classes) # Final output layer -# # nn.Softmax(dim = 1) # Sigmoid for binary/multi-label classification -# ) - -# def forward(self, x): -# # Pass through the base Xception model (up to global pooling) -# x = self.base_model.forward_features(x) # Get feature maps -# x = self.base_model.global_pool(x) # Apply AdaptiveAvgPool2d (output: [B, 2048, 1, 1]) -# x = x.flatten(1) # Flatten to [B, 2048] -# # x = self.base_model.fc(x) # Identity layer (still [B, 2048]) -# output = self.custom_head(x) # Custom head processing -# return output - -class ModifiedXception(nn.Module): - def __init__(self, num_classes): - super(ModifiedXception, self).__init__() - - # 加載 Xception 預訓練模型,去掉最後一層 (fc 層) - self.base_model = timm.create_model('xception', pretrained=True) - self.base_model.fc = nn.Identity() # 移除原來的 fully connected 層 - - # 新增全局平均池化層、隱藏層和輸出層 - GAP_Output = 2048 - self.global_avg_pool = nn.AdaptiveAvgPool1d(2048) # 全局平均池化 - self.hidden_layer = nn.Linear(2048, 1025) # 隱藏層,輸入大小取決於 Xception 的輸出大小 - self.output_layer = nn.Linear(1025, num_classes) # 輸出層,依據分類數目設定 - - # 激活函數與 dropout - self.relu = nn.ReLU() - self.softmax = nn.Softmax(1) - self.dropout = nn.Dropout(0.6) - - def forward(self, x): - x = self.base_model(x) # Xception 主體 - x = self.global_avg_pool(x) # 全局平均池化 - x = self.relu(self.hidden_layer(x)) # 隱藏層 + ReLU - x = self.dropout(x) # Dropout - x = self.output_layer(x) # 輸出層 - return x - -class Model_module(): - def __init__(self): - self.conv1 = nn.Conv2d(in_channels = 3, out_channels = 32, kernel_size = 3, padding = 1) - self.conv2 = nn.Conv2d(in_channels = 64, out_channels = 128, kernel_size = 3, padding = 1) - self.conv3 = nn.Conv2d(in_channels = 128, out_channels = 128, kernel_size = 3, padding = 1) - - self.relu = nn.ReLU() - self.sigmoid = nn.Sigmoid() - - self.max_Pool = nn.MaxPool2d(2, 2) - - self.fc1 = nn.Linear() - self.fc2 = nn.Linear() - pass - def forward(self, input): - pass \ No newline at end of file diff --git a/main.py b/main.py index 2d1f89b..3048cce 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,19 @@ from experiments.experiment import experiments -from Image_Process.load_and_ImageGenerator import Load_ImageGenerator -from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image +from Image_Process.Image_Generator import Image_generator from Training_Tools.Tools import Tool -from model_data_processing.processing import Balance_Process +from model_data_processing.processing import make_label_list, Read_Image_Root_And_Image_Enhance from Load_process.LoadData import Load_Data_Prepare from Calculate_Process.Calculate import Calculate from merge_class.merge import merge +from model_data_processing.processing_for_cut_image import Cut_Indepentend_Data +from Load_process.LoadData import Loding_Data_Root +from Load_process.file_processing import Process_File +from utils.Stomach_Config import Training_Config, Loading_Config +from Image_Process.Image_Mask_Ground_Truth_Processing import XMLAnnotationProcessor import time import torch - + if __name__ == "__main__": # 測試GPU是否可用 flag = torch.cuda.is_available() @@ -18,60 +22,117 @@ if __name__ == "__main__": else: print(f"CUDA可用,數量為{torch.cuda.device_count()}\n") - # 參數設定 - tool = Tool() - tool.Set_Labels() - tool.Set_Save_Roots() + # 测试GPU是否可用 + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + print(f"使用设备: {device}") + print(f"GPU: {torch.cuda.get_device_name(0)}") - Status = 1 # 決定要使用什麼資料集 - Labels = tool.Get_Data_Label() - Trainig_Root, Testing_Root = tool.Get_Save_Roots(Status) # 一般的 - Generator_Root = tool.Get_Generator_Save_Roots(Status) + tool = Tool() + Status = 1 # 取得One-hot encording 的資料 - tool.Set_OneHotEncording(Labels) + tool.Set_OneHotEncording(Loading_Config["Training_Labels"]) Encording_Label = tool.Get_OneHot_Encording_Label() - Label_Length = len(Labels) - - Model_Name = "Xception" # 取名,告訴我我是用哪個模型(可能是預處理模型/自己設計的模型) - Experiment_Name = "Xception Skin trains Stomach Cancer Dataset with original images" - Epoch = 10000 - Train_Batch_Size = 64 - Image_Size = 256 + Label_Length = len(Loading_Config["Training_Labels"]) Prepare = Load_Data_Prepare() - loading_data = Load_ImageGenerator(Trainig_Root, Testing_Root, Generator_Root, Labels, Image_Size) - experiment = experiments(Image_Size, Model_Name, Experiment_Name, Epoch, Train_Batch_Size, tool, Label_Length, Status) - image_processing = Read_image_and_Process_image(Image_Size) + Indepentend = Cut_Indepentend_Data(Loading_Config["Train_Data_Root"], Loading_Config["Training_Labels"]) Merge = merge() Calculate_Tool = Calculate() - - counter = 1 - Train_Size = 0 + file = Process_File() - # 讀取資料 - Data_Dict_Data = loading_data.process_main(Label_Length) - Total_Size_List = [] + Train_Size = 0 - for label in Labels: + # 讀取資料 + # 將測試資料獨立出來 + test_size = 0.2 + Indepentend.IndependentData_main(Loading_Config["Test_Data_Root"], test_size) + + # 創建處理切割的部分 + if not file.JudgeRoot_MakeDir(Loading_Config['Annotation_Training_Root']) and not file.JudgeRoot_MakeDir(Loading_Config['Annotation_Testing_Root']): + processor_train = XMLAnnotationProcessor( + dataset_root = Loading_Config["Train_Data_Root"], + ) + processor_test = XMLAnnotationProcessor( + dataset_root = Loading_Config["Test_Data_Root"], + ) + + # 設定自訂樣式(可選) + processor_train.set_drawing_style( + box_color=(0, 0, 255), # 紅色邊界框 + text_color=(255, 255, 255), # 白色文字 + box_thickness=3, + font_scale=0.7 + ) + processor_test.set_drawing_style( + box_color=(0, 0, 255), # 紅色邊界框 + text_color=(255, 255, 255), # 白色文字 + box_thickness=3, + font_scale=0.7 + ) + + print("XML標註處理器已準備就绪") + for Label_Iamge_List in Loading_Config["Label_Image_Labels"]: + if Label_Iamge_List == "CA": + Label = "stomach_cancer_Crop" + else: + Label = "Have_Question_Crop" + training_results = processor_train.process_multiple_xml(f"../Label_Image/{Label_Iamge_List}", Loading_Config['Annotation_Training_Root'], Label) + testing_results = processor_test.process_multiple_xml(f"../Label_Image/{Label_Iamge_List}", Loading_Config['Annotation_Testing_Root'], Label) + + + else: + print("Training and Testing annoation is exist!!!!") + + # 讀取切割完成後的檔案 + print("Mask Ground truth is Finished\n") + Mask_load = Loding_Data_Root(Loading_Config["XML_Loading_Label"], Loading_Config['Annotation_Training_Root'], "") + Mask_Data_Dict_Data = Mask_load.process_main(False) + + Total_Size_Lists = [] + print("Mask資料集總數") + for label in Loading_Config["XML_Loading_Label"]: + Train_Size += len(Mask_Data_Dict_Data[label]) + Total_Size_Lists.append(len(Mask_Data_Dict_Data[label])) + print(f"Labels: {label}, 總數為: {len(Mask_Data_Dict_Data[label])}") + + print("總共有 " + str(Train_Size) + " 筆資料") + + # 讀取原始資料集 + load = Loding_Data_Root(Loading_Config["Training_Labels"], Loading_Config["Train_Data_Root"], Loading_Config["ImageGenerator_Data_Root"]) + Data_Dict_Data = load.process_main(False) + + # 製作資料增強資料 + if not file.Judge_File_Exist(Loading_Config['Image enhance processing save root']): + for label in Loading_Config["Training_Labels"]: + Read_Image_Root_And_Image_Enhance(Data_Dict_Data[label], f"{Loading_Config['Image enhance processing save root']}/{label}") + + tmp_load = Loding_Data_Root(Loading_Config["Training_Labels"], Loading_Config['Image enhance processing save root'], Loading_Config["ImageGenerator_Data_Root"]) + Data_Dict_Data = tmp_load.process_main(False) + + Total_Size_List = [] + print("前處理後資料集總數") + for label in Loading_Config["Training_Labels"]: Train_Size += len(Data_Dict_Data[label]) Total_Size_List.append(len(Data_Dict_Data[label])) print(f"Labels: {label}, 總數為: {len(Data_Dict_Data[label])}") print("總共有 " + str(Train_Size) + " 筆資料") + print(f"要被Mask的資料共有{int(Total_Size_List[0]) + int(Total_Size_List[2])}筆") # 做出跟資料相同數量的Label Classes = [] i = 0 for encording in Encording_Label: - Classes.append(image_processing.make_label_list(Total_Size_List[i], encording)) + Classes.append(make_label_list(Total_Size_List[i], encording)) i += 1 # 將資料做成Dict的資料型態 - Prepare.Set_Final_Dict_Data(Labels, Data_Dict_Data, Classes, Label_Length) + Prepare.Set_Final_Dict_Data(Loading_Config["Training_Labels"], Data_Dict_Data, Classes, Label_Length) Final_Dict_Data = Prepare.Get_Final_Data_Dict() keys = list(Final_Dict_Data.keys()) + Mask_Keys = list(Mask_Data_Dict_Data.keys()) Training_Data = Merge.merge_all_image_data(Final_Dict_Data[keys[0]], Final_Dict_Data[keys[1]]) # 將訓練資料合併成一個list for i in range(2, Label_Length): @@ -81,18 +142,19 @@ if __name__ == "__main__": for i in range(Label_Length + 2, 2 * Label_Length): Training_Label = Merge.merge_all_image_data(Training_Label, Final_Dict_Data[keys[i]]) # 將訓練資料合併成一個list + Training_Mask_Data = Merge.merge_all_image_data(Mask_Data_Dict_Data[Mask_Keys[0]], Mask_Data_Dict_Data[Mask_Keys[1]]) # 將訓練資料合併成一個list + for i in range(2, len(Mask_Keys)): + Training_Mask_Data = Merge.merge_all_image_data(Training_Mask_Data, Mask_Data_Dict_Data[Mask_Keys[i]]) # 將訓練資料合併成一個list + + experiment = experiments( + Xception_Training_Data=Training_Data, + Xception_Training_Label=Training_Label, + Xception_Training_Mask_Data=Training_Mask_Data, + tools=tool, + Number_Of_Classes=Label_Length, + status=Status + ) start = time.time() - # trains_Data_Image = image_processing.Data_Augmentation_Image(training_data) # 讀檔 - # Training_Data, Training_Label = image_processing.image_data_processing(trains_Data_Image, training_label) # 將讀出來的檔做正規化。降label轉成numpy array 格式 - # Training_Data = image_processing.normalization(Training_Data) - - - # Balance_Data = list(zip(Training_Data, Training_Label)) - # Training_Data, Training_Label = Balance_Process(Balance_Data, Total_Size_List) - # training_data = training_data.permute(0, 3, 1, 2) - + experiment.processing_main() # 執行訓練方法 end = time.time() - print("\n\n\n讀取訓練資料(70000)執行時間:%f 秒\n\n" % (end - start)) - - experiment.processing_main(Training_Data, Training_Label) # 執行訓練方法 - \ No newline at end of file + print(f"\n\n\n訓練時間:{end - start} 秒\n\n") \ No newline at end of file diff --git a/merge_class/__pycache__/merge.cpython-311.pyc b/merge_class/__pycache__/merge.cpython-311.pyc index 0051519..fd04b00 100644 Binary files a/merge_class/__pycache__/merge.cpython-311.pyc and b/merge_class/__pycache__/merge.cpython-311.pyc differ diff --git a/merge_class/__pycache__/merge.cpython-312.pyc b/merge_class/__pycache__/merge.cpython-312.pyc new file mode 100644 index 0000000..41ae533 Binary files /dev/null and b/merge_class/__pycache__/merge.cpython-312.pyc differ diff --git a/model_data_processing/__pycache__/__init__.cpython-311.pyc b/model_data_processing/__pycache__/__init__.cpython-311.pyc index 58b90c4..ce5b212 100644 Binary files a/model_data_processing/__pycache__/__init__.cpython-311.pyc and b/model_data_processing/__pycache__/__init__.cpython-311.pyc differ diff --git a/model_data_processing/__pycache__/__init__.cpython-312.pyc b/model_data_processing/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..41a3149 Binary files /dev/null and b/model_data_processing/__pycache__/__init__.cpython-312.pyc differ diff --git a/model_data_processing/__pycache__/processing.cpython-311.pyc b/model_data_processing/__pycache__/processing.cpython-311.pyc index 7173a0f..2562b87 100644 Binary files a/model_data_processing/__pycache__/processing.cpython-311.pyc and b/model_data_processing/__pycache__/processing.cpython-311.pyc differ diff --git a/model_data_processing/__pycache__/processing.cpython-312.pyc b/model_data_processing/__pycache__/processing.cpython-312.pyc new file mode 100644 index 0000000..dfd0095 Binary files /dev/null and b/model_data_processing/__pycache__/processing.cpython-312.pyc differ diff --git a/model_data_processing/__pycache__/processing_for_cut_image.cpython-311.pyc b/model_data_processing/__pycache__/processing_for_cut_image.cpython-311.pyc index 08a73ba..834b383 100644 Binary files a/model_data_processing/__pycache__/processing_for_cut_image.cpython-311.pyc and b/model_data_processing/__pycache__/processing_for_cut_image.cpython-311.pyc differ diff --git a/model_data_processing/__pycache__/processing_for_cut_image.cpython-312.pyc b/model_data_processing/__pycache__/processing_for_cut_image.cpython-312.pyc new file mode 100644 index 0000000..59d62d0 Binary files /dev/null and b/model_data_processing/__pycache__/processing_for_cut_image.cpython-312.pyc differ diff --git a/model_data_processing/processing.py b/model_data_processing/processing.py index f7bd7fa..ddaa5a9 100644 --- a/model_data_processing/processing.py +++ b/model_data_processing/processing.py @@ -1,67 +1,40 @@ -import random -from merge_class.merge import merge +import os +from PIL import Image +from Load_process.file_processing import Process_File +from utils.Stomach_Config import Image_Enhance +def make_label_list(length, content): + '''製作label的列表''' + label_list = [] + for i in range(length): + label_list.append(content) + return label_list -def calculate_confusion_matrix(predict, result): - '''計算並畫出混淆矩陣''' - tp, fp, tn, fn = 0 - for i in range(len(predict)): - if predict[i] == [1., 0., 0.] and result[i] == [1., 0., 0.]: - pass +def Read_Image_Root_And_Image_Enhance(Image_Roots, Save_Root): + '''讀取Image Root''' + i = 0 + file = Process_File() + for Image_Root in Image_Roots: + try: + Images = Image.open(Image_Root).convert("RGB") + Images = Image_Enhance["gamma"](Images, Image_Enhance["Gamma_Value"]) + for j in range(5): + Images = Image_Enhance["Median"](Images) + Images = Image_Enhance["Shapen"](Images) -def shuffle_data(image, label, mode = 1): - ''' - ## 被用來做資料打亂的用途 - ### 有兩種不同的需求 - 1. 打亂影像資料(讀完檔後的影像) => 回傳Label與Image Root兩個List - 2. 打亂路徑資料(影像的路徑資料,還沒讀檔前) => 回傳打亂後的Dict - ''' - if mode == 1: - shuffle_image, shuffle_label = [], [] - - total = list(zip(image, label)) - random.shuffle(total) - - for total_data in total: - shuffle_image.append(total_data[0]) - shuffle_label.append(total_data[1]) - - return shuffle_image, shuffle_label - else: - shuffle_image = { - label[0] : [] - } - - for i in range(1, len(label)): - shuffle_image.update({label[i] : []}) + file.JudgeRoot_MakeDir(Save_Root) + # 使用原始檔名而不是索引編號 + original_filename = os.path.basename(Image_Root) + path = file.Make_Save_Root(original_filename, Save_Root) - for Label in label: - shuffle_image[Label] = image[Label] - random.shuffle(shuffle_image[Label]) + Images.save(path) + # Image = cv2.imread(Image_Root, cv2.IMREAD_COLOR) # 讀檔(彩色) + # Image = cv2.cvtColor(Image, cv2.COLOR_BGR2RGB) + except Exception as e: + print(e) - return shuffle_image - -def Balance_Process(Datas, Size_List): - # Data_Dict_Data = shuffle_data(Data_Content, Labels, 2) - Train_Size, start = 0, 0 - Image_List = [] - Images, Labels = [], [] - Merge = merge() + i += 1 + print("已處理 " + str(i) + " 張圖片") - Train_Size = min(Size_List[0], Size_List[1]) - for i in range(2, len(Size_List), 1): - Train_Size = min(Train_Size, Size_List[i]) - - for i in Size_List: - Image_List.append(Datas[start : Train_Size]) - start = Train_Size - Train_Size += Train_Size - - Image_List = Merge.merge_data_main(Image_List, 0, len(Image_List)) - - for Image in Image_List: - Images.append(Image[0]) - Labels.append(Image[1]) - - return Images, Labels \ No newline at end of file + pass \ No newline at end of file diff --git a/model_data_processing/processing_for_cut_image.py b/model_data_processing/processing_for_cut_image.py index 9b29881..19c01f5 100644 --- a/model_data_processing/processing_for_cut_image.py +++ b/model_data_processing/processing_for_cut_image.py @@ -1,6 +1,5 @@ -from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image +from model_data_processing.processing import make_label_list from sklearn.model_selection import train_test_split -from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image from Load_process.LoadData import Load_Data_Prepare, Process_File, Load_Data_Tools import shutil @@ -13,29 +12,14 @@ class Cut_Indepentend_Data(): Prepare = Load_Data_Prepare() Load_Tool = Load_Data_Tools() Prepare.Set_Data_Content([], len(self.Labels)) - Prepare.Set_Data_Dictionary(self.Labels, Prepare.Get_Data_Content(), 2) + Prepare.Set_Data_Dictionary(self.Labels, Prepare.Get_Data_Content(), len(self.Labels)) Get_Data_Dict_Content = Prepare.Get_Data_Dict() get_all_image_data = Load_Tool.get_data_root(self.Training_Root, Get_Data_Dict_Content, self.Labels) self.Cut_Of_Independent_Data(get_all_image_data, Indepentend_Data_Root, Test_Size) - def Balance_Cut_Of_Independent_Data(self, Independent_Dict_Data_Content, Test_Size): - image_processing = Read_image_and_Process_image(123) - Prepare = Load_Data_Prepare() - Prepare.Set_Data_Content([], len(self.Labels)) - Prepare.Set_Data_Dictionary(self.Labels, Prepare.Get_Data_Content(), 2) - Indepentend_Content = Prepare.Get_Data_Dictionary() - - for cut_TotalTestData_roots_label in self.Labels: # 將資料的label一個一個讀出來 - label = image_processing.make_label_list(len(Independent_Dict_Data_Content[cut_TotalTestData_roots_label]), 0) # 製作假的label - tmp = list(Cut_Data(Independent_Dict_Data_Content[cut_TotalTestData_roots_label], label, Test_Size)) # 切割出特定數量的資料 - Indepentend_Content[cut_TotalTestData_roots_label] = [tmp[0], tmp[1]] - - return Indepentend_Content - def Cut_Of_Independent_Data(self, Independent_Dict_Data_Content, IndependentDataRoot, Test_Size): '''切割獨立資料(e.g. Validation、training)''' - image_processing = Read_image_and_Process_image(122) File = Process_File() i = 0 @@ -44,7 +28,7 @@ class Cut_Indepentend_Data(): root = File.Make_Save_Root(cut_TotalTestData_roots_label, IndependentDataRoot) # 合併成一個路徑 File.Make_Dir(root) # 建檔 - label = image_processing.make_label_list(len(Independent_Dict_Data_Content[cut_TotalTestData_roots_label]), 0) # 製作假的label + label = make_label_list(len(Independent_Dict_Data_Content[cut_TotalTestData_roots_label]), 0) # 製作假的label cut_data = Cut_Data(Independent_Dict_Data_Content[cut_TotalTestData_roots_label], label, Test_Size) # 切割出特定數量的資料 for data in cut_data[1]: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8436f6c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "pytorch" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "matplotlib>=3.10.6", + "opencv-python-headless>=4.12.0.88", + "pandas>=2.3.2", + "scikit-image>=0.25.2", + "scikit-learn>=1.7.1", + "seaborn>=0.13.2", + "timm>=1.0.19", + "torch>=2.8.0", + "torchcam>=0.3.1", + "torchinfo>=1.8.0", + "torchmetrics>=1.8.2", + "torchvision>=0.23.0", + "tqdm>=4.67.1", +] diff --git a/test.ipynb b/test.ipynb index 48d7027..abe6272 100644 --- a/test.ipynb +++ b/test.ipynb @@ -2330,7 +2330,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -2349,16 +2349,1977 @@ "path = \"../Dataset/Training/stomach_cancer_Crop/A01.jpg\"\n", "\n", "with open(path, \"rb\") as f:\n", - " img = Image.open(f)\n", - " img.convert(\"RGB\")\n", + " image = Image.open(f)\n", + " image.convert(\"RGB\")\n", "\n", - " Image._show(img)" + " Image._show(image)" ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['123456789', '123456789', '123456789']" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = \"123456789\"\n", + "\n", + "b = [a for i in range(3)]\n", + "b" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "stomach_cancer_Crop 有 68 筆資料 \n", + "Normal_Crop 有 36 筆資料 \n", + "Have_Question_Crop 有 26 筆資料 \n", + "\n", + "test_labels有130筆資料\n", + "\n" + ] + } + ], + "source": [ + "from draw_tools.Grad_cam import GradCAM\n", + "from Load_process.Load_Indepentend import Load_Indepentend_Data\n", + "from Training_Tools.Tools import Tool\n", + "from experiments.pytorch_Model import ModifiedXception\n", + "from Training_Tools.PreProcess import Training_Precesses\n", + "import torch\n", + "import json\n", + "import torch.nn as nn\n", + "\n", + "model_path = \"../Result/save_the_best_model/Xception Skin trains Stomach Cancer Dataset, and uses WeightRandomSampler apply sharpen/Xception/best_model( 2025-05-13 )-_fold0.pt\"\n", + "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", + "\n", + "with open('utils/Stomach_Config.json') as f:\n", + " config = json.load(f)\n", + "Training_Configs = config['Training_Config']\n", + "Loading_Config = config['Loading_Config']\n", + "\n", + "tool = Tool()\n", + "\n", + "tool.Set_OneHotEncording(Loading_Config[\"Training_Labels\"])\n", + "Encording_Label = tool.Get_OneHot_Encording_Label()\n", + "\n", + "cut_image = Load_Indepentend_Data(Loading_Config[\"Training_Labels\"], Encording_Label)\n", + "\n", + "Model = ModifiedXception(3)\n", + "\n", + "if torch.cuda.device_count() > 1:\n", + " Model = nn.DataParallel(Model)\n", + "\n", + "Model = Model.to(device)\n", + "TargetLayer = Model.base_model.conv4.pointwise\n", + "\n", + "Model.load_state_dict(torch.load(model_path))\n", + "\n", + "# 预处理对象\n", + "preprocess = Training_Precesses(256)\n", + "\n", + "cut_image.process_main(Loading_Config[\"Test_Data_Root\"]) # 呼叫處理test Data與Validation Data的function\n", + "test, test_label = cut_image.test, cut_image.test_label\n", + "\n", + "# 只评估目标类别\n", + "# 转换为PyTorch张量并移动到设备\n", + "test_dataset = preprocess.Setting_DataSet(cut_image.test, cut_image.test_label, \"Transform\")\n", + "test_loader = preprocess.Dataloader_Sampler(test_dataset, 1, False)\n", + "\n", + "Grad = GradCAM(Model, TargetLayer)\n", + "Grad.Processing_Main(test_loader, f\"123456\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "f1\n" + ] + } + ], + "source": [ + "def f1():\n", + " return 1\n", + "\n", + "import json\n", + "with open('utils/Stomach_Config.json') as f:\n", + " config = json.load(f)\n", + "\n", + "a = config[\"a\"]\n", + "\n", + "print(a[\"Function\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "torch.Size([1, 1000])\n" + ] + } + ], + "source": [ + "import torch\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "\n", + "class SeparableConv2d(nn.Module):\n", + " def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=True):\n", + " super(SeparableConv2d, self).__init__()\n", + " self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size=kernel_size, stride=stride,\n", + " padding=padding, groups=in_channels, bias=bias)\n", + " self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1,\n", + " padding=0, bias=bias)\n", + " \n", + " def forward(self, x):\n", + " x = self.depthwise(x)\n", + " x = self.pointwise(x)\n", + " return x\n", + "\n", + "class EntryFlow(nn.Module):\n", + " def __init__(self):\n", + " super(EntryFlow, self).__init__()\n", + " self.conv1 = nn.Conv2d(3, 32, 3, stride=2, padding=1, bias=False)\n", + " self.bn1 = nn.BatchNorm2d(32)\n", + " self.conv2 = nn.Conv2d(32, 64, 3, padding=1, bias=False)\n", + " self.bn2 = nn.BatchNorm2d(64)\n", + " \n", + " self.conv3_residual = nn.Sequential(\n", + " SeparableConv2d(64, 128, 3, padding=1),\n", + " nn.BatchNorm2d(128),\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(128, 128, 3, padding=1),\n", + " nn.BatchNorm2d(128),\n", + " nn.MaxPool2d(3, stride=2, padding=1)\n", + " )\n", + " self.conv3_shortcut = nn.Conv2d(64, 128, 1, stride=2, bias=False)\n", + " self.bn3 = nn.BatchNorm2d(128)\n", + " \n", + " self.conv4_residual = nn.Sequential(\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(128, 256, 3, padding=1),\n", + " nn.BatchNorm2d(256),\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(256, 256, 3, padding=1),\n", + " nn.BatchNorm2d(256),\n", + " nn.MaxPool2d(3, stride=2, padding=1)\n", + " )\n", + " self.conv4_shortcut = nn.Conv2d(128, 256, 1, stride=2, bias=False)\n", + " self.bn4 = nn.BatchNorm2d(256)\n", + " \n", + " self.conv5_residual = nn.Sequential(\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(256, 728, 3, padding=1),\n", + " nn.BatchNorm2d(728),\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(728, 728, 3, padding=1),\n", + " nn.BatchNorm2d(728),\n", + " nn.MaxPool2d(3, stride=2, padding=1)\n", + " )\n", + " self.conv5_shortcut = nn.Conv2d(256, 728, 1, stride=2, bias=False)\n", + " self.bn5 = nn.BatchNorm2d(728)\n", + " \n", + " def forward(self, x):\n", + " x = F.relu(self.bn1(self.conv1(x)))\n", + " x = F.relu(self.bn2(self.conv2(x)))\n", + " \n", + " residual = self.conv3_residual(x)\n", + " shortcut = self.conv3_shortcut(x)\n", + " x = F.relu(self.bn3(residual + shortcut))\n", + " \n", + " residual = self.conv4_residual(x)\n", + " shortcut = self.conv4_shortcut(x)\n", + " x = F.relu(self.bn4(residual + shortcut))\n", + " \n", + " residual = self.conv5_residual(x)\n", + " shortcut = self.conv5_shortcut(x)\n", + " x = F.relu(self.bn5(residual + shortcut))\n", + " return x\n", + "\n", + "class MiddleFlow(nn.Module):\n", + " def __init__(self):\n", + " super(MiddleFlow, self).__init__()\n", + " self.conv_residual = nn.Sequential(\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(728, 728, 3, padding=1),\n", + " nn.BatchNorm2d(728),\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(728, 728, 3, padding=1),\n", + " nn.BatchNorm2d(728),\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(728, 728, 3, padding=1),\n", + " nn.BatchNorm2d(728)\n", + " )\n", + " \n", + " def forward(self, x):\n", + " return self.conv_residual(x) + x\n", + "\n", + "class ExitFlow(nn.Module):\n", + " def __init__(self, num_classes=1000):\n", + " super(ExitFlow, self).__init__()\n", + " self.conv1_residual = nn.Sequential(\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(728, 1024, 3, padding=1),\n", + " nn.BatchNorm2d(1024),\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(1024, 1024, 3, padding=1),\n", + " nn.BatchNorm2d(1024),\n", + " nn.MaxPool2d(3, stride=2, padding=1)\n", + " )\n", + " self.conv1_shortcut = nn.Conv2d(728, 1024, 1, stride=2, bias=False)\n", + " self.bn1 = nn.BatchNorm2d(1024)\n", + " \n", + " self.conv2 = nn.Sequential(\n", + " SeparableConv2d(1024, 1536, 3, padding=1),\n", + " nn.BatchNorm2d(1536),\n", + " nn.ReLU(inplace=True),\n", + " SeparableConv2d(1536, 2048, 3, padding=1),\n", + " nn.BatchNorm2d(2048),\n", + " nn.ReLU(inplace=True)\n", + " )\n", + " self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n", + " self.fc = nn.Linear(2048, num_classes)\n", + " \n", + " def forward(self, x):\n", + " residual = self.conv1_residual(x)\n", + " shortcut = self.conv1_shortcut(x)\n", + " x = F.relu(self.bn1(residual + shortcut))\n", + " \n", + " x = self.conv2(x)\n", + " x = self.avgpool(x)\n", + " x = x.view(x.size(0), -1)\n", + " x = self.fc(x)\n", + " return x\n", + "\n", + "class Xception(nn.Module):\n", + " def __init__(self, num_classes=1000):\n", + " super(Xception, self).__init__()\n", + " self.entry_flow = EntryFlow()\n", + " self.middle_flow = nn.Sequential(*[MiddleFlow() for _ in range(8)])\n", + " self.exit_flow = ExitFlow(num_classes)\n", + " \n", + " def forward(self, x):\n", + " x = self.entry_flow(x)\n", + " x = self.middle_flow(x)\n", + " x = self.exit_flow(x)\n", + " return x\n", + "\n", + "def xception(num_classes=1000):\n", + " return Xception(num_classes)\n", + "\n", + "# Example usage:\n", + "model = xception(num_classes=1000)\n", + "input_tensor = torch.randn(1, 3, 299, 299)\n", + "output = model(input_tensor)\n", + "print(output.shape) # Should be torch.Size([1, 1000])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "完成了0張\n", + "完成了1張\n", + "完成了2張\n", + "完成了3張\n", + "完成了4張\n", + "完成了5張\n", + "完成了6張\n", + "完成了7張\n", + "完成了8張\n", + "完成了9張\n", + "完成了10張\n", + "完成了11張\n", + "完成了12張\n", + "完成了13張\n", + "完成了14張\n", + "完成了15張\n", + "完成了16張\n", + "完成了17張\n", + "完成了18張\n", + "完成了19張\n", + "完成了20張\n", + "完成了21張\n", + "完成了22張\n", + "完成了23張\n", + "完成了24張\n", + "完成了25張\n", + "完成了26張\n", + "完成了27張\n", + "完成了28張\n", + "完成了29張\n", + "完成了30張\n", + "完成了31張\n", + "完成了32張\n", + "完成了33張\n", + "完成了34張\n", + "完成了35張\n", + "完成了36張\n", + "完成了37張\n", + "完成了38張\n", + "完成了39張\n", + "完成了40張\n", + "完成了41張\n", + "完成了42張\n", + "完成了43張\n", + "完成了44張\n", + "完成了45張\n", + "完成了46張\n", + "完成了47張\n", + "完成了48張\n", + "完成了49張\n", + "完成了50張\n", + "完成了51張\n", + "完成了52張\n", + "完成了53張\n", + "完成了54張\n", + "完成了55張\n", + "完成了56張\n", + "完成了57張\n", + "完成了58張\n", + "完成了59張\n", + "完成了60張\n", + "完成了61張\n", + "完成了62張\n", + "完成了63張\n", + "完成了64張\n", + "完成了65張\n", + "完成了66張\n", + "完成了67張\n", + "完成了68張\n", + "完成了69張\n", + "完成了70張\n", + "完成了71張\n", + "完成了72張\n", + "完成了73張\n", + "完成了74張\n", + "完成了75張\n", + "完成了76張\n", + "完成了77張\n", + "完成了78張\n", + "完成了79張\n", + "完成了80張\n", + "完成了81張\n", + "完成了82張\n", + "完成了83張\n", + "完成了84張\n", + "完成了85張\n", + "完成了86張\n", + "完成了87張\n", + "完成了88張\n", + "完成了89張\n", + "完成了90張\n", + "完成了91張\n", + "完成了92張\n", + "完成了93張\n", + "完成了94張\n", + "完成了95張\n", + "完成了96張\n", + "完成了97張\n", + "完成了98張\n", + "完成了99張\n", + "完成了100張\n", + "完成了101張\n", + "完成了102張\n", + "完成了103張\n", + "完成了104張\n", + "完成了105張\n", + "完成了106張\n", + "完成了107張\n", + "完成了108張\n", + "完成了109張\n", + "完成了110張\n", + "完成了111張\n", + "完成了112張\n", + "完成了113張\n", + "完成了114張\n", + "完成了115張\n", + "完成了116張\n", + "完成了117張\n", + "完成了118張\n", + "完成了119張\n", + "完成了120張\n", + "完成了121張\n", + "完成了122張\n", + "完成了123張\n", + "完成了124張\n", + "完成了125張\n", + "完成了126張\n", + "完成了127張\n", + "完成了128張\n", + "完成了129張\n", + "完成了130張\n", + "完成了131張\n", + "完成了132張\n", + "完成了133張\n", + "完成了134張\n", + "完成了135張\n", + "完成了136張\n", + "完成了137張\n", + "完成了138張\n", + "完成了139張\n", + "完成了140張\n", + "完成了141張\n", + "完成了142張\n", + "完成了143張\n", + "完成了144張\n", + "完成了145張\n", + "完成了146張\n", + "完成了147張\n", + "完成了148張\n", + "完成了149張\n", + "完成了150張\n", + "完成了151張\n", + "完成了152張\n", + "完成了153張\n", + "完成了154張\n", + "完成了155張\n", + "完成了156張\n", + "完成了157張\n", + "完成了158張\n", + "完成了159張\n", + "完成了160張\n", + "完成了161張\n", + "完成了162張\n", + "完成了163張\n", + "完成了164張\n", + "完成了165張\n", + "完成了166張\n", + "完成了167張\n", + "完成了168張\n", + "完成了169張\n", + "完成了170張\n", + "完成了171張\n", + "完成了172張\n", + "完成了173張\n", + "完成了174張\n", + "完成了175張\n", + "完成了176張\n", + "完成了177張\n", + "完成了178張\n", + "完成了179張\n", + "完成了180張\n", + "完成了181張\n", + "完成了182張\n", + "完成了183張\n", + "完成了184張\n", + "完成了185張\n", + "完成了186張\n", + "完成了187張\n", + "完成了188張\n", + "完成了189張\n", + "完成了190張\n", + "完成了191張\n", + "完成了192張\n", + "完成了193張\n", + "完成了194張\n", + "完成了195張\n", + "完成了196張\n", + "完成了197張\n", + "完成了198張\n", + "完成了199張\n", + "完成了200張\n", + "完成了201張\n", + "完成了202張\n", + "完成了203張\n", + "完成了204張\n", + "完成了205張\n", + "完成了206張\n", + "完成了207張\n", + "完成了208張\n", + "完成了209張\n", + "完成了210張\n", + "完成了211張\n", + "完成了212張\n", + "完成了213張\n", + "完成了214張\n", + "完成了215張\n", + "完成了216張\n", + "完成了217張\n", + "完成了218張\n", + "完成了219張\n", + "完成了220張\n", + "完成了221張\n", + "完成了222張\n", + "完成了223張\n", + "完成了224張\n", + "完成了225張\n", + "完成了226張\n", + "完成了227張\n", + "完成了228張\n", + "完成了229張\n", + "完成了230張\n", + "完成了231張\n", + "完成了232張\n", + "完成了233張\n", + "完成了234張\n", + "完成了235張\n", + "完成了236張\n", + "完成了237張\n" + ] + } + ], + "source": [ + "import cv2\n", + "import os\n", + "import glob\n", + "\n", + "from utils.Stomach_Config import Loading_Config\n", + "from Load_process.file_processing import Process_File\n", + "from Load_process.LoadData import Load_Data_Tools\n", + "\n", + "file = Process_File()\n", + "Load_Data_Tools = Load_Data_Tools()\n", + "Data_Dict_Data = {\"TestingProcess_Images\" : []}\n", + "\n", + "path = os.path.join(\"../TestProcess_Image\", \"*\")\n", + "path = glob.glob(path)\n", + "Training_Data = path\n", + "\n", + "i = 0\n", + "file.JudgeRoot_MakeDir(Loading_Config[\"Process_Roots\"])\n", + "for Data in Training_Data:\n", + " image = cv2.imread(Data)\n", + " # 轉換為 HSV 色彩空間\n", + " image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n", + " # 方法1:固定閾值二值化\n", + " threshold_value = 100 # 閾值,可以調整\n", + " _, binary = cv2.threshold(image, threshold_value, 255, cv2.THRESH_BINARY)\n", + "\n", + " kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) # 橢圓核\n", + " # closing_ellipse = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel_ellipse)\n", + " # closing_ellipse = cv2.erode(closing_ellipse, kernel_ellipse, iterations=1)\n", + "\n", + " path = file.Make_Save_Root(f\"{i}.png\", Loading_Config[\"Process_Roots\"])\n", + " cv2.imwrite(path, closing_ellipse)\n", + " print(f\"完成了{i}張\")\n", + " i += 1" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import xml.etree.ElementTree as ET\n", + "import cv2\n", + "import os\n", + "import numpy as np\n", + "from typing import List, Dict, Optional, Tuple\n", + "from utils.Stomach_Config import Loading_Config\n", + "\n", + "class XMLAnnotationProcessor:\n", + " \"\"\"\n", + " XML標註檔案處理器\n", + " 專門處理包含bounding box資訊的XML檔案,並在對應圖片上繪製邊界框\n", + " \"\"\"\n", + " \n", + " def __init__(self, dataset_root: str = \"../Dataset/Training\"):\n", + " \"\"\"\n", + " 初始化XML處理器\n", + " \n", + " Args:\n", + " dataset_root: 圖片資料集根目錄\n", + " output_folder: 輸出資料夾\n", + " \"\"\"\n", + " self.dataset_root = dataset_root\n", + " self.box_color = (0, 255, 0) # 綠色邊界框\n", + " self.text_color = (0, 255, 0) # 綠色文字\n", + " self.box_thickness = 2\n", + " self.font_scale = 0.5\n", + " self.font = cv2.FONT_HERSHEY_SIMPLEX\n", + " \n", + " def _ensure_output_folder(self, Save_Root: str) -> None:\n", + " \"\"\"確保輸出資料夾存在\"\"\"\n", + " if not os.path.exists(Save_Root):\n", + " os.makedirs(Save_Root)\n", + " \n", + " def parse_xml(self, xml_file_path: str, Label: str) -> Optional[Dict]:\n", + " \"\"\"\n", + " 解析XML檔案並提取所有相關資訊\n", + " \n", + " Args:\n", + " xml_file_path: XML檔案路徑\n", + " \n", + " Returns:\n", + " Dict: 包含檔案資訊和bounding box的字典,解析失敗時返回None\n", + " \"\"\"\n", + " try:\n", + " tree = ET.parse(xml_file_path)\n", + " root = tree.getroot()\n", + " \n", + " # 提取基本資訊\n", + " filename_element = root.find('filename')\n", + " \n", + " if filename_element is None:\n", + " print(f\"找不到path元素在 {xml_file_path}\")\n", + " return None\n", + " \n", + " filename = filename_element.text if filename_element is not None else \"Unknown\"\n", + " Original_Image_Data_Root = os.path.join(self.dataset_root, Label)\n", + " Original_Image_Data_Root = os.path.join(Original_Image_Data_Root, filename)\n", + " \n", + " # 提取圖片尺寸\n", + " size_element = root.find('size')\n", + " width = int(size_element.find('width').text) if size_element is not None else 0\n", + " height = int(size_element.find('height').text) if size_element is not None else 0\n", + " depth = int(size_element.find('depth').text) if size_element is not None else 3\n", + " \n", + " # 提取所有bounding box\n", + " bounding_boxes = []\n", + " objects = root.findall('object')\n", + " \n", + " for obj in objects:\n", + " bndbox = obj.find('bndbox')\n", + " if bndbox is not None:\n", + " bbox_info = {\n", + " 'name': obj.find('name').text if obj.find('name') is not None else \"Unknown\",\n", + " 'pose': obj.find('pose').text if obj.find('pose') is not None else \"Unspecified\",\n", + " 'truncated': int(obj.find('truncated').text) if obj.find('truncated') is not None else 0,\n", + " 'difficult': int(obj.find('difficult').text) if obj.find('difficult') is not None else 0,\n", + " 'xmin': int(bndbox.find('xmin').text),\n", + " 'ymin': int(bndbox.find('ymin').text),\n", + " 'xmax': int(bndbox.find('xmax').text),\n", + " 'ymax': int(bndbox.find('ymax').text)\n", + " }\n", + " bounding_boxes.append(bbox_info)\n", + " \n", + " return {\n", + " 'filename': filename,\n", + " 'image_path': Original_Image_Data_Root,\n", + " 'width': width,\n", + " 'height': height,\n", + " 'depth': depth,\n", + " 'bounding_boxes': bounding_boxes\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"解析XML檔案 {xml_file_path} 時發生錯誤: {str(e)}\")\n", + " return None\n", + " \n", + " def load_image(self, image_path: str) -> Optional[np.ndarray]:\n", + " \"\"\"\n", + " 載入圖片檔案\n", + " \n", + " Args:\n", + " image_path: 圖片檔案路徑\n", + " \n", + " Returns:\n", + " np.ndarray: 圖片陣列,載入失敗時返回None\n", + " \"\"\"\n", + " if not os.path.exists(image_path):\n", + " print(f\"圖片檔案不存在: {image_path}\")\n", + " return None\n", + " \n", + " image = cv2.imread(image_path)\n", + " if image is None:\n", + " print(f\"無法讀取圖片: {image_path}\")\n", + " return None\n", + " \n", + " return image\n", + " \n", + " def draw_bounding_boxes(self, image: np.ndarray, bounding_boxes: List[Dict]) -> np.ndarray:\n", + " \"\"\"\n", + " 創建遮罩圖片:bounding box內為白色,外部為黑色\n", + " \n", + " Args:\n", + " image: 圖片陣列\n", + " bounding_boxes: bounding box資訊列表\n", + " \n", + " Returns:\n", + " np.ndarray: 處理後的遮罩圖片陣列\n", + " \"\"\"\n", + " # 創建黑色背景圖片\n", + " height, width = image.shape[:2]\n", + " result_image = np.zeros((height, width, 3), dtype=np.uint8)\n", + " \n", + " for i, bbox in enumerate(bounding_boxes):\n", + " xmin, ymin = bbox['xmin'], bbox['ymin']\n", + " xmax, ymax = bbox['xmax'], bbox['ymax']\n", + " object_name = bbox['name']\n", + " \n", + " # 確保座標在圖片範圍內\n", + " xmin = max(0, min(xmin, width-1))\n", + " ymin = max(0, min(ymin, height-1))\n", + " xmax = max(0, min(xmax, width-1))\n", + " ymax = max(0, min(ymax, height-1))\n", + " \n", + " # 將bounding box範圍內設為白色 (255, 255, 255)\n", + " result_image[ymin:ymax, xmin:xmax] = [255, 255, 255]\n", + " \n", + " print(f\"Object {i+1}: {object_name} - 座標: ({xmin}, {ymin}, {xmax}, {ymax})\")\n", + " \n", + " return result_image\n", + " \n", + " def save_annotated_image(self, image: np.ndarray, original_filename: str, Label : str) -> str:\n", + " \"\"\"\n", + " 儲存標註後的圖片\n", + " \n", + " Args:\n", + " image: 標註後的圖片陣列\n", + " original_filename: 原始檔案名稱\n", + " \n", + " Returns:\n", + " str: 儲存的檔案路徑\n", + " \"\"\"\n", + " output_filename = f\"annotated_{original_filename}\"\n", + " output_path = os.path.join(Loading_Config[\"Annotation_Root\"], Label)\n", + " Save_Image_Roots = os.path.join(output_path, output_filename)\n", + " # 確保輸出資料夾存在\n", + " self._ensure_output_folder(output_path)\n", + "\n", + " cv2.imwrite(Save_Image_Roots, image)\n", + " print(f\"已儲存標註圖片至: {Save_Image_Roots}\")\n", + " return Save_Image_Roots\n", + " \n", + " def process_single_xml(self, xml_file_path: str, Label : str) -> Optional[Tuple[np.ndarray, str]]:\n", + " \"\"\"\n", + " 處理單一XML檔案\n", + " \n", + " Args:\n", + " xml_file_path: XML檔案路徑\n", + " \n", + " Returns:\n", + " Tuple[np.ndarray, str]: (標註後的圖片, 輸出路徑),處理失敗時返回None\n", + " \"\"\"\n", + " # 解析XML\n", + " xml_data = self.parse_xml(xml_file_path, Label)\n", + " if xml_data is None:\n", + " return None\n", + " \n", + " # 載入圖片\n", + " image = self.load_image(xml_data['image_path'])\n", + " if image is None:\n", + " return None\n", + " \n", + " # 繪製bounding box\n", + " annotated_image = self.draw_bounding_boxes(image, xml_data['bounding_boxes'])\n", + " \n", + " # 儲存結果\n", + " output_path = self.save_annotated_image(annotated_image, xml_data['filename'], Label)\n", + " \n", + " return annotated_image, output_path\n", + " \n", + " def process_multiple_xml(self, xml_folder_path: str, Label : str) -> List[Tuple[str, bool]]:\n", + " \"\"\"\n", + " 批量處理多個XML檔案\n", + " \n", + " Args:\n", + " xml_folder_path: 包含XML檔案的資料夾路徑\n", + " \n", + " Returns:\n", + " List[Tuple[str, bool]]: [(檔案名稱, 處理成功與否), ...]\n", + " \"\"\"\n", + " if not os.path.exists(xml_folder_path):\n", + " print(f\"XML資料夾不存在: {xml_folder_path}\")\n", + " return []\n", + " \n", + " xml_files = [f for f in os.listdir(xml_folder_path) if f.endswith('.xml')]\n", + " \n", + " if not xml_files:\n", + " print(f\"在 {xml_folder_path} 中找不到XML檔案\")\n", + " return []\n", + " \n", + " print(f\"找到 {len(xml_files)} 個XML檔案\")\n", + " for xml_file in xml_files: \n", + " try:\n", + " Read_XML_File = os.path.join(xml_folder_path, xml_file)\n", + " self.process_single_xml(Read_XML_File, Label)\n", + " print(f\"\\n處理檔案: {xml_file}\")\n", + " except Exception as e:\n", + " print(f\"處理 {xml_file} 時發生錯誤: {str(e)}\")\n", + " return\n", + " \n", + " def get_bounding_boxes_info(self, xml_file_path: str) -> Optional[Dict]:\n", + " \"\"\"\n", + " 僅提取XML中的bounding box資訊,不進行圖片處理\n", + " \n", + " Args:\n", + " xml_file_path: XML檔案路徑\n", + " \n", + " Returns:\n", + " Dict: 包含檔案資訊和bounding box座標的字典\n", + " \"\"\"\n", + " return self.parse_xml(xml_file_path)\n", + " \n", + " def set_drawing_style(self, box_color: Tuple[int, int, int] = None, \n", + " text_color: Tuple[int, int, int] = None,\n", + " box_thickness: int = None, \n", + " font_scale: float = None) -> None:\n", + " \"\"\"\n", + " 設定繪圖樣式\n", + " \n", + " Args:\n", + " box_color: 邊界框顏色 (B, G, R)\n", + " text_color: 文字顏色 (B, G, R)\n", + " box_thickness: 邊界框粗細\n", + " font_scale: 字體大小\n", + " \"\"\"\n", + " if box_color is not None:\n", + " self.box_color = box_color\n", + " if text_color is not None:\n", + " self.text_color = text_color\n", + " if box_thickness is not None:\n", + " self.box_thickness = box_thickness\n", + " if font_scale is not None:\n", + " self.font_scale = font_scale" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "XML標註處理器已準備就绪\n", + "找到 232 個XML檔案\n", + "Object 1: Have_Question - 座標: (263, 740, 333, 814)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a00.jpg\n", + "\n", + "處理檔案: a00.xml\n", + "Object 1: Have_Question - 座標: (406, 206, 608, 368)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1000.jpg\n", + "\n", + "處理檔案: A1000.xml\n", + "Object 1: Have_Question - 座標: (740, 330, 819, 401)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1002.jpg\n", + "\n", + "處理檔案: A1002.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A1010.jpg\n", + "\n", + "處理檔案: A1010.xml\n", + "Object 1: Have_Question - 座標: (349, 655, 474, 790)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1012.jpg\n", + "\n", + "處理檔案: A1012.xml\n", + "Object 1: Have_Question - 座標: (707, 89, 1206, 746)\n", + "Object 2: Have_Question - 座標: (100, 199, 410, 816)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1020.jpg\n", + "\n", + "處理檔案: A1020.xml\n", + "Object 1: Have_Question - 座標: (720, 391, 769, 439)\n", + "Object 2: Have_Question - 座標: (833, 621, 969, 733)\n", + "Object 3: Have_Question - 座標: (337, 595, 735, 900)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1021.jpg\n", + "\n", + "處理檔案: A1021.xml\n", + "Object 1: Have_Question - 座標: (227, 103, 518, 213)\n", + "Object 2: Have_Question - 座標: (264, 560, 423, 667)\n", + "Object 3: Have_Question - 座標: (423, 724, 522, 810)\n", + "Object 4: Have_Question - 座標: (686, 652, 903, 967)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1030.jpg\n", + "\n", + "處理檔案: A1030.xml\n", + "Object 1: Have_Question - 座標: (134, 266, 342, 414)\n", + "Object 2: Have_Question - 座標: (308, 539, 486, 676)\n", + "Object 3: Have_Question - 座標: (794, 479, 976, 669)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1031.jpg\n", + "\n", + "處理檔案: A1031.xml\n", + "Object 1: Have_Question - 座標: (396, 213, 905, 977)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A10310.jpg\n", + "\n", + "處理檔案: A10310.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A1032.jpg\n", + "\n", + "處理檔案: A1032.xml\n", + "Object 1: Have_Question - 座標: (126, 440, 190, 522)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1033.jpg\n", + "\n", + "處理檔案: A1033.xml\n", + "Object 1: Have_Question - 座標: (301, 379, 484, 527)\n", + "Object 2: Have_Question - 座標: (642, 484, 771, 615)\n", + "Object 3: Have_Question - 座標: (642, 772, 700, 828)\n", + "Object 4: Have_Question - 座標: (495, 836, 538, 871)\n", + "Object 5: Have_Question - 座標: (754, 866, 858, 943)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1034.jpg\n", + "\n", + "處理檔案: A1034.xml\n", + "Object 1: Have_Question - 座標: (375, 423, 629, 543)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1036.jpg\n", + "\n", + "處理檔案: A1036.xml\n", + "Object 1: Have_Question - 座標: (260, 360, 782, 748)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1037.jpg\n", + "\n", + "處理檔案: A1037.xml\n", + "Object 1: Have_Question - 座標: (764, 305, 901, 433)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1040.jpg\n", + "\n", + "處理檔案: A1040.xml\n", + "Object 1: Have_Question - 座標: (336, 622, 681, 930)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1051.jpg\n", + "\n", + "處理檔案: A1051.xml\n", + "Object 1: Have_Question - 座標: (32, 212, 441, 979)\n", + "Object 2: Have_Question - 座標: (536, 578, 769, 777)\n", + "Object 3: Have_Question - 座標: (981, 819, 1035, 858)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1052.jpg\n", + "\n", + "處理檔案: A1052.xml\n", + "Object 1: Have_Question - 座標: (28, 379, 614, 605)\n", + "Object 2: Have_Question - 座標: (1007, 285, 1190, 503)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1061.jpg\n", + "\n", + "處理檔案: A1061.xml\n", + "Object 1: Have_Question - 座標: (399, 236, 492, 333)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1070.jpg\n", + "\n", + "處理檔案: A1070.xml\n", + "Object 1: Have_Question - 座標: (395, 269, 664, 391)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1071.jpg\n", + "\n", + "處理檔案: A1071.xml\n", + "Object 1: Have_Question - 座標: (207, 330, 777, 726)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1072.jpg\n", + "\n", + "處理檔案: A1072.xml\n", + "Object 1: Have_Question - 座標: (160, 221, 218, 323)\n", + "Object 2: Have_Question - 座標: (140, 567, 221, 654)\n", + "Object 3: Have_Question - 座標: (495, 513, 663, 799)\n", + "Object 4: Have_Question - 座標: (600, 353, 797, 477)\n", + "Object 5: Have_Question - 座標: (735, 117, 793, 168)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1075.jpg\n", + "\n", + "處理檔案: A1075.xml\n", + "Object 1: Have_Question - 座標: (590, 478, 765, 612)\n", + "Object 2: Have_Question - 座標: (198, 607, 522, 845)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1080.jpg\n", + "\n", + "處理檔案: A1080.xml\n", + "Object 1: Have_Question - 座標: (805, 545, 963, 624)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a110.jpg\n", + "\n", + "處理檔案: a110.xml\n", + "Object 1: Have_Question - 座標: (427, 194, 472, 243)\n", + "Object 2: Have_Question - 座標: (863, 42, 970, 257)\n", + "Object 3: Have_Question - 座標: (595, 555, 791, 714)\n", + "Object 4: Have_Question - 座標: (648, 786, 747, 875)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1100.jpg\n", + "\n", + "處理檔案: A1100.xml\n", + "Object 1: Have_Question - 座標: (964, 71, 1131, 187)\n", + "Object 2: Have_Question - 座標: (774, 600, 861, 698)\n", + "Object 3: Have_Question - 座標: (756, 770, 849, 861)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1102.jpg\n", + "\n", + "處理檔案: A1102.xml\n", + "Object 1: Have_Question - 座標: (185, 685, 394, 825)\n", + "Object 2: Have_Question - 座標: (710, 482, 823, 550)\n", + "Object 3: Have_Question - 座標: (872, 657, 938, 725)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1103.jpg\n", + "\n", + "處理檔案: A1103.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A1104.jpg\n", + "\n", + "處理檔案: A1104.xml\n", + "Object 1: Have_Question - 座標: (721, 145, 1116, 581)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A1110.jpg\n", + "\n", + "處理檔案: A1110.xml\n", + "Object 1: Have_Question - 座標: (737, 198, 1036, 776)\n", + "Object 2: Have_Question - 座標: (499, 132, 608, 224)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a113.jpg\n", + "\n", + "處理檔案: a113.xml\n", + "Object 1: Have_Question - 座標: (385, 258, 928, 688)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a120.jpg\n", + "\n", + "處理檔案: a120.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a130.jpg\n", + "\n", + "處理檔案: a130.xml\n", + "Object 1: Have_Question - 座標: (297, 136, 773, 624)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a132.jpg\n", + "\n", + "處理檔案: a132.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a140.jpg\n", + "\n", + "處理檔案: a140.xml\n", + "Object 1: Have_Question - 座標: (855, 281, 1031, 667)\n", + "Object 2: Have_Question - 座標: (650, 664, 928, 900)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a144.jpg\n", + "\n", + "處理檔案: a144.xml\n", + "Object 1: Have_Question - 座標: (480, 290, 775, 513)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a150.jpg\n", + "\n", + "處理檔案: a150.xml\n", + "Object 1: Have_Question - 座標: (329, 180, 610, 435)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a172.jpg\n", + "\n", + "處理檔案: a172.xml\n", + "Object 1: Have_Question - 座標: (340, 399, 553, 563)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a180.jpg\n", + "\n", + "處理檔案: a180.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a181.jpg\n", + "\n", + "處理檔案: a181.xml\n", + "Object 1: Have_Question - 座標: (648, 280, 946, 569)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a182.jpg\n", + "\n", + "處理檔案: a182.xml\n", + "Object 1: Have_Question - 座標: (314, 770, 528, 953)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a184.jpg\n", + "\n", + "處理檔案: a184.xml\n", + "Object 1: Have_Question - 座標: (446, 468, 629, 640)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a190.jpg\n", + "\n", + "處理檔案: a190.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a200.jpg\n", + "\n", + "處理檔案: a200.xml\n", + "Object 1: Have_Question - 座標: (709, 777, 861, 909)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a211.jpg\n", + "\n", + "處理檔案: a211.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a233.jpg\n", + "\n", + "處理檔案: a233.xml\n", + "Object 1: Have_Question - 座標: (68, 108, 571, 624)\n", + "Object 2: Have_Question - 座標: (761, 473, 916, 670)\n", + "Object 3: Have_Question - 座標: (635, 886, 750, 978)\n", + "Object 4: Have_Question - 座標: (897, 960, 968, 1030)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a2410.jpg\n", + "\n", + "處理檔案: a2410.xml\n", + "Object 1: Have_Question - 座標: (916, 89, 1429, 411)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a249.jpg\n", + "\n", + "處理檔案: a249.xml\n", + "Object 1: Have_Question - 座標: (374, 477, 442, 546)\n", + "Object 2: Have_Question - 座標: (529, 431, 599, 505)\n", + "Object 3: Have_Question - 座標: (726, 415, 783, 525)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a2513.jpg\n", + "\n", + "處理檔案: a2513.xml\n", + "Object 1: Have_Question - 座標: (504, 416, 625, 480)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a30.jpg\n", + "\n", + "處理檔案: a30.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a300.jpg\n", + "\n", + "處理檔案: a300.xml\n", + "Object 1: Have_Question - 座標: (764, 487, 1269, 742)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a301.jpg\n", + "\n", + "處理檔案: a301.xml\n", + "Object 1: Have_Question - 座標: (516, 594, 1626, 1047)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a302.jpg\n", + "\n", + "處理檔案: a302.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a303.jpg\n", + "\n", + "處理檔案: a303.xml\n", + "Object 1: Have_Question - 座標: (628, 394, 844, 574)\n", + "Object 2: Have_Question - 座標: (1252, 405, 1327, 487)\n", + "Object 3: Have_Question - 座標: (706, 625, 1272, 980)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a316.jpg\n", + "\n", + "處理檔案: a316.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a360.jpg\n", + "\n", + "處理檔案: a360.xml\n", + "Object 1: Have_Question - 座標: (816, 122, 913, 195)\n", + "Object 2: Have_Question - 座標: (822, 262, 1023, 549)\n", + "Object 3: Have_Question - 座標: (881, 589, 942, 712)\n", + "Object 4: Have_Question - 座標: (738, 778, 796, 826)\n", + "Object 5: Have_Question - 座標: (332, 822, 637, 912)\n", + "Object 6: Have_Question - 座標: (687, 927, 823, 981)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a3611.jpg\n", + "\n", + "處理檔案: a3611.xml\n", + "Object 1: Have_Question - 座標: (962, 457, 1093, 640)\n", + "Object 2: Have_Question - 座標: (918, 796, 1176, 1034)\n", + "Object 3: Have_Question - 座標: (610, 935, 700, 1045)\n", + "Object 4: Have_Question - 座標: (602, 39, 710, 188)\n", + "Object 5: Have_Question - 座標: (385, 196, 476, 319)\n", + "Object 6: Have_Question - 座標: (867, 50, 1104, 343)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a3612.jpg\n", + "\n", + "處理檔案: a3612.xml\n", + "Object 1: Have_Question - 座標: (235, 364, 931, 912)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a3613.jpg\n", + "\n", + "處理檔案: a3613.xml\n", + "Object 1: Have_Question - 座標: (373, 254, 686, 406)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a3615.jpg\n", + "\n", + "處理檔案: a3615.xml\n", + "Object 1: Have_Question - 座標: (414, 246, 673, 388)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a3616.jpg\n", + "\n", + "處理檔案: a3616.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a364.jpg\n", + "\n", + "處理檔案: a364.xml\n", + "Object 1: Have_Question - 座標: (1213, 42, 1341, 150)\n", + "Object 2: Have_Question - 座標: (1174, 171, 1549, 382)\n", + "Object 3: Have_Question - 座標: (1219, 379, 1566, 596)\n", + "Object 4: Have_Question - 座標: (1086, 696, 1404, 826)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a365.jpg\n", + "\n", + "處理檔案: a365.xml\n", + "Object 1: Have_Question - 座標: (1542, 266, 1654, 407)\n", + "Object 2: Have_Question - 座標: (1151, 845, 1224, 922)\n", + "Object 3: Have_Question - 座標: (813, 931, 952, 1011)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a367.jpg\n", + "\n", + "處理檔案: a367.xml\n", + "Object 1: Have_Question - 座標: (1058, 337, 1152, 607)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a368.jpg\n", + "\n", + "處理檔案: a368.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a375.jpg\n", + "\n", + "處理檔案: a375.xml\n", + "Object 1: Have_Question - 座標: (132, 460, 354, 685)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a4710.jpg\n", + "\n", + "處理檔案: a4710.xml\n", + "Object 1: Have_Question - 座標: (90, 560, 328, 797)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a4711.jpg\n", + "\n", + "處理檔案: a4711.xml\n", + "Object 1: Have_Question - 座標: (132, 355, 366, 515)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a4712.jpg\n", + "\n", + "處理檔案: a4712.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a473.jpg\n", + "\n", + "處理檔案: a473.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a474.jpg\n", + "\n", + "處理檔案: a474.xml\n", + "Object 1: Have_Question - 座標: (622, 496, 696, 564)\n", + "Object 2: Have_Question - 座標: (1053, 678, 1162, 759)\n", + "Object 3: Have_Question - 座標: (715, 533, 1004, 761)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a4911.jpg\n", + "\n", + "處理檔案: a4911.xml\n", + "Object 1: Have_Question - 座標: (517, 570, 736, 712)\n", + "Object 2: Have_Question - 座標: (676, 723, 802, 798)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a4912.jpg\n", + "\n", + "處理檔案: a4912.xml\n", + "Object 1: Have_Question - 座標: (809, 234, 1010, 421)\n", + "Object 2: Have_Question - 座標: (383, 678, 448, 743)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a497.jpg\n", + "\n", + "處理檔案: a497.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a50.jpg\n", + "\n", + "處理檔案: a50.xml\n", + "Object 1: Have_Question - 座標: (117, 195, 708, 978)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a509.jpg\n", + "\n", + "處理檔案: a509.xml\n", + "Object 1: Have_Question - 座標: (413, 539, 567, 622)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a511.jpg\n", + "\n", + "處理檔案: a511.xml\n", + "Object 1: Have_Question - 座標: (528, 121, 581, 171)\n", + "Object 2: Have_Question - 座標: (885, 276, 964, 354)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a513.jpg\n", + "\n", + "處理檔案: a513.xml\n", + "Object 1: Have_Question - 座標: (978, 458, 1151, 664)\n", + "Object 2: Have_Question - 座標: (672, 873, 849, 1043)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a516.jpg\n", + "\n", + "處理檔案: a516.xml\n", + "Object 1: Have_Question - 座標: (201, 101, 904, 499)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a520.jpg\n", + "\n", + "處理檔案: a520.xml\n", + "Object 1: Have_Question - 座標: (46, 107, 746, 687)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a521.jpg\n", + "\n", + "處理檔案: a521.xml\n", + "Object 1: Have_Question - 座標: (921, 779, 1014, 860)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a523.jpg\n", + "\n", + "處理檔案: a523.xml\n", + "Object 1: Have_Question - 座標: (386, 101, 745, 377)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a524.jpg\n", + "\n", + "處理檔案: a524.xml\n", + "Object 1: Have_Question - 座標: (89, 619, 357, 827)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a526.jpg\n", + "\n", + "處理檔案: a526.xml\n", + "Object 1: Have_Question - 座標: (164, 110, 757, 294)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a5312.jpg\n", + "\n", + "處理檔案: a5312.xml\n", + "Object 1: Have_Question - 座標: (62, 368, 390, 731)\n", + "Object 2: Have_Question - 座標: (231, 276, 849, 446)\n", + "Object 3: Have_Question - 座標: (735, 337, 1025, 907)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a5314.jpg\n", + "\n", + "處理檔案: a5314.xml\n", + "Object 1: Have_Question - 座標: (260, 242, 478, 469)\n", + "Object 2: Have_Question - 座標: (736, 448, 919, 607)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a536.jpg\n", + "\n", + "處理檔案: a536.xml\n", + "Object 1: Have_Question - 座標: (163, 127, 273, 327)\n", + "Object 2: Have_Question - 座標: (470, 230, 576, 315)\n", + "Object 3: Have_Question - 座標: (827, 134, 925, 213)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a538.jpg\n", + "\n", + "處理檔案: a538.xml\n", + "Object 1: Have_Question - 座標: (176, 401, 455, 689)\n", + "Object 2: Have_Question - 座標: (556, 331, 632, 416)\n", + "Object 3: Have_Question - 座標: (746, 267, 803, 340)\n", + "Object 4: Have_Question - 座標: (567, 459, 632, 519)\n", + "Object 5: Have_Question - 座標: (794, 391, 858, 461)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a539.jpg\n", + "\n", + "處理檔案: a539.xml\n", + "Object 1: Have_Question - 座標: (400, 369, 452, 421)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a551.jpg\n", + "\n", + "處理檔案: a551.xml\n", + "Object 1: Have_Question - 座標: (324, 124, 836, 389)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a5511.jpg\n", + "\n", + "處理檔案: a5511.xml\n", + "Object 1: Have_Question - 座標: (46, 763, 177, 896)\n", + "Object 2: Have_Question - 座標: (392, 662, 587, 971)\n", + "Object 3: Have_Question - 座標: (635, 160, 1000, 972)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a560.jpg\n", + "\n", + "處理檔案: a560.xml\n", + "Object 1: Have_Question - 座標: (36, 399, 435, 712)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a563.jpg\n", + "\n", + "處理檔案: a563.xml\n", + "Object 1: Have_Question - 座標: (306, 617, 342, 675)\n", + "Object 2: Have_Question - 座標: (758, 581, 824, 658)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a571.jpg\n", + "\n", + "處理檔案: a571.xml\n", + "Object 1: Have_Question - 座標: (215, 288, 743, 762)\n", + "Object 2: Have_Question - 座標: (809, 423, 931, 681)\n", + "Object 3: Have_Question - 座標: (734, 830, 815, 972)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a590.jpg\n", + "\n", + "處理檔案: a590.xml\n", + "Object 1: Have_Question - 座標: (353, 93, 788, 428)\n", + "Object 2: Have_Question - 座標: (698, 406, 837, 641)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a591.jpg\n", + "\n", + "處理檔案: a591.xml\n", + "Object 1: Have_Question - 座標: (231, 215, 659, 603)\n", + "Object 2: Have_Question - 座標: (662, 250, 920, 770)\n", + "Object 3: Have_Question - 座標: (425, 775, 920, 984)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a592.jpg\n", + "\n", + "處理檔案: a592.xml\n", + "Object 1: Have_Question - 座標: (400, 509, 597, 800)\n", + "Object 2: Have_Question - 座標: (492, 801, 599, 981)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a593.jpg\n", + "\n", + "處理檔案: a593.xml\n", + "Object 1: Have_Question - 座標: (345, 379, 621, 684)\n", + "Object 2: Have_Question - 座標: (630, 433, 906, 869)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a594.jpg\n", + "\n", + "處理檔案: a594.xml\n", + "Object 1: Have_Question - 座標: (239, 317, 371, 431)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a60.jpg\n", + "\n", + "處理檔案: a60.xml\n", + "Object 1: Have_Question - 座標: (266, 567, 609, 873)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a61.jpg\n", + "\n", + "處理檔案: a61.xml\n", + "Object 1: Have_Question - 座標: (365, 69, 539, 135)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a610.jpg\n", + "\n", + "處理檔案: a610.xml\n", + "Object 1: Have_Question - 座標: (175, 577, 246, 636)\n", + "Object 2: Have_Question - 座標: (613, 54, 698, 140)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a613.jpg\n", + "\n", + "處理檔案: a613.xml\n", + "Object 1: Have_Question - 座標: (439, 325, 584, 441)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a614.jpg\n", + "\n", + "處理檔案: a614.xml\n", + "Object 1: Have_Question - 座標: (94, 560, 200, 675)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a616.jpg\n", + "\n", + "處理檔案: a616.xml\n", + "Object 1: Have_Question - 座標: (461, 489, 674, 640)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a617.jpg\n", + "\n", + "處理檔案: a617.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a62.jpg\n", + "\n", + "處理檔案: a62.xml\n", + "Object 1: Have_Question - 座標: (622, 190, 848, 413)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a622.jpg\n", + "\n", + "處理檔案: a622.xml\n", + "Object 1: Have_Question - 座標: (608, 764, 783, 932)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a625.jpg\n", + "\n", + "處理檔案: a625.xml\n", + "Object 1: Have_Question - 座標: (12, 578, 185, 823)\n", + "Object 2: Have_Question - 座標: (186, 764, 894, 981)\n", + "Object 3: Have_Question - 座標: (458, 287, 839, 767)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a626.jpg\n", + "\n", + "處理檔案: a626.xml\n", + "Object 1: Have_Question - 座標: (52, 461, 174, 626)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a632.jpg\n", + "\n", + "處理檔案: a632.xml\n", + "Object 1: Have_Question - 座標: (201, 230, 693, 507)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a64.jpg\n", + "\n", + "處理檔案: a64.xml\n", + "Object 1: Have_Question - 座標: (418, 559, 534, 768)\n", + "Object 2: Have_Question - 座標: (515, 398, 645, 455)\n", + "Object 3: Have_Question - 座標: (354, 427, 373, 445)\n", + "Object 4: Have_Question - 座標: (206, 599, 229, 625)\n", + "Object 5: Have_Question - 座標: (279, 75, 369, 115)\n", + "Object 6: Have_Question - 座標: (509, 36, 816, 200)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a641.jpg\n", + "\n", + "處理檔案: a641.xml\n", + "Object 1: Have_Question - 座標: (320, 228, 489, 757)\n", + "Object 2: Have_Question - 座標: (490, 43, 1172, 758)\n", + "Object 3: Have_Question - 座標: (49, 387, 315, 814)\n", + "Object 4: Have_Question - 座標: (195, 32, 319, 301)\n", + "Object 5: Have_Question - 座標: (8, 173, 109, 362)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a6410.jpg\n", + "\n", + "處理檔案: a6410.xml\n", + "Object 1: Have_Question - 座標: (37, 560, 1055, 1014)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a6411.jpg\n", + "\n", + "處理檔案: a6411.xml\n", + "Object 1: Have_Question - 座標: (652, 234, 840, 469)\n", + "Object 2: Have_Question - 座標: (1132, 525, 1159, 578)\n", + "Object 3: Have_Question - 座標: (726, 55, 816, 101)\n", + "Object 4: Have_Question - 座標: (505, 159, 523, 181)\n", + "Object 5: Have_Question - 座標: (369, 393, 397, 408)\n", + "Object 6: Have_Question - 座標: (723, 516, 891, 775)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a645.jpg\n", + "\n", + "處理檔案: a645.xml\n", + "Object 1: Have_Question - 座標: (193, 271, 358, 424)\n", + "Object 2: Have_Question - 座標: (411, 646, 524, 768)\n", + "Object 3: Have_Question - 座標: (347, 938, 569, 1036)\n", + "Object 4: Have_Question - 座標: (693, 640, 886, 933)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a647.jpg\n", + "\n", + "處理檔案: a647.xml\n", + "Object 1: Have_Question - 座標: (837, 263, 1081, 477)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a648.jpg\n", + "\n", + "處理檔案: a648.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\a649.jpg\n", + "\n", + "處理檔案: a649.xml\n", + "Object 1: Have_Question - 座標: (96, 176, 710, 463)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a650.jpg\n", + "\n", + "處理檔案: a650.xml\n", + "Object 1: Have_Question - 座標: (278, 464, 393, 673)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a654.jpg\n", + "\n", + "處理檔案: a654.xml\n", + "Object 1: Have_Question - 座標: (277, 197, 377, 266)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a655.jpg\n", + "\n", + "處理檔案: a655.xml\n", + "Object 1: Have_Question - 座標: (213, 599, 427, 700)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A697.jpg\n", + "\n", + "處理檔案: A697.xml\n", + "Object 1: Have_Question - 座標: (443, 260, 784, 872)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A698.jpg\n", + "\n", + "處理檔案: A698.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A700.jpg\n", + "\n", + "處理檔案: A700.xml\n", + "Object 1: Have_Question - 座標: (464, 187, 773, 435)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A702.jpg\n", + "\n", + "處理檔案: A702.xml\n", + "Object 1: Have_Question - 座標: (420, 166, 929, 363)\n", + "Object 2: Have_Question - 座標: (479, 497, 911, 908)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A703.jpg\n", + "\n", + "處理檔案: A703.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A704.jpg\n", + "\n", + "處理檔案: A704.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A705.jpg\n", + "\n", + "處理檔案: A705.xml\n", + "Object 1: Have_Question - 座標: (378, 610, 441, 716)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A706.jpg\n", + "\n", + "處理檔案: A706.xml\n", + "Object 1: Have_Question - 座標: (393, 467, 480, 561)\n", + "Object 2: Have_Question - 座標: (310, 184, 450, 351)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A707.jpg\n", + "\n", + "處理檔案: A707.xml\n", + "Object 1: Have_Question - 座標: (870, 386, 987, 497)\n", + "Object 2: Have_Question - 座標: (208, 300, 532, 533)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A710.jpg\n", + "\n", + "處理檔案: A710.xml\n", + "Object 1: Have_Question - 座標: (364, 326, 427, 405)\n", + "Object 2: Have_Question - 座標: (718, 121, 749, 179)\n", + "Object 3: Have_Question - 座標: (496, 326, 717, 532)\n", + "Object 4: Have_Question - 座標: (312, 408, 566, 652)\n", + "Object 5: Have_Question - 座標: (312, 662, 569, 961)\n", + "Object 6: Have_Question - 座標: (756, 458, 1098, 1003)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A714.jpg\n", + "\n", + "處理檔案: A714.xml\n", + "Object 1: Have_Question - 座標: (626, 221, 1203, 760)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A715.jpg\n", + "\n", + "處理檔案: A715.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A720.jpg\n", + "\n", + "處理檔案: A720.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A721.jpg\n", + "\n", + "處理檔案: A721.xml\n", + "Object 1: Have_Question - 座標: (338, 334, 562, 834)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A722.jpg\n", + "\n", + "處理檔案: A722.xml\n", + "Object 1: Have_Question - 座標: (462, 208, 583, 318)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A730.jpg\n", + "\n", + "處理檔案: A730.xml\n", + "Object 1: Have_Question - 座標: (599, 108, 760, 231)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A731.jpg\n", + "\n", + "處理檔案: A731.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A732.jpg\n", + "\n", + "處理檔案: A732.xml\n", + "Object 1: Have_Question - 座標: (669, 257, 798, 522)\n", + "Object 2: Have_Question - 座標: (639, 901, 737, 1041)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A734.jpg\n", + "\n", + "處理檔案: A734.xml\n", + "Object 1: Have_Question - 座標: (331, 280, 1064, 742)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A750.jpg\n", + "\n", + "處理檔案: A750.xml\n", + "Object 1: Have_Question - 座標: (182, 395, 891, 950)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A751.jpg\n", + "\n", + "處理檔案: A751.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A753.jpg\n", + "\n", + "處理檔案: A753.xml\n", + "Object 1: Have_Question - 座標: (477, 252, 651, 433)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A756.jpg\n", + "\n", + "處理檔案: A756.xml\n", + "Object 1: Have_Question - 座標: (543, 359, 609, 415)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A757.jpg\n", + "\n", + "處理檔案: A757.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A770.jpg\n", + "\n", + "處理檔案: A770.xml\n", + "Object 1: Have_Question - 座標: (147, 780, 188, 837)\n", + "Object 2: Have_Question - 座標: (431, 906, 477, 959)\n", + "Object 3: Have_Question - 座標: (633, 739, 655, 762)\n", + "Object 4: Have_Question - 座標: (755, 545, 784, 572)\n", + "Object 5: Have_Question - 座標: (928, 464, 947, 486)\n", + "Object 6: Have_Question - 座標: (575, 285, 597, 299)\n", + "Object 7: Have_Question - 座標: (702, 278, 719, 300)\n", + "Object 8: Have_Question - 座標: (841, 301, 872, 319)\n", + "Object 9: Have_Question - 座標: (461, 114, 669, 180)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A771.jpg\n", + "\n", + "處理檔案: A771.xml\n", + "Object 1: Have_Question - 座標: (43, 309, 68, 336)\n", + "Object 2: Have_Question - 座標: (35, 634, 62, 667)\n", + "Object 3: Have_Question - 座標: (830, 233, 962, 370)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A775.jpg\n", + "\n", + "處理檔案: A775.xml\n", + "Object 1: Have_Question - 座標: (256, 480, 390, 812)\n", + "Object 2: Have_Question - 座標: (82, 743, 250, 939)\n", + "Object 3: Have_Question - 座標: (58, 103, 888, 234)\n", + "Object 4: Have_Question - 座標: (509, 225, 1049, 480)\n", + "Object 5: Have_Question - 座標: (781, 437, 1009, 671)\n", + "Object 6: Have_Question - 座標: (588, 682, 801, 769)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A776.jpg\n", + "\n", + "處理檔案: A776.xml\n", + "Object 1: Have_Question - 座標: (819, 136, 1004, 500)\n", + "Object 2: Have_Question - 座標: (315, 727, 743, 939)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A777.jpg\n", + "\n", + "處理檔案: A777.xml\n", + "Object 1: Have_Question - 座標: (72, 149, 265, 433)\n", + "Object 2: Have_Question - 座標: (95, 453, 471, 816)\n", + "Object 3: Have_Question - 座標: (429, 270, 627, 779)\n", + "Object 4: Have_Question - 座標: (655, 97, 777, 217)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A778.jpg\n", + "\n", + "處理檔案: A778.xml\n", + "Object 1: Have_Question - 座標: (233, 386, 631, 940)\n", + "Object 2: Have_Question - 座標: (630, 391, 1123, 1027)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A780.jpg\n", + "\n", + "處理檔案: A780.xml\n", + "Object 1: Have_Question - 座標: (408, 467, 545, 797)\n", + "Object 2: Have_Question - 座標: (655, 518, 1161, 1040)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A781.jpg\n", + "\n", + "處理檔案: A781.xml\n", + "Object 1: Have_Question - 座標: (331, 209, 357, 240)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A790.jpg\n", + "\n", + "處理檔案: A790.xml\n", + "Object 1: Have_Question - 座標: (692, 704, 727, 733)\n", + "Object 2: Have_Question - 座標: (819, 960, 850, 984)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A794.jpg\n", + "\n", + "處理檔案: A794.xml\n", + "Object 1: Have_Question - 座標: (152, 313, 494, 767)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_a80.jpg\n", + "\n", + "處理檔案: a80.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A800.jpg\n", + "\n", + "處理檔案: A800.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A803.jpg\n", + "\n", + "處理檔案: A803.xml\n", + "Object 1: Have_Question - 座標: (328, 588, 632, 825)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A806.jpg\n", + "\n", + "處理檔案: A806.xml\n", + "Object 1: Have_Question - 座標: (462, 616, 649, 772)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A807.jpg\n", + "\n", + "處理檔案: A807.xml\n", + "Object 1: Have_Question - 座標: (268, 114, 311, 155)\n", + "Object 2: Have_Question - 座標: (94, 530, 248, 676)\n", + "Object 3: Have_Question - 座標: (502, 590, 824, 733)\n", + "Object 4: Have_Question - 座標: (502, 736, 993, 1044)\n", + "Object 5: Have_Question - 座標: (925, 550, 1195, 900)\n", + "Object 6: Have_Question - 座標: (496, 94, 787, 500)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A810.jpg\n", + "\n", + "處理檔案: A810.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A811.jpg\n", + "\n", + "處理檔案: A811.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A812.jpg\n", + "\n", + "處理檔案: A812.xml\n", + "Object 1: Have_Question - 座標: (523, 372, 562, 409)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A820.jpg\n", + "\n", + "處理檔案: A820.xml\n", + "Object 1: Have_Question - 座標: (603, 480, 761, 619)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A821.jpg\n", + "\n", + "處理檔案: A821.xml\n", + "Object 1: Have_Question - 座標: (631, 364, 692, 426)\n", + "Object 2: Have_Question - 座標: (766, 626, 829, 682)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A822.jpg\n", + "\n", + "處理檔案: A822.xml\n", + "Object 1: Have_Question - 座標: (825, 313, 885, 361)\n", + "Object 2: Have_Question - 座標: (1010, 504, 1054, 559)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A823.jpg\n", + "\n", + "處理檔案: A823.xml\n", + "Object 1: Have_Question - 座標: (357, 830, 454, 908)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A824.jpg\n", + "\n", + "處理檔案: A824.xml\n", + "Object 1: Have_Question - 座標: (550, 227, 593, 269)\n", + "Object 2: Have_Question - 座標: (623, 473, 681, 533)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A825.jpg\n", + "\n", + "處理檔案: A825.xml\n", + "Object 1: Have_Question - 座標: (523, 614, 610, 715)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A829.jpg\n", + "\n", + "處理檔案: A829.xml\n", + "Object 1: Have_Question - 座標: (156, 153, 194, 181)\n", + "Object 2: Have_Question - 座標: (110, 210, 137, 248)\n", + "Object 3: Have_Question - 座標: (327, 215, 347, 252)\n", + "Object 4: Have_Question - 座標: (523, 229, 559, 270)\n", + "Object 5: Have_Question - 座標: (640, 336, 786, 562)\n", + "Object 6: Have_Question - 座標: (730, 694, 754, 720)\n", + "Object 7: Have_Question - 座標: (159, 697, 273, 817)\n", + "Object 8: Have_Question - 座標: (57, 590, 83, 621)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A830.jpg\n", + "\n", + "處理檔案: A830.xml\n", + "Object 1: Have_Question - 座標: (284, 650, 651, 910)\n", + "Object 2: Have_Question - 座標: (579, 519, 887, 819)\n", + "Object 3: Have_Question - 座標: (607, 286, 1010, 551)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A831.jpg\n", + "\n", + "處理檔案: A831.xml\n", + "Object 1: Have_Question - 座標: (40, 102, 697, 632)\n", + "Object 2: Have_Question - 座標: (694, 106, 1040, 389)\n", + "Object 3: Have_Question - 座標: (796, 685, 906, 815)\n", + "Object 4: Have_Question - 座標: (494, 763, 586, 850)\n", + "Object 5: Have_Question - 座標: (220, 803, 330, 904)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A840.jpg\n", + "\n", + "處理檔案: A840.xml\n", + "Object 1: Have_Question - 座標: (118, 97, 284, 481)\n", + "Object 2: Have_Question - 座標: (285, 100, 701, 264)\n", + "Object 3: Have_Question - 座標: (703, 101, 1032, 431)\n", + "Object 4: Have_Question - 座標: (459, 421, 557, 498)\n", + "Object 5: Have_Question - 座標: (47, 786, 390, 928)\n", + "Object 6: Have_Question - 座標: (275, 574, 605, 811)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A841.jpg\n", + "\n", + "處理檔案: A841.xml\n", + "Object 1: Have_Question - 座標: (53, 101, 656, 844)\n", + "Object 2: Have_Question - 座標: (449, 825, 662, 940)\n", + "Object 3: Have_Question - 座標: (657, 98, 1045, 821)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A842.jpg\n", + "\n", + "處理檔案: A842.xml\n", + "Object 1: Have_Question - 座標: (21, 197, 353, 685)\n", + "Object 2: Have_Question - 座標: (15, 687, 357, 1052)\n", + "Object 3: Have_Question - 座標: (348, 580, 701, 1041)\n", + "Object 4: Have_Question - 座標: (698, 689, 1043, 1044)\n", + "Object 5: Have_Question - 座標: (835, 629, 1178, 1042)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A850.jpg\n", + "\n", + "處理檔案: A850.xml\n", + "Object 1: Have_Question - 座標: (165, 259, 511, 910)\n", + "Object 2: Have_Question - 座標: (509, 449, 1167, 948)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A851.jpg\n", + "\n", + "處理檔案: A851.xml\n", + "Object 1: Have_Question - 座標: (141, 304, 1199, 954)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A852.jpg\n", + "\n", + "處理檔案: A852.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A853.jpg\n", + "\n", + "處理檔案: A853.xml\n", + "Object 1: Have_Question - 座標: (32, 244, 440, 625)\n", + "Object 2: Have_Question - 座標: (34, 626, 1201, 1046)\n", + "Object 3: Have_Question - 座標: (720, 442, 1197, 627)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A854.jpg\n", + "\n", + "處理檔案: A854.xml\n", + "Object 1: Have_Question - 座標: (293, 320, 610, 578)\n", + "Object 2: Have_Question - 座標: (94, 466, 1196, 1067)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A855.jpg\n", + "\n", + "處理檔案: A855.xml\n", + "Object 1: Have_Question - 座標: (389, 200, 760, 464)\n", + "Object 2: Have_Question - 座標: (35, 35, 380, 488)\n", + "Object 3: Have_Question - 座標: (250, 487, 600, 795)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A870.jpg\n", + "\n", + "處理檔案: A870.xml\n", + "Object 1: Have_Question - 座標: (696, 34, 1192, 927)\n", + "Object 2: Have_Question - 座標: (26, 412, 699, 752)\n", + "Object 3: Have_Question - 座標: (25, 750, 947, 1043)\n", + "Object 4: Have_Question - 座標: (946, 970, 1100, 1044)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A872.jpg\n", + "\n", + "處理檔案: A872.xml\n", + "Object 1: Have_Question - 座標: (686, 285, 1034, 543)\n", + "Object 2: Have_Question - 座標: (772, 535, 1041, 772)\n", + "Object 3: Have_Question - 座標: (704, 706, 805, 841)\n", + "Object 4: Have_Question - 座標: (441, 718, 703, 849)\n", + "Object 5: Have_Question - 座標: (354, 544, 489, 805)\n", + "Object 6: Have_Question - 座標: (467, 447, 701, 584)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A873.jpg\n", + "\n", + "處理檔案: A873.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A880.jpg\n", + "\n", + "處理檔案: A880.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A881.jpg\n", + "\n", + "處理檔案: A881.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A890.jpg\n", + "\n", + "處理檔案: A890.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A892.jpg\n", + "\n", + "處理檔案: A892.xml\n", + "Object 1: Have_Question - 座標: (38, 43, 870, 632)\n", + "Object 2: Have_Question - 座標: (30, 635, 287, 806)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A900.jpg\n", + "\n", + "處理檔案: A900.xml\n", + "Object 1: Have_Question - 座標: (457, 29, 983, 570)\n", + "Object 2: Have_Question - 座標: (15, 39, 492, 885)\n", + "Object 3: Have_Question - 座標: (482, 669, 713, 950)\n", + "Object 4: Have_Question - 座標: (982, 32, 1182, 273)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A902.jpg\n", + "\n", + "處理檔案: A902.xml\n", + "Object 1: Have_Question - 座標: (174, 180, 1042, 1043)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A903.jpg\n", + "\n", + "處理檔案: A903.xml\n", + "Object 1: Have_Question - 座標: (683, 414, 1044, 972)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A905.jpg\n", + "\n", + "處理檔案: A905.xml\n", + "Object 1: Have_Question - 座標: (802, 278, 970, 512)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A910.jpg\n", + "\n", + "處理檔案: A910.xml\n", + "Object 1: Have_Question - 座標: (484, 518, 580, 618)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A911.jpg\n", + "\n", + "處理檔案: A911.xml\n", + "Object 1: Have_Question - 座標: (639, 570, 714, 642)\n", + "Object 2: Have_Question - 座標: (1023, 381, 1087, 446)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A913.jpg\n", + "\n", + "處理檔案: A913.xml\n", + "Object 1: Have_Question - 座標: (826, 142, 877, 207)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A914.jpg\n", + "\n", + "處理檔案: A914.xml\n", + "Object 1: Have_Question - 座標: (446, 90, 490, 124)\n", + "Object 2: Have_Question - 座標: (633, 98, 663, 124)\n", + "Object 3: Have_Question - 座標: (933, 477, 993, 593)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A915.jpg\n", + "\n", + "處理檔案: A915.xml\n", + "Object 1: Have_Question - 座標: (552, 31, 1102, 808)\n", + "Object 2: Have_Question - 座標: (29, 471, 180, 921)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A916.jpg\n", + "\n", + "處理檔案: A916.xml\n", + "Object 1: Have_Question - 座標: (68, 559, 451, 942)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A920.jpg\n", + "\n", + "處理檔案: A920.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A930.jpg\n", + "\n", + "處理檔案: A930.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A931.jpg\n", + "\n", + "處理檔案: A931.xml\n", + "Object 1: Have_Question - 座標: (893, 545, 1174, 957)\n", + "Object 2: Have_Question - 座標: (808, 29, 1154, 341)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A941.jpg\n", + "\n", + "處理檔案: A941.xml\n", + "Object 1: Have_Question - 座標: (555, 468, 1118, 885)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A942.jpg\n", + "\n", + "處理檔案: A942.xml\n", + "Object 1: Have_Question - 座標: (296, 449, 656, 750)\n", + "Object 2: Have_Question - 座標: (1060, 243, 1107, 309)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A944.jpg\n", + "\n", + "處理檔案: A944.xml\n", + "Object 1: Have_Question - 座標: (246, 396, 590, 674)\n", + "Object 2: Have_Question - 座標: (531, 126, 749, 318)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A946.jpg\n", + "\n", + "處理檔案: A946.xml\n", + "Object 1: Have_Question - 座標: (417, 363, 662, 669)\n", + "Object 2: Have_Question - 座標: (17, 414, 49, 497)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A950.jpg\n", + "\n", + "處理檔案: A950.xml\n", + "Object 1: Have_Question - 座標: (554, 597, 809, 835)\n", + "Object 2: Have_Question - 座標: (449, 872, 543, 972)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A951.jpg\n", + "\n", + "處理檔案: A951.xml\n", + "Object 1: Have_Question - 座標: (213, 98, 519, 476)\n", + "Object 2: Have_Question - 座標: (609, 105, 850, 422)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A952.jpg\n", + "\n", + "處理檔案: A952.xml\n", + "Object 1: Have_Question - 座標: (237, 413, 405, 881)\n", + "Object 2: Have_Question - 座標: (463, 528, 776, 799)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A953.jpg\n", + "\n", + "處理檔案: A953.xml\n", + "Object 1: Have_Question - 座標: (551, 798, 679, 929)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A955.jpg\n", + "\n", + "處理檔案: A955.xml\n", + "Object 1: Have_Question - 座標: (180, 319, 267, 618)\n", + "Object 2: Have_Question - 座標: (326, 233, 451, 631)\n", + "Object 3: Have_Question - 座標: (513, 258, 598, 601)\n", + "Object 4: Have_Question - 座標: (702, 232, 830, 511)\n", + "Object 5: Have_Question - 座標: (371, 832, 652, 979)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A956.jpg\n", + "\n", + "處理檔案: A956.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A957.jpg\n", + "\n", + "處理檔案: A957.xml\n", + "Object 1: Have_Question - 座標: (299, 614, 751, 939)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A960.jpg\n", + "\n", + "處理檔案: A960.xml\n", + "Object 1: Have_Question - 座標: (611, 507, 1014, 818)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A961.jpg\n", + "\n", + "處理檔案: A961.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A9610.jpg\n", + "\n", + "處理檔案: A9610.xml\n", + "Object 1: Have_Question - 座標: (66, 166, 858, 958)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A9611.jpg\n", + "\n", + "處理檔案: A9611.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A9612.jpg\n", + "\n", + "處理檔案: A9612.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A962.jpg\n", + "\n", + "處理檔案: A962.xml\n", + "Object 1: Have_Question - 座標: (75, 324, 454, 914)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A963.jpg\n", + "\n", + "處理檔案: A963.xml\n", + "Object 1: Have_Question - 座標: (103, 613, 704, 957)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A964.jpg\n", + "\n", + "處理檔案: A964.xml\n", + "Object 1: Have_Question - 座標: (24, 363, 643, 986)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A965.jpg\n", + "\n", + "處理檔案: A965.xml\n", + "Object 1: Have_Question - 座標: (28, 119, 377, 388)\n", + "Object 2: Have_Question - 座標: (844, 849, 942, 948)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A966.jpg\n", + "\n", + "處理檔案: A966.xml\n", + "Object 1: Have_Question - 座標: (7, 316, 76, 580)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A967.jpg\n", + "\n", + "處理檔案: A967.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A968.jpg\n", + "\n", + "處理檔案: A968.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A969.jpg\n", + "\n", + "處理檔案: A969.xml\n", + "Object 1: Have_Question - 座標: (218, 349, 514, 609)\n", + "Object 2: Have_Question - 座標: (574, 787, 757, 914)\n", + "Object 3: Have_Question - 座標: (958, 768, 1191, 918)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A970.jpg\n", + "\n", + "處理檔案: A970.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A972.jpg\n", + "\n", + "處理檔案: A972.xml\n", + "Object 1: Have_Question - 座標: (338, 449, 669, 796)\n", + "Object 2: Have_Question - 座標: (123, 197, 359, 458)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A980.jpg\n", + "\n", + "處理檔案: A980.xml\n", + "Object 1: Have_Question - 座標: (540, 125, 666, 219)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A990.jpg\n", + "\n", + "處理檔案: A990.xml\n", + "圖片檔案不存在: ../Dataset/Training\\Have_Question_Crop\\A991.jpg\n", + "\n", + "處理檔案: A991.xml\n", + "Object 1: Have_Question - 座標: (213, 339, 331, 459)\n", + "已儲存標註圖片至: ../Dataset/Annotation\\Have_Question_Crop\\annotated_A994.jpg\n", + "\n", + "處理檔案: A994.xml\n" + ] + } + ], + "source": [ + "# 創建處理器實例\n", + "processor = XMLAnnotationProcessor(\n", + " dataset_root = Loading_Config[\"Train_Data_Root\"],\n", + ")\n", + "\n", + "# 設定自訂樣式(可選)\n", + "processor.set_drawing_style(\n", + " box_color=(0, 0, 255), # 紅色邊界框\n", + " text_color=(255, 255, 255), # 白色文字\n", + " box_thickness=3,\n", + " font_scale=0.7\n", + ")\n", + "\n", + "print(\"XML標註處理器已準備就绪\")\n", + "results = processor.process_multiple_xml(f\"../Label_Image/Have_Question\", \"Have_Question_Crop\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "base", + "display_name": "Stomach_Cancer_Env", "language": "python", "name": "python3" }, @@ -2372,7 +4333,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/test_binary_cross_entropy.py b/test_binary_cross_entropy.py new file mode 100644 index 0000000..2912f3e --- /dev/null +++ b/test_binary_cross_entropy.py @@ -0,0 +1,68 @@ +import torch +import torch.nn as nn +import sys +import os + +# 添加當前目錄到系統路徑 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +# 導入自定義的 BinaryCrossEntropy 類 +from Model_Loss.binary_cross_entropy import BinaryCrossEntropy + +def test_binary_cross_entropy(): + print("開始測試 BinaryCrossEntropy 類...") + + # 創建損失函數實例 + criterion = BinaryCrossEntropy() + + # 測試 1: 維度匹配的情況 (二元分類) + print("\n測試 1: 維度匹配的情況 (二元分類)") + predictions_match = torch.tensor([[0.7, 0.3], [0.2, 0.8]], dtype=torch.float32) + targets_match = torch.tensor([[1.0, 0.0], [0.0, 1.0]], dtype=torch.float32) + + try: + loss_match = criterion(predictions_match, targets_match) + print(f"維度匹配的損失值: {loss_match.item()}") + print("測試 1 通過!") + except Exception as e: + print(f"測試 1 失敗: {e}") + + # 測試 2: 維度不匹配的情況 (多類別分類) + print("\n測試 2: 維度不匹配的情況 (多類別分類)") + predictions_mismatch = torch.tensor([[0.7, 0.2, 0.1], [0.1, 0.8, 0.1]], dtype=torch.float32) + targets_mismatch = torch.tensor([[1.0, 0.0], [0.0, 1.0]], dtype=torch.float32) + + try: + loss_mismatch = criterion(predictions_mismatch, targets_mismatch) + print(f"維度不匹配的損失值: {loss_mismatch.item()}") + print("測試 2 通過!") + except Exception as e: + print(f"測試 2 失敗: {e}") + + # 測試 3: 與 PyTorch 內建函數比較 + print("\n測試 3: 與 PyTorch 內建函數比較") + predictions_compare = torch.tensor([[0.7, 0.3], [0.2, 0.8]], dtype=torch.float32) + targets_compare = torch.tensor([[1.0, 0.0], [0.0, 1.0]], dtype=torch.float32) + + try: + # 自定義損失函數 + loss_custom = criterion(predictions_compare, targets_compare) + + # PyTorch 內建損失函數 + loss_pytorch = nn.BCEWithLogitsLoss()(predictions_compare, targets_compare) + + print(f"自定義損失值: {loss_custom.item()}") + print(f"PyTorch 損失值: {loss_pytorch.item()}") + print(f"差異: {abs(loss_custom.item() - loss_pytorch.item())}") + + if abs(loss_custom.item() - loss_pytorch.item()) < 1e-5: + print("測試 3 通過!") + else: + print("測試 3 失敗: 損失值與 PyTorch 內建函數不一致") + except Exception as e: + print(f"測試 3 失敗: {e}") + + print("\n測試完成!") + +if __name__ == "__main__": + test_binary_cross_entropy() \ No newline at end of file diff --git a/test_bounding_box.py b/test_bounding_box.py new file mode 100644 index 0000000..a351317 --- /dev/null +++ b/test_bounding_box.py @@ -0,0 +1,66 @@ +import cv2 +import numpy as np +from Image_Process.Image_Mask_Ground_Truth_Processing import XMLAnnotationProcessor + +def test_bounding_box_processing_with_mock_data(): + # 初始化XML處理器 + processor = XMLAnnotationProcessor() + + # 創建一個測試圖像 (300x300 彩色圖像) + test_image = np.ones((300, 300, 3), dtype=np.uint8) * 128 # 灰色背景 + + # 在圖像中央創建一個彩色區域 + test_image[100:200, 100:200, 0] = 255 # 紅色通道 + test_image[120:180, 120:180, 1] = 255 # 綠色通道 + test_image[140:160, 140:160, 2] = 255 # 藍色通道 + + # 創建模擬的bounding box數據 + mock_bounding_boxes = [ + { + 'name': 'test_object', + 'xmin': 50, + 'ymin': 50, + 'xmax': 150, + 'ymax': 150 + }, + { + 'name': 'test_object2', + 'xmin': 200, + 'ymin': 200, + 'xmax': 250, + 'ymax': 250 + } + ] + + # 調用draw_bounding_boxes方法 + result_image = processor.draw_bounding_boxes(test_image, mock_bounding_boxes) + + # 顯示處理結果 + print(f"原始圖像形狀: {test_image.shape}") + print(f"結果圖像形狀: {result_image.shape}") + + # 檢查圖像是否有非黑色區域(bounding box內的原圖) + non_black_pixels = np.sum(result_image > 0) + print(f"非黑色像素數量: {non_black_pixels}") + + # 檢查第一個bounding box區域是否保留了原圖 + box1 = result_image[50:150, 50:150] + box1_matches = np.array_equal(box1, test_image[50:150, 50:150]) + print(f"第一個bounding box區域是否與原圖相同: {box1_matches}") + + # 檢查框外區域是否為黑色 + outside_box = result_image[0:40, 0:40] # 選擇一個框外區域 + is_black = np.sum(outside_box) == 0 + print(f"框外區域是否為黑色: {is_black}") + + if non_black_pixels > 0 and box1_matches and is_black: + print("測試成功: bounding box內保持原圖,框外變為黑色") + else: + print("測試失敗") + + # 保存結果圖像以便查看 + cv2.imwrite("test_bounding_box_result.png", result_image) + print("已保存結果圖像: test_bounding_box_result.png") + +if __name__ == "__main__": + test_bounding_box_processing_with_mock_data() \ No newline at end of file diff --git a/test_bounding_box_result.png b/test_bounding_box_result.png new file mode 100644 index 0000000..baf6e9b Binary files /dev/null and b/test_bounding_box_result.png differ diff --git a/test_main.py b/test_main.py new file mode 100644 index 0000000..20acb50 --- /dev/null +++ b/test_main.py @@ -0,0 +1,121 @@ +import torch +import sys +import os +import time + +# 添加當前目錄到系統路徑 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from experiments.experiment import experiments +from utils.Stomach_Config import Training_Config, Loading_Config, Save_Result_File_Config +from Training_Tools.Tools import Tool +from merge_class.merge import merge + +def test_main(): + # 測試GPU是否可用 + flag = torch.cuda.is_available() + if not flag: + print("CUDA不可用\n") + else: + print(f"CUDA可用,數量為{torch.cuda.device_count()}\n") + + # 测试GPU是否可用 + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + print(f"使用设备: {device}") + if torch.cuda.is_available(): + print(f"GPU: {torch.cuda.get_device_name(0)}") + + # 創建一個簡單的測試數據集 + class SimpleDataset(torch.utils.data.Dataset): + def __init__(self, size=10): + self.size = size + self.data = torch.randn(size, 3, 256, 256) # 假設輸入是3通道256x256圖像 + self.mask = torch.randint(0, 2, (size, 1, 256, 256)).float() # 二值掩碼 + self.labels = torch.zeros(size, 3) # 假設有3個類別 + self.labels[:, 0] = 1 # 所有樣本都是第一個類別 + print(f"創建了測試數據集,大小: {size},數據形狀: {self.data.shape}") + + def __len__(self): + return self.size + + def __getitem__(self, idx): + return self.data[idx], self.mask[idx], self.labels[idx] + + # 創建測試數據 + print("創建測試數據...") + test_dataset = SimpleDataset() + training_data = [test_dataset.data[i] for i in range(test_dataset.size)] + training_label = [test_dataset.labels[i] for i in range(test_dataset.size)] + training_mask = [test_dataset.mask[i] for i in range(test_dataset.size)] + + # 初始化工具和配置 + tool = Tool() + Status = 1 + Label_Length = 3 + + # 模擬main.py中的數據處理 + print("\n模擬main.py中的數據處理...") + Merge = merge() + + # 確保Loading_Config中包含必要的配置項 + required_configs = [ + "Test_Data_Root", "Training_Labels", "Annotation_Root", + "Label_Image_Labels", "Image enhance processing save root" + ] + + for config in required_configs: + if config not in Loading_Config: + print(f"添加缺少的配置項: {config}") + if config == "Test_Data_Root": + Loading_Config[config] = "./test_data" + elif config == "Training_Labels": + Loading_Config[config] = ["class1", "class2", "class3"] + elif config == "Annotation_Root": + Loading_Config[config] = "./annotations" + elif config == "Label_Image_Labels": + Loading_Config[config] = ["CA", "Normal"] + elif config == "Image enhance processing save root": + Loading_Config[config] = "./enhanced_images" + + # 測試experiments類的初始化 + try: + print("\n測試experiments類的初始化...") + experiment = experiments( + Xception_Training_Data=training_data, + Xception_Training_Label=training_label, + GastroSegNet_Training_Data=training_data, + GastroSegNet_Training_Label=training_label, + GastroSegNet_Training_Mask=training_mask, + Training_Config=Training_Config, + Loading_Config=Loading_Config, + tools=tool, + Number_Of_Classes=Label_Length, + status=Status + ) + print("experiments類初始化成功!") + + # 測試processing_main方法(僅執行部分代碼) + print("\n測試processing_main方法...") + # 這裡我們不實際調用processing_main,因為它會執行完整的訓練流程 + # 而是檢查關鍵屬性是否正確設置 + print(f"模型名稱: {experiment.model_name}") + print(f"實驗名稱: {experiment.experiment_name}") + print(f"訓練批次大小: {experiment.train_batch_size}") + print(f"使用設備: {experiment.device}") + + # 模擬processing_main的部分功能 + print("\n模擬processing_main的部分功能...") + # 這裡我們只模擬一些基本操作,不執行實際的訓練 + print("模擬讀取測試數據...") + experiment.test = training_data + experiment.test_label = training_label + + print("\n測試成功!") + + except Exception as e: + print(f"\n測試失敗: {str(e)}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + test_main() \ No newline at end of file diff --git a/test_mask_loading.py b/test_mask_loading.py new file mode 100644 index 0000000..e31bdd6 --- /dev/null +++ b/test_mask_loading.py @@ -0,0 +1,58 @@ +import sys +import os + +# 添加當前目錄到系統路徑 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from Load_process.LoadData import Loding_Data_Root +from utils.Stomach_Config import Loading_Config +from merge_class.merge import merge + +def test_mask_loading(): + print("測試掩碼數據加載...") + + # 確保Loading_Config中包含必要的配置項 + required_configs = [ + "Label_Image_Labels", "Annotation_Root" + ] + + for config in required_configs: + if config not in Loading_Config: + print(f"錯誤:缺少配置項 {config}") + return + + print(f"Label_Image_Labels: {Loading_Config['Label_Image_Labels']}") + print(f"Annotation_Root: {Loading_Config['Annotation_Root']}") + + # 加載掩碼數據 + try: + print("\n嘗試加載掩碼數據...") + Mask_load = Loding_Data_Root(Loading_Config["Label_Image_Labels"], Loading_Config['Annotation_Root'], "") + Mask_Data_Dict_Data = Mask_load.process_main(False) + + print("\n掩碼數據加載結果:") + if not Mask_Data_Dict_Data: + print("錯誤:Mask_Data_Dict_Data為空") + else: + Mask_Keys = list(Mask_Data_Dict_Data.keys()) + print(f"Mask_Keys: {Mask_Keys}") + + for key in Mask_Keys: + print(f"Key: {key}, 數據長度: {len(Mask_Data_Dict_Data[key])}") + + # 嘗試合併掩碼數據 + if len(Mask_Keys) >= 2: + print("\n嘗試合併掩碼數據...") + Merge = merge() + Training_Mask_Data = Merge.merge_all_image_data(Mask_Data_Dict_Data[Mask_Keys[0]], Mask_Data_Dict_Data[Mask_Keys[1]]) + print(f"合併後的掩碼數據長度: {len(Training_Mask_Data)}") + else: + print("\n無法合併掩碼數據:Mask_Keys長度小於2") + + except Exception as e: + print(f"\n加載掩碼數據時出錯: {str(e)}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + test_mask_loading() \ No newline at end of file diff --git a/test_model_branch.py b/test_model_branch.py new file mode 100644 index 0000000..cd721b0 --- /dev/null +++ b/test_model_branch.py @@ -0,0 +1,91 @@ +import torch +import torch.nn.functional as F +import sys +import os + +# 添加項目根目錄到路徑 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +# 導入必要的模塊 +from experiments.Models.GastroSegNet_Model import GastroSegNet +from experiments.Training.Segmentation_Block_Training import Segmentation_Block_Training_Step + +def test_model_branch(): + # 初始化模型 + try: + model_step = Segmentation_Block_Training_Step() + print("初始化成功") + + # 獲取設備 + device = model_step.device + print(f"使用設備: {device}") + + # 創建一個簡單的測試數據 + batch_size = 2 + input_images = torch.rand(batch_size, 3, 256, 256) + + # 創建符合Model_Branch方法輸入要求的mask_gt + # 創建二值掩碼,形狀為[batch_size, 1, height, width] + mask_gt = torch.zeros(batch_size, 1, 256, 256) + + # 在掩碼中央創建一個矩形區域(模擬分割目標) + for i in range(batch_size): + # 設置矩形區域為1(白色) + mask_gt[i, 0, 50:200, 50:200] = 1.0 + + # 初始化模型 + model_step.Model = model_step.Construct_Segment_Model_CUDA() + print("模型初始化完成") + + # 測試Model_Branch方法 + try: + # 調用Model_Branch方法,設置return_processed_image=True + print("\n開始測試Model_Branch方法...") + processed_images, seg_outputs = model_step.Model_Branch(input_images, mask_gt, return_processed_image=True) + + # 檢查結果 + print("Model_Branch方法測試成功") + print(f"處理後的圖像形狀: {processed_images.shape}") + print(f"分割輸出形狀: {seg_outputs.shape}") + + # 測試Model_Branch方法,設置return_processed_image=False + print("\n開始測試Model_Branch方法 (return_processed_image=False)...") + + # 創建一個自定義的損失計算函數,用於捕獲輸入形狀 + def mock_losses(Segmentation_Output_Image, Segmentation_Mask_GroundTruth_Image): + print(f"Losses方法輸入形狀 - 預測: {Segmentation_Output_Image.shape}, 目標: {Segmentation_Mask_GroundTruth_Image.shape}") + return torch.tensor(0.5, device=model_step.device) + + # 保存原始的Losses方法 + original_losses = model_step.Losses + + try: + # 替換Losses方法 + model_step.Losses = mock_losses + + # 調用Model_Branch方法 + loss = model_step.Model_Branch(input_images, mask_gt, return_processed_image=False) + + print("Model_Branch方法測試成功 (return_processed_image=False)") + print(f"損失值: {loss}") + finally: + # 恢復原始的Losses方法 + model_step.Losses = original_losses + + return True + except Exception as e: + print(f"Model_Branch方法測試失敗: {str(e)}") + import traceback + traceback.print_exc() + return False + + except Exception as e: + print(f"初始化過程中出錯: {str(e)}") + return False + +if __name__ == "__main__": + success = test_model_branch() + if success: + print("\n所有測試通過!") + else: + print("\n測試失敗!") \ No newline at end of file diff --git a/test_processing_main.py b/test_processing_main.py new file mode 100644 index 0000000..a5a298a --- /dev/null +++ b/test_processing_main.py @@ -0,0 +1,117 @@ +import torch +import torch.nn.functional as F +import sys +import os +import time + +# 添加項目根目錄到路徑 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +# 導入Loading_Config +from utils.Stomach_Config import Loading_Config + +# 設置Segmentation_Plot_Image配置項 +Loading_Config["Segmentation_Plot_Image"] = "./test_plot.png" + +# 導入必要的模塊 +from experiments.Models.GastroSegNet_Model import GastroSegNet +from experiments.Training.Segmentation_Block_Training import Segmentation_Block_Training_Step + +def test_processing_main(): + # 初始化模型 + try: + model_step = Segmentation_Block_Training_Step() + print("初始化成功") + + # 獲取設備 + device = model_step.device + print(f"使用設備: {device}") + + # 創建一個簡單的測試數據集 + class SimpleDataset(torch.utils.data.Dataset): + def __init__(self, size=10): + self.size = size + # 將數據放在CPU上,避免CUDA轉NumPy的問題 + self.data = torch.rand(size, 3, 256, 256) + self.masks = torch.rand(size, 1, 256, 256) > 0.5 + self.masks = self.masks.float() + self.labels = torch.zeros(size, 3) # 假設有3個類別 + self.labels[:, 0] = 1 # 所有樣本都是第一個類別 + print(f"創建了測試數據集,大小: {size},數據形狀: {self.data.shape},掩碼形狀: {self.masks.shape},標籤形狀: {self.labels.shape}") + + def __len__(self): + return self.size + + def __getitem__(self, idx): + return self.data[idx], self.masks[idx], f"sample_{idx}", self.labels[idx] + + # 創建測試數據集 + print("創建測試數據集和數據加載器...") + test_dataset = SimpleDataset(size=10) # 使用10個樣本以滿足KFold的要求 + + # 創建測試數據加載器 + test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=2, shuffle=False) + print("測試數據集和數據加載器創建完成") + + # 創建一個自定義的損失計算函數,用於捕獲輸入形狀 + def mock_losses(Segmentation_Output_Image, Segmentation_Mask_GroundTruth_Image): + print(f"Losses方法輸入形狀 - 預測: {Segmentation_Output_Image.shape}, 目標: {Segmentation_Mask_GroundTruth_Image.shape}") + # 創建一個可以進行反向傳播的損失張量 + dummy_loss = Segmentation_Output_Image.mean() + print(f"創建了虛擬損失,值: {dummy_loss.item()}") + return dummy_loss + + # 創建一個簡化版的Calculate_Progress_And_Timing函數,避免遞歸調用 + def mock_calculate_progress_and_timing(*args): + # 直接返回最後一個參數(epoch_iterator) + return args[-1] + + # 保存原始的方法 + original_losses = model_step.Losses + original_calculate_progress = model_step.Calculate_Progress_And_Timing + + # 測試Processing_Main方法 + try: + # 替換方法 + model_step.Losses = mock_losses + model_step.Calculate_Progress_And_Timing = mock_calculate_progress_and_timing + + # Loading_Config已在全局設置 + + # 調用Processing_Main方法,設置return_processed_images=True並提供test_dataloader + print("\n開始測試Processing_Main方法...") + result = model_step.Processing_Main(test_dataset, return_processed_images=True, test_dataloader=test_loader) + + # 恢復原始的方法 + model_step.Losses = original_losses + model_step.Calculate_Progress_And_Timing = original_calculate_progress + + # 檢查結果 + print("Processing_Main方法測試成功") + print(f"結果類型: {type(result)}") + + # 如果結果是元組,打印每個元素 + if isinstance(result, tuple): + for i, item in enumerate(result): + if isinstance(item, torch.Tensor): + print(f"結果[{i}]: {type(item)} - 形狀: {item.shape}") + else: + print(f"結果[{i}]: {type(item)}") + + return True + except Exception as e: + print(f"Processing_Main方法測試失敗: {str(e)}") + import traceback + traceback.print_exc() + return False + + except Exception as e: + print(f"初始化過程中出錯: {str(e)}") + return False + +if __name__ == "__main__": + success = test_processing_main() + if success: + print("\n所有測試通過!") + else: + print("\n測試失敗!") \ No newline at end of file diff --git a/test_segmentation.py b/test_segmentation.py new file mode 100644 index 0000000..d49386a --- /dev/null +++ b/test_segmentation.py @@ -0,0 +1,48 @@ +import torch +import sys +import os + +# 添加項目根目錄到路徑 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +# 導入必要的模塊 +from experiments.Models.GastroSegNet_Model import GastroSegNet +from experiments.Training.Segmentation_Block_Training import Segmentation_Block_Training_Step + +def test_process_segmentation_output(): + # 初始化模型 + try: + model_step = Segmentation_Block_Training_Step() + print("初始化成功") + + # 創建測試數據 + batch_size = 2 + channels = 3 + height = 256 + width = 256 + + # 獲取設備 + device = model_step.device + print(f"使用設備: {device}") + + # 創建隨機輸入圖像並移到正確的設備上 + input_images = torch.rand(batch_size, channels, height, width).to(device) + + # 創建隨機分割輸出 (模擬模型輸出)並移到正確的設備上 + segmentation_output = torch.rand(batch_size, 1, height, width).to(device) + + # 測試處理方法 + try: + processed_images = model_step.process_segmentation_output(input_images, segmentation_output) + print(f"處理後圖像形狀: {processed_images.shape}") + print("處理成功") + return True + except Exception as e: + print(f"處理過程中出錯: {str(e)}") + return False + except Exception as e: + print(f"初始化過程中出錯: {str(e)}") + return False + +if __name__ == "__main__": + test_process_segmentation_output() \ No newline at end of file diff --git a/testing_Labels_Accuracy.py b/testing_Labels_Accuracy.py index b98e2fe..3f64286 100644 --- a/testing_Labels_Accuracy.py +++ b/testing_Labels_Accuracy.py @@ -1,103 +1,141 @@ from experiments.experiment import experiments -from concurrent.futures import ProcessPoolExecutor -from loadData_and_MakeImageGenerator.load_and_ImageGenerator import Load_ImageGenerator -from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image -import tensorflow as tf +import torch import numpy as np -from sklearn.metrics import confusion_matrix, accuracy_score -from draw_tools.draw import draw_heatmap +from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score from Load_process.LoadData import Loding_Data_Root import os import seaborn as sns import datetime -from Load_process.file_processing import judge_file_exist, make_dir, make_save_root -from matplotlib import pyplot as plt +from Load_process.file_processing import Process_File from Load_process.Load_Indepentend import Load_Indepentend_Data - -def draw(matrix, model_name, index): - # Using Seaborn heatmap to create the plot - - fx = sns.heatmap(matrix, annot=True, cmap='turbo') - # labels the title and x, y axis of plot - fx.set_title('Plotting Confusion Matrix using Seaborn\n\n') - fx.set_xlabel('Predicted Values') - fx.set_ylabel('answer Values ') - # labels the boxes - fx.xaxis.set_ticklabels(['False','True']) - fx.yaxis.set_ticklabels(['False','True']) - - model_dir = '../../Model_Confusion_matrix/model_matrix_image ( ' + str(datetime.date.today()) + " )/" + model_name - if not judge_file_exist(model_dir): - make_dir(model_dir) - modelfiles = make_save_root(str(model_name) + "-" + str(index) + ".png", model_dir) - plt.savefig(modelfiles) - plt.close("all") # 關閉圖表 +from Training_Tools.PreProcess import Training_Precesses +from Training_Tools.Tools import Tool +import matplotlib.figure as figure +import matplotlib.backends.backend_agg as agg +from Calculate_Process.Calculate import Calculate +import argparse +import json +from utils.Stomach_Config import Training_Config, Loading_Config +from model_data_processing.processing import shuffle_data if __name__ == "__main__": - with ProcessPoolExecutor() as executor: ## 默认为1 - print('TensorFlow version:', tf.__version__) - physical_devices = tf.config.experimental.list_physical_devices('GPU') - print(physical_devices) - assert len(physical_devices) > 0, "Not enough GPU hardware devices available" - tf.config.experimental.set_memory_growth(physical_devices[0], True) - os.environ["CUDA_VISIBLE_DEVICES"]='0' + # 解析命令行参数 + parser = argparse.ArgumentParser(description='评估单一类别的准确度、精确度、召回率和F1值') + parser.add_argument('--target_class', type=int, default=0, help='要评估的目标类别索引 (0, 1, 2)') + args = parser.parse_args() + + # 测试GPU是否可用 + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + print(f"使用设备: {device}") + + # 设置GPU + if torch.cuda.is_available(): + torch.cuda.set_device(0) + print(f"GPU: {torch.cuda.get_device_name(0)}") - load = Loding_Data_Root() - experiment = experiments() - image_processing = Read_image_and_Process_image() - cut_image = Load_Indepentend_Data() + # 初始化对象 + tool = Tool() + Status = 1 # 決定要使用什麼資料集 - model = experiment.construct_model() - model_dir = '../../best_model( 2023-10-17 )-2.h5' # 這是一個儲存模型權重的路徑,每一個模型都有一個自己權重儲存的檔 - if os.path.exists(model_dir): # 如果這個檔案存在 - model.load_weights(model_dir) # 將模型權重讀出來 - print("讀出權重\n") + # 取得One-hot encording 的資料 + tool.Set_OneHotEncording(Loading_Config["Training_Labels"]) + Encording_Label = tool.Get_OneHot_Encording_Label() + + Label_Length = len(Loading_Config["Training_Labels"]) + + # 检查目标类别是否有效 + target_class = args.target_class + training_labels = Loading_Config["Training_Labels"] + if target_class < 0 or target_class >= len(training_labels): + print(f"错误: 目标类别索引 {target_class} 无效,必须在 0 到 {len(training_labels)-1} 之间") + exit(1) + + print(f"正在评估类别: {training_labels[target_class]}") + + load = Loding_Data_Root(Loading_Config["Training_Labels"], Loading_Config["Train_Data_Root"], Loading_Config["ImageGenerator_Data_Root"]) + cut_image = Load_Indepentend_Data(Loading_Config["Training_Labels"], Encording_Label) + + # 创建模型 + experiment = experiments(Training_Config, Loading_Config, tool, 3, "Test") + model = experiment.construct_model() + + # 加载模型权重 + Model_Weight = [ + "../Result/save_the_best_model/Xception Skin trains Stomach Cancer Dataset, and uses WeightRandomSampler Change HSV Channel of V is -150/Xception/best_model( 2025-05-19 )-_fold0.pt", + "../Result/save_the_best_model/Xception Skin trains Stomach Cancer Dataset, and uses WeightRandomSampler Change HSV Channel of V is -150/Xception/best_model( 2025-05-19 )-_fold1.pt", + "../Result/save_the_best_model/Xception Skin trains Stomach Cancer Dataset, and uses WeightRandomSampler Change HSV Channel of V is -150/Xception/best_model( 2025-05-19 )-_fold2.pt", + "../Result/save_the_best_model/Xception Skin trains Stomach Cancer Dataset, and uses WeightRandomSampler Change HSV Channel of V is -150/Xception/best_model( 2025-05-19 )-_fold3.pt", + "../Result/save_the_best_model/Xception Skin trains Stomach Cancer Dataset, and uses WeightRandomSampler Change HSV Channel of V is -150/Xception/best_model( 2025-05-19 )-_fold4.pt" + ] + + Calculate_Tool = [Calculate() for i in range(3)] + i = 0 + for path in Model_Weight: + if os.path.exists(path): + model.load_state_dict(torch.load(path)) + print("读取权重完成\n") + model.eval() # 设置为评估模式 + + # 预处理对象 + preprocess = Training_Precesses(256) + + cut_image.process_main(Loading_Config["Test_Data_Root"]) # 呼叫處理test Data與Validation Data的function + test, test_label = cut_image.test, cut_image.test_label + test, test_label = shuffle_data(test, test_label) - for times in range(5): - name = ["BP", "PF", "PV", "Chickenpox", "Monkeypox", "Normal", "Another"] - cut_image.process_main() # 呼叫處理test Data與Validation Data的function - test, test_label = cut_image.test, cut_image.test_label + # 只评估目标类别 + # 转换为PyTorch张量并移动到设备 + test_dataset = preprocess.Setting_DataSet(cut_image.test, cut_image.test_label, "Transform") + test_loader = preprocess.Dataloader_Sampler(test_dataset, 1, False) + + all_results = [] + all_labels = [] + + # 使用PyTorch的预测方式 + with torch.no_grad(): # 不计算梯度 + for inputs, labels, _, _ in test_loader: + inputs = inputs.to(device) + labels = labels.to(device) - total_data = [[], [], [], [], [], [], []] - total_labels = [[], [], [], [], [], [], []] - start = 0 - end = 22 - for k in range(7): - for i in range(start, end): - total_data[k].append(test[i]) - total_labels[k].append(test_label[i]) - - total_data[k], total_labels[k] = image_processing.image_data_processing(total_data[k], total_labels[k]) - total_data[k] = image_processing.normalization(total_data[k]) + outputs = model(inputs) + _, predicted = torch.max(outputs, 1) + all_results.append(predicted.cpu().numpy()) + all_labels.append(np.argmax(labels.cpu().numpy(), axis=1)) + + # 合并结果 + Predict = np.concatenate(all_results) + y_test = np.concatenate(all_labels) + + print(f"{training_labels[target_class]} 预测结果: {Predict}\n") - start = end - end += 22 - + # 计算评估指标 + accuracy = accuracy_score(y_test, Predict) + precision = precision_score(y_test, Predict, average=None) + recall = recall_score(y_test, Predict, average=None) + F1 = f1_score(y_test, Predict, average=None) + + # 计算每个类别的准确率 + class_accuracies = [] + for class_idx in range(len(training_labels)): + class_mask = (y_test == class_idx) + class_accuracy = accuracy_score(y_test[class_mask], Predict[class_mask]) + class_accuracies.append(class_accuracy) + + print(f"运行 {i+1}:\n") + print(f"整体准确率 (Accuracy): {accuracy:.4f}") + for class_idx in range(len(training_labels)): + print(f"类别 {training_labels[class_idx]} 的评估指标:") + print(f" 准确率 (Accuracy): {class_accuracies[class_idx]:.4f}") + print(f" 精确率 (Precision): {precision[class_idx]:.4f}") + print(f" 召回率 (Recall): {recall[class_idx]:.4f}") + print(f" F1值: {F1[class_idx]:.4f}\n") + Calculate_Tool[class_idx].Append_numbers(0, class_accuracies[class_idx], precision[class_idx], recall[class_idx], 0, F1[class_idx]) + + i += 1 - j = 0 - for total_label in range(7): - result = model.predict(total_data[j]) # 利用predict function來預測結果 - result = np.argmax(result, axis = 1) # 將預測出來的結果從one-hot encoding轉成label-encoding - y_test = np.argmax(total_labels[j], axis = 1) - - print(name[j] + str(result), "\n") - - y_pre = [] - for i in range(len(result)): - if result[i] != j: - result[i] = 0 - else: - result[i] = 1 - - y_test[i] = 1 - - matrix = confusion_matrix(y_test, result, labels = [0, 1]) # 丟入confusion matrix的function中,以形成混淆矩陣 - draw(matrix, name[j], times) # 呼叫畫出confusion matrix的function - tn, fp, fn, tp = matrix.ravel() - - accuracy = (tn + tp) / (tn + fp + fn + tp) - print(name[j] + " 權重為: ", accuracy) - - experiment.record_everyTime_test_result(0, accuracy, 0, 0, 0, 0, times, name[j]) - - j += 1 + # 计算平均值和标准差 + for i in range(3): + Calculate_Tool[i].Calculate_Mean() + Calculate_Tool[i].Calculate_Std() + print(f"\n{training_labels[target_class]} 类别的评估结果:") + print(Calculate_Tool[i].Output_Style()) diff --git a/training_files.txt b/training_files.txt new file mode 100644 index 0000000..2955450 Binary files /dev/null and b/training_files.txt differ diff --git a/training_question_files.txt b/training_question_files.txt new file mode 100644 index 0000000..6f61db7 Binary files /dev/null and b/training_question_files.txt differ diff --git a/utils/Stomach_Config.py b/utils/Stomach_Config.py new file mode 100644 index 0000000..779a164 --- /dev/null +++ b/utils/Stomach_Config.py @@ -0,0 +1,73 @@ +# 在import部分添加median_filter +from Image_Process.image_enhancement import histogram_equalization, adaptive_histogram_equalization_without_limit, unsharp_mask, laplacian_sharpen, adjust_hsv, gamma_correction, Contrast_Limited_Adaptive_Histogram_Equalization, Hight_Light, mean_filter, median_filter +import datetime + +Image_Enhance = { + "Shapen" : laplacian_sharpen, + "CLAHE": histogram_equalization, + "CLAHE_Adaptive" : adaptive_histogram_equalization_without_limit, + "CLAHE_Adaptive_have_Limit" : Contrast_Limited_Adaptive_Histogram_Equalization, + "HSV" : adjust_hsv, + "gamma" : gamma_correction, + "Hight_Light" : Hight_Light, + "Mean" : mean_filter, + "Median" : median_filter, # 添加中值濾波 + "Gamma_Value" : 1.0 +} + +Loading_Config = { + "Test_Data_Root": "../Dataset/Testing", + "Train_Data_Root": "../Dataset/Training", + "Annotation_Training_Root": "../Dataset/Annotation/Training", + "Annotation_Testing_Root": "../Dataset/Annotation/Testing", + "TestProcess_Image_Root" : "../TestProcess_Image", + "ImageGenerator_Data_Root": "../Dataset/ImageGenerator", + "Process_Roots" : "../Dataset/test_Images", + # "Image enhance processing save root": f'../Dataset/image_enhancement_Result/New_{Image_Enhance["gamma"].__name__}_and_gamma_value_is_{Image_Enhance["Gamma_Value"]}', + "Image enhance processing save root": f'../Dataset/image_enhancement_Result/Resetting the training and testing dataset', + "Training_Labels": ["stomach_cancer_Crop", "Normal_Crop", "Have_Question_Crop"], + "Label_Image_Labels" : ["CA", "Have_Question"], + "XML_Loading_Label" : ["stomach_cancer_Crop", "Have_Question_Crop"], + "Identification_Label_Length" : 2, +} + +Training_Config = { + "Model_Name": "Xception and GastoSegNet", + "CA_Experiment_Name": f"New architecture of Xception to CA and Have Question", + "Normal_Experiment_Name": f"New architecture of Xception to Normal and Others", + "Mask_Experiment_Name" : "New architecture of GastoSegNet", + "Epoch": 10000, + "Train_Batch_Size": 64, + "Image_Size": 256, + "Class_Count": 904, + "Get_Generator": "True", + "weight_decay": 0.01, + "Number_Of_Classes" : len(Loading_Config["Training_Labels"]) +} + +Model_Config = { + "Model Name": "xception", + "GPA Output Nodes": 2048, + "Linear Hidden Nodes": 1025, + "Output Linear Nodes": 2, + "Dropout Rate": 0.6 +} + +Save_Result_File_Config = { + "Identification_Plot_Image" : f"../Result/Training_Image/save_the_train_image({str(datetime.date.today())})", # 分類模型的走勢圖存檔路徑 + "Segument_Plot_Image" : f"../Result/Training_Image/save_the_train_image({str(datetime.date.today())})/Segument_Plot_Image", # 分割模型的走勢圖存檔路徑 + + "Identification_Marix_Image" : f"../Result/Matrix_Image/model_matrix_image({str(datetime.date.today())})/Identification_Plot_Marix_Image", # 分類模型的混淆矩陣存檔路徑 + + "Identification_Every_Fold_Training_Result" : f'../Result/Training_Result/save_the_train_result({str(datetime.date.today())})/Identification_Every_Fold', # 分類模型每折訓練結果存檔路徑 + "Identification_Average_Result" : f'../Result/Training_Average_Result/Average_Result({str(datetime.date.today())})/Identification_Average_Result', # 分類模型平均訓練結果存檔路徑 + + "Segument_Every_Fold_Training_Result" : f'../Result/Training_Result/save_the_train_result({str(datetime.date.today())})/Segument_Every_Fold', # 分割模型每折訓練結果存檔路徑 + "Segument_Average_Result" : f'../Result/Training_Average_Result/Average_Result({str(datetime.date.today())})/Segument_Average_Result', # 分割模型平均訓練結果存檔路徑 + "Segument_Bounding_Box_Image" : f'../Result/Bounding_Box_Image/save_bounding_box_image({str(datetime.date.today())})', # 分割模型邊界框圖像存檔路徑 + "Segument_Test_Bounding_Box_Image" : f'../Result/Test_Bounding_Box_Image/save_bounding_box_image({str(datetime.date.today())})', # 分割模型邊界框圖像存檔路徑 + + "Normal_Identification_Best_Model" : '../Result/save_the_best_model/Identification_Normal', + "CA_Identification_Best_Model" : "../Result/save_the_best_model/Identification_CA", + "Segmentation_Best_Model" : "../Result/save_the_best_model/Segmentation", +} \ No newline at end of file diff --git a/utils/__pycache__/Stomach_Config.cpython-311.pyc b/utils/__pycache__/Stomach_Config.cpython-311.pyc new file mode 100644 index 0000000..3708fdb Binary files /dev/null and b/utils/__pycache__/Stomach_Config.cpython-311.pyc differ diff --git a/utils/__pycache__/Stomach_Config.cpython-312.pyc b/utils/__pycache__/Stomach_Config.cpython-312.pyc new file mode 100644 index 0000000..12911a1 Binary files /dev/null and b/utils/__pycache__/Stomach_Config.cpython-312.pyc differ diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..ebd1dc0 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1404 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, + { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, + { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, + { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, + { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +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 = "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/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +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 = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "fonttools" +version = "4.59.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/a5/fba25f9fbdab96e26dedcaeeba125e5f05a09043bf888e0305326e55685b/fonttools-4.59.2.tar.gz", hash = "sha256:e72c0749b06113f50bcb80332364c6be83a9582d6e3db3fe0b280f996dc2ef22", size = 3540889, upload-time = "2025-08-27T16:40:30.97Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/53/742fcd750ae0bdc74de4c0ff923111199cc2f90a4ee87aaddad505b6f477/fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:511946e8d7ea5c0d6c7a53c4cb3ee48eda9ab9797cd9bf5d95829a398400354f", size = 2774961, upload-time = "2025-08-27T16:38:47.536Z" }, + { url = "https://files.pythonhosted.org/packages/57/2a/976f5f9fa3b4dd911dc58d07358467bec20e813d933bc5d3db1a955dd456/fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5e2682cf7be766d84f462ba8828d01e00c8751a8e8e7ce12d7784ccb69a30d", size = 2344690, upload-time = "2025-08-27T16:38:49.723Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/b7eefc274fcf370911e292e95565c8253b0b87c82a53919ab3c795a4f50e/fonttools-4.59.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5729e12a982dba3eeae650de48b06f3b9ddb51e9aee2fcaf195b7d09a96250e2", size = 5026910, upload-time = "2025-08-27T16:38:51.904Z" }, + { url = "https://files.pythonhosted.org/packages/69/95/864726eaa8f9d4e053d0c462e64d5830ec7c599cbdf1db9e40f25ca3972e/fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c52694eae5d652361d59ecdb5a2246bff7cff13b6367a12da8499e9df56d148d", size = 4971031, upload-time = "2025-08-27T16:38:53.676Z" }, + { url = "https://files.pythonhosted.org/packages/24/4c/b8c4735ebdea20696277c70c79e0de615dbe477834e5a7c2569aa1db4033/fonttools-4.59.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f1bbc23ba1312bd8959896f46f667753b90216852d2a8cfa2d07e0cb234144", size = 5006112, upload-time = "2025-08-27T16:38:55.69Z" }, + { url = "https://files.pythonhosted.org/packages/3b/23/f9ea29c292aa2fc1ea381b2e5621ac436d5e3e0a5dee24ffe5404e58eae8/fonttools-4.59.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a1bfe5378962825dabe741720885e8b9ae9745ec7ecc4a5ec1f1ce59a6062bf", size = 5117671, upload-time = "2025-08-27T16:38:58.984Z" }, + { url = "https://files.pythonhosted.org/packages/ba/07/cfea304c555bf06e86071ff2a3916bc90f7c07ec85b23bab758d4908c33d/fonttools-4.59.2-cp311-cp311-win32.whl", hash = "sha256:e937790f3c2c18a1cbc7da101550a84319eb48023a715914477d2e7faeaba570", size = 2218157, upload-time = "2025-08-27T16:39:00.75Z" }, + { url = "https://files.pythonhosted.org/packages/d7/de/35d839aa69db737a3f9f3a45000ca24721834d40118652a5775d5eca8ebb/fonttools-4.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:9836394e2f4ce5f9c0a7690ee93bd90aa1adc6b054f1a57b562c5d242c903104", size = 2265846, upload-time = "2025-08-27T16:39:02.453Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3d/1f45db2df51e7bfa55492e8f23f383d372200be3a0ded4bf56a92753dd1f/fonttools-4.59.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82906d002c349cad647a7634b004825a7335f8159d0d035ae89253b4abf6f3ea", size = 2769711, upload-time = "2025-08-27T16:39:04.423Z" }, + { url = "https://files.pythonhosted.org/packages/29/df/cd236ab32a8abfd11558f296e064424258db5edefd1279ffdbcfd4fd8b76/fonttools-4.59.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a10c1bd7644dc58f8862d8ba0cf9fb7fef0af01ea184ba6ce3f50ab7dfe74d5a", size = 2340225, upload-time = "2025-08-27T16:39:06.143Z" }, + { url = "https://files.pythonhosted.org/packages/98/12/b6f9f964fe6d4b4dd4406bcbd3328821c3de1f909ffc3ffa558fe72af48c/fonttools-4.59.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:738f31f23e0339785fd67652a94bc69ea49e413dfdb14dcb8c8ff383d249464e", size = 4912766, upload-time = "2025-08-27T16:39:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/82bde2f2d2c306ef3909b927363170b83df96171f74e0ccb47ad344563cd/fonttools-4.59.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ec99f9bdfee9cdb4a9172f9e8fd578cce5feb231f598909e0aecf5418da4f25", size = 4955178, upload-time = "2025-08-27T16:39:10.094Z" }, + { url = "https://files.pythonhosted.org/packages/92/77/7de766afe2d31dda8ee46d7e479f35c7d48747e558961489a2d6e3a02bd4/fonttools-4.59.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0476ea74161322e08c7a982f83558a2b81b491509984523a1a540baf8611cc31", size = 4897898, upload-time = "2025-08-27T16:39:12.087Z" }, + { url = "https://files.pythonhosted.org/packages/c5/77/ce0e0b905d62a06415fda9f2b2e109a24a5db54a59502b769e9e297d2242/fonttools-4.59.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95922a922daa1f77cc72611747c156cfb38030ead72436a2c551d30ecef519b9", size = 5049144, upload-time = "2025-08-27T16:39:13.84Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ea/870d93aefd23fff2e07cbeebdc332527868422a433c64062c09d4d5e7fe6/fonttools-4.59.2-cp312-cp312-win32.whl", hash = "sha256:39ad9612c6a622726a6a130e8ab15794558591f999673f1ee7d2f3d30f6a3e1c", size = 2206473, upload-time = "2025-08-27T16:39:15.854Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/e44bad000c4a4bb2e9ca11491d266e857df98ab6d7428441b173f0fe2517/fonttools-4.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:980fd7388e461b19a881d35013fec32c713ffea1fc37aef2f77d11f332dfd7da", size = 2254706, upload-time = "2025-08-27T16:39:17.893Z" }, + { url = "https://files.pythonhosted.org/packages/13/7b/d0d3b9431642947b5805201fbbbe938a47b70c76685ef1f0cb5f5d7140d6/fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:381bde13216ba09489864467f6bc0c57997bd729abfbb1ce6f807ba42c06cceb", size = 2761563, upload-time = "2025-08-27T16:39:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/fc5fe58dd76af7127b769b68071dbc32d4b95adc8b58d1d28d42d93c90f2/fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f33839aa091f7eef4e9078f5b7ab1b8ea4b1d8a50aeaef9fdb3611bba80869ec", size = 2335671, upload-time = "2025-08-27T16:39:22.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6235fc06bcbdb40186f483ba9d5d68f888ea68aa3c8dac347e05a7c54346fbc8", size = 4893967, upload-time = "2025-08-27T16:39:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/26/a9/d46d2ad4fcb915198504d6727f83aa07f46764c64f425a861aa38756c9fd/fonttools-4.59.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83ad6e5d06ef3a2884c4fa6384a20d6367b5cfe560e3b53b07c9dc65a7020e73", size = 4951986, upload-time = "2025-08-27T16:39:25.379Z" }, + { url = "https://files.pythonhosted.org/packages/07/90/1cc8d7dd8f707dfeeca472b82b898d3add0ebe85b1f645690dcd128ee63f/fonttools-4.59.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d029804c70fddf90be46ed5305c136cae15800a2300cb0f6bba96d48e770dde0", size = 4891630, upload-time = "2025-08-27T16:39:27.494Z" }, + { url = "https://files.pythonhosted.org/packages/d8/04/f0345b0d9fe67d65aa8d3f2d4cbf91d06f111bc7b8d802e65914eb06194d/fonttools-4.59.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:95807a3b5e78f2714acaa26a33bc2143005cc05c0217b322361a772e59f32b89", size = 5035116, upload-time = "2025-08-27T16:39:29.406Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7d/5ba5eefffd243182fbd067cdbfeb12addd4e5aec45011b724c98a344ea33/fonttools-4.59.2-cp313-cp313-win32.whl", hash = "sha256:b3ebda00c3bb8f32a740b72ec38537d54c7c09f383a4cfefb0b315860f825b08", size = 2204907, upload-time = "2025-08-27T16:39:31.42Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a9/be7219fc64a6026cc0aded17fa3720f9277001c185434230bd351bf678e6/fonttools-4.59.2-cp313-cp313-win_amd64.whl", hash = "sha256:a72155928d7053bbde499d32a9c77d3f0f3d29ae72b5a121752481bcbd71e50f", size = 2253742, upload-time = "2025-08-27T16:39:33.079Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c7/486580d00be6fa5d45e41682e5ffa5c809f3d25773c6f39628d60f333521/fonttools-4.59.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d09e487d6bfbe21195801323ba95c91cb3523f0fcc34016454d4d9ae9eaa57fe", size = 2762444, upload-time = "2025-08-27T16:39:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9b/950ea9b7b764ceb8d18645c62191e14ce62124d8e05cb32a4dc5e65fde0b/fonttools-4.59.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dec2f22486d7781087b173799567cffdcc75e9fb2f1c045f05f8317ccce76a3e", size = 2333256, upload-time = "2025-08-27T16:39:40.777Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4d/8ee9d563126de9002eede950cde0051be86cc4e8c07c63eca0c9fc95734a/fonttools-4.59.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1647201af10993090120da2e66e9526c4e20e88859f3e34aa05b8c24ded2a564", size = 4834846, upload-time = "2025-08-27T16:39:42.885Z" }, + { url = "https://files.pythonhosted.org/packages/03/26/f26d947b0712dce3d118e92ce30ca88f98938b066498f60d0ee000a892ae/fonttools-4.59.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47742c33fe65f41eabed36eec2d7313a8082704b7b808752406452f766c573fc", size = 4930871, upload-time = "2025-08-27T16:39:44.818Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/ebe878061a5a5e6b6502f0548489e01100f7e6c0049846e6546ba19a3ab4/fonttools-4.59.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92ac2d45794f95d1ad4cb43fa07e7e3776d86c83dc4b9918cf82831518165b4b", size = 4876971, upload-time = "2025-08-27T16:39:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0d/0d22e3a20ac566836098d30718092351935487e3271fd57385db1adb2fde/fonttools-4.59.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fa9ecaf2dcef8941fb5719e16322345d730f4c40599bbf47c9753de40eb03882", size = 4987478, upload-time = "2025-08-27T16:39:48.774Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a3/960cc83182a408ffacc795e61b5f698c6f7b0cfccf23da4451c39973f3c8/fonttools-4.59.2-cp314-cp314-win32.whl", hash = "sha256:a8d40594982ed858780e18a7e4c80415af65af0f22efa7de26bdd30bf24e1e14", size = 2208640, upload-time = "2025-08-27T16:39:50.592Z" }, + { url = "https://files.pythonhosted.org/packages/d8/74/55e5c57c414fa3965fee5fc036ed23f26a5c4e9e10f7f078a54ff9c7dfb7/fonttools-4.59.2-cp314-cp314-win_amd64.whl", hash = "sha256:9cde8b6a6b05f68516573523f2013a3574cb2c75299d7d500f44de82ba947b80", size = 2258457, upload-time = "2025-08-27T16:39:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/e1/dc/8e4261dc591c5cfee68fecff3ffee2a9b29e1edc4c4d9cbafdc5aefe74ee/fonttools-4.59.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:036cd87a2dbd7ef72f7b68df8314ced00b8d9973aee296f2464d06a836aeb9a9", size = 2829901, upload-time = "2025-08-27T16:39:55.014Z" }, + { url = "https://files.pythonhosted.org/packages/fb/05/331538dcf21fd6331579cd628268150e85210d0d2bdae20f7598c2b36c05/fonttools-4.59.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:14870930181493b1d740b6f25483e20185e5aea58aec7d266d16da7be822b4bb", size = 2362717, upload-time = "2025-08-27T16:39:56.843Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/d26428ca9ede809c0a93f0af91f44c87433dc0251e2aec333da5ed00d38f/fonttools-4.59.2-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7ff58ea1eb8fc7e05e9a949419f031890023f8785c925b44d6da17a6a7d6e85d", size = 4835120, upload-time = "2025-08-27T16:39:59.06Z" }, + { url = "https://files.pythonhosted.org/packages/07/c4/0f6ac15895de509e07688cb1d45f1ae583adbaa0fa5a5699d73f3bd58ca0/fonttools-4.59.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dee142b8b3096514c96ad9e2106bf039e2fe34a704c587585b569a36df08c3c", size = 5071115, upload-time = "2025-08-27T16:40:01.009Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b6/147a711b7ecf7ea39f9da9422a55866f6dd5747c2f36b3b0a7a7e0c6820b/fonttools-4.59.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8991bdbae39cf78bcc9cd3d81f6528df1f83f2e7c23ccf6f990fa1f0b6e19708", size = 4943905, upload-time = "2025-08-27T16:40:03.179Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4e/2ab19006646b753855e2b02200fa1cabb75faa4eeca4ef289f269a936974/fonttools-4.59.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:53c1a411b7690042535a4f0edf2120096a39a506adeb6c51484a232e59f2aa0c", size = 4960313, upload-time = "2025-08-27T16:40:05.45Z" }, + { url = "https://files.pythonhosted.org/packages/98/3d/df77907e5be88adcca93cc2cee00646d039da220164be12bee028401e1cf/fonttools-4.59.2-cp314-cp314t-win32.whl", hash = "sha256:59d85088e29fa7a8f87d19e97a1beae2a35821ee48d8ef6d2c4f965f26cb9f8a", size = 2269719, upload-time = "2025-08-27T16:40:07.553Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a0/d4c4bc5b50275449a9a908283b567caa032a94505fe1976e17f994faa6be/fonttools-4.59.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7ad5d8d8cc9e43cb438b3eb4a0094dd6d4088daa767b0a24d52529361fd4c199", size = 2333169, upload-time = "2025-08-27T16:40:09.656Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/d2f7be3c86708912c02571db0b550121caab8cd88a3c0aacb9cfa15ea66e/fonttools-4.59.2-py3-none-any.whl", hash = "sha256:8bd0f759020e87bb5d323e6283914d9bf4ae35a7307dafb2cbd1e379e720ad37", size = 1132315, upload-time = "2025-08-27T16:40:28.984Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/0f/5b60fc28ee7f8cc17a5114a584fd6b86e11c3e0a6e142a7f97a161e9640a/hf_xet-1.1.9.tar.gz", hash = "sha256:c99073ce404462e909f1d5839b2d14a3827b8fe75ed8aed551ba6609c026c803", size = 484242, upload-time = "2025-08-27T23:05:19.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/12/56e1abb9a44cdef59a411fe8a8673313195711b5ecce27880eb9c8fa90bd/hf_xet-1.1.9-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a3b6215f88638dd7a6ff82cb4e738dcbf3d863bf667997c093a3c990337d1160", size = 2762553, upload-time = "2025-08-27T23:05:15.153Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e6/2d0d16890c5f21b862f5df3146519c182e7f0ae49b4b4bf2bd8a40d0b05e/hf_xet-1.1.9-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9b486de7a64a66f9a172f4b3e0dfe79c9f0a93257c501296a2521a13495a698a", size = 2623216, upload-time = "2025-08-27T23:05:13.778Z" }, + { url = "https://files.pythonhosted.org/packages/81/42/7e6955cf0621e87491a1fb8cad755d5c2517803cea174229b0ec00ff0166/hf_xet-1.1.9-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c5a840c2c4e6ec875ed13703a60e3523bc7f48031dfd750923b2a4d1a5fc3c", size = 3186789, upload-time = "2025-08-27T23:05:12.368Z" }, + { url = "https://files.pythonhosted.org/packages/df/8b/759233bce05457f5f7ec062d63bbfd2d0c740b816279eaaa54be92aa452a/hf_xet-1.1.9-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:96a6139c9e44dad1c52c52520db0fffe948f6bce487cfb9d69c125f254bb3790", size = 3088747, upload-time = "2025-08-27T23:05:10.439Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3c/28cc4db153a7601a996985bcb564f7b8f5b9e1a706c7537aad4b4809f358/hf_xet-1.1.9-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ad1022e9a998e784c97b2173965d07fe33ee26e4594770b7785a8cc8f922cd95", size = 3251429, upload-time = "2025-08-27T23:05:16.471Z" }, + { url = "https://files.pythonhosted.org/packages/84/17/7caf27a1d101bfcb05be85850d4aa0a265b2e1acc2d4d52a48026ef1d299/hf_xet-1.1.9-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86754c2d6d5afb11b0a435e6e18911a4199262fe77553f8c50d75e21242193ea", size = 3354643, upload-time = "2025-08-27T23:05:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/cd/50/0c39c9eed3411deadcc98749a6699d871b822473f55fe472fad7c01ec588/hf_xet-1.1.9-cp37-abi3-win_amd64.whl", hash = "sha256:5aad3933de6b725d61d51034e04174ed1dce7a57c63d530df0014dea15a40127", size = 2804797, upload-time = "2025-08-27T23:05:20.77Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.34.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "imageio" +version = "2.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload-time = "2025-01-20T02:42:37.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload-time = "2025-01-20T02:42:34.931Z" }, +] + +[[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 = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + +[[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/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[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-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 = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.6" +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/a0/59/c3e6453a9676ffba145309a73c462bb407f4400de7de3f2b41af70720a3c/matplotlib-3.10.6.tar.gz", hash = "sha256:ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c", size = 34804264, upload-time = "2025-08-30T00:14:25.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/d6/5d3665aa44c49005aaacaa68ddea6fcb27345961cd538a98bb0177934ede/matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f", size = 8257527, upload-time = "2025-08-30T00:12:45.31Z" }, + { url = "https://files.pythonhosted.org/packages/8c/af/30ddefe19ca67eebd70047dabf50f899eaff6f3c5e6a1a7edaecaf63f794/matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76", size = 8119583, upload-time = "2025-08-30T00:12:47.236Z" }, + { url = "https://files.pythonhosted.org/packages/d3/29/4a8650a3dcae97fa4f375d46efcb25920d67b512186f8a6788b896062a81/matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6", size = 8692682, upload-time = "2025-08-30T00:12:48.781Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d3/b793b9cb061cfd5d42ff0f69d1822f8d5dbc94e004618e48a97a8373179a/matplotlib-3.10.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3276c85370bc0dfca051ec65c5817d1e0f8f5ce1b7787528ec8ed2d524bbc2f", size = 9521065, upload-time = "2025-08-30T00:12:50.602Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c5/53de5629f223c1c66668d46ac2621961970d21916a4bc3862b174eb2a88f/matplotlib-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9df5851b219225731f564e4b9e7f2ac1e13c9e6481f941b5631a0f8e2d9387ce", size = 9576888, upload-time = "2025-08-30T00:12:52.92Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/0a18d6d7d2d0a2e66585032a760d13662e5250c784d53ad50434e9560991/matplotlib-3.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e", size = 8115158, upload-time = "2025-08-30T00:12:54.863Z" }, + { url = "https://files.pythonhosted.org/packages/07/b3/1a5107bb66c261e23b9338070702597a2d374e5aa7004b7adfc754fbed02/matplotlib-3.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:886f989ccfae63659183173bb3fced7fd65e9eb793c3cc21c273add368536951", size = 7992444, upload-time = "2025-08-30T00:12:57.067Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1a/7042f7430055d567cc3257ac409fcf608599ab27459457f13772c2d9778b/matplotlib-3.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31ca662df6a80bd426f871105fdd69db7543e28e73a9f2afe80de7e531eb2347", size = 8272404, upload-time = "2025-08-30T00:12:59.112Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5d/1d5f33f5b43f4f9e69e6a5fe1fb9090936ae7bc8e2ff6158e7a76542633b/matplotlib-3.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1678bb61d897bb4ac4757b5ecfb02bfb3fddf7f808000fb81e09c510712fda75", size = 8128262, upload-time = "2025-08-30T00:13:01.141Z" }, + { url = "https://files.pythonhosted.org/packages/67/c3/135fdbbbf84e0979712df58e5e22b4f257b3f5e52a3c4aacf1b8abec0d09/matplotlib-3.10.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:56cd2d20842f58c03d2d6e6c1f1cf5548ad6f66b91e1e48f814e4fb5abd1cb95", size = 8697008, upload-time = "2025-08-30T00:13:03.24Z" }, + { url = "https://files.pythonhosted.org/packages/9c/be/c443ea428fb2488a3ea7608714b1bd85a82738c45da21b447dc49e2f8e5d/matplotlib-3.10.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:662df55604a2f9a45435566d6e2660e41efe83cd94f4288dfbf1e6d1eae4b0bb", size = 9530166, upload-time = "2025-08-30T00:13:05.951Z" }, + { url = "https://files.pythonhosted.org/packages/a9/35/48441422b044d74034aea2a3e0d1a49023f12150ebc58f16600132b9bbaf/matplotlib-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08f141d55148cd1fc870c3387d70ca4df16dee10e909b3b038782bd4bda6ea07", size = 9593105, upload-time = "2025-08-30T00:13:08.356Z" }, + { url = "https://files.pythonhosted.org/packages/45/c3/994ef20eb4154ab84cc08d033834555319e4af970165e6c8894050af0b3c/matplotlib-3.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:590f5925c2d650b5c9d813c5b3b5fc53f2929c3f8ef463e4ecfa7e052044fb2b", size = 8122784, upload-time = "2025-08-30T00:13:10.367Z" }, + { url = "https://files.pythonhosted.org/packages/57/b8/5c85d9ae0e40f04e71bedb053aada5d6bab1f9b5399a0937afb5d6b02d98/matplotlib-3.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:f44c8d264a71609c79a78d50349e724f5d5fc3684ead7c2a473665ee63d868aa", size = 7992823, upload-time = "2025-08-30T00:13:12.24Z" }, + { url = "https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:819e409653c1106c8deaf62e6de6b8611449c2cd9939acb0d7d4e57a3d95cc7a", size = 8273231, upload-time = "2025-08-30T00:13:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59c8ac8382fefb9cb71308dde16a7c487432f5255d8f1fd32473523abecfecdf", size = 8128730, upload-time = "2025-08-30T00:13:15.556Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84e82d9e0fd70c70bc55739defbd8055c54300750cbacf4740c9673a24d6933a", size = 8698539, upload-time = "2025-08-30T00:13:17.297Z" }, + { url = "https://files.pythonhosted.org/packages/71/34/44c7b1f075e1ea398f88aeabcc2907c01b9cc99e2afd560c1d49845a1227/matplotlib-3.10.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25f7a3eb42d6c1c56e89eacd495661fc815ffc08d9da750bca766771c0fd9110", size = 9529702, upload-time = "2025-08-30T00:13:19.248Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7f/e5c2dc9950c7facaf8b461858d1b92c09dd0cf174fe14e21953b3dda06f7/matplotlib-3.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9c862d91ec0b7842920a4cfdaaec29662195301914ea54c33e01f1a28d014b2", size = 9593742, upload-time = "2025-08-30T00:13:21.181Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:1b53bd6337eba483e2e7d29c5ab10eee644bc3a2491ec67cc55f7b44583ffb18", size = 8122753, upload-time = "2025-08-30T00:13:23.44Z" }, + { url = "https://files.pythonhosted.org/packages/e8/74/0e1670501fc7d02d981564caf7c4df42974464625935424ca9654040077c/matplotlib-3.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:cbd5eb50b7058b2892ce45c2f4e92557f395c9991f5c886d1bb74a1582e70fd6", size = 7992973, upload-time = "2025-08-30T00:13:26.632Z" }, + { url = "https://files.pythonhosted.org/packages/b1/4e/60780e631d73b6b02bd7239f89c451a72970e5e7ec34f621eda55cd9a445/matplotlib-3.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:acc86dd6e0e695c095001a7fccff158c49e45e0758fdf5dcdbb0103318b59c9f", size = 8316869, upload-time = "2025-08-30T00:13:28.262Z" }, + { url = "https://files.pythonhosted.org/packages/f8/15/baa662374a579413210fc2115d40c503b7360a08e9cc254aa0d97d34b0c1/matplotlib-3.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e228cd2ffb8f88b7d0b29e37f68ca9aaf83e33821f24a5ccc4f082dd8396bc27", size = 8178240, upload-time = "2025-08-30T00:13:30.007Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3f/3c38e78d2aafdb8829fcd0857d25aaf9e7dd2dfcf7ec742765b585774931/matplotlib-3.10.6-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:658bc91894adeab669cf4bb4a186d049948262987e80f0857216387d7435d833", size = 8711719, upload-time = "2025-08-30T00:13:31.72Z" }, + { url = "https://files.pythonhosted.org/packages/96/4b/2ec2bbf8cefaa53207cc56118d1fa8a0f9b80642713ea9390235d331ede4/matplotlib-3.10.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8913b7474f6dd83ac444c9459c91f7f0f2859e839f41d642691b104e0af056aa", size = 9541422, upload-time = "2025-08-30T00:13:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/83/7d/40255e89b3ef11c7871020563b2dd85f6cb1b4eff17c0f62b6eb14c8fa80/matplotlib-3.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:091cea22e059b89f6d7d1a18e2c33a7376c26eee60e401d92a4d6726c4e12706", size = 9594068, upload-time = "2025-08-30T00:13:35.833Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a9/0213748d69dc842537a113493e1c27daf9f96bd7cc316f933dc8ec4de985/matplotlib-3.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:491e25e02a23d7207629d942c666924a6b61e007a48177fdd231a0097b7f507e", size = 8200100, upload-time = "2025-08-30T00:13:37.668Z" }, + { url = "https://files.pythonhosted.org/packages/be/15/79f9988066ce40b8a6f1759a934ea0cde8dc4adc2262255ee1bc98de6ad0/matplotlib-3.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3d80d60d4e54cda462e2cd9a086d85cd9f20943ead92f575ce86885a43a565d5", size = 8042142, upload-time = "2025-08-30T00:13:39.426Z" }, + { url = "https://files.pythonhosted.org/packages/7c/58/e7b6d292beae6fb4283ca6fb7fa47d7c944a68062d6238c07b497dd35493/matplotlib-3.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:70aaf890ce1d0efd482df969b28a5b30ea0b891224bb315810a3940f67182899", size = 8273802, upload-time = "2025-08-30T00:13:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f6/7882d05aba16a8cdd594fb9a03a9d3cca751dbb6816adf7b102945522ee9/matplotlib-3.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1565aae810ab79cb72e402b22facfa6501365e73ebab70a0fdfb98488d2c3c0c", size = 8131365, upload-time = "2025-08-30T00:13:42.664Z" }, + { url = "https://files.pythonhosted.org/packages/94/bf/ff32f6ed76e78514e98775a53715eca4804b12bdcf35902cdd1cf759d324/matplotlib-3.10.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3b23315a01981689aa4e1a179dbf6ef9fbd17143c3eea77548c2ecfb0499438", size = 9533961, upload-time = "2025-08-30T00:13:44.372Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/6bf88c2fc2da7708a2ff8d2eeb5d68943130f50e636d5d3dcf9d4252e971/matplotlib-3.10.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30fdd37edf41a4e6785f9b37969de57aea770696cb637d9946eb37470c94a453", size = 9804262, upload-time = "2025-08-30T00:13:46.614Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7a/e05e6d9446d2d577b459427ad060cd2de5742d0e435db3191fea4fcc7e8b/matplotlib-3.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bc31e693da1c08012c764b053e702c1855378e04102238e6a5ee6a7117c53a47", size = 9595508, upload-time = "2025-08-30T00:13:48.731Z" }, + { url = "https://files.pythonhosted.org/packages/39/fb/af09c463ced80b801629fd73b96f726c9f6124c3603aa2e480a061d6705b/matplotlib-3.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:05be9bdaa8b242bc6ff96330d18c52f1fc59c6fb3a4dd411d953d67e7e1baf98", size = 8252742, upload-time = "2025-08-30T00:13:50.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f9/b682f6db9396d9ab8f050c0a3bfbb5f14fb0f6518f08507c04cc02f8f229/matplotlib-3.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:f56a0d1ab05d34c628592435781d185cd99630bdfd76822cd686fb5a0aecd43a", size = 8124237, upload-time = "2025-08-30T00:13:54.3Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d2/b69b4a0923a3c05ab90527c60fdec899ee21ca23ede7f0fb818e6620d6f2/matplotlib-3.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:94f0b4cacb23763b64b5dace50d5b7bfe98710fed5f0cef5c08135a03399d98b", size = 8316956, upload-time = "2025-08-30T00:13:55.932Z" }, + { url = "https://files.pythonhosted.org/packages/28/e9/dc427b6f16457ffaeecb2fc4abf91e5adb8827861b869c7a7a6d1836fa73/matplotlib-3.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cc332891306b9fb39462673d8225d1b824c89783fee82840a709f96714f17a5c", size = 8178260, upload-time = "2025-08-30T00:14:00.942Z" }, + { url = "https://files.pythonhosted.org/packages/c4/89/1fbd5ad611802c34d1c7ad04607e64a1350b7fb9c567c4ec2c19e066ed35/matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee1d607b3fb1590deb04b69f02ea1d53ed0b0bf75b2b1a5745f269afcbd3cdd3", size = 9541422, upload-time = "2025-08-30T00:14:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/65fec8716025b22c1d72d5a82ea079934c76a547696eaa55be6866bc89b1/matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:376a624a218116461696b27b2bbf7a8945053e6d799f6502fc03226d077807bf", size = 9803678, upload-time = "2025-08-30T00:14:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/40fb2b3a1ab9381bb39a952e8390357c8be3bdadcf6d5055d9c31e1b35ae/matplotlib-3.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:83847b47f6524c34b4f2d3ce726bb0541c48c8e7692729865c3df75bfa0f495a", size = 9594077, upload-time = "2025-08-30T00:14:07.012Z" }, + { url = "https://files.pythonhosted.org/packages/76/34/c4b71b69edf5b06e635eee1ed10bfc73cf8df058b66e63e30e6a55e231d5/matplotlib-3.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c7e0518e0d223683532a07f4b512e2e0729b62674f1b3a1a69869f98e6b1c7e3", size = 8342822, upload-time = "2025-08-30T00:14:09.041Z" }, + { url = "https://files.pythonhosted.org/packages/e8/62/aeabeef1a842b6226a30d49dd13e8a7a1e81e9ec98212c0b5169f0a12d83/matplotlib-3.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:4dd83e029f5b4801eeb87c64efd80e732452781c16a9cf7415b7b63ec8f374d7", size = 8172588, upload-time = "2025-08-30T00:14:11.166Z" }, + { url = "https://files.pythonhosted.org/packages/12/bb/02c35a51484aae5f49bd29f091286e7af5f3f677a9736c58a92b3c78baeb/matplotlib-3.10.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f2d684c3204fa62421bbf770ddfebc6b50130f9cad65531eeba19236d73bb488", size = 8252296, upload-time = "2025-08-30T00:14:19.49Z" }, + { url = "https://files.pythonhosted.org/packages/7d/85/41701e3092005aee9a2445f5ee3904d9dbd4a7df7a45905ffef29b7ef098/matplotlib-3.10.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6f4a69196e663a41d12a728fab8751177215357906436804217d6d9cf0d4d6cf", size = 8116749, upload-time = "2025-08-30T00:14:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/16/53/8d8fa0ea32a8c8239e04d022f6c059ee5e1b77517769feccd50f1df43d6d/matplotlib-3.10.6-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d6ca6ef03dfd269f4ead566ec6f3fb9becf8dab146fb999022ed85ee9f6b3eb", size = 8693933, upload-time = "2025-08-30T00:14:22.942Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +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/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 = "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 = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, +] + +[[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" }, +] +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" }, +] +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" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, +] +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" }, +] +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.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/5b/4e4fff7bad39adf89f735f2bc87248c81db71205b62bcc0d5ca5b606b3c3/nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039", size = 322364134, upload-time = "2025-06-03T21:58:04.013Z" }, +] + +[[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-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 = "opencv-python-headless" +version = "4.12.0.88" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/63/6861102ec149c3cd298f4d1ea7ce9d6adbc7529221606ff1dab991a19adb/opencv-python-headless-4.12.0.88.tar.gz", hash = "sha256:cfdc017ddf2e59b6c2f53bc12d74b6b0be7ded4ec59083ea70763921af2b6c09", size = 95379675, upload-time = "2025-07-07T09:21:06.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/7d/414e243c5c8216a5277afd104a319cc1291c5e23f5eeef512db5629ee7f4/opencv_python_headless-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1e58d664809b3350c1123484dd441e1667cd7bed3086db1b9ea1b6f6cb20b50e", size = 37877864, upload-time = "2025-07-07T09:14:41.693Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/7e162714beed1cd5e7b5eb66fcbcba2f065c51b1d9da2463024c84d2f7c0/opencv_python_headless-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:365bb2e486b50feffc2d07a405b953a8f3e8eaa63865bc650034e5c71e7a5154", size = 57326608, upload-time = "2025-07-07T09:14:51.885Z" }, + { url = "https://files.pythonhosted.org/packages/69/4e/116720df7f1f7f3b59abc608ca30fbec9d2b3ae810afe4e4d26483d9dfa0/opencv_python_headless-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:aeb4b13ecb8b4a0beb2668ea07928160ea7c2cd2d9b5ef571bbee6bafe9cc8d0", size = 33145800, upload-time = "2025-07-07T09:15:00.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/53/e19c21e0c4eb1275c3e2c97b081103b6dfb3938172264d283a519bf728b9/opencv_python_headless-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:236c8df54a90f4d02076e6f9c1cc763d794542e886c576a6fee46ec8ff75a7a9", size = 54023419, upload-time = "2025-07-07T09:15:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9c/a76fd5414de6ec9f21f763a600058a0c3e290053cea87e0275692b1375c0/opencv_python_headless-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:fde2cf5c51e4def5f2132d78e0c08f9c14783cd67356922182c6845b9af87dbd", size = 30225230, upload-time = "2025-07-07T09:15:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/f2/35/0858e9e71b36948eafbc5e835874b63e515179dc3b742cbe3d76bc683439/opencv_python_headless-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:86b413bdd6c6bf497832e346cd5371995de148e579b9774f8eba686dee3f5528", size = 38923559, upload-time = "2025-07-07T09:15:25.229Z" }, +] + +[[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 = "pandas" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/8e/0e90233ac205ad182bd6b422532695d2b9414944a280488105d598c70023/pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb", size = 4488684, upload-time = "2025-08-21T10:28:29.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/59/f3e010879f118c2d400902d2d871c2226cef29b08c09fb8dc41111730400/pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743", size = 11563308, upload-time = "2025-08-21T10:26:56.656Z" }, + { url = "https://files.pythonhosted.org/packages/38/18/48f10f1cc5c397af59571d638d211f494dba481f449c19adbd282aa8f4ca/pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4", size = 10820319, upload-time = "2025-08-21T10:26:59.162Z" }, + { url = "https://files.pythonhosted.org/packages/95/3b/1e9b69632898b048e223834cd9702052bcf06b15e1ae716eda3196fb972e/pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2", size = 11790097, upload-time = "2025-08-21T10:27:02.204Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/0e2ffb30b1f7fbc9a588bd01e3c14a0d96854d09a887e15e30cc19961227/pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e", size = 12397958, upload-time = "2025-08-21T10:27:05.409Z" }, + { url = "https://files.pythonhosted.org/packages/23/82/e6b85f0d92e9afb0e7f705a51d1399b79c7380c19687bfbf3d2837743249/pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea", size = 13225600, upload-time = "2025-08-21T10:27:07.791Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f1/f682015893d9ed51611948bd83683670842286a8edd4f68c2c1c3b231eef/pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372", size = 13879433, upload-time = "2025-08-21T10:27:10.347Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e7/ae86261695b6c8a36d6a4c8d5f9b9ede8248510d689a2f379a18354b37d7/pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f", size = 11336557, upload-time = "2025-08-21T10:27:12.983Z" }, + { url = "https://files.pythonhosted.org/packages/ec/db/614c20fb7a85a14828edd23f1c02db58a30abf3ce76f38806155d160313c/pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9", size = 11587652, upload-time = "2025-08-21T10:27:15.888Z" }, + { url = "https://files.pythonhosted.org/packages/99/b0/756e52f6582cade5e746f19bad0517ff27ba9c73404607c0306585c201b3/pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b", size = 10717686, upload-time = "2025-08-21T10:27:18.486Z" }, + { url = "https://files.pythonhosted.org/packages/37/4c/dd5ccc1e357abfeee8353123282de17997f90ff67855f86154e5a13b81e5/pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175", size = 11278722, upload-time = "2025-08-21T10:27:21.149Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a4/f7edcfa47e0a88cda0be8b068a5bae710bf264f867edfdf7b71584ace362/pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9", size = 11987803, upload-time = "2025-08-21T10:27:23.767Z" }, + { url = "https://files.pythonhosted.org/packages/f6/61/1bce4129f93ab66f1c68b7ed1c12bac6a70b1b56c5dab359c6bbcd480b52/pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4", size = 12766345, upload-time = "2025-08-21T10:27:26.6Z" }, + { url = "https://files.pythonhosted.org/packages/8e/46/80d53de70fee835531da3a1dae827a1e76e77a43ad22a8cd0f8142b61587/pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811", size = 13439314, upload-time = "2025-08-21T10:27:29.213Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/8114832daff7489f179971dbc1d854109b7f4365a546e3ea75b6516cea95/pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae", size = 10983326, upload-time = "2025-08-21T10:27:31.901Z" }, + { url = "https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e", size = 11531061, upload-time = "2025-08-21T10:27:34.647Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9", size = 10668666, upload-time = "2025-08-21T10:27:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/f775ba76ecfb3424d7f5862620841cf0edb592e9abd2d2a5387d305fe7a8/pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a", size = 11332835, upload-time = "2025-08-21T10:27:40.188Z" }, + { url = "https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b", size = 12057211, upload-time = "2025-08-21T10:27:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9d/2df913f14b2deb9c748975fdb2491da1a78773debb25abbc7cbc67c6b549/pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6", size = 12749277, upload-time = "2025-08-21T10:27:45.474Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/da1a2417026bd14d98c236dba88e39837182459d29dcfcea510b2ac9e8a1/pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a", size = 13415256, upload-time = "2025-08-21T10:27:49.885Z" }, + { url = "https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b", size = 10982579, upload-time = "2025-08-21T10:28:08.435Z" }, + { url = "https://files.pythonhosted.org/packages/f3/98/8df69c4097a6719e357dc249bf437b8efbde808038268e584421696cbddf/pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57", size = 12028163, upload-time = "2025-08-21T10:27:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/0e/23/f95cbcbea319f349e10ff90db488b905c6883f03cbabd34f6b03cbc3c044/pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2", size = 11391860, upload-time = "2025-08-21T10:27:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1b/6a984e98c4abee22058aa75bfb8eb90dce58cf8d7296f8bc56c14bc330b0/pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9", size = 11309830, upload-time = "2025-08-21T10:27:56.957Z" }, + { url = "https://files.pythonhosted.org/packages/15/d5/f0486090eb18dd8710bf60afeaf638ba6817047c0c8ae5c6a25598665609/pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2", size = 11883216, upload-time = "2025-08-21T10:27:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/10/86/692050c119696da19e20245bbd650d8dfca6ceb577da027c3a73c62a047e/pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012", size = 12699743, upload-time = "2025-08-21T10:28:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/612123674d7b17cf345aad0a10289b2a384bff404e0463a83c4a3a59d205/pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370", size = 13186141, upload-time = "2025-08-21T10:28:05.377Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +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/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +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 = "pytorch" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "matplotlib" }, + { name = "opencv-python-headless" }, + { name = "pandas" }, + { name = "scikit-image" }, + { name = "scikit-learn" }, + { name = "seaborn" }, + { name = "timm" }, + { name = "torch" }, + { name = "torchcam" }, + { name = "torchinfo" }, + { name = "torchmetrics" }, + { name = "torchvision" }, + { name = "tqdm" }, +] + +[package.metadata] +requires-dist = [ + { name = "matplotlib", specifier = ">=3.10.6" }, + { name = "opencv-python-headless", specifier = ">=4.12.0.88" }, + { name = "pandas", specifier = ">=2.3.2" }, + { name = "scikit-image", specifier = ">=0.25.2" }, + { name = "scikit-learn", specifier = ">=1.7.1" }, + { name = "seaborn", specifier = ">=0.13.2" }, + { name = "timm", specifier = ">=1.0.19" }, + { name = "torch", specifier = ">=2.8.0" }, + { name = "torchcam", specifier = ">=0.3.1" }, + { name = "torchinfo", specifier = ">=1.8.0" }, + { name = "torchmetrics", specifier = ">=1.8.2" }, + { name = "torchvision", specifier = ">=0.23.0" }, + { name = "tqdm", specifier = ">=4.67.1" }, +] + +[[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 = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +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" }, +] + +[[package]] +name = "safetensors" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9", size = 197968, upload-time = "2025-08-08T13:13:58.654Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba", size = 454797, upload-time = "2025-08-08T13:13:52.066Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b", size = 432206, upload-time = "2025-08-08T13:13:50.931Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd", size = 473261, upload-time = "2025-08-08T13:13:41.259Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a", size = 485117, upload-time = "2025-08-08T13:13:43.506Z" }, + { url = "https://files.pythonhosted.org/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1", size = 616154, upload-time = "2025-08-08T13:13:45.096Z" }, + { url = "https://files.pythonhosted.org/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda", size = 520713, upload-time = "2025-08-08T13:13:46.25Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f", size = 485835, upload-time = "2025-08-08T13:13:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19", size = 521503, upload-time = "2025-08-08T13:13:47.651Z" }, + { url = "https://files.pythonhosted.org/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce", size = 652256, upload-time = "2025-08-08T13:13:53.167Z" }, + { url = "https://files.pythonhosted.org/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7", size = 747281, upload-time = "2025-08-08T13:13:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5", size = 692286, upload-time = "2025-08-08T13:13:55.884Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac", size = 655957, upload-time = "2025-08-08T13:13:57.029Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1", size = 308926, upload-time = "2025-08-08T13:14:01.095Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c", size = 320192, upload-time = "2025-08-08T13:13:59.467Z" }, +] + +[[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/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, + { 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 = "scikit-learn" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445, upload-time = "2025-07-18T08:01:54.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/bd/a23177930abd81b96daffa30ef9c54ddbf544d3226b8788ce4c3ef1067b4/scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b", size = 9334838, upload-time = "2025-07-18T08:01:11.239Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a1/d3a7628630a711e2ac0d1a482910da174b629f44e7dd8cfcd6924a4ef81a/scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518", size = 8651241, upload-time = "2025-07-18T08:01:13.234Z" }, + { url = "https://files.pythonhosted.org/packages/26/92/85ec172418f39474c1cd0221d611345d4f433fc4ee2fc68e01f524ccc4e4/scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8", size = 9718677, upload-time = "2025-07-18T08:01:15.649Z" }, + { url = "https://files.pythonhosted.org/packages/df/ce/abdb1dcbb1d2b66168ec43b23ee0cee356b4cc4100ddee3943934ebf1480/scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7", size = 9511189, upload-time = "2025-07-18T08:01:18.013Z" }, + { url = "https://files.pythonhosted.org/packages/b2/3b/47b5eaee01ef2b5a80ba3f7f6ecf79587cb458690857d4777bfd77371c6f/scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650", size = 8914794, upload-time = "2025-07-18T08:01:20.357Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/57f176585b35ed865f51b04117947fe20f130f78940c6477b6d66279c9c2/scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087", size = 9260431, upload-time = "2025-07-18T08:01:22.77Z" }, + { url = "https://files.pythonhosted.org/packages/67/4e/899317092f5efcab0e9bc929e3391341cec8fb0e816c4789686770024580/scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f", size = 8637191, upload-time = "2025-07-18T08:01:24.731Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1b/998312db6d361ded1dd56b457ada371a8d8d77ca2195a7d18fd8a1736f21/scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87", size = 9486346, upload-time = "2025-07-18T08:01:26.713Z" }, + { url = "https://files.pythonhosted.org/packages/ad/09/a2aa0b4e644e5c4ede7006748f24e72863ba2ae71897fecfd832afea01b4/scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7", size = 9290988, upload-time = "2025-07-18T08:01:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/15/fa/c61a787e35f05f17fc10523f567677ec4eeee5f95aa4798dbbbcd9625617/scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88", size = 8735568, upload-time = "2025-07-18T08:01:30.936Z" }, + { url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143, upload-time = "2025-07-18T08:01:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977, upload-time = "2025-07-18T08:01:34.967Z" }, + { url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142, upload-time = "2025-07-18T08:01:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996, upload-time = "2025-07-18T08:01:39.721Z" }, + { url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418, upload-time = "2025-07-18T08:01:42.124Z" }, + { url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466, upload-time = "2025-07-18T08:01:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467, upload-time = "2025-07-18T08:01:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052, upload-time = "2025-07-18T08:01:48.676Z" }, + { url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575, upload-time = "2025-07-18T08:01:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310, upload-time = "2025-07-18T08:01:52.547Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/91/812adc6f74409b461e3a5fa97f4f74c769016919203138a3bf6fc24ba4c5/scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", size = 36552519, upload-time = "2025-07-27T16:26:29.658Z" }, + { url = "https://files.pythonhosted.org/packages/47/18/8e355edcf3b71418d9e9f9acd2708cc3a6c27e8f98fde0ac34b8a0b45407/scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", size = 28638010, upload-time = "2025-07-27T16:26:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/d9/eb/e931853058607bdfbc11b86df19ae7a08686121c203483f62f1ecae5989c/scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", size = 20909790, upload-time = "2025-07-27T16:26:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/45/0c/be83a271d6e96750cd0be2e000f35ff18880a46f05ce8b5d3465dc0f7a2a/scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", size = 23513352, upload-time = "2025-07-27T16:26:50.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bf/fe6eb47e74f762f933cca962db7f2c7183acfdc4483bd1c3813cfe83e538/scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", size = 33534643, upload-time = "2025-07-27T16:26:57.503Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ba/63f402e74875486b87ec6506a4f93f6d8a0d94d10467280f3d9d7837ce3a/scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", size = 35376776, upload-time = "2025-07-27T16:27:06.639Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b4/04eb9d39ec26a1b939689102da23d505ea16cdae3dbb18ffc53d1f831044/scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", size = 35698906, upload-time = "2025-07-27T16:27:14.943Z" }, + { url = "https://files.pythonhosted.org/packages/04/d6/bb5468da53321baeb001f6e4e0d9049eadd175a4a497709939128556e3ec/scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", size = 38129275, upload-time = "2025-07-27T16:27:23.873Z" }, + { url = "https://files.pythonhosted.org/packages/c4/94/994369978509f227cba7dfb9e623254d0d5559506fe994aef4bea3ed469c/scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", size = 38644572, upload-time = "2025-07-27T16:27:32.637Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194, upload-time = "2025-07-27T16:27:41.321Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590, upload-time = "2025-07-27T16:27:49.204Z" }, + { url = "https://files.pythonhosted.org/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458, upload-time = "2025-07-27T16:27:54.98Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318, upload-time = "2025-07-27T16:28:01.604Z" }, + { url = "https://files.pythonhosted.org/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899, upload-time = "2025-07-27T16:28:09.147Z" }, + { url = "https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637, upload-time = "2025-07-27T16:28:17.535Z" }, + { url = "https://files.pythonhosted.org/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507, upload-time = "2025-07-27T16:28:25.705Z" }, + { url = "https://files.pythonhosted.org/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998, upload-time = "2025-07-27T16:28:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060, upload-time = "2025-07-27T16:28:43.242Z" }, + { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, + { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, + { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, + { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, + { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, + { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, + { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, + { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, + { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" }, + { url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" }, + { url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" }, +] + +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +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 = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +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 = "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 = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tifffile" +version = "2025.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/a7/b77bd01f97d72bb70194f036e77f45927978f43017254762c784d7e10f49/tifffile-2025.9.9.tar.gz", hash = "sha256:6cf97ef548970eee9940cf8fc4203e57b4462a72e1e5e7a667ecdeb96113bc5f", size = 369652, upload-time = "2025-09-10T00:02:19.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/c5/0d57e3547add58285f401afbc421bd3ffeddbbd275a2c0b980b9067fda4a/tifffile-2025.9.9-py3-none-any.whl", hash = "sha256:239247551fa10b5679036ee030cdbeb7762bc1b3f11b1ddaaf50759ef8b4eb26", size = 230668, upload-time = "2025-09-10T00:02:17.839Z" }, +] + +[[package]] +name = "timm" +version = "1.0.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, + { name = "torchvision" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/78/0789838cf20ba1cc09907914a008c1823d087132b48aa1ccde5e7934175a/timm-1.0.19.tar.gz", hash = "sha256:6e71e1f67ac80c229d3a78ca58347090514c508aeba8f2e2eb5289eda86e9f43", size = 2353261, upload-time = "2025-07-24T03:04:05.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/74/661c63260cccf19ed5932e8b3f22f95ecd8bb34b9d9e6af9e1e7b961f254/timm-1.0.19-py3-none-any.whl", hash = "sha256:c07b56c32f3d3226c656f75c1b5479c08eb34eefed927c82fd8751a852f47931", size = 2497950, upload-time = "2025-07-24T03:04:03.097Z" }, +] + +[[package]] +name = "torch" +version = "2.8.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-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/c4/3e7a3887eba14e815e614db70b3b529112d1513d9dae6f4d43e373360b7f/torch-2.8.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:220a06fd7af8b653c35d359dfe1aaf32f65aa85befa342629f716acb134b9710", size = 102073391, upload-time = "2025-08-06T14:53:20.937Z" }, + { url = "https://files.pythonhosted.org/packages/5a/63/4fdc45a0304536e75a5e1b1bbfb1b56dd0e2743c48ee83ca729f7ce44162/torch-2.8.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c12fa219f51a933d5f80eeb3a7a5d0cbe9168c0a14bbb4055f1979431660879b", size = 888063640, upload-time = "2025-08-06T14:55:05.325Z" }, + { url = "https://files.pythonhosted.org/packages/84/57/2f64161769610cf6b1c5ed782bd8a780e18a3c9d48931319f2887fa9d0b1/torch-2.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c7ef765e27551b2fbfc0f41bcf270e1292d9bf79f8e0724848b1682be6e80aa", size = 241366752, upload-time = "2025-08-06T14:53:38.692Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5e/05a5c46085d9b97e928f3f037081d3d2b87fb4b4195030fc099aaec5effc/torch-2.8.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:5ae0524688fb6707c57a530c2325e13bb0090b745ba7b4a2cd6a3ce262572916", size = 73621174, upload-time = "2025-08-06T14:53:25.44Z" }, + { url = "https://files.pythonhosted.org/packages/49/0c/2fd4df0d83a495bb5e54dca4474c4ec5f9c62db185421563deeb5dabf609/torch-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e2fab4153768d433f8ed9279c8133a114a034a61e77a3a104dcdf54388838705", size = 101906089, upload-time = "2025-08-06T14:53:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/99/a8/6acf48d48838fb8fe480597d98a0668c2beb02ee4755cc136de92a0a956f/torch-2.8.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2aca0939fb7e4d842561febbd4ffda67a8e958ff725c1c27e244e85e982173c", size = 887913624, upload-time = "2025-08-06T14:56:44.33Z" }, + { url = "https://files.pythonhosted.org/packages/af/8a/5c87f08e3abd825c7dfecef5a0f1d9aa5df5dd0e3fd1fa2f490a8e512402/torch-2.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f4ac52f0130275d7517b03a33d2493bab3693c83dcfadf4f81688ea82147d2e", size = 241326087, upload-time = "2025-08-06T14:53:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/be/66/5c9a321b325aaecb92d4d1855421e3a055abd77903b7dab6575ca07796db/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0", size = 73630478, upload-time = "2025-08-06T14:53:57.144Z" }, + { url = "https://files.pythonhosted.org/packages/10/4e/469ced5a0603245d6a19a556e9053300033f9c5baccf43a3d25ba73e189e/torch-2.8.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2b2f96814e0345f5a5aed9bf9734efa913678ed19caf6dc2cddb7930672d6128", size = 101936856, upload-time = "2025-08-06T14:54:01.526Z" }, + { url = "https://files.pythonhosted.org/packages/16/82/3948e54c01b2109238357c6f86242e6ecbf0c63a1af46906772902f82057/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:65616ca8ec6f43245e1f5f296603e33923f4c30f93d65e103d9e50c25b35150b", size = 887922844, upload-time = "2025-08-06T14:55:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/e3/54/941ea0a860f2717d86a811adf0c2cd01b3983bdd460d0803053c4e0b8649/torch-2.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:659df54119ae03e83a800addc125856effda88b016dfc54d9f65215c3975be16", size = 241330968, upload-time = "2025-08-06T14:54:45.293Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/8b7b13bba430f5e21d77708b616f767683629fc4f8037564a177d20f90ed/torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:1a62a1ec4b0498930e2543535cf70b1bef8c777713de7ceb84cd79115f553767", size = 73915128, upload-time = "2025-08-06T14:54:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/15/0e/8a800e093b7f7430dbaefa80075aee9158ec22e4c4fc3c1a66e4fb96cb4f/torch-2.8.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:83c13411a26fac3d101fe8035a6b0476ae606deb8688e904e796a3534c197def", size = 102020139, upload-time = "2025-08-06T14:54:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/4a/15/5e488ca0bc6162c86a33b58642bc577c84ded17c7b72d97e49b5833e2d73/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8f0a9d617a66509ded240add3754e462430a6c1fc5589f86c17b433dd808f97a", size = 887990692, upload-time = "2025-08-06T14:56:18.286Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/6a04e4b54472fc5dba7ca2341ab219e529f3c07b6941059fbf18dccac31f/torch-2.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a7242b86f42be98ac674b88a4988643b9bc6145437ec8f048fea23f72feb5eca", size = 241603453, upload-time = "2025-08-06T14:55:22.945Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/650bb7f28f771af0cb791b02348db8b7f5f64f40f6829ee82aa6ce99aabe/torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7b677e17f5a3e69fdef7eb3b9da72622f8d322692930297e4ccb52fefc6c8211", size = 73632395, upload-time = "2025-08-06T14:55:28.645Z" }, +] + +[[package]] +name = "torchcam" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/96/c4d2c08eb9d6c6d30cd14561a56e46e24c3a12058b50d7af28dbdbd6a03e/torchcam-0.3.1.tar.gz", hash = "sha256:400d451cbbafe0dbddbf3a22feab9e23d1317e90cf6eae16bca96e0e2ff4f076", size = 25310, upload-time = "2021-10-31T17:55:46.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/70/a99d8ea8cf84a2d4805735a9d83cefb93a9d36e145882b04340e40242854/torchcam-0.3.1-py3-none-any.whl", hash = "sha256:50fff3a2996f9cacc76d96faa76de44fda0896f98e2dcef59e94bddbd813059d", size = 23181, upload-time = "2021-10-31T17:55:44.761Z" }, +] + +[[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.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161, upload-time = "2025-09-03T14:00:51.921Z" }, +] + +[[package]] +name = "torchvision" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/d7/15d3d7bd8d0239211b21673d1bac7bc345a4ad904a8e25bb3fd8a9cf1fbc/torchvision-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:49aa20e21f0c2bd458c71d7b449776cbd5f16693dd5807195a820612b8a229b7", size = 1856884, upload-time = "2025-08-06T14:58:00.237Z" }, + { url = "https://files.pythonhosted.org/packages/dd/14/7b44fe766b7d11e064c539d92a172fa9689a53b69029e24f2f1f51e7dc56/torchvision-0.23.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01dc33ee24c79148aee7cdbcf34ae8a3c9da1674a591e781577b716d233b1fa6", size = 2395543, upload-time = "2025-08-06T14:58:04.373Z" }, + { url = "https://files.pythonhosted.org/packages/79/9c/fcb09aff941c8147d9e6aa6c8f67412a05622b0c750bcf796be4c85a58d4/torchvision-0.23.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:35c27941831b653f5101edfe62c03d196c13f32139310519e8228f35eae0e96a", size = 8628388, upload-time = "2025-08-06T14:58:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/93/40/3415d890eb357b25a8e0a215d32365a88ecc75a283f75c4e919024b22d97/torchvision-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:09bfde260e7963a15b80c9e442faa9f021c7e7f877ac0a36ca6561b367185013", size = 1600741, upload-time = "2025-08-06T14:57:59.158Z" }, + { url = "https://files.pythonhosted.org/packages/df/1d/0ea0b34bde92a86d42620f29baa6dcbb5c2fc85990316df5cb8f7abb8ea2/torchvision-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e0e2c04a91403e8dd3af9756c6a024a1d9c0ed9c0d592a8314ded8f4fe30d440", size = 1856885, upload-time = "2025-08-06T14:58:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/e2/00/2f6454decc0cd67158c7890364e446aad4b91797087a57a78e72e1a8f8bc/torchvision-0.23.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6dd7c4d329a0e03157803031bc856220c6155ef08c26d4f5bbac938acecf0948", size = 2396614, upload-time = "2025-08-06T14:58:03.116Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b5/3e580dcbc16f39a324f3dd71b90edbf02a42548ad44d2b4893cc92b1194b/torchvision-0.23.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4e7d31c43bc7cbecbb1a5652ac0106b436aa66e26437585fc2c4b2cf04d6014c", size = 8627108, upload-time = "2025-08-06T14:58:12.956Z" }, + { url = "https://files.pythonhosted.org/packages/82/c1/c2fe6d61e110a8d0de2f94276899a2324a8f1e6aee559eb6b4629ab27466/torchvision-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2e45272abe7b8bf0d06c405e78521b5757be1bd0ed7e5cd78120f7fdd4cbf35", size = 1600723, upload-time = "2025-08-06T14:57:57.986Z" }, + { url = "https://files.pythonhosted.org/packages/91/37/45a5b9407a7900f71d61b2b2f62db4b7c632debca397f205fdcacb502780/torchvision-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1c37e325e09a184b730c3ef51424f383ec5745378dc0eca244520aca29722600", size = 1856886, upload-time = "2025-08-06T14:58:05.491Z" }, + { url = "https://files.pythonhosted.org/packages/ac/da/a06c60fc84fc849377cf035d3b3e9a1c896d52dbad493b963c0f1cdd74d0/torchvision-0.23.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2f7fd6c15f3697e80627b77934f77705f3bc0e98278b989b2655de01f6903e1d", size = 2353112, upload-time = "2025-08-06T14:58:26.265Z" }, + { url = "https://files.pythonhosted.org/packages/a0/27/5ce65ba5c9d3b7d2ccdd79892ab86a2f87ac2ca6638f04bb0280321f1a9c/torchvision-0.23.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a76fafe113b2977be3a21bf78f115438c1f88631d7a87203acb3dd6ae55889e6", size = 8627658, upload-time = "2025-08-06T14:58:15.999Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e4/028a27b60aa578a2fa99d9d7334ff1871bb17008693ea055a2fdee96da0d/torchvision-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:07d069cb29691ff566e3b7f11f20d91044f079e1dbdc9d72e0655899a9b06938", size = 1600749, upload-time = "2025-08-06T14:58:10.719Z" }, + { url = "https://files.pythonhosted.org/packages/05/35/72f91ad9ac7c19a849dedf083d347dc1123f0adeb401f53974f84f1d04c8/torchvision-0.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2df618e1143805a7673aaf82cb5720dd9112d4e771983156aaf2ffff692eebf9", size = 2047192, upload-time = "2025-08-06T14:58:11.813Z" }, + { url = "https://files.pythonhosted.org/packages/1d/9d/406cea60a9eb9882145bcd62a184ee61e823e8e1d550cdc3c3ea866a9445/torchvision-0.23.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2a3299d2b1d5a7aed2d3b6ffb69c672ca8830671967eb1cee1497bacd82fe47b", size = 2359295, upload-time = "2025-08-06T14:58:17.469Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f4/34662f71a70fa1e59de99772142f22257ca750de05ccb400b8d2e3809c1d/torchvision-0.23.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:76bc4c0b63d5114aa81281390f8472a12a6a35ce9906e67ea6044e5af4cab60c", size = 8800474, upload-time = "2025-08-06T14:58:22.53Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/b5a2d841a8d228b5dbda6d524704408e19e7ca6b7bb0f24490e081da1fa1/torchvision-0.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b9e2dabf0da9c8aa9ea241afb63a8f3e98489e706b22ac3f30416a1be377153b", size = 1527667, upload-time = "2025-08-06T14:58:14.446Z" }, +] + +[[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.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/39/43325b3b651d50187e591eefa22e236b2981afcebaefd4f2fc0ea99df191/triton-3.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b70f5e6a41e52e48cfc087436c8a28c17ff98db369447bcaff3b887a3ab4467", size = 155531138, upload-time = "2025-07-30T19:58:29.908Z" }, + { url = "https://files.pythonhosted.org/packages/d0/66/b1eb52839f563623d185f0927eb3530ee4d5ffe9d377cdaf5346b306689e/triton-3.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c1d84a5c0ec2c0f8e8a072d7fd150cab84a9c239eaddc6706c081bfae4eb04", size = 155560068, upload-time = "2025-07-30T19:58:37.081Z" }, + { url = "https://files.pythonhosted.org/packages/30/7b/0a685684ed5322d2af0bddefed7906674f67974aa88b0fae6e82e3b766f6/triton-3.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00be2964616f4c619193cb0d1b29a99bd4b001d7dc333816073f92cf2a8ccdeb", size = 155569223, upload-time = "2025-07-30T19:58:44.017Z" }, + { url = "https://files.pythonhosted.org/packages/20/63/8cb444ad5cdb25d999b7d647abac25af0ee37d292afc009940c05b82dda0/triton-3.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7936b18a3499ed62059414d7df563e6c163c5e16c3773678a3ee3d417865035d", size = 155659780, upload-time = "2025-07-30T19:58:51.171Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +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 = "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 = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +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" }, +]