# perlin

Python API: `luxonis_train.loaders.perlin`

Perlin noise masks, and the blend that turns a texture into an anomaly.

[LuxonisLoaderPerlinNoise](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/luxonis_perlin_loader_torch.md)
calls
[apply_anomaly_to_img](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md),
which draws its mask with
[generate_perlin_noise](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md).
The other functions are the steps of the noise.

## Functions

### apply_anomaly_to_img

```python
def apply_anomaly_to_img(img: Tensor, anomaly_img: Tensor, beta: float | None = None) -> tuple[Tensor, Tensor]:
```

Blend a texture into an image inside a random Perlin noise mask.

The function draws a mask M with
[generate_perlin_noise](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md).
Then it blends the image I and the texture A:

I’ = (1 − M)⊙I + (1 − β)M⊙A + β**M⊙I

The image does not change outside the mask. Inside the mask, `beta` is the weight of the image and `1 - beta` is the weight of the
texture. `H` and `W` must be multiples of `32`, see
[generate_perlin_noise](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md).

> **Example**
> With `beta=0.0`, the texture replaces the image inside the mask:

```pycon
>>> import torch
>>> _ = torch.manual_seed(0)
>>> img = torch.zeros(3, 64, 64)
>>> texture = torch.ones(3, 64, 64)
>>> augmented, mask = apply_anomaly_to_img(img, texture, beta=0.0)
>>> augmented.shape, mask.shape
(torch.Size([3, 64, 64]), torch.Size([64, 64]))
>>> torch.equal(augmented, mask.expand(3, -1, -1))
True
```

Parameters

 * `img` (`Tensor`): The clean image of shape `[C, H, W]`.
 * `anomaly_img` (`Tensor`): The texture image of shape `[C, H, W]`.
 * `beta` (`float | None`): The weight of the image inside the mask. `None` draws a value from `[0, 0.8)`.

Returns

 * `tuple[Tensor, Tensor]`: The image with the anomaly, of shape `[C, H, W]`, and the mask of shape `[H, W]`. The mask is `1.0`
   inside the anomaly and `0.0` outside.

### compute_gradients

```python
def compute_gradients(res: tuple[int, int]) -> Tensor:
```

Draw a random unit gradient at each point of the lattice.

The angles are uniform in [0, 2π). They use the global `torch` random state.

> **Example**
> ```pycon
>>> import torch
>>> gradients = compute_gradients((2, 3))
>>> gradients.shape
torch.Size([3, 4, 2])
>>> torch.allclose(gradients.norm(dim=-1), torch.ones(3, 4))
True
```

Parameters

 * `res` (`tuple[int, int]`): The number of lattice cells along each dimension.

Returns

 * `Tensor`: The gradients `(cos, sin)` of shape `[res[0] + 1, res[1] + 1, 2]`.

### dot

```python
def dot(grad: Tensor, shift: tuple[int, int], grid: Tensor, shape: tuple[int, int]) -> Tensor:
```

Compute the dot product of each pixel offset and its corner gradient.

The offset from a cell corner to a pixel is `grid + shift`. The function crops `grid` and `grad` to `shape` before the product.

> **Example**
> A pixel at `(0.25, 0.75)` in its cell, and the corner `(1, 0)` with the gradient `(1, 0)`:

```pycon
>>> import torch
>>> grad = torch.tensor([[[1.0, 0.0]]])
>>> grid = torch.tensor([[[0.25, 0.75]]])
>>> dot(grad, (-1, 0), grid, (1, 1)).tolist()
[[-0.75]]
```

Parameters

 * `grad` (`Tensor`): The gradient of the corner for each pixel, of shape `[H', W', 2]`, where `H' >= H` and `W' >= W`.
 * `shift` (`tuple[int, int]`): The negative position of the corner in the cell: `(0, 0)`, `(-1, 0)`, `(0, -1)`, or `(-1, -1)`.
 * `grid` (`Tensor`): The position of each pixel inside its cell, in `[0, 1)`, of shape `[H'', W'', 2]`, where `H'' >= H` and `W'' >= W`.
 * `shape` (`tuple[int, int]`): The output shape `(H, W)`.

Returns

 * `Tensor`: The dot products of shape `[H, W]`.

### fade_function

```python
def fade_function(t: Tensor) -> Tensor:
```

Apply the quintic fade curve of Perlin noise.

f(t) = 6t5 − 15t4 + 10t3

The curve maps `0` to `0` and `1` to `1`. Its first and second derivatives are zero at both ends. Thus the first and the second derivatives of the noise are continuous across the borders of the lattice cells.

> **Example**
> ```pycon
>>> import torch
>>> fade_function(torch.tensor([0.0, 0.25, 0.5, 1.0])).tolist()
[0.0, 0.103515625, 0.5, 1.0]
```

Parameters

 * `t` (`Tensor`): The positions inside a cell, in `[0, 1]`.

Returns

 * `Tensor`: The faded positions, with the shape of `t`.

### generate_perlin_noise

```python
def generate_perlin_noise(shape: tuple[int, int], min_perlin_scale: int = 0, perlin_scale: int = 6, threshold: float = 0.5) -> Tensor:
```

Generate a random binary mask from thresholded Perlin noise.

The function draws an exponent `k` for each dimension, uniform over the integers in `[min_perlin_scale, perlin_scale)`. The noise
from
[rand_perlin_2d](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md)
has `2 ** k` cells along that dimension. A pixel with noise above `threshold` gets `1.0`, and the other pixels get `0.0`. Then
[rotate_noise](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md)
rotates the mask by a random angle.

