diffusers-源码解析-三十七-

diffusers 源码解析(三十七)

.\diffusers\pipelines\lumina\pipeline_lumina.py

# 版权所有 2024 Alpha-VLLM 和 HuggingFace 团队。保留所有权利。
#
# 根据 Apache 许可证,版本 2.0(“许可证”)进行许可;
# 您只能在遵守许可证的情况下使用此文件。
# 您可以在以下地址获取许可证的副本:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有约定,分发的软件均按“原样”提供,
# 不附带任何形式的明示或暗示的担保或条件。
# 有关许可证的具体权限和限制,请参见许可证。

import html  # 导入处理 HTML 实体的模块
import inspect  # 导入用于获取对象信息的模块
import math  # 导入数学函数模块
import re  # 导入正则表达式模块
import urllib.parse as ul  # 导入用于 URL 解析的模块,并将其重命名为 ul
from typing import List, Optional, Tuple, Union  # 导入类型注解模块中的相关类型

import torch  # 导入 PyTorch 库
from transformers import AutoModel, AutoTokenizer  # 从 transformers 导入自动模型和自动分词器

from ...image_processor import VaeImageProcessor  # 从上级模块导入 VAE 图像处理器
from ...models import AutoencoderKL  # 从上级模块导入自动编码器模型
from ...models.embeddings import get_2d_rotary_pos_embed_lumina  # 从上级模块导入获取 2D 旋转位置嵌入的函数
from ...models.transformers.lumina_nextdit2d import LuminaNextDiT2DModel  # 从上级模块导入 LuminaNextDiT2D 模型
from ...schedulers import FlowMatchEulerDiscreteScheduler  # 从上级模块导入调度器
from ...utils import (  # 从上级模块导入多个实用工具
    BACKENDS_MAPPING,  # 后端映射
    is_bs4_available,  # 检查 bs4 库是否可用的函数
    is_ftfy_available,  # 检查 ftfy 库是否可用的函数
    logging,  # 日志记录模块
    replace_example_docstring,  # 替换示例文档字符串的函数
)
from ...utils.torch_utils import randn_tensor  # 从上级模块导入生成随机张量的函数
from ..pipeline_utils import DiffusionPipeline, ImagePipelineOutput  # 从上级模块导入扩散管道和图像输出类

logger = logging.get_logger(__name__)  # 获取当前模块的日志记录器

if is_bs4_available():  # 如果 bs4 库可用
    from bs4 import BeautifulSoup  # 导入 BeautifulSoup 库用于解析 HTML

if is_ftfy_available():  # 如果 ftfy 库可用
    import ftfy  # 导入 ftfy 库用于文本修复

EXAMPLE_DOC_STRING = """  # 示例文档字符串,用于展示用法
    Examples:
        ```py
        >>> import torch  # 导入 PyTorch 库
        >>> from diffusers import LuminaText2ImgPipeline  # 从 diffusers 导入 LuminaText2ImgPipeline

        >>> pipe = LuminaText2ImgPipeline.from_pretrained(  # 从预训练模型加载管道
        ...     "Alpha-VLLM/Lumina-Next-SFT-diffusers", torch_dtype=torch.bfloat16  # 指定模型路径和数据类型
        ... ).cuda()  # 将管道移到 GPU
        >>> # 启用内存优化。
        >>> pipe.enable_model_cpu_offload()  # 启用模型的 CPU 卸载以节省内存

        >>> prompt = "Upper body of a young woman in a Victorian-era outfit with brass goggles and leather straps. Background shows an industrial revolution cityscape with smoky skies and tall, metal structures"  # 定义生成图像的提示
        >>> image = pipe(prompt).images[0]  # 生成图像并提取第一张图像
        ```py
"""

# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion 导入的函数
def retrieve_timesteps(  # 定义函数以检索时间步
    scheduler,  # 调度器对象
    num_inference_steps: Optional[int] = None,  # 可选推理步骤数
    device: Optional[Union[str, torch.device]] = None,  # 可选设备类型
    timesteps: Optional[List[int]] = None,  # 可选时间步列表
    sigmas: Optional[List[float]] = None,  # 可选 sigma 值列表
    **kwargs,  # 接收其他关键字参数
):
    """
    调用调度器的 `set_timesteps` 方法,并在调用后从调度器检索时间步。处理自定义时间步。
    任何关键字参数将传递给 `scheduler.set_timesteps`。
    # 参数说明
        Args:
            scheduler (`SchedulerMixin`):  # 定义调度器类型,用于获取时间步长
                The scheduler to get timesteps from.  # 调度器的描述
            num_inference_steps (`int`):  # 定义推理步骤数量
                The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`  # 生成样本时的扩散步骤数,如果使用,`timesteps` 必须为 `None`
                must be `None`.  # 说明条件
            device (`str` or `torch.device`, *optional*):  # 定义设备类型(字符串或torch.device),可选
                The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.  # 指定将时间步移动到的设备,如果为 `None`,则不移动
            timesteps (`List[int]`, *optional*):  # 自定义时间步,列表类型,可选
                Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,  # 自定义时间步,覆盖调度器的时间步策略,如果传递了 `timesteps`
                `num_inference_steps` and `sigmas` must be `None`.  # 则 `num_inference_steps` 和 `sigmas` 必须为 `None`
            sigmas (`List[float]`, *optional*):  # 自定义sigmas,列表类型,可选
                Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,  # 自定义sigmas,覆盖调度器的时间步策略,如果传递了 `sigmas`
                `num_inference_steps` and `timesteps` must be `None`.  # 则 `num_inference_steps` 和 `timesteps` 必须为 `None`
    
        Returns:
            `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the  # 返回类型为元组,包含调度器的时间步计划和推理步骤数量
            second element is the number of inference steps.  # 返回第二个元素为推理步骤数量
        """
        if timesteps is not None and sigmas is not None:  # 检查是否同时传入了时间步和sigmas
            raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")  # 抛出错误,要求只能选择一个
        if timesteps is not None:  # 如果传入了时间步
            accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())  # 检查调度器是否接受时间步
            if not accepts_timesteps:  # 如果不接受
                raise ValueError(  # 抛出错误,说明当前调度器不支持自定义时间步
                    f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"  # 当前调度器不支持自定义时间步
                    f" timestep schedules. Please check whether you are using the correct scheduler."  # 检查是否使用了正确的调度器
                )
            scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)  # 设置时间步,传入设备和其他参数
            timesteps = scheduler.timesteps  # 从调度器获取设置后的时间步
            num_inference_steps = len(timesteps)  # 计算推理步骤数量
        elif sigmas is not None:  # 如果传入了sigmas
            accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())  # 检查调度器是否接受sigmas
            if not accept_sigmas:  # 如果不接受
                raise ValueError(  # 抛出错误,说明当前调度器不支持自定义sigmas
                    f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"  # 当前调度器不支持自定义sigmas
                    f" sigmas schedules. Please check whether you are using the correct scheduler."  # 检查是否使用了正确的调度器
                )
            scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)  # 设置sigmas,传入设备和其他参数
            timesteps = scheduler.timesteps  # 从调度器获取设置后的时间步
            num_inference_steps = len(timesteps)  # 计算推理步骤数量
        else:  # 如果没有传入时间步和sigmas
            scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)  # 直接使用推理步骤设置时间步,传入设备和其他参数
            timesteps = scheduler.timesteps  # 从调度器获取设置后的时间步
        return timesteps, num_inference_steps  # 返回时间步和推理步骤数量
