import torch import numpy as np import random import os from typing import Tuple, Optional def get_prompt_template(mode: str = 'default') -> Tuple[str, int, int]: ''' Generate a prompt template based on the specified mode. Args: mode (str, optional): The mode for selecting the prompt template. Default is 'default'. Returns: Tuple[str, int, int]: A tuple containing the generated prompt template, the position of the placeholder '{}', and the length of the prompt. Notes: If the mode is 'random', a random prompt template is chosen from a predefined list. ''' prompt_template = 'A photo of {}' if mode == 'random': prompt_templates = [ 'a photo of a {}', 'a photograph of a {}', 'an image of a {}', '{}', 'a cropped photo of a {}', 'a good photo of a {}', 'a photo of one {}', 'a bad photo of a {}', 'a photo of the {}', 'a photo of {}', 'a blurry photo of a {}', 'a picture of a {}', 'a photo of a scene where {}' ] prompt_template = random.choice(prompt_templates) # Calculate prompt length and text position prompt_length = 1 + len(prompt_template.split(' ')) + 1 - 1 # eos, sos => 1 + 1, {} => -1 text_pos_at_prompt = 1 + prompt_template.split(' ').index('{}') return prompt_template, text_pos_at_prompt, prompt_length # Reproducibility def fix_seed(seed: int = 0) -> None: ''' Set seeds for random number generators to ensure reproducibility. Args: seed (int, optional): The seed value. Default is 0. ''' np.random.seed(seed) random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) # multi-GPU torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False os.environ['PYTHONHASHSEED'] = str(seed) def seed_worker(worker_id: int) -> None: ''' Set a seed for a worker process to ensure reproducibility in PyTorch DataLoader. Args: worker_id (int): The ID of the worker process. ''' worker_seed = torch.initial_seed() % 2**32 np.random.seed(worker_seed) random.seed(worker_seed) ''' Backported from TorchAudio (torchaudio.functional.add_noise) Source: https://github.com/pytorch/audio/blob/e284e58c83f69c95a7f4a8a7d402f6c27ef56f5d/src/torchaudio/functional/functional.py#L2317 Copyright (c) 2017 Facebook Inc. (Soumith Chintala) Licensed under the BSD 2-Clause License. Reason: Version compatibility for torchaudio==0.13.0 ''' def add_noise( waveform: torch.Tensor, noise: torch.Tensor, snr: torch.Tensor, lengths: Optional[torch.Tensor] = None ) -> torch.Tensor: r'''Scales and adds noise to waveform per signal-to-noise ratio. Specifically, for each pair of waveform vector :math:`x \in \mathbb{R}^L` and noise vector :math:`n \in \mathbb{R}^L`, the function computes output :math:`y` as .. math:: y = x + a n \, \text{,} where .. math:: a = \sqrt{ \frac{ ||x||_{2}^{2} }{ ||n||_{2}^{2} } \cdot 10^{-\frac{\text{SNR}}{10}} } \, \text{,} with :math:`\text{SNR}` being the desired signal-to-noise ratio between :math:`x` and :math:`n`, in dB. Note that this function broadcasts singleton leading dimensions in its inputs in a manner that is consistent with the above formulae and PyTorch's broadcasting semantics. .. devices:: CPU CUDA .. properties:: Autograd TorchScript Args: waveform (torch.Tensor): Input waveform, with shape `(..., L)`. noise (torch.Tensor): Noise, with shape `(..., L)` (same shape as ``waveform``). snr (torch.Tensor): Signal-to-noise ratios in dB, with shape `(...,)`. lengths (torch.Tensor or None, optional): Valid lengths of signals in ``waveform`` and ``noise``, with shape `(...,)` (leading dimensions must match those of ``waveform``). If ``None``, all elements in ``waveform`` and ``noise`` are treated as valid. (Default: ``None``) Returns: torch.Tensor: Result of scaling and adding ``noise`` to ``waveform``, with shape `(..., L)` (same shape as ``waveform``). ''' if not (waveform.ndim - 1 == noise.ndim - 1 == snr.ndim and (lengths is None or lengths.ndim == snr.ndim)): raise ValueError("Input leading dimensions don't match.") L = waveform.size(-1) if L != noise.size(-1): raise ValueError(f"Length dimensions of waveform and noise don't match (got {L} and {noise.size(-1)}).") # compute scale if lengths is not None: mask = torch.arange(0, L, device=lengths.device).expand(waveform.shape) < lengths.unsqueeze( -1 ) # (*, L) < (*, 1) = (*, L) masked_waveform = waveform * mask masked_noise = noise * mask else: masked_waveform = waveform masked_noise = noise energy_signal = torch.linalg.vector_norm(masked_waveform, ord=2, dim=-1) ** 2 # (*,) energy_noise = torch.linalg.vector_norm(masked_noise, ord=2, dim=-1) ** 2 # (*,) original_snr_db = 10 * (torch.log10(energy_signal) - torch.log10(energy_noise)) scale = 10 ** ((original_snr_db - snr) / 20.0) # (*,) # scale noise scaled_noise = scale.unsqueeze(-1) * noise # (*, 1) * (*, L) = (*, L) return waveform + scaled_noise # (*, L) ''' Modified/Backported from TorchAudio (torchaudio.transforms) Source: https://github.com/pytorch/audio/blob/e284e58c83f69c95a7f4a8a7d402f6c27ef56f5d/src/torchaudio/transforms/_transforms.py#L2058 Copyright (c) 2017 Facebook Inc. (Soumith Chintala) Licensed under the BSD 2-Clause License. Reason: Version compatibility for torchaudio==0.13.0 ''' class AddRandomNoise(torch.nn.Module): r'''Scales and adds noise to waveform per signal-to-noise ratio. See :meth:`torchaudio.functional.add_noise` for more details. .. devices:: CPU CUDA .. properties:: Autograd TorchScript ''' def __init__(self, lengths: Optional[torch.Tensor] = None, snr: float = None): r''' Args: snr (torch.Tensor): Signal-to-noise ratios in dB, with shape `(...,)`. lengths (torch.Tensor or None, optional): Valid lengths of signals in ``waveform`` and ``noise``, ''' super().__init__() if snr != None: self.snr = torch.Tensor([snr]) else: self.snr = torch.Tensor([1000.0]) # high value --> no noise initially self.epoch = 0 self.lengths = lengths def forward(self, waveform: torch.Tensor) -> torch.Tensor: r''' Args: waveform (torch.Tensor): Input waveform, with shape `(..., L)`. with shape `(...,)` (leading dimensions must match those of ``waveform``). If ``None``, all elements in ``waveform`` and ``noise`` are treated as valid. (Default: ``None``) Returns: torch.Tensor: Result of scaling and adding ``noise`` to ``waveform``, with shape `(..., L)` (same shape as ``waveform``). ''' # slightly changed waveform = waveform.unsqueeze(0) noise = torch.clip(torch.randn(waveform.shape), min=-1., max=1.) noisy_waveform = add_noise(waveform, noise, self.snr, self.lengths) return noisy_waveform.squeeze(0) def step(self, epoch, k=5): self.epoch = epoch self.snr = torch.Tensor([np.exp(-epoch/k + k) + k]) # negative exponential profile ''' Useful implementation for randomly applying transforms :) Source: https://github.com/Spijkervet/torchaudio-augmentations/blob/891b3b6e19551c211e7cdab36376c7e67e9d199c/torchaudio_augmentations/apply.py#L34 Copyright (c) 2021 Janne Spijkervet Licensed under the MIT License (to my best knowledge) ''' class RandomApply(torch.nn.Module): '''Apply randomly a list of transformations with a given probability. .. note:: In order to script the transformation, please use ``torch.nn.ModuleList`` as input instead of list/tuple of transforms as shown below: >>> transforms = transforms.RandomApply(torch.nn.ModuleList([ >>> transforms.ColorJitter(), >>> ]), p=0.3) >>> scripted_transforms = torch.jit.script(transforms) Make sure to use only scriptable transformations, i.e. that work with ``torch.Tensor``, does not require `lambda` functions or ``PIL.Image``. Args: transforms (list or tuple or torch.nn.Module): list of transformations p (float): probability ''' def __init__(self, transforms, p=0.5): super().__init__() self.transforms = transforms self.p = p def forward(self, tensor, force=False): if not force: if self.p < torch.rand(1): return tensor for t in self.transforms: tensor = t(tensor) return tensor def step(self, idx, *args): if self.transforms: if len(self.transforms) > idx: self.transforms[idx].step(*args) def __repr__(self): format_string = self.__class__.__name__ + "(" format_string += "\n p={}".format(self.p) for t in self.transforms: format_string += "\n" format_string += " {0}".format(t) format_string += "\n)" return format_string ''' If applying more audio transforms with different probability each will need also this https://github.com/Spijkervet/torchaudio-augmentations/blob/891b3b6e19551c211e7cdab36376c7e67e9d199c/torchaudio_augmentations/compose.py#L4 ''' def remove_diagonal(x: torch.Tensor) -> torch.Tensor: B, _, H, W = x.shape # For each row i, collect column indices [0..i-1, i+1..B-1] idx = torch.arange(B, device=x.device) # Build [B, B-1] index matrix of column indices to keep col_idx = torch.stack([ torch.cat([idx[:i], idx[i+1:]]) for i in range(B) ]) # [B, B-1] # Expand for gathering over H and W col_idx = col_idx[:, :, None, None].expand(B, B - 1, H, W) # [B, B-1, H, W] return x.gather(1, col_idx) def get_key(dct, val): list_keys = list(dct.keys()) list_classes = [] for key in list_keys: values = dct[key] if val in values: list_classes.append(key) return set(list_classes)