Each size in `shape` must be a multiple of `2 ** (perlin_scale - 1)`, which is `32` for the default. Otherwise some draws fail,
see
[rand_perlin_2d](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md).

> **Example**
> ```pycon
>>> import torch
>>> _ = torch.manual_seed(0)
>>> mask = generate_perlin_noise((64, 64))
>>> mask.shape, mask.dtype
(torch.Size([64, 64]), torch.float32)
>>> set(mask.unique().tolist()) <= {0.0, 1.0}
True
```

Parameters

 * `shape` (`tuple[int, int]`): The mask shape `(H, W)`.
 * `min_perlin_scale` (`int`): The smallest exponent.
 * `perlin_scale` (`int`): The upper bound of the exponent, exclusive. A larger exponent gives smaller noise blobs. A value that is not greater than `min_perlin_scale` makes `torch.randint` raise `RuntimeError`.
 * `threshold` (`float`): The noise value that a pixel must exceed to get `1.0`. The noise is in `[-1, 1]`, so a higher threshold gives a smaller mask area.

Returns

 * `Tensor`: The `torch.float32` mask of shape `[H, W]`, with the values `0.0` and `1.0`.

### lerp_torch

```python
def lerp_torch(x: Tensor, y: Tensor, w: Tensor) -> Tensor:
```

Interpolate linearly from `x` to `y` with the weight `w`.

lerp(x, y, w) = x + w(y − x)

TorchScript compiles the function.

Parameters

 * `x` (`Tensor`): The values at `w = 0`.
 * `y` (`Tensor`): The values at `w = 1`.
 * `w` (`Tensor`): The weights. They broadcast with `x` and `y`.

Returns

 * `Tensor`: The interpolated values, with the broadcast shape of `x`, `y`, and `w`.

### rand_perlin_2d

```python
def rand_perlin_2d(shape: tuple[int, int], res: tuple[int, int], fade: Callable[[Tensor], Tensor] = fade_function) -> Tensor:
```

Generate 2D Perlin noise with random gradients.

The function splits the output into `res[0]` by `res[1]` lattice cells. It draws a random gradient at each lattice point with [compute_gradients](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md). The value of a pixel blends the dot products of its four cell corners, with the weights from `fade`. The factor √(2) scales the values to `[-1, 1]`. The noise is `0` at each lattice point.

Each size in `shape` must be a multiple of the value in `res` for the same dimension. Otherwise a tensor operation raises `RuntimeError`. A size of `1` with more than one cell gives an empty tensor instead.

> **Example**
> ```pycon
>>> import torch
>>> _ = torch.manual_seed(0)
>>> noise = rand_perlin_2d((8, 8), (2, 2))
>>> noise.shape
torch.Size([8, 8])
>>> bool(noise.abs().max() <= 1)
True
>>> bool((noise[::4, ::4] == 0).all())
True
```

Parameters

 * `shape` (`tuple[int, int]`): The output shape `(H, W)`.
 * `res` (`tuple[int, int]`): The number of lattice cells along each dimension. More cells give smaller noise features.
 * `fade` (`Callable[[Tensor], Tensor]`): The interpolation curve. It gets the position of each pixel inside its cell.

Returns

 * `Tensor`: The noise of shape `[H, W]`, with values in `[-1, 1]`.

### rotate_noise

```python
def rotate_noise(noise: Tensor) -> Tensor:
```

Rotate a 2D tensor by a random angle around its center.

The angle is uniform in [0, 2π) and uses the global `torch` random state. The center is `(H // 2, W // 2)`. Each output pixel
takes the value of the input pixel at the rotated position, with the coordinates rounded down. A position outside the input moves
to the nearest border. TorchScript compiles the function.

Parameters

 * `noise` (`Tensor`): The tensor of shape `[H, W]`.

Returns

 * `Tensor`: The rotated tensor of shape `[H, W]`. It holds only values from `noise`.

### tile_grads

```python
def tile_grads(slice1: tuple[int, int | None], slice2: tuple[int, int | None], gradients: Tensor, d: tuple[int, int]) -> Tensor:
```

Repeat one corner of the gradient lattice over the pixels of each cell.

The function takes the slice `gradients[slice1[0]:slice1[1], slice2[0]:slice2[1]]`. Then it repeats each row `d[0]` times and each
column `d[1]` times.
[rand_perlin_2d](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md)
calls it once for each of the four cell corners. The slice `(0, -1)` selects the first corner along a dimension, and `(1, None)`
selects the second corner.

> **Example**
> A 2D tensor shows the pattern of the indices:

```pycon
>>> import torch
>>> gradients = torch.arange(9).reshape(3, 3)
>>> tile_grads((0, -1), (1, None), gradients, (2, 1)).tolist()
[[1, 2], [1, 2], [4, 5], [4, 5]]
```

Parameters

 * `slice1` (`tuple[int, int | None]`): The start and the stop of the slice along dimension `0`.
 * `slice2` (`tuple[int, int | None]`): The start and the stop of the slice along dimension `1`.
 * `gradients` (`Tensor`): The lattice gradients.
   [rand_perlin_2d](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md)
   gives the shape `[res[0] + 1, res[1] + 1, 2]`.
 * `d` (`tuple[int, int]`): The number of pixels in a cell along each dimension.

Returns

 * `Tensor`: The repeated gradients. For the slices of
   [rand_perlin_2d](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/loaders/perlin.md),
   the shape is `[res[0] * d[0], res[1] * d[1], 2]`.