# 定义 LuminaText2ImgPipeline 类,继承自 DiffusionPipeline
class LuminaText2ImgPipeline(DiffusionPipeline):
    r"""
    Lumina-T2I 的文本到图像生成管道。

    此模型继承自 [`DiffusionPipeline`]。请查看父类文档以了解库为所有管道实现的通用方法(例如下载或保存、在特定设备上运行等)。

    参数:
        vae ([`AutoencoderKL`]):
            用于编码和解码图像到潜在表示的变分自编码器(VAE)模型。
        text_encoder ([`AutoModel`]):
            冻结的文本编码器。Lumina-T2I 使用
            [T5](https://huggingface.co/docs/transformers/model_doc/t5#transformers.AutoModel),具体是
            [t5-v1_1-xxl](https://huggingface.co/Alpha-VLLM/tree/main/t5-v1_1-xxl) 变体。
        tokenizer (`AutoModel`):
            [AutoModel](https://huggingface.co/docs/transformers/model_doc/t5#transformers.AutoModel) 类的分词器。
        transformer ([`Transformer2DModel`]):
            一种文本条件的 `Transformer2DModel`,用于去噪编码的图像潜在表示。
        scheduler ([`SchedulerMixin`]):
            用于与 `transformer` 结合使用的调度器,以去噪编码的图像潜在表示。
    """

    # 编译一个正则表达式,用于匹配不良标点
    bad_punct_regex = re.compile(
        r"["
        + "#®•©™&@·º½¾¿¡§~"
        + r"\)"
        + r"\("
        + r"\]"
        + r"\["
        + r"\}"
        + r"\{"
        + r"\|"
        + "\\"
        + r"\/"
        + r"\*"
        + r"]{1,}"
    )  # noqa

    # 定义可选组件的空列表
    _optional_components = []
    # 定义模型的 CPU 卸载顺序
    model_cpu_offload_seq = "text_encoder->transformer->vae"

    # 初始化方法,接受多个模型组件作为参数
    def __init__(
        self,
        transformer: LuminaNextDiT2DModel,
        scheduler: FlowMatchEulerDiscreteScheduler,
        vae: AutoencoderKL,
        text_encoder: AutoModel,
        tokenizer: AutoTokenizer,
    ):
        # 调用父类的初始化方法
        super().__init__()

        # 注册各个模块,便于后续使用
        self.register_modules(
            vae=vae,
            text_encoder=text_encoder,
            tokenizer=tokenizer,
            transformer=transformer,
            scheduler=scheduler,
        )
        # 设置 VAE 的缩放因子
        self.vae_scale_factor = 8
        # 创建图像处理器,用于处理 VAE 输出
        self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
        # 设置最大序列长度
        self.max_sequence_length = 256
        # 设置默认样本大小,根据 transformer 的配置或设置为 128
        self.default_sample_size = (
            self.transformer.config.sample_size
            if hasattr(self, "transformer") and self.transformer is not None
            else 128
        )
        # 计算默认图像大小
        self.default_image_size = self.default_sample_size * self.vae_scale_factor

    # 定义获取 gemma 提示嵌入的方法,接受多个参数
    def _get_gemma_prompt_embeds(
        self,
        prompt: Union[str, List[str]],
        num_images_per_prompt: int = 1,
        device: Optional[torch.device] = None,
        clean_caption: Optional[bool] = False,
        max_length: Optional[int] = None,
    # 处理输入的设备,使用给定设备或默认执行设备
        ):
            device = device or self._execution_device
            # 将字符串类型的提示转为列表形式
            prompt = [prompt] if isinstance(prompt, str) else prompt
            # 获取提示的批次大小
            batch_size = len(prompt)
    
            # 对提示文本进行预处理,选项可清理标题
            prompt = self._text_preprocessing(prompt, clean_caption=clean_caption)
            # 使用 tokenizer 处理提示文本,设置填充、最大长度等参数
            text_inputs = self.tokenizer(
                prompt,
                pad_to_multiple_of=8,
                max_length=self.max_sequence_length,
                truncation=True,
                padding=True,
                return_tensors="pt",
            )
            # 将输入 ID 移动到指定设备
            text_input_ids = text_inputs.input_ids.to(device)
            # 获取未截断的输入 ID,使用最长填充
            untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids.to(device)
    
            # 检查是否存在截断,且文本输入 ID 与未截断 ID 不同
            if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
                # 解码被截断的文本,并记录警告信息
                removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.max_sequence_length - 1 : -1])
                logger.warning(
                    "The following part of your input was truncated because Gemma can only handle sequences up to"
                    f" {self.max_sequence_length} tokens: {removed_text}"
                )
    
            # 将提示的注意力掩码移动到指定设备
            prompt_attention_mask = text_inputs.attention_mask.to(device)
            # 使用文本编码器获取提示的嵌入,输出隐藏状态
            prompt_embeds = self.text_encoder(
                text_input_ids, attention_mask=prompt_attention_mask, output_hidden_states=True
            )
            # 获取倒数第二层的隐藏状态作为提示嵌入
            prompt_embeds = prompt_embeds.hidden_states[-2]
    
            # 确定数据类型,优先使用文本编码器的数据类型
            if self.text_encoder is not None:
                dtype = self.text_encoder.dtype
            elif self.transformer is not None:
                dtype = self.transformer.dtype
            else:
                dtype = None
    
            # 将嵌入移动到指定的数据类型和设备
            prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
    
            # 解包嵌入的形状以获取序列长度
            _, seq_len, _ = prompt_embeds.shape
            # 为每个提示生成多个图像,重复嵌入和注意力掩码
            prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
            # 重新调整嵌入的形状
            prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
            # 重复注意力掩码以适应生成的图像数量
            prompt_attention_mask = prompt_attention_mask.repeat(num_images_per_prompt, 1)
            # 重新调整注意力掩码的形状
            prompt_attention_mask = prompt_attention_mask.view(batch_size * num_images_per_prompt, -1)
    
            # 返回嵌入和注意力掩码
            return prompt_embeds, prompt_attention_mask
    
        # 从 diffusers.pipelines.deepfloyd_if.pipeline_if.encode_prompt 适配
        def encode_prompt(
            # 定义编码提示的函数参数,包括提示和其他可选参数
            prompt: Union[str, List[str]],
            do_classifier_free_guidance: bool = True,
            negative_prompt: Union[str, List[str]] = None,
            num_images_per_prompt: int = 1,
            device: Optional[torch.device] = None,
            prompt_embeds: Optional[torch.Tensor] = None,
            negative_prompt_embeds: Optional[torch.Tensor] = None,
            prompt_attention_mask: Optional[torch.Tensor] = None,
            negative_prompt_attention_mask: Optional[torch.Tensor] = None,
            clean_caption: bool = False,
            **kwargs,
        # 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs 复制
    # 定义一个方法,准备额外的参数以供调度器步骤使用
    def prepare_extra_step_kwargs(self, generator, eta):
        # 为调度器步骤准备额外的关键字参数,因为不同调度器的签名可能不同
        # eta (η) 仅在 DDIMScheduler 中使用,其他调度器将忽略它
        # eta 对应于 DDIM 论文中的 η: https://arxiv.org/abs/2010.02502
        # eta 的值应在 [0, 1] 之间

        # 检查调度器的 step 方法是否接受 eta 参数
        accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
        # 初始化一个空的字典以存储额外的步骤参数
        extra_step_kwargs = {}
        # 如果调度器接受 eta 参数,将其添加到字典中
        if accepts_eta:
            extra_step_kwargs["eta"] = eta

        # 检查调度器的 step 方法是否接受 generator 参数
        accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
        # 如果调度器接受 generator 参数,将其添加到字典中
        if accepts_generator:
            extra_step_kwargs["generator"] = generator
        # 返回包含额外参数的字典
        return extra_step_kwargs

    # 定义一个方法,用于检查输入的有效性
    def check_inputs(
        self,
        prompt,  # 输入的提示文本
        height,  # 图像的高度
        width,   # 图像的宽度
        negative_prompt,  # 输入的负面提示文本
        prompt_embeds=None,  # 可选的提示嵌入
        negative_prompt_embeds=None,  # 可选的负面提示嵌入
        prompt_attention_mask=None,  # 可选的提示注意力掩码
        negative_prompt_attention_mask=None,  # 可选的负面提示注意力掩码
    ):
        # 检查高度和宽度是否是8的倍数,若不是,则抛出值错误
        if height % 8 != 0 or width % 8 != 0:
            raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")

        # 检查是否同时提供了 `prompt` 和 `prompt_embeds`,若是,则抛出值错误
        if prompt is not None and prompt_embeds is not None:
            raise ValueError(
                f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
                " only forward one of the two."
            )
        # 检查是否都没有提供 `prompt` 和 `prompt_embeds`,若是,则抛出值错误
        elif prompt is None and prompt_embeds is None:
            raise ValueError(
                "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
            )
        # 检查 `prompt` 是否为字符串或列表,若不是,则抛出值错误
        elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
            raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")

        # 检查是否同时提供了 `prompt` 和 `negative_prompt_embeds`,若是,则抛出值错误
        if prompt is not None and negative_prompt_embeds is not None:
            raise ValueError(
                f"Cannot forward both `prompt`: {prompt} and `negative_prompt_embeds`:"
                f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
            )

        # 检查是否同时提供了 `negative_prompt` 和 `negative_prompt_embeds`,若是,则抛出值错误
        if negative_prompt is not None and negative_prompt_embeds is not None:
            raise ValueError(
                f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
                f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
            )

        # 检查如果提供了 `prompt_embeds`,则必须提供 `prompt_attention_mask`,若没有,则抛出值错误
        if prompt_embeds is not None and prompt_attention_mask is None:
            raise ValueError("Must provide `prompt_attention_mask` when specifying `prompt_embeds`.")

        # 检查如果提供了 `negative_prompt_embeds`,则必须提供 `negative_prompt_attention_mask`,若没有,则抛出值错误
        if negative_prompt_embeds is not None and negative_prompt_attention_mask is None:
            raise ValueError("Must provide `negative_prompt_attention_mask` when specifying `negative_prompt_embeds`.")

        # 检查 `prompt_embeds` 和 `negative_prompt_embeds` 的形状是否一致,若不一致,则抛出值错误
        if prompt_embeds is not None and negative_prompt_embeds is not None:
            if prompt_embeds.shape != negative_prompt_embeds.shape:
                raise ValueError(
                    "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
                    f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
                    f" {negative_prompt_embeds.shape}."
                )
            # 检查 `prompt_attention_mask` 和 `negative_prompt_attention_mask` 的形状是否一致,若不一致,则抛出值错误
            if prompt_attention_mask.shape != negative_prompt_attention_mask.shape:
                raise ValueError(
                    "`prompt_attention_mask` and `negative_prompt_attention_mask` must have the same shape when passed directly, but"
                    f" got: `prompt_attention_mask` {prompt_attention_mask.shape} != `negative_prompt_attention_mask`"
                    f" {negative_prompt_attention_mask.shape}."
                )

    # 从 diffusers.pipelines.deepfloyd_if.pipeline_if.IFPipeline._text_preprocessing 复制的内容
    # 定义文本预处理的私有方法,接受文本和一个可选的清理标题参数
        def _text_preprocessing(self, text, clean_caption=False):
            # 如果设置了清理标题且 bs4 库不可用,记录警告并将清理标题设为 False
            if clean_caption and not is_bs4_available():
                logger.warning(BACKENDS_MAPPING["bs4"][-1].format("Setting `clean_caption=True`"))
                logger.warning("Setting `clean_caption` to False...")
                clean_caption = False
    
            # 如果设置了清理标题且 ftfy 库不可用,记录警告并将清理标题设为 False
            if clean_caption and not is_ftfy_available():
                logger.warning(BACKENDS_MAPPING["ftfy"][-1].format("Setting `clean_caption=True`"))
                logger.warning("Setting `clean_caption` to False...")
                clean_caption = False
    
            # 如果输入文本不是元组或列表,则将其转换为列表
            if not isinstance(text, (tuple, list)):
                text = [text]
    
            # 定义内部处理文本的函数
            def process(text: str):
                # 如果设置了清理标题,则进行标题清理两次
                if clean_caption:
                    text = self._clean_caption(text)
                    text = self._clean_caption(text)
                else:
                    # 否则将文本转换为小写并去除空格
                    text = text.lower().strip()
                return text
    
            # 对文本列表中的每个元素进行处理,并返回处理后的结果列表
            return [process(t) for t in text]
    
        # 从 diffusers.pipelines.deepfloyd_if.pipeline_if.IFPipeline._clean_caption 复制而来
        def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None):
            # 定义潜在变量的形状,根据输入参数计算
            shape = (
                batch_size,
                num_channels_latents,
                int(height) // self.vae_scale_factor,
                int(width) // self.vae_scale_factor,
            )
            # 如果生成器是列表且长度与批量大小不匹配,则引发错误
            if isinstance(generator, list) and len(generator) != batch_size:
                raise ValueError(
                    f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
                    f" size of {batch_size}. Make sure the batch size matches the length of the generators."
                )
    
            # 如果潜在变量为空,则生成随机张量作为潜在变量
            if latents is None:
                latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
            else:
                # 如果潜在变量已给出,则将其转移到指定设备
                latents = latents.to(device)
    
            # 返回生成的或已转换的潜在变量
            return latents
    
        # 返回指导比例的属性值
        @property
        def guidance_scale(self):
            return self._guidance_scale
    
        # 此处的指导比例与 Imagen 论文中方程 (2) 的指导权重 `w` 相似: https://arxiv.org/pdf/2205.11487.pdf 
        # 指导比例 = 1 表示没有进行无分类器的指导
        @property
        def do_classifier_free_guidance(self):
            return self._guidance_scale > 1
    
        # 返回时间步数的属性值
        @property
        def num_timesteps(self):
            return self._num_timesteps
    
        # 关闭梯度计算以节省内存
        @torch.no_grad()
        # 替换示例文档字符串
        @replace_example_docstring(EXAMPLE_DOC_STRING)
    # 定义可调用对象的方法
        def __call__(
            # 输入的提示,可以是字符串或字符串列表
            self,
            prompt: Union[str, List[str]] = None,
            # 输出图像的宽度
            width: Optional[int] = None,
            # 输出图像的高度
            height: Optional[int] = None,
            # 推理步骤的数量,默认为30
            num_inference_steps: int = 30,
            # 定义时间步的列表,默认为None
            timesteps: List[int] = None,
            # 引导尺度,默认为4.0
            guidance_scale: float = 4.0,
            # 负提示,可以是字符串或字符串列表
            negative_prompt: Union[str, List[str]] = None,
            # sigma值的列表,默认为None
            sigmas: List[float] = None,
            # 每个提示生成的图像数量,默认为1
            num_images_per_prompt: Optional[int] = 1,
            # 随机数生成器,可以是单个或多个torch生成器
            generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
            # 潜在变量,默认为None
            latents: Optional[torch.Tensor] = None,
            # 提示的嵌入向量,默认为None
            prompt_embeds: Optional[torch.Tensor] = None,
            # 负提示的嵌入向量,默认为None
            negative_prompt_embeds: Optional[torch.Tensor] = None,
            # 提示的注意力掩码,默认为None
            prompt_attention_mask: Optional[torch.Tensor] = None,
            # 负提示的注意力掩码,默认为None
            negative_prompt_attention_mask: Optional[torch.Tensor] = None,
            # 输出类型,默认为"pil"
            output_type: Optional[str] = "pil",
            # 是否返回字典格式,默认为True
            return_dict: bool = True,
            # 是否清理提示文本,默认为True
            clean_caption: bool = True,
            # 最大序列长度,默认为256
            max_sequence_length: int = 256,
            # 缩放阈值,默认为1.0
            scaling_watershed: Optional[float] = 1.0,
            # 是否使用比例注意力,默认为True
            proportional_attn: Optional[bool] = True,

.\diffusers\pipelines\lumina\__init__.py

# 导入类型检查模块
from typing import TYPE_CHECKING

# 从相对路径的 utils 模块中导入多个工具和常量
from ...utils import (
    DIFFUSERS_SLOW_IMPORT,  # 表示是否慢加载
    OptionalDependencyNotAvailable,  # 可选依赖未找到异常
    _LazyModule,  # 延迟加载模块的工具
    get_objects_from_module,  # 从模块中获取对象的工具
    is_torch_available,  # 检查 PyTorch 是否可用的工具
    is_transformers_available,  # 检查 Transformers 是否可用的工具
)

# 存储虚拟对象的字典
_dummy_objects = {}
# 存储模块导入结构的字典
_import_structure = {}

# 尝试执行以下代码块
try:
    # 检查 Transformers 和 PyTorch 是否可用,若都不可用则抛出异常
    if not (is_transformers_available() and is_torch_available()):
        raise OptionalDependencyNotAvailable()
# 捕获可选依赖未找到异常
except OptionalDependencyNotAvailable:
    # 从 utils 模块中导入虚拟对象(占位符)
    from ...utils import dummy_torch_and_transformers_objects  # noqa F403

    # 更新 _dummy_objects 字典,加入虚拟对象
    _dummy_objects.update(get_objects_from_module(dummy_torch_and_transformers_objects))
# 如果没有异常,执行以下代码
else:
    # 在导入结构字典中添加键值对,表示可用的管道
    _import_structure["pipeline_lumina"] = ["LuminaText2ImgPipeline"]

# 如果是类型检查或者慢加载标志为真,执行以下代码块
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
    try:
        # 再次检查 Transformers 和 PyTorch 是否可用,若都不可用则抛出异常
        if not (is_transformers_available() and is_torch_available()):
            raise OptionalDependencyNotAvailable()

    # 捕获可选依赖未找到异常
    except OptionalDependencyNotAvailable:
        # 从 utils 中导入虚拟对象(占位符)
        from ...utils.dummy_torch_and_transformers_objects import *
    else:
        # 从 pipeline_lumina 模块导入 LuminaText2ImgPipeline
        from .pipeline_lumina import LuminaText2ImgPipeline

# 否则,执行以下代码块
else:
    # 导入 sys 模块
    import sys

    # 将当前模块替换为延迟加载模块
    sys.modules[__name__] = _LazyModule(
        __name__,  # 模块名称
        globals()["__file__"],  # 当前文件路径
        _import_structure,  # 导入结构
        module_spec=__spec__,  # 模块规格
    )

    # 遍历 _dummy_objects 字典,将每个虚拟对象设置到当前模块
    for name, value in _dummy_objects.items():
        setattr(sys.modules[__name__], name, value)

.\diffusers\pipelines\marigold\marigold_image_processing.py

# 从类型提示模块导入所需类型
from typing import List, Optional, Tuple, Union

# 导入 NumPy 库作为 np
import numpy as np
# 导入 PIL 库
import PIL
# 导入 PyTorch 库
import torch
# 导入 PyTorch 的功能模块
import torch.nn.functional as F
# 从 PIL 导入 Image 类
from PIL import Image

# 从上级模块导入 ConfigMixin 类
from ... import ConfigMixin
# 从配置工具模块导入注册配置的装饰器
from ...configuration_utils import register_to_config
# 从图像处理模块导入管道图像输入类
from ...image_processor import PipelineImageInput
# 从工具模块导入配置名称和日志工具
from ...utils import CONFIG_NAME, logging
# 从导入工具模块导入判断是否可用的 Matplotlib
from ...utils.import_utils import is_matplotlib_available

# 创建一个日志记录器,使用当前模块的名称
logger = logging.get_logger(__name__)  # pylint: disable=invalid-name

# 定义 MarigoldImageProcessor 类,继承自 ConfigMixin
class MarigoldImageProcessor(ConfigMixin):
    # 设置配置名称为常量 CONFIG_NAME
    config_name = CONFIG_NAME

    # 注册初始化方法到配置中
    @register_to_config
    def __init__(
        self,
        vae_scale_factor: int = 8,  # 变分自编码器的缩放因子,默认为 8
        do_normalize: bool = True,   # 是否进行归一化,默认为 True
        do_range_check: bool = True,  # 是否进行范围检查,默认为 True
    ):
        super().__init__()  # 调用父类的初始化方法

    # 定义静态方法,扩展张量或数组
    @staticmethod
    def expand_tensor_or_array(images: Union[torch.Tensor, np.ndarray]) -> Union[torch.Tensor, np.ndarray]:
        """
        扩展张量或数组到指定数量的图像。
        """
        if isinstance(images, np.ndarray):  # 检查是否为 NumPy 数组
            if images.ndim == 2:  # 如果是二维数组 [H,W] -> [1,H,W,1]
                images = images[None, ..., None]
            if images.ndim == 3:  # 如果是三维数组 [H,W,C] -> [1,H,W,C]
                images = images[None]
        elif isinstance(images, torch.Tensor):  # 检查是否为 PyTorch 张量
            if images.ndim == 2:  # 如果是二维张量 [H,W] -> [1,1,H,W]
                images = images[None, None]
            elif images.ndim == 3:  # 如果是三维张量 [1,H,W] -> [1,1,H,W]
                images = images[None]
        else:  # 如果类型不匹配,抛出错误
            raise ValueError(f"Unexpected input type: {type(images)}")
        return images  # 返回处理后的图像

    # 定义静态方法,将 PyTorch 张量转换为 NumPy 图像
    @staticmethod
    def pt_to_numpy(images: torch.Tensor) -> np.ndarray:
        """
        将 PyTorch 张量转换为 NumPy 图像。
        """
        images = images.cpu().permute(0, 2, 3, 1).float().numpy()  # 转移到 CPU,调整维度并转换为 NumPy 数组
        return images  # 返回转换后的图像

    # 定义静态方法,将 NumPy 图像转换为 PyTorch 张量
    @staticmethod
    def numpy_to_pt(images: np.ndarray) -> torch.Tensor:
        """
        将 NumPy 图像转换为 PyTorch 张量。
        """
        if np.issubdtype(images.dtype, np.integer) and not np.issubdtype(images.dtype, np.unsignedinteger):
            # 如果图像数据类型是有符号整数,抛出错误
            raise ValueError(f"Input image dtype={images.dtype} cannot be a signed integer.")
        if np.issubdtype(images.dtype, np.complexfloating):
            # 如果图像数据类型是复数,抛出错误
            raise ValueError(f"Input image dtype={images.dtype} cannot be complex.")
        if np.issubdtype(images.dtype, bool):
            # 如果图像数据类型是布尔型,抛出错误
            raise ValueError(f"Input image dtype={images.dtype} cannot be boolean.")

        images = torch.from_numpy(images.transpose(0, 3, 1, 2))  # 将 NumPy 数组转换为 PyTorch 张量,并调整维度
        return images  # 返回转换后的张量

    # 定义静态方法,用于带抗锯齿的图像调整大小
    @staticmethod
    def resize_antialias(
        image: torch.Tensor, size: Tuple[int, int], mode: str, is_aa: Optional[bool] = None  # 接受图像、目标大小、模式和抗锯齿参数
    ) -> torch.Tensor:  # 定义一个返回类型为 torch.Tensor 的函数
        # 检查输入是否为 tensor,如果不是,抛出 ValueError 异常
        if not torch.is_tensor(image):
            raise ValueError(f"Invalid input type={type(image)}.")
        # 检查输入的 dtype 是否为浮点型,如果不是,抛出 ValueError 异常
        if not torch.is_floating_point(image):
            raise ValueError(f"Invalid input dtype={image.dtype}.")
        # 检查输入的维度是否为 4,如果不是,抛出 ValueError 异常
        if image.dim() != 4:
            raise ValueError(f"Invalid input dimensions; shape={image.shape}.")

        # 判断是否需要抗锯齿处理,并检查模式是否为双线性或双三次插值
        antialias = is_aa and mode in ("bilinear", "bicubic")
        # 对输入图像进行插值处理,调整到指定大小
        image = F.interpolate(image, size, mode=mode, antialias=antialias)

        # 返回调整大小后的图像
        return image

    @staticmethod  # 表示这是一个静态方法
    def resize_to_max_edge(image: torch.Tensor, max_edge_sz: int, mode: str) -> torch.Tensor:  # 定义一个返回类型为 torch.Tensor 的函数
        # 检查输入是否为 tensor,如果不是,抛出 ValueError 异常
        if not torch.is_tensor(image):
            raise ValueError(f"Invalid input type={type(image)}.")
        # 检查输入的 dtype 是否为浮点型,如果不是,抛出 ValueError 异常
        if not torch.is_floating_point(image):
            raise ValueError(f"Invalid input dtype={image.dtype}.")
        # 检查输入的维度是否为 4,如果不是,抛出 ValueError 异常
        if image.dim() != 4:
            raise ValueError(f"Invalid input dimensions; shape={image.shape}.")

        # 获取图像的高度和宽度
        h, w = image.shape[-2:]
        # 计算原始图像的最大边长度
        max_orig = max(h, w)
        # 计算新的高度和宽度,保持最大边不超过 max_edge_sz
        new_h = h * max_edge_sz // max_orig
        new_w = w * max_edge_sz // max_orig

        # 检查新的高度和宽度是否为 0,如果是,抛出 ValueError 异常
        if new_h == 0 or new_w == 0:
            raise ValueError(f"Extreme aspect ratio of the input image: [{w} x {h}]")

        # 调用静态方法进行抗锯齿处理并调整图像大小
        image = MarigoldImageProcessor.resize_antialias(image, (new_h, new_w), mode, is_aa=True)

        # 返回调整后的图像
        return image

    @staticmethod  # 表示这是一个静态方法
    def pad_image(image: torch.Tensor, align: int) -> Tuple[torch.Tensor, Tuple[int, int]]:  # 定义一个返回类型为元组的函数
        # 检查输入是否为 tensor,如果不是,抛出 ValueError 异常
        if not torch.is_tensor(image):
            raise ValueError(f"Invalid input type={type(image)}.")
        # 检查输入的 dtype 是否为浮点型,如果不是,抛出 ValueError 异常
        if not torch.is_floating_point(image):
            raise ValueError(f"Invalid input dtype={image.dtype}.")
        # 检查输入的维度是否为 4,如果不是,抛出 ValueError 异常
        if image.dim() != 4:
            raise ValueError(f"Invalid input dimensions; shape={image.shape}.")

        # 获取图像的高度和宽度
        h, w = image.shape[-2:]
        # 计算需要的填充量以满足对齐要求
        ph, pw = -h % align, -w % align

        # 使用重复模式对图像进行填充
        image = F.pad(image, (0, pw, 0, ph), mode="replicate")

        # 返回填充后的图像和填充量
        return image, (ph, pw)

    @staticmethod  # 表示这是一个静态方法
    def unpad_image(image: torch.Tensor, padding: Tuple[int, int]) -> torch.Tensor:  # 定义一个返回类型为 torch.Tensor 的函数
        # 检查输入是否为 tensor,如果不是,抛出 ValueError 异常
        if not torch.is_tensor(image):
            raise ValueError(f"Invalid input type={type(image)}.")
        # 检查输入的 dtype 是否为浮点型,如果不是,抛出 ValueError 异常
        if not torch.is_floating_point(image):
            raise ValueError(f"Invalid input dtype={image.dtype}.")
        # 检查输入的维度是否为 4,如果不是,抛出 ValueError 异常
        if image.dim() != 4:
            raise ValueError(f"Invalid input dimensions; shape={image.shape}.")

        # 从填充元组中提取高度和宽度的填充量
        ph, pw = padding
        # 如果填充量为 0,设置为 None,否则取负填充量
        uh = None if ph == 0 else -ph
        uw = None if pw == 0 else -pw

        # 根据计算的新的高度和宽度裁剪图像
        image = image[:, :, :uh, :uw]

        # 返回裁剪后的图像
        return image

    @staticmethod  # 表示这是一个静态方法
    def load_image_canonical(  # 定义一个接受多种类型的输入图像的函数
        image: Union[torch.Tensor, np.ndarray, Image.Image],  # 接受 tensor、numpy 数组或 PIL 图像
        device: torch.device = torch.device("cpu"),  # 设置默认设备为 CPU
        dtype: torch.dtype = torch.float32,  # 设置默认数据类型为 float32
    # 返回类型为元组,包括一个张量和一个整数
    ) -> Tuple[torch.Tensor, int]:
        # 检查输入是否为 PIL 图像类型
        if isinstance(image, Image.Image):
            # 将 PIL 图像转换为 NumPy 数组
            image = np.array(image)
    
        # 初始化图像数据类型的最大值
        image_dtype_max = None
        # 检查输入是否为 NumPy 数组或 PyTorch 张量
        if isinstance(image, (np.ndarray, torch.Tensor)):
            # 扩展张量或数组的维度
            image = MarigoldImageProcessor.expand_tensor_or_array(image)
            # 确保图像的维度是 2、3 或 4
            if image.ndim != 4:
                raise ValueError("Input image is not 2-, 3-, or 4-dimensional.")
        # 检查输入是否为 NumPy 数组
        if isinstance(image, np.ndarray):
            # 检查图像数据类型是否为有符号整数
            if np.issubdtype(image.dtype, np.integer) and not np.issubdtype(image.dtype, np.unsignedinteger):
                raise ValueError(f"Input image dtype={image.dtype} cannot be a signed integer.")
            # 检查图像数据类型是否为复数
            if np.issubdtype(image.dtype, np.complexfloating):
                raise ValueError(f"Input image dtype={image.dtype} cannot be complex.")
            # 检查图像数据类型是否为布尔值
            if np.issubdtype(image.dtype, bool):
                raise ValueError(f"Input image dtype={image.dtype} cannot be boolean.")
            # 检查图像数据类型是否为无符号整数
            if np.issubdtype(image.dtype, np.unsignedinteger):
                # 获取无符号整数的最大值
                image_dtype_max = np.iinfo(image.dtype).max
                # 转换数据类型为浮点数
                image = image.astype(np.float32)  # 因为 torch 不支持无符号数据类型超过 torch.uint8
            # 将 NumPy 数组转换为 PyTorch 张量
            image = MarigoldImageProcessor.numpy_to_pt(image)
    
        # 检查是否为张量并且不是浮点类型且没有最大值
        if torch.is_tensor(image) and not torch.is_floating_point(image) and image_dtype_max is None:
            # 检查图像数据类型是否为 uint8
            if image.dtype != torch.uint8:
                raise ValueError(f"Image dtype={image.dtype} is not supported.")
            # 设置最大值为 255
            image_dtype_max = 255
    
        # 确保输入是张量
        if not torch.is_tensor(image):
            raise ValueError(f"Input type unsupported: {type(image)}.")
    
        # 如果图像的通道数为 1,则重复通道以形成 RGB 图像
        if image.shape[1] == 1:
            image = image.repeat(1, 3, 1, 1)  # [N,1,H,W] -> [N,3,H,W]
        # 确保图像是 1 通道或 3 通道
        if image.shape[1] != 3:
            raise ValueError(f"Input image is not 1- or 3-channel: {image.shape}.")
    
        # 将图像移动到指定设备,并转换为指定数据类型
        image = image.to(device=device, dtype=dtype)
    
        # 如果存在数据类型最大值,则将图像数据归一化
        if image_dtype_max is not None:
            image = image / image_dtype_max
    
        # 返回处理后的图像
        return image
    
    # 静态方法,检查图像的值范围
    @staticmethod
    def check_image_values_range(image: torch.Tensor) -> None:
        # 确保输入是张量
        if not torch.is_tensor(image):
            raise ValueError(f"Invalid input type={type(image)}.")
        # 确保输入是浮点类型
        if not torch.is_floating_point(image):
            raise ValueError(f"Invalid input dtype={image.dtype}.")
        # 检查图像数据是否在 [0,1] 范围内
        if image.min().item() < 0.0 or image.max().item() > 1.0:
            raise ValueError("Input image data is partially outside of the [0,1] range.")
    
    # 预处理方法
    def preprocess(
        self,
        # 输入图像,可以是多种格式
        image: PipelineImageInput,
        # 可选的处理分辨率
        processing_resolution: Optional[int] = None,
        # 输入的重采样方法
        resample_method_input: str = "bilinear",
        # 指定设备(CPU 或 GPU)
        device: torch.device = torch.device("cpu"),
        # 指定数据类型,默认为浮点数
        dtype: torch.dtype = torch.float32,
    ):
        # 检查输入的图像是否为列表类型
        if isinstance(image, list):
            # 初始化图像变量
            images = None
            # 遍历图像列表,获取每个图像的索引和内容
            for i, img in enumerate(image):
                # 加载图像并标准化为指定的格式,返回形状为[N,3,H,W]
                img = self.load_image_canonical(img, device, dtype)  # [N,3,H,W]
                # 如果还没有图像,直接赋值
                if images is None:
                    images = img
                else:
                    # 检查当前图像的维度是否与已有图像兼容
                    if images.shape[2:] != img.shape[2:]:
                        # 如果不兼容,抛出错误并给出详细信息
                        raise ValueError(
                            f"Input image[{i}] has incompatible dimensions {img.shape[2:]} with the previous images "
                            f"{images.shape[2:]}"
                        )
                    # 将当前图像与已有图像在第一维拼接
                    images = torch.cat((images, img), dim=0)
            # 将最终图像集赋值回原变量
            image = images
            # 删除临时图像变量以释放内存
            del images
        else:
            # 加载单个图像并标准化为指定的格式,返回形状为[N,3,H,W]
            image = self.load_image_canonical(image, device, dtype)  # [N,3,H,W]

        # 获取图像的原始分辨率
        original_resolution = image.shape[2:]

        # 如果配置要求进行值范围检查,则执行检查
        if self.config.do_range_check:
            self.check_image_values_range(image)

        # 如果配置要求进行归一化处理,则进行操作
        if self.config.do_normalize:
            image = image * 2.0 - 1.0

        # 如果处理分辨率被指定且大于0,则调整图像大小
        if processing_resolution is not None and processing_resolution > 0:
            # 调整图像到最大边长,返回形状为[N,3,PH,PW]
            image = self.resize_to_max_edge(image, processing_resolution, resample_method_input)  # [N,3,PH,PW]

        # 对图像进行填充,返回填充后的图像和填充信息,形状为[N,3,PPH,PPW]
        image, padding = self.pad_image(image, self.config.vae_scale_factor)  # [N,3,PPH,PPW]

        # 返回处理后的图像、填充信息和原始分辨率
        return image, padding, original_resolution

    # 定义静态方法 colormap,用于图像上色
    @staticmethod
    def colormap(
        # 输入图像,支持多种类型
        image: Union[np.ndarray, torch.Tensor],
        # 颜色映射名称,默认为 "Spectral"
        cmap: str = "Spectral",
        # 是否返回字节类型的图像
        bytes: bool = False,
        # 强制使用的特定方法,默认为 None
        _force_method: Optional[str] = None,
    # 定义静态方法 visualize_depth,用于可视化深度信息
    @staticmethod
    def visualize_depth(
        # 输入深度图像,支持多种类型
        depth: Union[
            PIL.Image.Image,
            np.ndarray,
            torch.Tensor,
            List[PIL.Image.Image],
            List[np.ndarray],
            List[torch.Tensor],
        ],
        # 深度值的最小阈值,默认为0.0
        val_min: float = 0.0,
        # 深度值的最大阈值,默认为1.0
        val_max: float = 1.0,
        # 颜色映射名称,默认为 "Spectral"
        color_map: str = "Spectral",
    # 返回深度图像的可视化结果,可以是单个图像或图像列表
    ) -> Union[PIL.Image.Image, List[PIL.Image.Image]]:
        """
        可视化深度图,例如 `MarigoldDepthPipeline` 的预测结果。
    
        参数:
            depth (`Union[PIL.Image.Image, np.ndarray, torch.Tensor, List[PIL.Image.Image], List[np.ndarray],
                List[torch.Tensor]]`): 深度图。
            val_min (`float`, *可选*, 默认值为 `0.0`): 可视化深度范围的最小值。
            val_max (`float`, *可选*, 默认值为 `1.0`): 可视化深度范围的最大值。
            color_map (`str`, *可选*, 默认值为 `"Spectral"`): 用于将单通道深度预测转换为彩色表示的颜色映射。
    
        返回: `PIL.Image.Image` 或 `List[PIL.Image.Image]`,包含深度图可视化结果。
        """
        # 检查最大值是否小于等于最小值,若是则抛出错误
        if val_max <= val_min:
            raise ValueError(f"Invalid values range: [{val_min}, {val_max}].")
    
        # 定义用于可视化单个深度图的函数
        def visualize_depth_one(img, idx=None):
            # 为图像前缀生成字符串,包含索引(如果存在)
            prefix = "Depth" + (f"[{idx}]" if idx else "")
            # 检查输入图像是否为 PIL 图像
            if isinstance(img, PIL.Image.Image):
                # 验证图像模式是否为 "I;16"
                if img.mode != "I;16":
                    raise ValueError(f"{prefix}: invalid PIL mode={img.mode}.")
                # 将 PIL 图像转换为 numpy 数组并归一化
                img = np.array(img).astype(np.float32) / (2**16 - 1)
            # 检查输入图像是否为 numpy 数组或 PyTorch 张量
            if isinstance(img, np.ndarray) or torch.is_tensor(img):
                # 确保输入图像是二维的
                if img.ndim != 2:
                    raise ValueError(f"{prefix}: unexpected shape={img.shape}.")
                # 若为 numpy 数组,则转换为 PyTorch 张量
                if isinstance(img, np.ndarray):
                    img = torch.from_numpy(img)
                # 确保图像是浮点类型
                if not torch.is_floating_point(img):
                    raise ValueError(f"{prefix}: unexected dtype={img.dtype}.")
            else:
                # 如果输入类型不匹配,则抛出错误
                raise ValueError(f"{prefix}: unexpected type={type(img)}.")
            # 如果最小值或最大值不为默认值,则进行归一化处理
            if val_min != 0.0 or val_max != 1.0:
                img = (img - val_min) / (val_max - val_min)
            # 使用颜色映射处理深度图像并转换为 RGB 格式
            img = MarigoldImageProcessor.colormap(img, cmap=color_map, bytes=True)  # [H,W,3]
            # 将数组转换回 PIL 图像
            img = PIL.Image.fromarray(img.cpu().numpy())
            return img
    
        # 检查输入深度是否为 None 或列表中的元素为 None
        if depth is None or isinstance(depth, list) and any(o is None for o in depth):
            raise ValueError("Input depth is `None`")
        # 如果输入深度为 numpy 数组或 PyTorch 张量
        if isinstance(depth, (np.ndarray, torch.Tensor)):
            # 扩展张量或数组以匹配预期形状
            depth = MarigoldImageProcessor.expand_tensor_or_array(depth)
            # 若为 numpy 数组,则转换为 PyTorch 张量,形状调整为 [N,1,H,W]
            if isinstance(depth, np.ndarray):
                depth = MarigoldImageProcessor.numpy_to_pt(depth)  # [N,H,W,1] -> [N,1,H,W]
            # 验证深度图形状是否符合预期
            if not (depth.ndim == 4 and depth.shape[1] == 1):  # [N,1,H,W]
                raise ValueError(f"Unexpected input shape={depth.shape}, expecting [N,1,H,W].")
            # 返回每个图像的可视化结果列表
            return [visualize_depth_one(img[0], idx) for idx, img in enumerate(depth)]
        # 如果输入深度为列表,则对每个图像进行可视化
        elif isinstance(depth, list):
            return [visualize_depth_one(img, idx) for idx, img in enumerate(depth)]
        else:
            # 如果输入类型不匹配,则抛出错误
            raise ValueError(f"Unexpected input type: {type(depth)}")
    
    # 定义静态方法标识
        @staticmethod
    # 导出深度图为16位PNG格式
    def export_depth_to_16bit_png(
            # 深度数据,支持多种输入格式
            depth: Union[np.ndarray, torch.Tensor, List[np.ndarray], List[torch.Tensor]],
            # 深度值的最小范围
            val_min: float = 0.0,
            # 深度值的最大范围
            val_max: float = 1.0,
        ) -> Union[PIL.Image.Image, List[PIL.Image.Image]]:
            # 导出单张深度图为16位PNG格式的内部函数
            def export_depth_to_16bit_png_one(img, idx=None):
                # 生成深度图的前缀,用于错误信息
                prefix = "Depth" + (f"[{idx}]" if idx else "")
                # 检查输入是否为有效类型
                if not isinstance(img, np.ndarray) and not torch.is_tensor(img):
                    raise ValueError(f"{prefix}: unexpected type={type(img)}.")
                # 检查输入的维度是否为2D
                if img.ndim != 2:
                    raise ValueError(f"{prefix}: unexpected shape={img.shape}.")
                # 将PyTorch张量转换为NumPy数组
                if torch.is_tensor(img):
                    img = img.cpu().numpy()
                # 检查数据类型是否为浮点数
                if not np.issubdtype(img.dtype, np.floating):
                    raise ValueError(f"{prefix}: unexected dtype={img.dtype}.")
                # 根据给定范围标准化深度图
                if val_min != 0.0 or val_max != 1.0:
                    img = (img - val_min) / (val_max - val_min)
                # 将深度图值转换为16位整数
                img = (img * (2**16 - 1)).astype(np.uint16)
                # 将数组转换为16位PNG格式的图像
                img = PIL.Image.fromarray(img, mode="I;16")
                # 返回生成的图像
                return img
    
            # 检查输入深度数据是否为None或包含None
            if depth is None or isinstance(depth, list) and any(o is None for o in depth):
                raise ValueError("Input depth is `None`")
            # 如果输入为NumPy数组或PyTorch张量
            if isinstance(depth, (np.ndarray, torch.Tensor)):
                # 扩展张量或数组的维度
                depth = MarigoldImageProcessor.expand_tensor_or_array(depth)
                # 如果输入是NumPy数组,转换为PyTorch张量
                if isinstance(depth, np.ndarray):
                    depth = MarigoldImageProcessor.numpy_to_pt(depth)  # [N,H,W,1] -> [N,1,H,W]
                # 检查扩展后的深度图形状
                if not (depth.ndim == 4 and depth.shape[1] == 1):
                    raise ValueError(f"Unexpected input shape={depth.shape}, expecting [N,1,H,W].")
                # 返回每张深度图的16位PNG图像
                return [export_depth_to_16bit_png_one(img[0], idx) for idx, img in enumerate(depth)]
            # 如果输入是列表
            elif isinstance(depth, list):
                # 返回每张深度图的16位PNG图像
                return [export_depth_to_16bit_png_one(img, idx) for idx, img in enumerate(depth)]
            else:
                # 抛出不支持的输入类型错误
                raise ValueError(f"Unexpected input type: {type(depth)}")
    
        # 可视化法线的静态方法
        @staticmethod
        def visualize_normals(
            # 法线数据,支持多种输入格式
            normals: Union[
                np.ndarray,
                torch.Tensor,
                List[np.ndarray],
                List[torch.Tensor],
            ],
            # 是否沿X轴翻转
            flip_x: bool = False,
            # 是否沿Y轴翻转
            flip_y: bool = False,
            # 是否沿Z轴翻转
            flip_z: bool = False,
    # 返回类型为 PIL.Image.Image 或 List[PIL.Image.Image],用于可视化表面法线
    ) -> Union[PIL.Image.Image, List[PIL.Image.Image]]:
        """
        可视化表面法线,例如 `MarigoldNormalsPipeline` 的预测结果。
    
        参数:
            normals (`Union[np.ndarray, torch.Tensor, List[np.ndarray], List[torch.Tensor]]`):
                表面法线数据。
            flip_x (`bool`, *可选*, 默认值为 `False`): 翻转法线参考系的 X 轴。
                      默认方向为右。
            flip_y (`bool`, *可选*, 默认值为 `False`): 翻转法线参考系的 Y 轴。
                      默认方向为上。
            flip_z (`bool`, *可选*, 默认值为 `False`): 翻转法线参考系的 Z 轴。
                      默认方向为面向观察者。
    
        返回值: `PIL.Image.Image` 或 `List[PIL.Image.Image]`,包含表面法线的可视化图像。
        """
        # 初始化翻转向量为 None
        flip_vec = None
        # 如果任一翻转标志为真,则创建翻转向量
        if any((flip_x, flip_y, flip_z)):
            flip_vec = torch.tensor(
                [
                    (-1) ** flip_x,  # 根据 flip_x 计算 X 轴的翻转因子
                    (-1) ** flip_y,  # 根据 flip_y 计算 Y 轴的翻转因子
                    (-1) ** flip_z,  # 根据 flip_z 计算 Z 轴的翻转因子
                ],
                dtype=torch.float32,  # 数据类型为浮点数
            )
    
        # 定义一个用于可视化单个法线图像的函数
        def visualize_normals_one(img, idx=None):
            img = img.permute(1, 2, 0)  # 改变图像维度顺序为 (H, W, C)
            if flip_vec is not None:
                img *= flip_vec.to(img.device)  # 应用翻转向量
            img = (img + 1.0) * 0.5  # 将图像数据归一化到 [0, 1]
            img = (img * 255).to(dtype=torch.uint8, device="cpu").numpy()  # 转换为 uint8 格式并转为 numpy 数组
            img = PIL.Image.fromarray(img)  # 将 numpy 数组转换为 PIL 图像
            return img  # 返回处理后的图像
    
        # 检查输入法线是否为 None 或含有 None 的列表
        if normals is None or isinstance(normals, list) and any(o is None for o in normals):
            raise ValueError("Input normals is `None`")  # 抛出异常
    
        # 如果法线数据为 numpy 数组或 torch 张量
        if isinstance(normals, (np.ndarray, torch.Tensor)):
            normals = MarigoldImageProcessor.expand_tensor_or_array(normals)  # 扩展法线数据
            if isinstance(normals, np.ndarray):
                normals = MarigoldImageProcessor.numpy_to_pt(normals)  # 转换 numpy 数组为 PyTorch 张量,形状为 [N,3,H,W]
            # 检查法线数据的维度和形状
            if not (normals.ndim == 4 and normals.shape[1] == 3):
                raise ValueError(f"Unexpected input shape={normals.shape}, expecting [N,3,H,W].")  # 抛出异常
            # 可视化每个法线图像并返回图像列表
            return [visualize_normals_one(img, idx) for idx, img in enumerate(normals)]
        # 如果法线数据为列表
        elif isinstance(normals, list):
            # 可视化每个法线图像并返回图像列表
            return [visualize_normals_one(img, idx) for idx, img in enumerate(normals)]
        else:
            raise ValueError(f"Unexpected input type: {type(normals)}")  # 抛出异常,处理未知类型
    
    # 定义静态方法可视化不确定性
    @staticmethod
    def visualize_uncertainty(
        uncertainty: Union[
            np.ndarray,
            torch.Tensor,
            List[np.ndarray],
            List[torch.Tensor],
        ],
        saturation_percentile=95,  # 定义饱和度百分位参数,默认为95%
    ) -> Union[PIL.Image.Image, List[PIL.Image.Image]]:
        # 指定函数返回类型为单个 PIL.Image.Image 或者 PIL.Image.Image 的列表
        """
        # 文档字符串,说明函数的功能,参数及返回值
        Visualizes dense uncertainties, such as produced by `MarigoldDepthPipeline` or `MarigoldNormalsPipeline`.

        Args:
            # 参数说明,uncertainty 可以是不同类型的数组
            uncertainty (`Union[np.ndarray, torch.Tensor, List[np.ndarray], List[torch.Tensor]]`):
                Uncertainty maps.
            # 参数说明,饱和度百分位数,默认为 95
            saturation_percentile (`int`, *optional*, defaults to `95`):
                Specifies the percentile uncertainty value visualized with maximum intensity.

        Returns: # 返回值说明
            `PIL.Image.Image` or `List[PIL.Image.Image]` with uncertainty visualization.
        """

        # 定义内部函数,用于可视化单张不确定性图
        def visualize_uncertainty_one(img, idx=None):
            # 构建图像前缀,包含索引(如果提供)
            prefix = "Uncertainty" + (f"[{idx}]" if idx else "")
            # 检查图像最小值是否小于 0,若是则抛出异常
            if img.min() < 0:
                raise ValueError(f"{prefix}: unexected data range, min={img.min()}.")
            # 将图像张量降维并转换为 NumPy 数组
            img = img.squeeze(0).cpu().numpy()
            # 计算图像的饱和度值,基于给定的百分位数
            saturation_value = np.percentile(img, saturation_percentile)
            # 将图像值归一化并限制在 0 到 255 之间
            img = np.clip(img * 255 / saturation_value, 0, 255)
            # 将图像数据类型转换为无符号整型(uint8)
            img = img.astype(np.uint8)
            # 从 NumPy 数组创建 PIL 图像对象
            img = PIL.Image.fromarray(img)
            # 返回处理后的图像
            return img

        # 检查不确定性输入是否为 None 或者是包含 None 的列表
        if uncertainty is None or isinstance(uncertainty, list) and any(o is None for o in uncertainty):
            # 抛出异常,输入不确定性为 None
            raise ValueError("Input uncertainty is `None`")
        # 如果不确定性是 NumPy 数组或 PyTorch 张量
        if isinstance(uncertainty, (np.ndarray, torch.Tensor)):
            # 扩展张量或数组以适应处理
            uncertainty = MarigoldImageProcessor.expand_tensor_or_array(uncertainty)
            # 如果不确定性为 NumPy 数组,将其转换为 PyTorch 张量
            if isinstance(uncertainty, np.ndarray):
                uncertainty = MarigoldImageProcessor.numpy_to_pt(uncertainty)  # [N,1,H,W]
            # 检查不确定性数组的维度和形状是否符合预期
            if not (uncertainty.ndim == 4 and uncertainty.shape[1] == 1):
                # 抛出异常,形状不符合预期
                raise ValueError(f"Unexpected input shape={uncertainty.shape}, expecting [N,1,H,W].")
            # 返回每个图像的可视化结果,生成图像列表
            return [visualize_uncertainty_one(img, idx) for idx, img in enumerate(uncertainty)]
        # 如果不确定性是一个列表
        elif isinstance(uncertainty, list):
            # 返回每个图像的可视化结果,生成图像列表
            return [visualize_uncertainty_one(img, idx) for idx, img in enumerate(uncertainty)]
        else:
            # 抛出异常,输入类型不符合预期
            raise ValueError(f"Unexpected input type: {type(uncertainty)}")

.\diffusers\pipelines\marigold\pipeline_marigold_depth.py

# 版权声明,说明该代码的版权归属
# Copyright 2024 Marigold authors, PRS ETH Zurich. All rights reserved.
# 版权声明,说明该代码的版权归属
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 根据 Apache 2.0 许可证对该文件的使用进行说明
# Licensed under the Apache License, Version 2.0 (the "License");
# 说明除非遵循许可证,否则不可使用该文件
# you may not use this file except in compliance with the License.
# 提供获取许可证的链接
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 提供关于该软件的使用条款
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 不提供任何类型的保证或条件
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 提供许可证的详细信息
# See the License for the specific language governing permissions and
# limitations under the License.
# --------------------------------------------------------------------------
# 提供更多信息和引用说明的来源
# More information and citation instructions are available on the
# Marigold project website: https://marigoldmonodepth.github.io
# --------------------------------------------------------------------------
# 从 dataclass 模块导入 dataclass 装饰器
from dataclasses import dataclass
# 从 functools 模块导入 partial 函数
from functools import partial
# 导入用于类型提示的类型
from typing import Any, Dict, List, Optional, Tuple, Union

# 导入 numpy 库
import numpy as np
# 导入 torch 库
import torch
# 从 PIL 导入 Image 类
from PIL import Image
# 从 tqdm 导入进度条显示工具
from tqdm.auto import tqdm
# 从 transformers 导入 CLIP 模型和标记器
from transformers import CLIPTextModel, CLIPTokenizer

# 从图像处理模块导入 PipelineImageInput 类
from ...image_processor import PipelineImageInput
# 从模型模块导入相关模型
from ...models import (
    AutoencoderKL,
    UNet2DConditionModel,
)
# 从调度器模块导入调度器类
from ...schedulers import (
    DDIMScheduler,
    LCMScheduler,
)
# 从工具模块导入基本输出和日志功能
from ...utils import (
    BaseOutput,
    logging,
    replace_example_docstring,
)
# 导入 SciPy 可用性检查函数
from ...utils.import_utils import is_scipy_available
# 导入生成随机张量的工具
from ...utils.torch_utils import randn_tensor
# 从管道工具模块导入 DiffusionPipeline 类
from ..pipeline_utils import DiffusionPipeline
# 从图像处理模块导入 MarigoldImageProcessor 类
from .marigold_image_processing import MarigoldImageProcessor

# 创建一个日志记录器实例
logger = logging.get_logger(__name__)  # pylint: disable=invalid-name

# 示例文档字符串,展示如何使用该管道
EXAMPLE_DOC_STRING = """
Examples:

>>> import diffusers
>>> import torch

>>> pipe = diffusers.MarigoldDepthPipeline.from_pretrained(
...     "prs-eth/marigold-depth-lcm-v1-0", variant="fp16", torch_dtype=torch.float16
... ).to("cuda")

>>> image = diffusers.utils.load_image("https://marigoldmonodepth.github.io/images/einstein.jpg")
>>> depth = pipe(image)

>>> vis = pipe.image_processor.visualize_depth(depth.prediction)
>>> vis[0].save("einstein_depth.png")

>>> depth_16bit = pipe.image_processor.export_depth_to_16bit_png(depth.prediction)
>>> depth_16bit[0].save("einstein_depth_16bit.png")

"""

# 定义 MarigoldDepthOutput 类,表示单目深度预测的输出
@dataclass
class MarigoldDepthOutput(BaseOutput):
    """
    Output class for Marigold monocular depth prediction pipeline.
    # 函数参数文档字符串,说明参数的类型和形状
        Args:
            prediction (`np.ndarray`, `torch.Tensor`):  # 预测的深度图,值范围在 [0, 1] 之间
                # 形状为 numimages × 1 × height × width,无论图像是作为 4D 数组还是列表传入
            uncertainty (`None`, `np.ndarray`, `torch.Tensor`):  # 从集成计算的置信度图,值范围在 [0, 1] 之间
                # 形状为 numimages × 1 × height × width
            latent (`None`, `torch.Tensor`):  # 与预测对应的潜在特征,兼容于管道的 latents 参数
                # 形状为 numimages * numensemble × 4 × latentheight × latentwidth
        """  # 结束文档字符串
    
        prediction: Union[np.ndarray, torch.Tensor]  # 声明 prediction 为 np.ndarray 或 torch.Tensor 类型
        uncertainty: Union[None, np.ndarray, torch.Tensor]  # 声明 uncertainty 为 None、np.ndarray 或 torch.Tensor 类型
        latent: Union[None, torch.Tensor]  # 声明 latent 为 None 或 torch.Tensor 类型
# 定义一个名为 MarigoldDepthPipeline 的类,继承自 DiffusionPipeline
class MarigoldDepthPipeline(DiffusionPipeline):
    """
    使用 Marigold 方法进行单目深度估计的管道: https://marigoldmonodepth.github.io。

    此模型继承自 [`DiffusionPipeline`]。请查阅父类文档,以了解库为所有管道实现的通用方法
    (例如下载或保存,在特定设备上运行等)。

    参数:
        unet (`UNet2DConditionModel`):
            条件 U-Net,用于在图像潜在空间的条件下去噪深度潜在。
        vae (`AutoencoderKL`):
            变分自编码器(VAE)模型,用于将图像和预测编码和解码为潜在表示。
        scheduler (`DDIMScheduler` 或 `LCMScheduler`):
            用于与 `unet` 结合使用的调度器,以去噪编码的图像潜在。
        text_encoder (`CLIPTextModel`):
            文本编码器,用于空文本嵌入。
        tokenizer (`CLIPTokenizer`):
            CLIP 分词器。
        prediction_type (`str`, *可选*):
            模型所做预测的类型。
        scale_invariant (`bool`, *可选*):
            指定预测深度图是否具有尺度不变性的模型属性。此值必须在模型配置中设置。
            与 `shift_invariant=True` 标志一起使用时,模型也被称为“仿射不变”。注意:不支持覆盖此值。
        shift_invariant (`bool`, *可选*):
            指定预测深度图是否具有平移不变性的模型属性。此值必须在模型配置中设置。
            与 `scale_invariant=True` 标志一起使用时,模型也被称为“仿射不变”。注意:不支持覆盖此值。
        default_denoising_steps (`int`, *可选*):
            生成合理质量预测所需的最小去噪扩散步骤数。此值必须在模型配置中设置。
            当调用管道而未显式设置 `num_inference_steps` 时,使用默认值。这是为了确保与各种模型
            变体兼容的合理结果,例如依赖于非常短的去噪调度的模型(`LCMScheduler`)和具有完整扩散
            调度的模型(`DDIMScheduler`)。
        default_processing_resolution (`int`, *可选*):
            管道 `processing_resolution` 参数的推荐值。此值必须在模型配置中设置。
            当调用管道而未显式设置 `processing_resolution` 时,使用默认值。这是为了确保与训练
            使用不同最佳处理分辨率值的各种模型变体兼容的合理结果。
    """

    # 定义模型在 CPU 上的卸载顺序
    model_cpu_offload_seq = "text_encoder->unet->vae"
    # 定义支持的预测类型,包括深度和视差
    supported_prediction_types = ("depth", "disparity")

    # 初始化方法,设置模型的基本参数
    def __init__(
        # UNet2DConditionModel 对象,进行图像生成
        self,
        unet: UNet2DConditionModel,
        # 自动编码器,用于处理图像
        vae: AutoencoderKL,
        # 调度器,控制生成过程中的步伐
        scheduler: Union[DDIMScheduler, LCMScheduler],
        # 文本编码器,处理输入文本信息
        text_encoder: CLIPTextModel,
        # 分词器,用于将文本转换为模型可处理的格式
        tokenizer: CLIPTokenizer,
        # 可选的预测类型,默认为 None
        prediction_type: Optional[str] = None,
        # 可选,指示是否使用尺度不变性,默认为 True
        scale_invariant: Optional[bool] = True,
        # 可选,指示是否使用平移不变性,默认为 True
        shift_invariant: Optional[bool] = True,
        # 可选,默认去噪步骤数,默认为 None
        default_denoising_steps: Optional[int] = None,
        # 可选,默认处理分辨率,默认为 None
        default_processing_resolution: Optional[int] = None,
    ):
        # 调用父类的初始化方法
        super().__init__()

        # 检查给定的预测类型是否在支持的范围内
        if prediction_type not in self.supported_prediction_types:
            # 记录警告,提示可能使用了不支持的预测类型
            logger.warning(
                f"Potentially unsupported `prediction_type='{prediction_type}'`; values supported by the pipeline: "
                f"{self.supported_prediction_types}."
            )

        # 注册模型组件,包括 UNet、VAE、调度器、文本编码器和分词器
        self.register_modules(
            unet=unet,
            vae=vae,
            scheduler=scheduler,
            text_encoder=text_encoder,
            tokenizer=tokenizer,
        )
        # 将配置参数注册到当前实例
        self.register_to_config(
            prediction_type=prediction_type,
            scale_invariant=scale_invariant,
            shift_invariant=shift_invariant,
            default_denoising_steps=default_denoising_steps,
            default_processing_resolution=default_processing_resolution,
        )

        # 计算 VAE 的缩放因子,基于其配置
        self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)

        # 设置尺度不变性
        self.scale_invariant = scale_invariant
        # 设置平移不变性
        self.shift_invariant = shift_invariant
        # 设置默认去噪步骤数
        self.default_denoising_steps = default_denoising_steps
        # 设置默认处理分辨率
        self.default_processing_resolution = default_processing_resolution

        # 初始化空文本嵌入,初始值为 None
        self.empty_text_embedding = None

        # 创建图像处理器,基于 VAE 的缩放因子
        self.image_processor = MarigoldImageProcessor(vae_scale_factor=self.vae_scale_factor)

    # 检查输入的有效性的方法
    def check_inputs(
        # 输入的图像,类型为管道图像输入
        self,
        image: PipelineImageInput,
        # 推理步骤的数量
        num_inference_steps: int,
        # 集成的大小
        ensemble_size: int,
        # 处理分辨率
        processing_resolution: int,
        # 输入的重采样方法
        resample_method_input: str,
        # 输出的重采样方法
        resample_method_output: str,
        # 批量大小
        batch_size: int,
        # 可选的集成参数
        ensembling_kwargs: Optional[Dict[str, Any]],
        # 可选的潜在变量
        latents: Optional[torch.Tensor],
        # 可选的随机数生成器,支持单个或列表形式
        generator: Optional[Union[torch.Generator, List[torch.Generator]]],
        # 输出类型的字符串
        output_type: str,
        # 是否输出不确定性,布尔值
        output_uncertainty: bool,
    # 定义一个进度条的方法,接受可选参数用于控制进度条的显示
        def progress_bar(self, iterable=None, total=None, desc=None, leave=True):
            # 检查实例是否已有进度条配置属性
            if not hasattr(self, "_progress_bar_config"):
                # 如果没有,初始化一个空字典作为配置
                self._progress_bar_config = {}
            # 如果已有配置,检查其是否为字典类型
            elif not isinstance(self._progress_bar_config, dict):
                # 如果不是,抛出类型错误
                raise ValueError(
                    f"`self._progress_bar_config` should be of type `dict`, but is {type(self._progress_bar_config)}."
                )
    
            # 创建一个进度条配置字典,复制已有配置
            progress_bar_config = dict(**self._progress_bar_config)
            # 从配置中获取描述,如果没有则使用传入的描述
            progress_bar_config["desc"] = progress_bar_config.get("desc", desc)
            # 从配置中获取是否保留进度条,默认值为传入的参数
            progress_bar_config["leave"] = progress_bar_config.get("leave", leave)
            # 如果提供了可迭代对象,返回带进度条的迭代器
            if iterable is not None:
                return tqdm(iterable, **progress_bar_config)
            # 如果提供了总数,返回总数进度条
            elif total is not None:
                return tqdm(total=total, **progress_bar_config)
            # 如果两个参数都未提供,抛出错误
            else:
                raise ValueError("Either `total` or `iterable` has to be defined.")
    
        # 禁用梯度计算以节省内存
        @torch.no_grad()
        # 替换示例文档字符串
        @replace_example_docstring(EXAMPLE_DOC_STRING)
        # 定义调用方法,接受多个参数以处理图像
        def __call__(
            self,
            image: PipelineImageInput,  # 输入图像
            num_inference_steps: Optional[int] = None,  # 推理步骤的数量
            ensemble_size: int = 1,  # 集成模型的数量
            processing_resolution: Optional[int] = None,  # 处理图像的分辨率
            match_input_resolution: bool = True,  # 是否匹配输入分辨率
            resample_method_input: str = "bilinear",  # 输入图像的重采样方法
            resample_method_output: str = "bilinear",  # 输出图像的重采样方法
            batch_size: int = 1,  # 批处理的大小
            ensembling_kwargs: Optional[Dict[str, Any]] = None,  # 集成模型的额外参数
            latents: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,  # 潜在变量
            generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,  # 随机数生成器
            output_type: str = "np",  # 输出类型,默认是 NumPy
            output_uncertainty: bool = False,  # 是否输出不确定性
            output_latent: bool = False,  # 是否输出潜在变量
            return_dict: bool = True,  # 是否以字典形式返回结果
        # 定义准备潜在变量的方法
        def prepare_latents(
            self,
            image: torch.Tensor,  # 输入图像的张量表示
            latents: Optional[torch.Tensor],  # 潜在变量的张量
            generator: Optional[torch.Generator],  # 随机数生成器
            ensemble_size: int,  # 集成模型的数量
            batch_size: int,  # 批处理的大小
    # 返回两个张量的元组
    ) -> Tuple[torch.Tensor, torch.Tensor]:
        # 定义一个函数来提取潜在向量
        def retrieve_latents(encoder_output):
            # 检查 encoder_output 是否具有 latent_dist 属性
            if hasattr(encoder_output, "latent_dist"):
                # 返回潜在分布的众数
                return encoder_output.latent_dist.mode()
            # 检查 encoder_output 是否具有 latents 属性
            elif hasattr(encoder_output, "latents"):
                # 返回潜在向量
                return encoder_output.latents
            # 如果没有找到潜在向量,则抛出异常
            else:
                raise AttributeError("Could not access latents of provided encoder_output")
    
        # 将编码后的图像潜在向量按批次拼接在一起
        image_latent = torch.cat(
            [
                # 对每个批次的图像调用 retrieve_latents 函数
                retrieve_latents(self.vae.encode(image[i : i + batch_size]))
                for i in range(0, image.shape[0], batch_size)
            ],
            dim=0,
        )  # 结果形状为 [N,4,h,w]
        # 将图像潜在向量乘以缩放因子
        image_latent = image_latent * self.vae.config.scaling_factor
        # 在第0维重复潜在向量以适应集成大小
        image_latent = image_latent.repeat_interleave(ensemble_size, dim=0)  # 结果形状为 [N*E,4,h,w]
    
        # 将潜在预测初始化为 latents
        pred_latent = latents
        # 如果预测潜在向量为空,生成随机张量
        if pred_latent is None:
            pred_latent = randn_tensor(
                # 生成与图像潜在向量相同形状的随机张量
                image_latent.shape,
                generator=generator,
                device=image_latent.device,
                dtype=image_latent.dtype,
            )  # 结果形状为 [N*E,4,h,w]
    
        # 返回图像潜在向量和预测潜在向量
        return image_latent, pred_latent
    
    # 解码潜在预测,返回张量
    def decode_prediction(self, pred_latent: torch.Tensor) -> torch.Tensor:
        # 检查预测潜在向量的维度和形状是否符合预期
        if pred_latent.dim() != 4 or pred_latent.shape[1] != self.vae.config.latent_channels:
            # 抛出值错误,如果形状不匹配
            raise ValueError(
                f"Expecting 4D tensor of shape [B,{self.vae.config.latent_channels},H,W]; got {pred_latent.shape}."
            )
    
        # 解码预测潜在向量,返回字典中的第一个元素
        prediction = self.vae.decode(pred_latent / self.vae.config.scaling_factor, return_dict=False)[0]  # 结果形状为 [B,3,H,W]
    
        # 计算预测的均值,保持维度
        prediction = prediction.mean(dim=1, keepdim=True)  # 结果形状为 [B,1,H,W]
        # 将预测限制在 [-1.0, 1.0] 的范围内
        prediction = torch.clip(prediction, -1.0, 1.0)  # 结果形状为 [B,1,H,W]
        # 将预测从 [-1, 1] 转换到 [0, 1]
        prediction = (prediction + 1.0) / 2.0
    
        # 返回最终预测结果
        return prediction  # 结果形状为 [B,1,H,W]
    
    # 定义一个静态方法,用于处理深度信息
    @staticmethod
    def ensemble_depth(
        # 输入深度张量
        depth: torch.Tensor,
        # 是否使用尺度不变性
        scale_invariant: bool = True,
        # 是否使用位移不变性
        shift_invariant: bool = True,
        # 是否输出不确定性
        output_uncertainty: bool = False,
        # 指定聚合方式,默认为中位数
        reduction: str = "median",
        # 正则化强度
        regularizer_strength: float = 0.02,
        # 最大迭代次数
        max_iter: int = 2,
        # 收敛容忍度
        tol: float = 1e-3,
        # 最大分辨率
        max_res: int = 1024,

.\diffusers\pipelines\marigold\pipeline_marigold_normals.py

# 版权所有声明,说明作者和版权信息
# Copyright 2024 Marigold authors, PRS ETH Zurich. All rights reserved.
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 根据 Apache 2.0 许可证进行授权
# Licensed under the Apache License, Version 2.0 (the "License");
# 您只能在遵守许可证的情况下使用此文件
# you may not use this file except in compliance with the License.
# 您可以在此处获取许可证副本
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,软件按“原样”分发,没有任何明示或暗示的担保或条件
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 不提供任何形式的保证或条件
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 查看许可证以获取有关权限和限制的具体信息
# See the License for the specific language governing permissions and
# limitations under the License.
# --------------------------------------------------------------------------
# 额外信息和引用说明可在 Marigold 项目网站上找到
# More information and citation instructions are available on the
# Marigold project website: https://marigoldmonodepth.github.io
# --------------------------------------------------------------------------
# 导入数据类装饰器
from dataclasses import dataclass
# 导入类型提示相关的类型
from typing import Any, Dict, List, Optional, Tuple, Union

# 导入 numpy 库
import numpy as np
# 导入 PyTorch 库
import torch
# 导入图像处理库 PIL
from PIL import Image
# 导入进度条库
from tqdm.auto import tqdm
# 导入 CLIP 模型和分词器
from transformers import CLIPTextModel, CLIPTokenizer

# 导入图像处理的管道输入
from ...image_processor import PipelineImageInput
# 导入自动编码器和 UNet 模型
from ...models import (
    AutoencoderKL,
    UNet2DConditionModel,
)
# 导入调度器
from ...schedulers import (
    DDIMScheduler,
    LCMScheduler,
)
# 导入工具函数
from ...utils import (
    BaseOutput,
    logging,
    replace_example_docstring,
)
# 导入随机张量生成工具
from ...utils.torch_utils import randn_tensor
# 导入扩散管道工具
from ..pipeline_utils import DiffusionPipeline
# 导入 Marigold 图像处理工具
from .marigold_image_processing import MarigoldImageProcessor

# 创建日志记录器,用于记录日志信息
logger = logging.get_logger(__name__)  # pylint: disable=invalid-name

# 示例文档字符串,提供用法示例
EXAMPLE_DOC_STRING = """
Examples:

>>> import diffusers
>>> import torch

>>> pipe = diffusers.MarigoldNormalsPipeline.from_pretrained(
...     "prs-eth/marigold-normals-lcm-v0-1", variant="fp16", torch_dtype=torch.float16
... ).to("cuda")

>>> image = diffusers.utils.load_image("https://marigoldmonodepth.github.io/images/einstein.jpg")
>>> normals = pipe(image)

>>> vis = pipe.image_processor.visualize_normals(normals.prediction)
>>> vis[0].save("einstein_normals.png")

"""

# 定义 Marigold 单目法线预测管道的输出类
@dataclass
class MarigoldNormalsOutput(BaseOutput):
    """
    Marigold 单目法线预测管道的输出类
    # 定义函数参数的文档字符串
    Args:
        # 预测法线的参数,类型可以是 numpy 数组或 PyTorch 张量
        prediction (`np.ndarray`, `torch.Tensor`):
            # 预测的法线值范围在 [-1, 1] 之间,形状为 $numimages \times 3 \times height
            \times width$,无论图像是作为 4D 数组还是列表传递。
        # 不确定性地图的参数,类型可以是 None、numpy 数组或 PyTorch 张量
        uncertainty (`None`, `np.ndarray`, `torch.Tensor`):
            # 从集合中计算得到的不确定性地图,值范围在 [0, 1] 之间,形状为 $numimages
            \times 1 \times height \times width$。
        # 潜在特征的参数,类型可以是 None 或 PyTorch 张量
        latent (`None`, `torch.Tensor`):
            # 与预测相对应的潜在特征,兼容于管道的 `latents` 参数。
            # 形状为 $numimages * numensemble \times 4 \times latentheight \times latentwidth$。
    """

    # 声明预测参数的类型,支持 numpy 数组或 PyTorch 张量
    prediction: Union[np.ndarray, torch.Tensor]
    # 声明不确定性参数的类型,支持 None、numpy 数组或 PyTorch 张量
    uncertainty: Union[None, np.ndarray, torch.Tensor]
    # 声明潜在特征参数的类型,支持 None 或 PyTorch 张量
    latent: Union[None, torch.Tensor]
# 定义一个名为 MarigoldNormalsPipeline 的类,继承自 DiffusionPipeline
class MarigoldNormalsPipeline(DiffusionPipeline):
    """
    使用 Marigold 方法进行单目法线估计的管道: https://marigoldmonodepth.github.io.

    此模型继承自 [`DiffusionPipeline`]。请查看父类文档,以了解库为所有管道实现的通用方法
    (例如下载或保存、在特定设备上运行等)。

    参数:
        unet (`UNet2DConditionModel`):
            条件 U-Net,用于在图像潜在空间条件下对法线潜在进行去噪。
        vae (`AutoencoderKL`):
            变分自编码器 (VAE) 模型,用于将图像和预测编码和解码为潜在表示。
        scheduler (`DDIMScheduler` 或 `LCMScheduler`):
            与 `unet` 结合使用的调度器,用于对编码的图像潜在进行去噪。
        text_encoder (`CLIPTextModel`):
            文本编码器,用于生成空的文本嵌入。
        tokenizer (`CLIPTokenizer`):
            CLIP 令牌化器。
        prediction_type (`str`, *可选*):
            模型生成的预测类型。
        use_full_z_range (`bool`, *可选*):
            该模型预测的法线是否使用 Z 维度的完整范围,还是仅使用其正半。
        default_denoising_steps (`int`, *可选*):
            生成合理质量预测所需的最小去噪扩散步骤数。此值必须在模型配置中设置。当
            管道被调用而未明确设置 `num_inference_steps` 时,使用默认值。这样可以确保
            与与管道兼容的各种模型版本产生合理结果,例如依赖非常短去噪调度的模型
            (`LCMScheduler`) 和具有完整扩散调度的模型 (`DDIMScheduler`)。
        default_processing_resolution (`int`, *可选*):
            管道的 `processing_resolution` 参数的推荐值。此值必须在模型配置中设置。当
            管道被调用而未明确设置 `processing_resolution` 时,使用默认值。这样可以确保
            与不同最佳处理分辨率值训练的各种模型版本产生合理结果。
    """

    # 定义模型的 CPU 卸载顺序
    model_cpu_offload_seq = "text_encoder->unet->vae"
    # 支持的预测类型,当前仅支持 "normals"
    supported_prediction_types = ("normals",)

    # 初始化方法,接受多个参数以配置模型
    def __init__(
        # 条件 U-Net 模型
        self,
        unet: UNet2DConditionModel,
        # 变分自编码器模型
        vae: AutoencoderKL,
        # 调度器,可以是 DDIMScheduler 或 LCMScheduler
        scheduler: Union[DDIMScheduler, LCMScheduler],
        # 文本编码器
        text_encoder: CLIPTextModel,
        # CLIP 令牌化器
        tokenizer: CLIPTokenizer,
        # 可选的预测类型
        prediction_type: Optional[str] = None,
        # 是否使用 Z 维度的完整范围
        use_full_z_range: Optional[bool] = True,
        # 默认去噪步骤数
        default_denoising_steps: Optional[int] = None,
        # 默认处理分辨率
        default_processing_resolution: Optional[int] = None,
    ):
        # 调用父类的构造函数
        super().__init__()

        # 检查预测类型是否在支持的类型中
        if prediction_type not in self.supported_prediction_types:
            # 如果不支持,记录警告信息
            logger.warning(
                f"Potentially unsupported `prediction_type='{prediction_type}'`; values supported by the pipeline: "
                f"{self.supported_prediction_types}."
            )

        # 注册各个模块到当前实例中
        self.register_modules(
            unet=unet,  # 注册 UNet 模块
            vae=vae,    # 注册变分自编码器模块
            scheduler=scheduler,  # 注册调度器模块
            text_encoder=text_encoder,  # 注册文本编码器模块
            tokenizer=tokenizer,  # 注册分词器模块
        )
        # 将配置参数注册到当前实例中
        self.register_to_config(
            use_full_z_range=use_full_z_range,  # 注册使用完整的 z 范围
            default_denoising_steps=default_denoising_steps,  # 注册默认去噪步骤
            default_processing_resolution=default_processing_resolution,  # 注册默认处理分辨率
        )

        # 计算 VAE 的缩放因子,基于块输出通道的数量
        self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)

        # 将使用完整 z 范围的设置保存到实例中
        self.use_full_z_range = use_full_z_range
        # 将默认去噪步骤保存到实例中
        self.default_denoising_steps = default_denoising_steps
        # 将默认处理分辨率保存到实例中
        self.default_processing_resolution = default_processing_resolution

        # 初始化空的文本嵌入
        self.empty_text_embedding = None

        # 创建图像处理器实例,并传入 VAE 缩放因子
        self.image_processor = MarigoldImageProcessor(vae_scale_factor=self.vae_scale_factor)

    def check_inputs(
        # 定义检查输入参数的方法,接收多个参数
        image: PipelineImageInput,  # 输入图像
        num_inference_steps: int,  # 推理步骤数
        ensemble_size: int,  # 集成大小
        processing_resolution: int,  # 处理分辨率
        resample_method_input: str,  # 输入重采样方法
        resample_method_output: str,  # 输出重采样方法
        batch_size: int,  # 批处理大小
        ensembling_kwargs: Optional[Dict[str, Any]],  # 集成相关的关键字参数
        latents: Optional[torch.Tensor],  # 潜在变量
        generator: Optional[Union[torch.Generator, List[torch.Generator]]],  # 随机数生成器
        output_type: str,  # 输出类型
        output_uncertainty: bool,  # 是否输出不确定性
    def progress_bar(self, iterable=None, total=None, desc=None, leave=True):
        # 检查是否已经初始化了进度条配置
        if not hasattr(self, "_progress_bar_config"):
            # 如果没有,初始化为空字典
            self._progress_bar_config = {}
        # 如果存在配置,但不是字典类型,抛出错误
        elif not isinstance(self._progress_bar_config, dict):
            raise ValueError(
                f"`self._progress_bar_config` should be of type `dict`, but is {type(self._progress_bar_config)}."
            )

        # 复制当前的进度条配置
        progress_bar_config = dict(**self._progress_bar_config)
        # 设置描述,如果未提供则使用现有值
        progress_bar_config["desc"] = progress_bar_config.get("desc", desc)
        # 设置是否在完成后保留进度条
        progress_bar_config["leave"] = progress_bar_config.get("leave", leave)
        # 如果提供了可迭代对象,返回带进度条的可迭代对象
        if iterable is not None:
            return tqdm(iterable, **progress_bar_config)
        # 如果提供了总数,返回带进度条的总数
        elif total is not None:
            return tqdm(total=total, **progress_bar_config)
        # 如果两者都未提供,抛出错误
        else:
            raise ValueError("Either `total` or `iterable` has to be defined.")

    # 使用 torch.no_grad() 装饰器,禁用梯度计算
    @torch.no_grad()
    # 替换示例文档字符串
    @replace_example_docstring(EXAMPLE_DOC_STRING)
    # 定义可调用的类方法,处理图像并生成推理结果
        def __call__(
            self,
            image: PipelineImageInput,  # 输入的图像数据
            num_inference_steps: Optional[int] = None,  # 推理步骤数,默认为 None
            ensemble_size: int = 1,  # 集成模型的大小,默认为 1
            processing_resolution: Optional[int] = None,  # 处理分辨率,默认为 None
            match_input_resolution: bool = True,  # 是否匹配输入分辨率
            resample_method_input: str = "bilinear",  # 输入重采样方法,默认为双线性
            resample_method_output: str = "bilinear",  # 输出重采样方法,默认为双线性
            batch_size: int = 1,  # 批处理大小,默认为 1
            ensembling_kwargs: Optional[Dict[str, Any]] = None,  # 集成参数,默认为 None
            latents: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,  # 潜在变量,默认为 None
            generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,  # 随机数生成器,默认为 None
            output_type: str = "np",  # 输出类型,默认为 NumPy
            output_uncertainty: bool = False,  # 是否输出不确定性,默认为 False
            output_latent: bool = False,  # 是否输出潜在变量,默认为 False
            return_dict: bool = True,  # 是否返回字典格式,默认为 True
        # 从 diffusers.pipelines.marigold.pipeline_marigold_depth.MarigoldDepthPipeline.prepare_latents 复制
        def prepare_latents(
            self,
            image: torch.Tensor,  # 输入图像的张量格式
            latents: Optional[torch.Tensor],  # 潜在变量的张量格式
            generator: Optional[torch.Generator],  # 随机数生成器,默认为 None
            ensemble_size: int,  # 集成大小
            batch_size: int,  # 批处理大小
        ) -> Tuple[torch.Tensor, torch.Tensor]:  # 返回两个张量的元组
            def retrieve_latents(encoder_output):  # 定义内部函数,用于获取潜在变量
                if hasattr(encoder_output, "latent_dist"):  # 检查输出是否包含潜在分布
                    return encoder_output.latent_dist.mode()  # 返回潜在分布的众数
                elif hasattr(encoder_output, "latents"):  # 检查输出是否包含潜在变量
                    return encoder_output.latents  # 返回潜在变量
                else:  # 如果都没有,抛出异常
                    raise AttributeError("Could not access latents of provided encoder_output")
    
            image_latent = torch.cat(  # 将处理后的潜在变量进行拼接
                [
                    retrieve_latents(self.vae.encode(image[i : i + batch_size]))  # 对每个批次的图像编码并获取潜在变量
                    for i in range(0, image.shape[0], batch_size)  # 按批处理大小遍历图像
                ],
                dim=0,  # 在第0维进行拼接
            )  # [N,4,h,w]  # 得到的潜在变量张量的形状
            image_latent = image_latent * self.vae.config.scaling_factor  # 应用缩放因子调整潜在变量
            image_latent = image_latent.repeat_interleave(ensemble_size, dim=0)  # [N*E,4,h,w]  # 重复以匹配集成大小
    
            pred_latent = latents  # 初始化预测潜在变量
            if pred_latent is None:  # 如果未提供潜在变量
                pred_latent = randn_tensor(  # 生成随机潜在变量
                    image_latent.shape,  # 形状与 image_latent 相同
                    generator=generator,  # 使用提供的随机数生成器
                    device=image_latent.device,  # 使用 image_latent 的设备
                    dtype=image_latent.dtype,  # 使用 image_latent 的数据类型
                )  # [N*E,4,h,w]  # 生成的潜在变量形状
    
            return image_latent, pred_latent  # 返回图像潜在变量和预测潜在变量
    
        def decode_prediction(self, pred_latent: torch.Tensor) -> torch.Tensor:  # 解码预测潜在变量的方法
            if pred_latent.dim() != 4 or pred_latent.shape[1] != self.vae.config.latent_channels:  # 检查预测潜在变量的维度和通道数
                raise ValueError(  # 如果不符合要求,抛出异常
                    f"Expecting 4D tensor of shape [B,{self.vae.config.latent_channels},H,W]; got {pred_latent.shape}."
                )
    
            prediction = self.vae.decode(pred_latent / self.vae.config.scaling_factor, return_dict=False)[0]  # [B,3,H,W]  # 解码潜在变量,得到预测图像
    
            prediction = torch.clip(prediction, -1.0, 1.0)  # 限制预测值在 -1.0 到 1.0 之间
    
            if not self.use_full_z_range:  # 如果不使用完整的潜在范围
                prediction[:, 2, :, :] *= 0.5  # 对第三个通道进行缩放
                prediction[:, 2, :, :] += 0.5  # 对第三个通道进行偏移
    
            prediction = self.normalize_normals(prediction)  # [B,3,H,W]  # 正常化预测结果
    
            return prediction  # [B,3,H,W]  # 返回最终的预测图像
    
        @staticmethod  # 静态方法的标记
    # 规范化法线向量,使其单位长度,避免数值不稳定
    def normalize_normals(normals: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
        # 检查输入的法线张量是否为4维且第二维的大小为3
        if normals.dim() != 4 or normals.shape[1] != 3:
            # 如果不满足条件,抛出错误
            raise ValueError(f"Expecting 4D tensor of shape [B,3,H,W]; got {normals.shape}.")

        # 计算法线张量在第二维的范数,并保持维度
        norm = torch.norm(normals, dim=1, keepdim=True)
        # 将法线张量除以范数,使用clamp限制最小值以避免除以零
        normals /= norm.clamp(min=eps)

        # 返回规范化后的法线张量
        return normals

    @staticmethod
    # 对法线张量进行集成处理,返回集成后的法线和可选的不确定性
    def ensemble_normals(
        normals: torch.Tensor, output_uncertainty: bool, reduction: str = "closest"
    ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
        """
        对法线图进行集成,期望输入形状为 `(B, 3, H, W)`,其中 B 是
        每个预测的集成成员数量,大小为 `(H x W)`。

        Args:
            normals (`torch.Tensor`):
                输入的集成法线图。
            output_uncertainty (`bool`, *可选*, 默认值为 `False`):
                是否输出不确定性图。
            reduction (`str`, *可选*, 默认值为 `"closest"`):
                用于集成对齐预测的归约方法。接受的值为:`"closest"` 和
                `"mean"`。

        Returns:
            返回形状为 `(1, 3, H, W)` 的对齐和集成法线图,以及可选的不确定性张量,形状为 `(1, 1, H, W)`。
        """
        # 检查输入的法线张量是否为4维且第二维的大小为3
        if normals.dim() != 4 or normals.shape[1] != 3:
            # 如果不满足条件,抛出错误
            raise ValueError(f"Expecting 4D tensor of shape [B,3,H,W]; got {normals.shape}.")
        # 检查归约方法是否有效
        if reduction not in ("closest", "mean"):
            # 如果不合法,抛出错误
            raise ValueError(f"Unrecognized reduction method: {reduction}.")

        # 计算法线的均值,保持维度
        mean_normals = normals.mean(dim=0, keepdim=True)  # [1,3,H,W]
        # 规范化均值法线
        mean_normals = MarigoldNormalsPipeline.normalize_normals(mean_normals)  # [1,3,H,W]

        # 计算均值法线与所有法线的点积,得到相似度
        sim_cos = (mean_normals * normals).sum(dim=1, keepdim=True)  # [E,1,H,W]
        # 限制相似度值在 -1 到 1 之间,以避免在 fp16 中出现 NaN
        sim_cos = sim_cos.clamp(-1, 1)  # required to avoid NaN in uncertainty with fp16

        uncertainty = None
        # 如果需要输出不确定性
        if output_uncertainty:
            # 计算相似度的反余弦,得到不确定性
            uncertainty = sim_cos.arccos()  # [E,1,H,W]
            # 计算均值并归一化
            uncertainty = uncertainty.mean(dim=0, keepdim=True) / np.pi  # [1,1,H,W]

        # 如果选择平均归约方法
        if reduction == "mean":
            # 返回均值法线和不确定性
            return mean_normals, uncertainty  # [1,3,H,W], [1,1,H,W]

        # 找到相似度最大的索引
        closest_indices = sim_cos.argmax(dim=0, keepdim=True)  # [1,1,H,W]
        # 将索引扩展到法线的通道数
        closest_indices = closest_indices.repeat(1, 3, 1, 1)  # [1,3,H,W]
        # 根据索引从法线中提取相应的法线
        closest_normals = torch.gather(normals, 0, closest_indices)  # [1,3,H,W]

        # 返回最近法线和不确定性
        return closest_normals, uncertainty  # [1,3,H,W], [1,1,H,W]

.\diffusers\pipelines\marigold\__init__.py

# 导入类型检查相关的模块
from typing import TYPE_CHECKING

# 从相对路径导入所需的工具函数和常量
from ...utils import (
    DIFFUSERS_SLOW_IMPORT,  # 慢导入标志
    OptionalDependencyNotAvailable,  # 可选依赖未可用的异常类
    _LazyModule,  # 延迟模块加载类
    get_objects_from_module,  # 从模块获取对象的函数
    is_torch_available,  # 检查 PyTorch 是否可用的函数
    is_transformers_available,  # 检查 Transformers 是否可用的函数
)

# 初始化一个空字典,用于存储占位对象
_dummy_objects = {}
# 初始化一个空字典,用于存储导入结构
_import_structure = {}

# 尝试检测依赖关系
try:
    # 检查 Transformers 和 PyTorch 是否都可用
    if not (is_transformers_available() and is_torch_available()):
        # 如果不可用,则抛出异常
        raise OptionalDependencyNotAvailable()
# 捕获可选依赖未可用的异常
except OptionalDependencyNotAvailable:
    # 从工具模块中导入占位对象(忽略 F403 警告)
    from ...utils import dummy_torch_and_transformers_objects  # noqa F403

    # 更新占位对象字典
    _dummy_objects.update(get_objects_from_module(dummy_torch_and_transformers_objects))
else:
    # 如果依赖可用,定义模块的导入结构
    _import_structure["marigold_image_processing"] = ["MarigoldImageProcessor"]
    _import_structure["pipeline_marigold_depth"] = ["MarigoldDepthOutput", "MarigoldDepthPipeline"]
    _import_structure["pipeline_marigold_normals"] = ["MarigoldNormalsOutput", "MarigoldNormalsPipeline"]

# 检查类型检查或慢导入标志
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
    # 尝试检测依赖关系
    try:
        # 检查 Transformers 和 PyTorch 是否都可用
        if not (is_transformers_available() and is_torch_available()):
            # 如果不可用,则抛出异常
            raise OptionalDependencyNotAvailable()

    # 捕获可选依赖未可用的异常
    except OptionalDependencyNotAvailable:
        # 导入占位对象以避免错误
        from ...utils.dummy_torch_and_transformers_objects import *
    else:
        # 导入实际模块中的类
        from .marigold_image_processing import MarigoldImageProcessor
        from .pipeline_marigold_depth import MarigoldDepthOutput, MarigoldDepthPipeline
        from .pipeline_marigold_normals import MarigoldNormalsOutput, MarigoldNormalsPipeline

else:
    # 导入系统模块
    import sys

    # 用延迟模块加载类替代当前模块
    sys.modules[__name__] = _LazyModule(
        __name__,
        globals()["__file__"],  # 当前文件的全局名称
        _import_structure,  # 导入结构
        module_spec=__spec__,  # 模块的规范
    )
    # 将占位对象添加到当前模块中
    for name, value in _dummy_objects.items():
        setattr(sys.modules[__name__], name, value)

.\diffusers\pipelines\musicldm\pipeline_musicldm.py

# 版权声明,表明该代码的版权归 HuggingFace 团队所有
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 许可声明,指明该代码遵循 Apache 许可证 2.0 版本
# Licensed under the Apache License, Version 2.0 (the "License");
# 除非遵循许可证,否则不可使用此文件
# you may not use this file except in compliance with the License.
# 提供许可证获取链接
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 指出软件按“原样”分发,没有任何明示或暗示的保证
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 参考许可证以了解具体权限和限制
# See the License for the specific language governing permissions and
# limitations under the License.

# 导入 inspect 模块以获取有关对象的信息
import inspect
# 从 typing 模块导入类型提示相关的类型
from typing import Any, Callable, Dict, List, Optional, Union

# 导入 numpy 库以进行数值计算
import numpy as np
# 导入 PyTorch 库以进行深度学习
import torch
# 从 transformers 库导入多个模型和特征提取器
from transformers import (
    ClapFeatureExtractor,  # 导入 Clap 的特征提取器
    ClapModel,  # 导入 Clap 模型
    ClapTextModelWithProjection,  # 导入带投影的 Clap 文本模型
    RobertaTokenizer,  # 导入 Roberta 分词器
    RobertaTokenizerFast,  # 导入快速 Roberta 分词器
    SpeechT5HifiGan,  # 导入 SpeechT5 的 HiFiGan 模型
)

# 从相对路径导入模型和调度器
from ...models import AutoencoderKL, UNet2DConditionModel  # 导入自编码器和条件 UNet 模型
from ...schedulers import KarrasDiffusionSchedulers  # 导入 Karras 扩散调度器
from ...utils import (
    is_accelerate_available,  # 检查 accelerate 是否可用
    is_accelerate_version,  # 检查 accelerate 版本
    is_librosa_available,  # 检查 librosa 是否可用
    logging,  # 导入日志记录功能
    replace_example_docstring,  # 导入替换示例文档字符串的工具
)
from ...utils.torch_utils import randn_tensor  # 导入生成随机张量的工具
from ..pipeline_utils import AudioPipelineOutput, DiffusionPipeline, StableDiffusionMixin  # 导入音频管道输出和扩散管道类


# 如果 librosa 库可用,则导入它
if is_librosa_available():
    import librosa

# 创建一个日志记录器,用于记录当前模块的信息
logger = logging.get_logger(__name__)  # pylint: disable=invalid-name

# 定义示例文档字符串,展示如何使用 MusicLDMPipeline
EXAMPLE_DOC_STRING = """
    Examples:
        ```py
        >>> from diffusers import MusicLDMPipeline  # 从 diffusers 导入音乐生成管道
        >>> import torch  # 导入 PyTorch 库
        >>> import scipy  # 导入 SciPy 库用于处理音频

        >>> repo_id = "ucsd-reach/musicldm"  # 定义模型仓库 ID
        >>> pipe = MusicLDMPipeline.from_pretrained(repo_id, torch_dtype=torch.float16)  # 从预训练模型创建管道
        >>> pipe = pipe.to("cuda")  # 将管道移至 CUDA 设备

        >>> prompt = "Techno music with a strong, upbeat tempo and high melodic riffs"  # 定义生成音乐的提示
        >>> audio = pipe(prompt, num_inference_steps=10, audio_length_in_s=5.0).audios[0]  # 生成音频

        >>> # 将生成的音频保存为 .wav 文件
        >>> scipy.io.wavfile.write("techno.wav", rate=16000, data=audio)  # 保存音频文件
        ```py
"""

# 定义 MusicLDMPipeline 类,继承自 DiffusionPipeline 和 StableDiffusionMixin
class MusicLDMPipeline(DiffusionPipeline, StableDiffusionMixin):
    r"""
    用于基于文本生成音频的管道,使用 MusicLDM 模型。

    该模型继承自 [`DiffusionPipeline`]。请查看超类文档以获取所有管道实现的通用方法
    (下载、保存、在特定设备上运行等)。
    # 文档字符串,描述构造函数的参数及其类型
    Args:
        vae ([`AutoencoderKL`]):
            # 变分自编码器(VAE)模型,用于将图像编码和解码为潜在表示
        text_encoder ([`~transformers.ClapModel`]):
            # 冻结的文本-音频嵌入模型(`ClapTextModel`),特别是
            # [laion/clap-htsat-unfused](https://huggingface.co/laion/clap-htsat-unfused) 变体
        tokenizer ([`PreTrainedTokenizer`]):
            # [`~transformers.RobertaTokenizer`] 用于对文本进行分词
        feature_extractor ([`~transformers.ClapFeatureExtractor`]):
            # 特征提取器,用于从音频波形计算梅尔谱图
        unet ([`UNet2DConditionModel`]):
            # `UNet2DConditionModel` 用于去噪编码后的音频潜在表示
        scheduler ([`SchedulerMixin`]):
            # 调度器,与 `unet` 结合使用以去噪编码的音频潜在表示,可以是
            # [`DDIMScheduler`], [`LMSDiscreteScheduler`] 或 [`PNDMScheduler`]
        vocoder ([`~transformers.SpeechT5HifiGan`]):
            # `SpeechT5HifiGan` 类的声码器
    """

    # 构造函数,初始化对象的属性
    def __init__(
        self,
        vae: AutoencoderKL,  # 变分自编码器模型
        text_encoder: Union[ClapTextModelWithProjection, ClapModel],  # 文本编码器
        tokenizer: Union[RobertaTokenizer, RobertaTokenizerFast],  # 分词器
        feature_extractor: Optional[ClapFeatureExtractor],  # 可选的特征提取器
        unet: UNet2DConditionModel,  # UNet 模型用于去噪
        scheduler: KarrasDiffusionSchedulers,  # 调度器
        vocoder: SpeechT5HifiGan,  # 声码器
    ):
        super().__init__()  # 调用父类的构造函数

        # 注册模块,存储各种组件到对象的属性中
        self.register_modules(
            vae=vae,  # 注册 VAE 模型
            text_encoder=text_encoder,  # 注册文本编码器
            tokenizer=tokenizer,  # 注册分词器
            feature_extractor=feature_extractor,  # 注册特征提取器
            unet=unet,  # 注册 UNet 模型
            scheduler=scheduler,  # 注册调度器
            vocoder=vocoder,  # 注册声码器
        )
        # 计算 VAE 的缩放因子,根据 VAE 配置的块输出通道数计算
        self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)

    # 编码提示的私有方法,处理输入提示
    def _encode_prompt(
        self,
        prompt,  # 输入提示文本
        device,  # 设备信息
        num_waveforms_per_prompt,  # 每个提示生成的波形数量
        do_classifier_free_guidance,  # 是否进行无分类器引导
        negative_prompt=None,  # 可选的负面提示
        prompt_embeds: Optional[torch.Tensor] = None,  # 可选的提示嵌入
        negative_prompt_embeds: Optional[torch.Tensor] = None,  # 可选的负面提示嵌入
    # 从 diffusers.pipelines.audioldm.pipeline_audioldm.AudioLDMPipeline 复制的梅尔谱图到波形的转换方法
    def mel_spectrogram_to_waveform(self, mel_spectrogram):  # 定义方法,将梅尔谱图转换为波形
        # 如果梅尔谱图是四维的,则去掉第一维
        if mel_spectrogram.dim() == 4:
            mel_spectrogram = mel_spectrogram.squeeze(1)

        # 使用声码器将梅尔谱图转换为波形
        waveform = self.vocoder(mel_spectrogram)
        # 始终转换为 float32 类型,因为这不会造成显著的开销,并且与 bfloat16 兼容
        waveform = waveform.cpu().float()  # 将波形移动到 CPU 并转换为 float32 类型
        return waveform  # 返回生成的波形

    # 从 diffusers.pipelines.audioldm2.pipeline_audioldm2.AudioLDM2Pipeline 复制的得分波形的方法
    # 评分音频波形与文本提示之间的匹配度
        def score_waveforms(self, text, audio, num_waveforms_per_prompt, device, dtype):
            # 检查是否安装了 librosa 包
            if not is_librosa_available():
                # 记录信息,提示用户安装 librosa 包以启用自动评分
                logger.info(
                    "Automatic scoring of the generated audio waveforms against the input prompt text requires the "
                    "`librosa` package to resample the generated waveforms. Returning the audios in the order they were "
                    "generated. To enable automatic scoring, install `librosa` with: `pip install librosa`."
                )
                # 返回原始音频
                return audio
            # 对文本进行标记化并返回张量格式的输入
            inputs = self.tokenizer(text, return_tensors="pt", padding=True)
            # 使用 librosa 对音频进行重采样,调整采样率
            resampled_audio = librosa.resample(
                audio.numpy(), orig_sr=self.vocoder.config.sampling_rate, target_sr=self.feature_extractor.sampling_rate
            )
            # 将重采样后的音频特征提取并转换为指定数据类型
            inputs["input_features"] = self.feature_extractor(
                list(resampled_audio), return_tensors="pt", sampling_rate=self.feature_extractor.sampling_rate
            ).input_features.type(dtype)
            # 将输入数据移动到指定设备上
            inputs = inputs.to(device)
    
            # 使用 CLAP 模型计算音频与文本的相似性得分
            logits_per_text = self.text_encoder(**inputs).logits_per_text
            # 根据文本匹配度对生成的音频进行排序
            indices = torch.argsort(logits_per_text, dim=1, descending=True)[:, :num_waveforms_per_prompt]
            # 选择根据排序结果的音频
            audio = torch.index_select(audio, 0, indices.reshape(-1).cpu())
            # 返回排序后的音频
            return audio
    
        # 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs 复制
        def prepare_extra_step_kwargs(self, generator, eta):
            # 准备调度器步骤的额外参数,因为并非所有调度器都有相同的参数签名
            # eta (η) 仅在 DDIMScheduler 中使用,其他调度器会被忽略。
            # eta 对应 DDIM 论文中的 η: https://arxiv.org/abs/2010.02502
            # 其值应在 [0, 1] 之间
    
            # 检查调度器是否接受 eta 参数
            accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
            extra_step_kwargs = {}
            # 如果接受 eta,则将其添加到额外参数中
            if accepts_eta:
                extra_step_kwargs["eta"] = eta
    
            # 检查调度器是否接受 generator 参数
            accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
            # 如果接受 generator,则将其添加到额外参数中
            if accepts_generator:
                extra_step_kwargs["generator"] = generator
            # 返回包含额外参数的字典
            return extra_step_kwargs
    
        # 从 diffusers.pipelines.audioldm.pipeline_audioldm.AudioLDMPipeline.check_inputs 复制
        def check_inputs(
            self,
            prompt,
            audio_length_in_s,
            vocoder_upsample_factor,
            callback_steps,
            negative_prompt=None,
            prompt_embeds=None,
            negative_prompt_embeds=None,
    ):
        # 计算最小音频长度(秒),基于声码器上采样因子和 VAE 缩放因子
        min_audio_length_in_s = vocoder_upsample_factor * self.vae_scale_factor
        # 如果输入音频长度小于最小音频长度,则抛出错误
        if audio_length_in_s < min_audio_length_in_s:
            raise ValueError(
                # 提示音频长度必须大于等于最小音频长度
                f"`audio_length_in_s` has to be a positive value greater than or equal to {min_audio_length_in_s}, but "
                f"is {audio_length_in_s}."
            )

        # 检查声码器模型输入维度是否可以被 VAE 缩放因子整除
        if self.vocoder.config.model_in_dim % self.vae_scale_factor != 0:
            raise ValueError(
                # 提示频率 bins 数量必须可以被 VAE 缩放因子整除
                f"The number of frequency bins in the vocoder's log-mel spectrogram has to be divisible by the "
                f"VAE scale factor, but got {self.vocoder.config.model_in_dim} bins and a scale factor of "
                f"{self.vae_scale_factor}."
            )

        # 检查 callback_steps 是否有效(必须为正整数)
        if (callback_steps is None) or (
            callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)
        ):
            raise ValueError(
                # 提示 callback_steps 必须为正整数
                f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
                f" {type(callback_steps)}."
            )

        # 检查同时传入 prompt 和 prompt_embeds 是否有效
        if prompt is not None and prompt_embeds is not None:
            raise ValueError(
                # 提示不能同时提供 prompt 和 prompt_embeds
                f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
                " only forward one of the two."
            )
        # 检查 prompt 和 prompt_embeds 是否同时为 None
        elif prompt is None and prompt_embeds is None:
            raise ValueError(
                # 提示必须提供 prompt 或 prompt_embeds,不能都为空
                "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
            )
        # 检查 prompt 的类型是否为字符串或列表
        elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
            raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")

        # 检查是否同时传入 negative_prompt 和 negative_prompt_embeds
        if negative_prompt is not None and negative_prompt_embeds is not None:
            raise ValueError(
                # 提示不能同时提供 negative_prompt 和 negative_prompt_embeds
                f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
                f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
            )

        # 检查 prompt_embeds 和 negative_prompt_embeds 是否形状一致
        if prompt_embeds is not None and negative_prompt_embeds is not None:
            if prompt_embeds.shape != negative_prompt_embeds.shape:
                raise ValueError(
                    # 提示 prompt_embeds 和 negative_prompt_embeds 形状必须相同
                    "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
                    f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
                    f" {negative_prompt_embeds.shape}."
                )

    # 从 diffusers.pipelines.audioldm.pipeline_audioldm.AudioLDMPipeline.prepare_latents 中复制的代码
    # 准备潜在变量,参数包括批次大小、通道数、高度等
        def prepare_latents(self, batch_size, num_channels_latents, height, dtype, device, generator, latents=None):
            # 定义潜在变量的形状,考虑 VAE 的缩放因子
            shape = (
                batch_size,
                num_channels_latents,
                int(height) // self.vae_scale_factor,
                int(self.vocoder.config.model_in_dim) // self.vae_scale_factor,
            )
            # 检查生成器列表的长度是否与批次大小匹配
            if isinstance(generator, list) and len(generator) != batch_size:
                raise ValueError(
                    f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
                    f" size of {batch_size}. Make sure the batch size matches the length of the generators."
                )
    
            # 如果没有提供潜在变量,则随机生成
            if latents is None:
                latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
            else:
                # 如果提供了潜在变量,将其移动到指定设备
                latents = latents.to(device)
    
            # 根据调度器需要的标准差缩放初始噪声
            latents = latents * self.scheduler.init_noise_sigma
            # 返回处理后的潜在变量
            return latents
    
        # 启用模型的 CPU 卸载,以减少内存使用
        def enable_model_cpu_offload(self, gpu_id=0):
            r"""
            Offloads all models to CPU using accelerate, reducing memory usage with a low impact on performance. Compared
            to `enable_sequential_cpu_offload`, this method moves one whole model at a time to the GPU when its `forward`
            method is called, and the model remains in GPU until the next model runs. Memory savings are lower than with
            `enable_sequential_cpu_offload`, but performance is much better due to the iterative execution of the `unet`.
            """
            # 检查 accelerate 是否可用且版本是否合适
            if is_accelerate_available() and is_accelerate_version(">=", "0.17.0.dev0"):
                from accelerate import cpu_offload_with_hook
            else:
                # 抛出错误以提示用户需要更新 accelerate
                raise ImportError("`enable_model_cpu_offload` requires `accelerate v0.17.0` or higher.")
    
            # 设置设备为指定的 GPU
            device = torch.device(f"cuda:{gpu_id}")
    
            # 如果当前设备不是 CPU,则将模型移动到 CPU
            if self.device.type != "cpu":
                self.to("cpu", silence_dtype_warnings=True)
                # 清空 GPU 缓存以查看内存节省
                torch.cuda.empty_cache()  # otherwise we don't see the memory savings (but they probably exist)
    
            # 定义需要卸载到 CPU 的模型序列
            model_sequence = [
                self.text_encoder.text_model,
                self.text_encoder.text_projection,
                self.unet,
                self.vae,
                self.vocoder,
                self.text_encoder,
            ]
    
            hook = None
            # 遍历模型序列,逐个卸载到 CPU
            for cpu_offloaded_model in model_sequence:
                _, hook = cpu_offload_with_hook(cpu_offloaded_model, device, prev_module_hook=hook)
    
            # 手动卸载最后一个模型
            self.final_offload_hook = hook
    
        # 禁用梯度计算,优化性能
        @torch.no_grad()
        # 替换示例文档字符串
        @replace_example_docstring(EXAMPLE_DOC_STRING)
    # 定义可调用对象的方法,允许使用不同参数进行推断
    def __call__(
        # 提示内容,可以是单个字符串或字符串列表
        self,
        prompt: Union[str, List[str]] = None,
        # 音频长度,单位为秒,默认为 None 表示不限制
        audio_length_in_s: Optional[float] = None,
        # 推理步骤数量,默认为 200
        num_inference_steps: int = 200,
        # 引导比例,用于控制生成结果的引导强度,默认为 2.0
        guidance_scale: float = 2.0,
        # 负提示,可以是单个字符串或字符串列表,默认为 None
        negative_prompt: Optional[Union[str, List[str]]] = None,
        # 每个提示生成的波形数量,默认为 1
        num_waveforms_per_prompt: Optional[int] = 1,
        # 采样的 eta 值,默认为 0.0
        eta: float = 0.0,
        # 随机数生成器,可以是单个或多个 PyTorch 生成器,默认为 None
        generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
        # 可选的潜在表示,默认为 None
        latents: Optional[torch.Tensor] = None,
        # 可选的提示嵌入,默认为 None
        prompt_embeds: Optional[torch.Tensor] = None,
        # 可选的负提示嵌入,默认为 None
        negative_prompt_embeds: Optional[torch.Tensor] = None,
        # 是否返回字典形式的结果,默认为 True
        return_dict: bool = True,
        # 可选的回调函数,用于在推理过程中执行
        callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
        # 回调执行的步骤间隔,默认为 1
        callback_steps: Optional[int] = 1,
        # 可选的交叉注意力参数,默认为 None
        cross_attention_kwargs: Optional[Dict[str, Any]] = None,
        # 输出类型,默认为 "np",表示返回 NumPy 数组
        output_type: Optional[str] = "np",
posted @ 2024-10-22 12:33  绝不原创的飞龙  阅读(11)  评论(0编辑  收藏  举报