# blocks

Python API: `luxonis_train.nodes.backbones.pplcnet_v3.blocks`

The blocks of the PPLCNetV3 backbone.

## Classes

### AffineActivation

`Hardswish` followed by a learnable affine map.

The output is `scale * hardswish(x) + bias`. An
[AffineBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md)
holds `scale` and `bias`. They start at `1.0` and `0.0`, so the module starts as a plain `torch.nn.Hardswish`.

> **Example**
> ```pycon
>>> import torch
>>> act = AffineActivation()
>>> act(torch.tensor([-4.0, 0.0, 4.0])).tolist()
[0.0, 0.0, 4.0]
```

#### Methods

##### init

```python
def __init__(self):
```

##### forward

```python
def forward(x: Tensor) -> Tensor:
```

Apply `Hardswish`, then the affine map.

Parameters

 * `x` (`Tensor`): A tensor of any shape.

Returns

 * `Tensor`: `scale * hardswish(x) + bias`, of the same shape as `x`.

#### Attributes

##### activation

##### affine

### AffineBlock

Learnable affine map `scale * x + bias` with scalar parameters.

`scale` and `bias` are `torch.nn.Parameter` objects of shape `[1]`. The same two values apply to all elements of the input.

> **Example**
> ```pycon
>>> import torch
>>> block = AffineBlock(scale_value=2.0, bias_value=1.0)
>>> block(torch.tensor([1.0, 2.0])).tolist()
[3.0, 5.0]
```

#### Methods

##### init

```python
def __init__(scale_value: float = 1.0, bias_value: float = 0.0):
```

Initialize the scale and the bias parameters.

Parameters

 * `scale_value` (`float`): The initial value of `scale`. Give a `float`. An `int` passes the type check, but it makes an integer
   tensor. Then the constructor raises `RuntimeError`.
 * `bias_value` (`float`): The initial value of `bias`. The same rule applies.

##### forward

```python
def forward(x: Tensor) -> Tensor:
```

Apply the affine map.

Parameters

 * `x` (`Tensor`): A tensor of any shape.

Returns

 * `Tensor`: `scale * x + bias`, of the same shape as `x`.

#### Attributes

##### bias

##### scale

### LCNetV3Block

Depthwise separable block of PPLCNetV3.

The block runs these layers in order:

 * A k×k depthwise
   [GeneralReparameterizableBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/blocks/blocks.md).
   It has `n_branches` dense branches, a 1×1 scale branch, and an identity branch when `stride` is `1`.
 * An optional
   [SqueezeExciteBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/blocks/blocks.md)
   with `in_channels // 4` hidden channels, a `ReLU`, and a hard sigmoid gate.
 * A 1×1 pointwise
   [GeneralReparameterizableBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/blocks/blocks.md).
   It has `n_branches` dense branches, no scale branch, and an identity branch when `in_channels` is equal to `out_channels`.

Each of the two convolutions applies an
[AffineBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md)
to the sum of its branches, then an
[AffineActivation](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md).
With a `stride` of `2`, the depthwise convolution has no activation.

> **Example**
> ```pycon
>>> import torch
>>> block = LCNetV3Block(8, 16, kernel_size=3, stride=2, use_se=True)
>>> block(torch.zeros(1, 8, 32, 32)).shape
torch.Size([1, 16, 16, 16])
```

#### Methods

##### init

```python
def __init__(in_channels: int, out_channels: int, kernel_size: int, stride: int, use_se: bool = False, n_branches: int = 4):
```

Initialize the two convolutions and the optional attention.

Parameters

 * `in_channels` (`int`): The number of input channels. The depthwise convolution keeps this number.
 * `out_channels` (`int`): The number of output channels of the pointwise convolution.
 * `kernel_size` (`int`): The kernel size of the depthwise convolution. The padding is `(kernel_size - 1) // 2`. Use an odd value.
 * `stride` (`int`): The stride of the depthwise convolution. `2` also removes the activation after it.
 * `use_se` (`bool`): Whether to add the [SqueezeExciteBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/blocks/blocks.md) between the two convolutions.
 * `n_branches` (`int`): The number of dense branches of each convolution.

