# Copyright (c) 2024 Jaerin Lee # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import copy from typing import Optional, Union from PIL import Image import torch class BackgroundObject: def __init__( self, image: Optional[Image.Image] = None, prompt: Optional[str] = None, negative_prompt: Optional[str] = None, ) -> None: self.image = image self.prompt = prompt self.negative_prompt = negative_prompt @property def is_empty(self) -> bool: return ( self.image is None and self.prompt is None and self.negative_prompt is None ) def extra_repr(self) -> str: return '' def __repr__(self) -> str: strings = [] if self.image is not None: if isinstance(self.image, Image.Image): image_str = f'Image(size={str(self.image.size)})' else: image_str = f'Tensor(shape={str(self.image.shape)})' strings.append(f'image={image_str}') if self.prompt is not None: strings.append(f'prompt="{self.prompt}"') if self.negative_prompt is not None: strings.append(f'negative_prompt="{self.negative_prompt}"') extra_repr = self.extra_repr() if extra_repr != '': strings.append(extra_repr) return f'{type(self).__name__}({", ".join(strings)})' class LayerObject: def __init__( self, idx: Optional[int] = None, prompt: Optional[str] = None, negative_prompt: Optional[str] = None, suffix: Optional[str] = None, prompt_strength: Optional[float] = None, mask: Optional[Union[torch.Tensor, Image.Image]] = None, mask_std: Optional[float] = None, mask_strength: Optional[float] = None, ) -> None: self.idx = idx self.prompt = prompt self.negative_prompt = negative_prompt self.suffix = suffix self.prompt_strength = prompt_strength self.mask = mask self.mask_std = mask_std self.mask_strength = mask_strength @property def is_empty(self) -> bool: return ( self.prompt is None and self.negative_prompt is None and self.prompt_strength is None and self.mask is None and self.mask_strength is None and self.mask_std is None ) def merge(self, other: 'LayerObject') -> bool: # Overriden or not. if self.idx != other.idx: # Merge only the modification requests for the same layer. return False if self.prompt is None and other.prompt is not None: self.prompt = copy.deepcopy(other.prompt) if self.negative_prompt is None and other.negative_prompt is not None: self.negative_prompt = copy.deepcopy(other.negative_prompt) if self.suffix is None and other.suffix is not None: self.suffix = copy.deepcopy(other.suffix) if self.prompt_strength is None and other.prompt_strength is not None: self.prompt_strength = copy.deepcopy(other.prompt_strength) if self.mask is None and other.mask is not None: self.mask = copy.deepcopy(other.mask) if self.mask_strength is None and other.mask_strength is not None: self.mask_strength = copy.deepcopy(other.mask_strength) if self.mask_std is None and other.mask_std is not None: self.mask_std = copy.deepcopy(other.mask_std) return True def extra_repr(self) -> str: return '' def __repr__(self) -> str: strings = [] if self.idx is not None: strings.append(f'idx={self.idx}') if self.prompt is not None: strings.append(f'prompt="{self.prompt}"') if self.negative_prompt is not None: strings.append(f'negative_prompt="{self.negative_prompt}"') if self.suffix is not None: strings.append(f'suffix="{self.suffix}"') if self.prompt_strength is not None: strings.append(f'prompt_strength={self.prompt_strength}') if self.mask is not None: if isinstance(self.mask, Image.Image): mask_str = f'Image(size={str(self.mask.size)})' else: mask_str = f'Tensor(shape={str(self.mask.shape)})' strings.append(f'mask={mask_str}') if self.mask_std is not None: strings.append(f'mask_std={self.mask_std}') if self.mask_strength is not None: strings.append(f'mask_strength={self.mask_strength}') extra_repr = self.extra_repr() if extra_repr != '': strings.append(extra_repr) return f'{type(self).__name__}({", ".join(strings)})' class BackgroundState(BackgroundObject): def __init__( self, image: Optional[Image.Image] = None, prompt: Optional[str] = None, negative_prompt: Optional[str] = None, latent: Optional[torch.Tensor] = None, embed: Optional[torch.Tensor] = None, ) -> None: super().__init__(image, prompt, negative_prompt) self.latent = latent self.embed = embed @property def is_incomplete(self) -> bool: return ( self.image is None or self.prompt is None or self.negative_prompt is None or self.latent is None or self.embed is None ) def extra_repr(self) -> str: strings = [] if self.latent is not None: strings.append(f'latent=Tensor(shape={str(self.latent.shape)})') if self.embed is not None: strings.append(f'embed=Tuple[Tensor(shape={str(self.embed[0].shape)})]') return ', '.join(strings) # TODO # class LayerState: # def __init__( # self, # prompst: List[str] = [], # negative_prompts: List[str] = [], # suffix: List[str] = [], # masks: Optional[torch.Tensor] = None, # mask_std: Optional[torch.Tensor] = None, # mask_strength: Optional[torch.Tensor] = None, # original_masks: Optional[Union[torch.Tensor, List[Image.Image]]] = None, # ) -> None: # self.prompts = prompts # self.negative_prompts = negative_prompts # self.suffix = suffix # self.masks = masks # self.mask_std = mask_std # self.mask_strength = mask_strength # self.original_masks = original_masks # def __len__(self) -> int: # self.check_integrity(True) # return len(self.prompts) # @property # def is_empty(self) -> bool: # self.check_integrity(True) # return len(self.prompt) == 0 # def check_integrity(self, throw_error: bool = True) -> bool: # p = len(self.prompts) # flag = ( # p != len(self.negative_prompts) or # p != len(self.suffix) or # p != len(self.masks) or # p != len(self.mask_std) or # p != len(self.mask_strength) or # p != len(self.original_masks) # ) # if flag and throw_error: # print( # f'LayerState(\n\tlen(prompts): {p},\n\tlen(negative_prompts): {len(self.negative_prompts)},\n\t' # f'len(suffix): {len(self.suffix)},\n\tlen(masks): {len(self.masks)},\n\t' # f'len(mask_std): {len(self.mask_std)},\n\tlen(mask_strength): {len(self.mask_strength)},\n\t' # f'len(original_masks): {len(self.original_masks)}\n)' # ) # raise ValueError('LayerState is corrupted!') # return not flag # def extra_repr(self) -> str: # strings = [] # if self.idx is not None: # strings.append(f'idx={self.idx}') # if self.prompt is not None: # strings.append(f'prompt="{self.prompt}"') # if self.negative_prompt is not None: # strings.append(f'negative_prompt="{self.negative_prompt}"') # if self.suffix is not None: # strings.append(f'suffix="{self.suffix}"') # if self.mask is not None: # if isinstance(self.mask, Image.Image): # mask_str = f'PIL.Image.Image(size={str(self.mask.size)})' # else: # mask_str = f'torch.Tensor(shape={str(self.mask.shape)})' # strings.append(f'mask={mask_str}') # if self.mask_std is not None: # strings.append(f'mask_std={self.mask_std}') # if self.mask_strength is not None: # strings.append(f'mask_strength={self.mask_strength}') # return f'{type(self).__name__}({", ".join(strings)})'