""" Oxford Pets 图像分割 Gradio 交互界面。 这个文件把分割 Pipeline 包装成可体验的页面,方便上传图片或选择内置示例后查看模型预测的 灰度 mask。 """ # ASK: 和 Yolo 的 gradio 还有测试文件做比较 from typing import cast import gradio as gr import numpy as np from PIL import Image from deep_learning.env.resolve import display_path, resolve_path, resolve_saved from deep_learning.pipeline.services.checkpoint import describe_checkpoint_lookup, resolve_checkpoint from deep_learning.pipeline.services.model_loader import load_inference_artifact from tasks.segmentation.runner import pipeline IMAGE_SIZE = (200, 200) class SegmentationTool: def __init__(self, pipeline, inference_artifact, resource, image_size: tuple[int, int]): self.pipeline = pipeline self.inference_artifact = inference_artifact self.resource = resource self.image_size = image_size def segment(self, image) -> Image.Image: input_image = Image.fromarray(np.asarray(image, dtype="uint8")).convert("RGB") resized_image = input_image.resize( (self.image_size[1], self.image_size[0]) ) image_array = np.asarray(resized_image, dtype="float32") model_input = np.expand_dims(image_array, axis=0) prediction = self.inference_artifact.model(model_input, training=False) mask = np.argmax(np.asarray(prediction)[0], axis=-1).astype("uint8") * 127 return Image.fromarray(mask) class SegmentationPageAdapter: def __init__(self, pipeline): self.pipeline = pipeline self.sample_dir = resolve_path("data/dev/oxford_pets/images") def list_sample_images(self) -> list[str]: return sorted( path.name for path in self.sample_dir.iterdir() if path.is_file() and path.suffix.lower() in [".jpg", ".jpeg", ".png"] ) def load_sample_image(self, sample_name: str): image_path = self.sample_dir / sample_name return np.asarray(Image.open(image_path).convert("RGB")) def extra_model_info(self) -> str: return f"**示例图片目录**: {display_path(self.sample_dir)}" def create_tool(self, inference_artifact, resource) -> SegmentationTool: return SegmentationTool(self.pipeline, inference_artifact, resource, IMAGE_SIZE) class SegmentationAppBuilder: def __init__( self, pipeline, tool_factory, sample_images: list[str], load_sample_image, title: str = "Oxford Pets 图像分割", input_label: str = "输入图片", sample_label: str = "示例图片", output_image_label: str = "分割结果", extra_model_info=None ): self.pipeline = pipeline self.tool_factory = tool_factory self.sample_images = sample_images self.load_sample_image = load_sample_image self.title = title self.input_label = input_label self.sample_label = sample_label self.output_image_label = output_image_label self.extra_model_info = extra_model_info self._tool = None def _resolve_checkpoint_rule(self) -> dict: checkpoint_rule = self.pipeline.checkpoint_load_rules.resolve_test_rule( default_dirs=[resolve_saved(f"models/{self.pipeline.name}")] ) if checkpoint_rule["suffix"] is None: checkpoint_rule["suffix"] = ".keras" return checkpoint_rule def _load_inference_artifact(self) -> tuple: checkpoint_rule = self._resolve_checkpoint_rule() return load_inference_artifact( checkpoint_rule, self.pipeline.model_builder.load_inference_artifact ) def get_model_info(self) -> str: parts = [] checkpoint_rule = self._resolve_checkpoint_rule() checkpoint_path, _ = resolve_checkpoint(**checkpoint_rule) if checkpoint_path is None: lookup_info = describe_checkpoint_lookup( dirs=checkpoint_rule.get("dirs"), path=checkpoint_rule.get("path"), suffix=checkpoint_rule.get("suffix") ) raise FileNotFoundError(f"未找到模型检查点文件。查找信息: {lookup_info}") file_name = checkpoint_path.name file_size = checkpoint_path.stat().st_size parts.append(f"**模型文件**: {file_name}({file_size / (1024 * 1024):.2f} MB)") if self.extra_model_info is not None: extra_info = self.extra_model_info() if extra_info: parts.append(extra_info) return ",".join(parts) def _init_tool(self): print("正在加载分割模型...") inference_artifact, resource = self._load_inference_artifact() print("模型加载完成!") return self.tool_factory(inference_artifact, resource) def _ensure_tool_initialized(self) -> None: if self._tool is None: self._tool = self._init_tool() def select_sample_image(self, sample_name: str): return self.load_sample_image(sample_name) def segment_image(self, image): self._ensure_tool_initialized() return self._tool.segment(image) def create_ui(self): with gr.Blocks(title=self.title) as demo: gr.Markdown(f"# {self.title}") gr.Markdown("请选择示例图片或直接上传图片,分割时只会读取当前输入框中的图片。") try: gr.Markdown(self.get_model_info()) except Exception as e: gr.Markdown(f"**模型信息加载失败**: {str(e)}") with gr.Row(): with gr.Column(): input_image = gr.Image( label=self.input_label, type="numpy" ) sample_name = gr.Dropdown( choices=self.sample_images, value=cast(str | None, None), label=self.sample_label ) segment_btn = gr.Button("开始分割", variant="primary") with gr.Column(): output_image = gr.Image( label=self.output_image_label, type="pil", interactive=False ) sample_name.change( fn=self.select_sample_image, inputs=[sample_name], outputs=[input_image] ) segment_btn.click( fn=self.segment_image, inputs=[input_image], outputs=[output_image] ) original_get_config_file = demo.get_config_file def get_config_file(): config = original_get_config_file() for component in config["components"]: if component["type"] == "dropdown" and component["props"].get("label") == self.sample_label: component["props"]["value"] = None return config demo.get_config_file = get_config_file return demo adapter = SegmentationPageAdapter(pipeline) app = SegmentationAppBuilder( pipeline=pipeline, tool_factory=adapter.create_tool, sample_images=adapter.list_sample_images(), load_sample_image=adapter.load_sample_image, extra_model_info=adapter.extra_model_info ) demo = app.create_ui() if __name__ == "__main__": from deep_learning.env.keras import enable_mixed_precision enable_mixed_precision() demo.launch()