##### forward

```python
def forward(x: Tensor) -> Tensor:
```

Apply the two convolutions and the optional attention.

Parameters

 * `x` (`Tensor`): The input of shape `[B, in_channels, H, W]`.

Returns

 * `Tensor`: The output of shape `[B, out_channels, ceil(H / stride), ceil(W / stride)]`, for an odd `kernel_size`.

#### Attributes

##### dw_conv

##### pw_conv

##### se

### LCNetV3Layer

Sequence of [LCNetV3Block](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md) blocks that forms one PPLCNetV3 layer.

The layer builds one block for each position of the four lists. [scale_up](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md) scales each value of `out_channels` by `scale`. Each block takes the output channels of the block before it. The `forward` of `torch.nn.Sequential` runs the blocks in order.

> **Example**
> ```pycon
>>> import torch
>>> layer = LCNetV3Layer(
...     16, [64, 64], [3, 3], [2, 1], [False, False], scale=0.95
... )
>>> layer.out_channels, len(layer)
(64, 2)
>>> layer(torch.zeros(1, 16, 8, 8)).shape
torch.Size([1, 64, 4, 4])
```

#### Methods

##### init

```python
def __init__(in_channels: int, out_channels: list[int], kernel_sizes: list[int], strides: list[int], use_se: list[bool], n_branches: int = 4, scale: float = 1.0):
```

Build one
[LCNetV3Block](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md)
for each position of the lists.

Parameters

 * `in_channels` (`int`): The number of input channels of the first block.
 * `out_channels` (`list[int]`): The output channels of each block, before
   [scale_up](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md)
   scales them.
 * `kernel_sizes` (`list[int]`): The depthwise kernel size of each block.
 * `strides` (`list[int]`): The depthwise stride of each block.
 * `use_se` (`list[bool]`): Whether each block has a
   [SqueezeExciteBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/blocks/blocks.md).
 * `n_branches` (`int`): The number of dense branches of each convolution of each block.
 * `scale` (`float`): The width multiplier that
   [scale_up](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md)
   applies to `out_channels`.

Raises

 * `ValueError`: When the four lists do not have the same length.

#### Attributes

##### out_channels

The number of output channels of the layer, `scale_up(out_channels[-1], scale)`.

### QuantizedAffineBlock

Quantized form of
[AffineBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md)
for AIMET.

The class exists only when `aimet_torch` is installed. The decorator `QuantizationMixin.implements` registers it for
[AffineBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md).
The block has one input quantizer and one output quantizer.

#### Methods

##### forward

```python
def forward(x: Tensor) -> Tensor:
```

Run
[AffineBlock](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md)
with quantized inputs and parameters.

The method quantizes `x` with the input quantizer. It runs
[AffineBlock.forward](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/nodes/backbones/pplcnet_v3/blocks.md)
with the quantized parameters in place. Then it quantizes the result with the output quantizer. It skips a quantizer that is
`None`.

Parameters

 * `x` (`Tensor`): A tensor of any shape.

Returns

 * `Tensor`: `scale * x + bias`, of the same shape as `x`.

#### Attributes

##### input_quantizers

##### output_quantizers

## Functions

### scale_up

```python
def scale_up(v: float, scale: float, divisor: int = 16, min_value: int | None = None) -> int:
```

Scale a channel count and round it to a multiple of `divisor`.

The function multiplies `v` by `scale`. It rounds the product to the nearest multiple of `divisor`, and a product halfway between
two multiples rounds up. The result is at least `min_value`. When the result is below 90% of the product, the function adds one
`divisor`.

> **Example**
> ```pycon
>>> scale_up(512, 0.95)
480
```

`10` rounds to `8`, which is below 90% of `10`. Thus the function adds `8`:

```pycon
>>> scale_up(10, 1.0, divisor=8)
16
```

Parameters

 * `v` (`float`): The channel count to scale.
 * `scale` (`float`): The multiplier.
 * `divisor` (`int`): The multiple to round to.
 * `min_value` (`int | None`): The smallest result. `None` selects `divisor`.

Returns

 * `int`: The scaled and rounded channel count.
