CoolFace
Datasetpublic

diffusers/community-pipelines-mirror

Community Pipeline Examples For more information about community pipelines, please have a look at this issue. Community pipeline examples consist pipelines that have been added by the community. Please have a look at the following tables to get an overview of all community examples. Click on the Code Example to get a copy-and-paste ready code example that you can try out. If a community pipeline doesn't work as expected, please open an issue and ping the author on it. Please… See the full description on the dataset page: https://huggingface.co/datasets/diffusers/community-pipelines-mirror.

sourceHugging Faceupdated 28d agoView on Hugging Face
9likes22kdownloads
mixture_canvas.py502 linesDownload Raw Back to root
1import re2from copy import deepcopy3from dataclasses import asdict, dataclass4from enum import Enum5from typing import List, Optional, Union6 7import numpy as np8import torch9from numpy import exp, pi, sqrt10from torchvision.transforms.functional import resize11from tqdm.auto import tqdm12from transformers import CLIPFeatureExtractor, CLIPTextModel, CLIPTokenizer13 14from diffusers.models import AutoencoderKL, UNet2DConditionModel15from diffusers.pipelines.pipeline_utils import DiffusionPipeline, StableDiffusionMixin16from diffusers.pipelines.stable_diffusion import StableDiffusionSafetyChecker17from diffusers.schedulers import DDIMScheduler, LMSDiscreteScheduler, PNDMScheduler18 19 20def preprocess_image(image):21    from PIL import Image22 23    """Preprocess an input image24 25    Same as26    https://github.com/huggingface/diffusers/blob/1138d63b519e37f0ce04e027b9f4a3261d27c628/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_img2img.py#L4427    """28    w, h = image.size29    w, h = (x - x % 32 for x in (w, h))  # resize to integer multiple of 3230    image = image.resize((w, h), resample=Image.LANCZOS)31    image = np.array(image).astype(np.float32) / 255.032    image = image[None].transpose(0, 3, 1, 2)33    image = torch.from_numpy(image)34    return 2.0 * image - 1.035 36 37@dataclass38class CanvasRegion:39    """Class defining a rectangular region in the canvas"""40 41    row_init: int  # Region starting row in pixel space (included)42    row_end: int  # Region end row in pixel space (not included)43    col_init: int  # Region starting column in pixel space (included)44    col_end: int  # Region end column in pixel space (not included)45    region_seed: int = None  # Seed for random operations in this region46    noise_eps: float = 0.0  # Deviation of a zero-mean gaussian noise to be applied over the latents in this region. Useful for slightly "rerolling" latents47 48    def __post_init__(self):49        # Initialize arguments if not specified50        if self.region_seed is None:51            self.region_seed = np.random.randint(9999999999)52        # Check coordinates are non-negative53        for coord in [self.row_init, self.row_end, self.col_init, self.col_end]:54            if coord < 0:55                raise ValueError(56                    f"A CanvasRegion must be defined with non-negative indices, found ({self.row_init}, {self.row_end}, {self.col_init}, {self.col_end})"57                )58        # Check coordinates are divisible by 8, else we end up with nasty rounding error when mapping to latent space59        for coord in [self.row_init, self.row_end, self.col_init, self.col_end]:60            if coord // 8 != coord / 8:61                raise ValueError(62                    f"A CanvasRegion must be defined with locations divisible by 8, found ({self.row_init}-{self.row_end}, {self.col_init}-{self.col_end})"63                )64        # Check noise eps is non-negative65        if self.noise_eps < 0:66            raise ValueError(f"A CanvasRegion must be defined noises eps non-negative, found {self.noise_eps}")67        # Compute coordinates for this region in latent space68        self.latent_row_init = self.row_init // 869        self.latent_row_end = self.row_end // 870        self.latent_col_init = self.col_init // 871        self.latent_col_end = self.col_end // 872 73    @property74    def width(self):75        return self.col_end - self.col_init76 77    @property78    def height(self):79        return self.row_end - self.row_init80 81    def get_region_generator(self, device="cpu"):82        """Creates a torch.Generator based on the random seed of this region"""83        # Initialize region generator84        return torch.Generator(device).manual_seed(self.region_seed)85 86    @property87    def __dict__(self):88        return asdict(self)89 90 91class MaskModes(Enum):92    """Modes in which the influence of diffuser is masked"""93 94    CONSTANT = "constant"95    GAUSSIAN = "gaussian"96    QUARTIC = "quartic"  # See https://en.wikipedia.org/wiki/Kernel_(statistics)97 98 99@dataclass100class DiffusionRegion(CanvasRegion):101    """Abstract class defining a region where some class of diffusion process is acting"""102 103    pass104 105 106@dataclass107class Text2ImageRegion(DiffusionRegion):108    """Class defining a region where a text guided diffusion process is acting"""109 110    prompt: str = ""  # Text prompt guiding the diffuser in this region111    guidance_scale: float = 7.5  # Guidance scale of the diffuser in this region. If None, randomize112    mask_type: MaskModes = MaskModes.GAUSSIAN.value  # Kind of weight mask applied to this region113    mask_weight: float = 1.0  # Global weights multiplier of the mask114    tokenized_prompt = None  # Tokenized prompt115    encoded_prompt = None  # Encoded prompt116 117    def __post_init__(self):118        super().__post_init__()119        # Mask weight cannot be negative120        if self.mask_weight < 0:121            raise ValueError(122                f"A Text2ImageRegion must be defined with non-negative mask weight, found {self.mask_weight}"123            )124        # Mask type must be an actual known mask125        if self.mask_type not in [e.value for e in MaskModes]:126            raise ValueError(127                f"A Text2ImageRegion was defined with mask {self.mask_type}, which is not an accepted mask ({[e.value for e in MaskModes]})"128            )129        # Randomize arguments if given as None130        if self.guidance_scale is None:131            self.guidance_scale = np.random.randint(5, 30)132        # Clean prompt133        self.prompt = re.sub(" +", " ", self.prompt).replace("\n", " ")134 135    def tokenize_prompt(self, tokenizer):136        """Tokenizes the prompt for this diffusion region using a given tokenizer"""137        self.tokenized_prompt = tokenizer(138            self.prompt,139            padding="max_length",140            max_length=tokenizer.model_max_length,141            truncation=True,142            return_tensors="pt",143        )144 145    def encode_prompt(self, text_encoder, device):146        """Encodes the previously tokenized prompt for this diffusion region using a given encoder"""147        assert self.tokenized_prompt is not None, ValueError(148            "Prompt in diffusion region must be tokenized before encoding"149        )150        self.encoded_prompt = text_encoder(self.tokenized_prompt.input_ids.to(device))[0]151 152 153@dataclass154class Image2ImageRegion(DiffusionRegion):155    """Class defining a region where an image guided diffusion process is acting"""156 157    reference_image: torch.Tensor = None158    strength: float = 0.8  # Strength of the image159 160    def __post_init__(self):161        super().__post_init__()162        if self.reference_image is None:163            raise ValueError("Must provide a reference image when creating an Image2ImageRegion")164        if self.strength < 0 or self.strength > 1:165            raise ValueError(f"The value of strength should in [0.0, 1.0] but is {self.strength}")166        # Rescale image to region shape167        self.reference_image = resize(self.reference_image, size=[self.height, self.width])168 169    def encode_reference_image(self, encoder, device, generator, cpu_vae=False):170        """Encodes the reference image for this Image2Image region into the latent space"""171        # Place encoder in CPU or not following the parameter cpu_vae172        if cpu_vae:173            # Note here we use mean instead of sample, to avoid moving also generator to CPU, which is troublesome174            self.reference_latents = encoder.cpu().encode(self.reference_image).latent_dist.mean.to(device)175        else:176            self.reference_latents = encoder.encode(self.reference_image.to(device)).latent_dist.sample(177                generator=generator178            )179        self.reference_latents = 0.18215 * self.reference_latents180 181    @property182    def __dict__(self):183        # This class requires special casting to dict because of the reference_image tensor. Otherwise it cannot be casted to JSON184 185        # Get all basic fields from parent class186        super_fields = {key: getattr(self, key) for key in DiffusionRegion.__dataclass_fields__.keys()}187        # Pack other fields188        return {**super_fields, "reference_image": self.reference_image.cpu().tolist(), "strength": self.strength}189 190 191class RerollModes(Enum):192    """Modes in which the reroll regions operate"""193 194    RESET = "reset"  # Completely reset the random noise in the region195    EPSILON = "epsilon"  # Alter slightly the latents in the region196 197 198@dataclass199class RerollRegion(CanvasRegion):200    """Class defining a rectangular canvas region in which initial latent noise will be rerolled"""201 202    reroll_mode: RerollModes = RerollModes.RESET.value203 204 205@dataclass206class MaskWeightsBuilder:207    """Auxiliary class to compute a tensor of weights for a given diffusion region"""208 209    latent_space_dim: int  # Size of the U-net latent space210    nbatch: int = 1  # Batch size in the U-net211 212    def compute_mask_weights(self, region: DiffusionRegion) -> torch.tensor:213        """Computes a tensor of weights for a given diffusion region"""214        MASK_BUILDERS = {215            MaskModes.CONSTANT.value: self._constant_weights,216            MaskModes.GAUSSIAN.value: self._gaussian_weights,217            MaskModes.QUARTIC.value: self._quartic_weights,218        }219        return MASK_BUILDERS[region.mask_type](region)220 221    def _constant_weights(self, region: DiffusionRegion) -> torch.tensor:222        """Computes a tensor of constant for a given diffusion region"""223        latent_width = region.latent_col_end - region.latent_col_init224        latent_height = region.latent_row_end - region.latent_row_init225        return torch.ones(self.nbatch, self.latent_space_dim, latent_height, latent_width) * region.mask_weight226 227    def _gaussian_weights(self, region: DiffusionRegion) -> torch.tensor:228        """Generates a gaussian mask of weights for tile contributions"""229        latent_width = region.latent_col_end - region.latent_col_init230        latent_height = region.latent_row_end - region.latent_row_init231 232        var = 0.01233        midpoint = (latent_width - 1) / 2  # -1 because index goes from 0 to latent_width - 1234        x_probs = [235            exp(-(x - midpoint) * (x - midpoint) / (latent_width * latent_width) / (2 * var)) / sqrt(2 * pi * var)236            for x in range(latent_width)237        ]238        midpoint = (latent_height - 1) / 2239        y_probs = [240            exp(-(y - midpoint) * (y - midpoint) / (latent_height * latent_height) / (2 * var)) / sqrt(2 * pi * var)241            for y in range(latent_height)242        ]243 244        weights = np.outer(y_probs, x_probs) * region.mask_weight245        return torch.tile(torch.tensor(weights), (self.nbatch, self.latent_space_dim, 1, 1))246 247    def _quartic_weights(self, region: DiffusionRegion) -> torch.tensor:248        """Generates a quartic mask of weights for tile contributions249 250        The quartic kernel has bounded support over the diffusion region, and a smooth decay to the region limits.251        """252        quartic_constant = 15.0 / 16.0253 254        support = (np.array(range(region.latent_col_init, region.latent_col_end)) - region.latent_col_init) / (255            region.latent_col_end - region.latent_col_init - 1256        ) * 1.99 - (1.99 / 2.0)257        x_probs = quartic_constant * np.square(1 - np.square(support))258        support = (np.array(range(region.latent_row_init, region.latent_row_end)) - region.latent_row_init) / (259            region.latent_row_end - region.latent_row_init - 1260        ) * 1.99 - (1.99 / 2.0)261        y_probs = quartic_constant * np.square(1 - np.square(support))262 263        weights = np.outer(y_probs, x_probs) * region.mask_weight264        return torch.tile(torch.tensor(weights), (self.nbatch, self.latent_space_dim, 1, 1))265 266 267class StableDiffusionCanvasPipeline(DiffusionPipeline, StableDiffusionMixin):268    """Stable Diffusion pipeline that mixes several diffusers in the same canvas"""269 270    def __init__(271        self,272        vae: AutoencoderKL,273        text_encoder: CLIPTextModel,274        tokenizer: CLIPTokenizer,275        unet: UNet2DConditionModel,276        scheduler: Union[DDIMScheduler, LMSDiscreteScheduler, PNDMScheduler],277        safety_checker: StableDiffusionSafetyChecker,278        feature_extractor: CLIPFeatureExtractor,279    ):280        super().__init__()281        self.register_modules(282            vae=vae,283            text_encoder=text_encoder,284            tokenizer=tokenizer,285            unet=unet,286            scheduler=scheduler,287            safety_checker=safety_checker,288            feature_extractor=feature_extractor,289        )290 291    def decode_latents(self, latents, cpu_vae=False):292        """Decodes a given array of latents into pixel space"""293        # scale and decode the image latents with vae294        if cpu_vae:295            lat = deepcopy(latents).cpu()296            vae = deepcopy(self.vae).cpu()297        else:298            lat = latents299            vae = self.vae300 301        lat = 1 / 0.18215 * lat302        image = vae.decode(lat).sample303 304        image = (image / 2 + 0.5).clamp(0, 1)305        image = image.cpu().permute(0, 2, 3, 1).numpy()306 307        return self.numpy_to_pil(image)308 309    def get_latest_timestep_img2img(self, num_inference_steps, strength):310        """Finds the latest timesteps where an img2img strength does not impose latents anymore"""311        # get the original timestep using init_timestep312        offset = self.scheduler.config.get("steps_offset", 0)313        init_timestep = int(num_inference_steps * (1 - strength)) + offset314        init_timestep = min(init_timestep, num_inference_steps)315 316        t_start = min(max(num_inference_steps - init_timestep + offset, 0), num_inference_steps - 1)317        latest_timestep = self.scheduler.timesteps[t_start]318 319        return latest_timestep320 321    @torch.no_grad()322    def __call__(323        self,324        canvas_height: int,325        canvas_width: int,326        regions: List[DiffusionRegion],327        num_inference_steps: Optional[int] = 50,328        seed: Optional[int] = 12345,329        reroll_regions: Optional[List[RerollRegion]] = None,330        cpu_vae: Optional[bool] = False,331        decode_steps: Optional[bool] = False,332    ):333        if reroll_regions is None:334            reroll_regions = []335        batch_size = 1336 337        if decode_steps:338            steps_images = []339 340        # Prepare scheduler341        self.scheduler.set_timesteps(num_inference_steps, device=self.device)342 343        # Split diffusion regions by their kind344        text2image_regions = [region for region in regions if isinstance(region, Text2ImageRegion)]345        image2image_regions = [region for region in regions if isinstance(region, Image2ImageRegion)]346 347        # Prepare text embeddings348        for region in text2image_regions:349            region.tokenize_prompt(self.tokenizer)350            region.encode_prompt(self.text_encoder, self.device)351 352        # Create original noisy latents using the timesteps353        latents_shape = (batch_size, self.unet.config.in_channels, canvas_height // 8, canvas_width // 8)354        generator = torch.Generator(self.device).manual_seed(seed)355        init_noise = torch.randn(latents_shape, generator=generator, device=self.device)356 357        # Reset latents in seed reroll regions, if requested358        for region in reroll_regions:359            if region.reroll_mode == RerollModes.RESET.value:360                region_shape = (361                    latents_shape[0],362                    latents_shape[1],363                    region.latent_row_end - region.latent_row_init,364                    region.latent_col_end - region.latent_col_init,365                )366                init_noise[367                    :,368                    :,369                    region.latent_row_init : region.latent_row_end,370                    region.latent_col_init : region.latent_col_end,371                ] = torch.randn(region_shape, generator=region.get_region_generator(self.device), device=self.device)372 373        # Apply epsilon noise to regions: first diffusion regions, then reroll regions374        all_eps_rerolls = regions + [r for r in reroll_regions if r.reroll_mode == RerollModes.EPSILON.value]375        for region in all_eps_rerolls:376            if region.noise_eps > 0:377                region_noise = init_noise[378                    :,379                    :,380                    region.latent_row_init : region.latent_row_end,381                    region.latent_col_init : region.latent_col_end,382                ]383                eps_noise = (384                    torch.randn(385                        region_noise.shape, generator=region.get_region_generator(self.device), device=self.device386                    )387                    * region.noise_eps388                )389                init_noise[390                    :,391                    :,392                    region.latent_row_init : region.latent_row_end,393                    region.latent_col_init : region.latent_col_end,394                ] += eps_noise395 396        # scale the initial noise by the standard deviation required by the scheduler397        latents = init_noise * self.scheduler.init_noise_sigma398 399        # Get unconditional embeddings for classifier free guidance in text2image regions400        for region in text2image_regions:401            max_length = region.tokenized_prompt.input_ids.shape[-1]402            uncond_input = self.tokenizer(403                [""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"404            )405            uncond_embeddings = self.text_encoder(uncond_input.input_ids.to(self.device))[0]406 407            # For classifier free guidance, we need to do two forward passes.408            # Here we concatenate the unconditional and text embeddings into a single batch409            # to avoid doing two forward passes410            region.encoded_prompt = torch.cat([uncond_embeddings, region.encoded_prompt])411 412        # Prepare image latents413        for region in image2image_regions:414            region.encode_reference_image(self.vae, device=self.device, generator=generator)415 416        # Prepare mask of weights for each region417        mask_builder = MaskWeightsBuilder(latent_space_dim=self.unet.config.in_channels, nbatch=batch_size)418        mask_weights = [mask_builder.compute_mask_weights(region).to(self.device) for region in text2image_regions]419 420        # Diffusion timesteps421        for i, t in tqdm(enumerate(self.scheduler.timesteps)):422            # Diffuse each region423            noise_preds_regions = []424 425            # text2image regions426            for region in text2image_regions:427                region_latents = latents[428                    :,429                    :,430                    region.latent_row_init : region.latent_row_end,431                    region.latent_col_init : region.latent_col_end,432                ]433                # expand the latents if we are doing classifier free guidance434                latent_model_input = torch.cat([region_latents] * 2)435                # scale model input following scheduler rules436                latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)437                # predict the noise residual438                noise_pred = self.unet(latent_model_input, t, encoder_hidden_states=region.encoded_prompt)["sample"]439                # perform guidance440                noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)441                noise_pred_region = noise_pred_uncond + region.guidance_scale * (noise_pred_text - noise_pred_uncond)442                noise_preds_regions.append(noise_pred_region)443 444            # Merge noise predictions for all tiles445            noise_pred = torch.zeros(latents.shape, device=self.device)446            contributors = torch.zeros(latents.shape, device=self.device)447            # Add each tile contribution to overall latents448            for region, noise_pred_region, mask_weights_region in zip(449                text2image_regions, noise_preds_regions, mask_weights450            ):451                noise_pred[452                    :,453                    :,454                    region.latent_row_init : region.latent_row_end,455                    region.latent_col_init : region.latent_col_end,456                ] += noise_pred_region * mask_weights_region457                contributors[458                    :,459                    :,460                    region.latent_row_init : region.latent_row_end,461                    region.latent_col_init : region.latent_col_end,462                ] += mask_weights_region463            # Average overlapping areas with more than 1 contributor464            noise_pred /= contributors465            noise_pred = torch.nan_to_num(466                noise_pred467            )  # Replace NaNs by zeros: NaN can appear if a position is not covered by any DiffusionRegion468 469            # compute the previous noisy sample x_t -> x_t-1470            latents = self.scheduler.step(noise_pred, t, latents).prev_sample471 472            # Image2Image regions: override latents generated by the scheduler473            for region in image2image_regions:474                influence_step = self.get_latest_timestep_img2img(num_inference_steps, region.strength)475                # Only override in the timesteps before the last influence step of the image (given by its strength)476                if t > influence_step:477                    timestep = t.repeat(batch_size)478                    region_init_noise = init_noise[479                        :,480                        :,481                        region.latent_row_init : region.latent_row_end,482                        region.latent_col_init : region.latent_col_end,483                    ]484                    region_latents = self.scheduler.add_noise(region.reference_latents, region_init_noise, timestep)485                    latents[486                        :,487                        :,488                        region.latent_row_init : region.latent_row_end,489                        region.latent_col_init : region.latent_col_end,490                    ] = region_latents491 492            if decode_steps:493                steps_images.append(self.decode_latents(latents, cpu_vae))494 495        # scale and decode the image latents with vae496        image = self.decode_latents(latents, cpu_vae)497 498        output = {"images": image}499        if decode_steps:500            output = {**output, "steps_images": steps_images}501        return output502