# utils

Python API: `luxonis_train.lightning.utils`

The helpers behind the Lightning module.

[Nodes](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md)
builds the node graph from a config and wraps each node in a
[NodeWrapper](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md)
together with its losses, metrics, and visualizers.
[LossAccumulator](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md)
keeps the running mean of every loss over an epoch. The functions sum the losses, build the training strategy, flatten the metric
results, and log the metric artifacts and the visualizations to the tracker.

## Classes

### LossAccumulator

A running mean of every loss value over one epoch.

The Lightning module keeps one accumulator for each of the `"train"`, `"val"`, and `"test"` stages. It calls
[update](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md)
after every step and
[clear](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md)
at the end of the epoch. A key that
[update](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md)
never received reads as `0.0`.

> **Example**
> ```pycon
>>> import torch
>>> losses = LossAccumulator()
>>> losses.update({"loss": torch.tensor(2.0)})
>>> losses.update({"loss": torch.tensor(4.0)})
>>> losses["loss"]
3.0
>>> losses.clear()
>>> dict(losses)
{}
```

#### Methods

##### init

```python
def __init__(*args, **kwargs):
```

##### clear

```python
def clear(self):
```

Drop every stored mean and every count.

##### update

```python
def update(losses: dict[str, Tensor]):
```

Fold one more value of each loss into its running mean.

This replaces `dict.update`. A value does not overwrite the stored one. It moves the mean.

Parameters

 * `losses` (`dict[str, Tensor]`): Loss names mapped to one-element tensors, as in the second value that [compute_losses](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md) returns.

### MainMetric

The metric that selects the best checkpoint.

#### Attributes

##### metric_name

The identifier of the metric. A metric whose class name contains `"ConfusionMatrix"` gets the identifier `"mcc"`.

##### node_name

The identifier of the node that holds the metric. It is the alias of the node when the config sets one, and the class name otherwise.

### Nodes

The node graph of a model, built from a config.

A `torch.nn.ModuleDict` that maps each node identifier to its [NodeWrapper](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md). The constructor builds the nodes in topological order. It runs each node on zero tensors, so the nodes that follow know the shapes of their inputs.

#### Methods

##### init

```python
def __init__(cfg: Config, dataset_metadata: DatasetMetadata, input_shapes: dict[str, Size]):
```

Build every node of the config and wrap it.

Nodes are built in topological order and run once on zero tensors to infer the shapes consumed by later nodes. The same pass builds each node's losses, metrics, and visualizers.

Parameters

 * `cfg` (`Config`): The config. `model.nodes` lists the nodes, `loader.image_source` names the image input, and `trainer.epochs` resolves a fractional or a missing `freezing.unfreeze_after`.
 * `dataset_metadata` (`DatasetMetadata`): The metadata of the dataset. It fixes the task name of a node that sets none, and it validates the metadata label types. It also reaches every node constructor.
 * `input_shapes` (`dict[str, Size]`): Each loader input name mapped to its shape, without the batch dimension.

Raises

 * `RuntimeError`: When a node sets no `task_name` and the dataset holds no task. Also when a node lists an input that no node produces, or when the graph has a cycle.
 * `ValueError`: When a head sets no `task_name` and the dataset holds more than one task. Also when an `input_sources` entry is not a loader input. Also when `metadata_task_override` is a string but the task does not require exactly one metadata label. Also when a metadata label has a type the task does not accept. That check reads `task_name` from the config entry, so it runs only for a node that sets one.

##### build_callbacks

```python
def build_callbacks(save_dir: Path) -> list[pl.Callback]:
```

Build the Lightning callbacks of a training run.

The list holds, in this order:

 * [TrainingManager](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/callbacks/training_manager.md), which applies the freeze schedule and calls the training strategy after each backward pass.
 * [LuxonisModelSummary](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/callbacks/luxonis_model_summary.md) with a depth of 2, as a rich table when `rich_logging` is true.
 * A `ModelCheckpoint` that monitors `val/loss`, keeps the lowest values, and writes to `save_dir / "min_val_loss"`.
 * [AIMETCallback](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/callbacks/aimet_callback.md), when `exporter.aimet.active` is set.
 * A `ModelCheckpoint` that monitors `val/metric/<node>/<metric>` of the main metric, when the config defines one. `<node>` is the log name from [formatted_name](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md). The checkpoint keeps the highest values and writes to `save_dir / "best_val_metric"`.
 * Every active callback of `trainer.callbacks`, built from the [CALLBACKS](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/registry.md) registry. The function logs and skips an inactive one.
 * A `GradientAccumulationScheduler` for `trainer.accumulate_grad_batches`, when the config sets that value and the callbacks above hold no such scheduler. When they do, the function logs a warning and ignores the config value.

Both checkpoints keep `trainer.save_top_k` files.

Parameters

 * `save_dir` (`Path`): The directory that receives the checkpoint subdirectories.

Returns

 * `list[pl.Callback]`: The callbacks, in the order above.

##### formatted_name

```python
def formatted_name(node_name: str) -> str:
```

Return the log name of a node.

Parameters

 * `node_name` (`str`): The identifier of the node.

Returns

 * `str`: `"<task_name>-<node_name>"` when the node has a task name, and `node_name` alone otherwise.

##### traverse

```python
def traverse(self) -> Iterator[tuple[str, NodeWrapper, list[str], list[str]]]:
```

Walk the graph in topological order.

The walk yields a node only after every node that feeds it.

Yields

 * The node identifier, its wrapper, the identifiers of the nodes that feed it, and the identifiers of the nodes not yet yielded.

Raises

 * `RuntimeError`: When the walk makes no progress. A node then lists an input that no node of the graph produces, or the graph has a cycle.

#### Attributes

##### freeze_schedule

The freeze schedule of the nodes with `freezing.active`.

##### graph

Each node identifier mapped to the identifiers of the nodes that feed it.

##### loader_input_shapes

Each node identifier mapped to the loader inputs the node reads, as input name to shape without the batch dimension. A node fed only by other nodes maps to an empty dictionary.

##### main_metric

The metric that selects the best checkpoint, or `None` when the config has none.

##### main_metric_reference

The metric instance that `main_metric` names.

Raises

 * `RuntimeError`: When the config defines no main metric.

### NodeWrapper

A node of the graph with its attached modules and its schedule.

[Nodes](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md) creates one wrapper for each node in the config. The wrapper stores every constructor argument under the same name. `module` is a registered submodule. The losses, the metrics, and the visualizers live in plain dictionaries, so the recursive methods of `torch.nn.Module` do not reach them.

#### Methods

##### init

```python
def __init__(name: str, module: BaseNode, losses: dict[str, BaseLoss], metrics: dict[str, BaseMetric], visualizers: dict[str,
BaseVisualizer], unfreeze_after: int | None, lr_after_unfreeze: float | None, finetuning: list[FinetuningConfig], inputs:
list[str] | None = None):
```

Initialize the wrapper.

Parameters

 * `name` (`str`): The identifier of the node in the graph.
 * `module` (`BaseNode`): The node.
 * `losses` (`dict[str, BaseLoss]`): The losses attached to the node, keyed by their identifier.
 * `metrics` (`dict[str, BaseMetric]`): The metrics attached to the node, keyed by their identifier.
 * `visualizers` (`dict[str, BaseVisualizer]`): The visualizers attached to the node, keyed by their identifier.
 * `unfreeze_after` (`int | None`): The epoch at which the node starts to train. `None` when the node is not frozen.
 * `lr_after_unfreeze` (`float | None`): The base learning rate of the node from the unfreeze epoch on. `None` keeps the rate that the scheduler reached.
 * `finetuning` (`list[FinetuningConfig]`): The optimizer and scheduler overrides of the node.
 * `inputs` (`list[str] | None`): The names of the nodes and the loader sources that feed this node. `None` becomes an empty list.

##### train

```python
def train(mode: bool = True) -> NodeWrapper:
```

Set the training mode of the node and its attached modules.

`torch.nn.Module.train` reaches only the registered submodules, so this override also sets the mode of every loss, metric, and visualizer.

Parameters

 * `mode` (`bool`): `True` for training mode, `False` for evaluation mode.

Returns

 * `NodeWrapper`: This wrapper.

#### Attributes

##### finetuning

##### formatted_name

The name of the node in the logs and the checkpoint names.

It is `"<task_name>-<name>"` when the node has a task name, and `name` alone otherwise.

##### inputs

##### losses

##### lr_after_unfreeze

##### metrics

##### module

##### name

##### task_name

The task name of the wrapped node.

It comes from `task_name` in the node config, or from the dataset when the dataset holds one task. Otherwise it is an empty string.

##### unfreeze_after

##### visualizers

## Functions

### build_training_strategy

```python
def build_training_strategy(cfg: Config, pl_module: pl.LightningModule) -> BaseTrainingStrategy | None:
```

Build the training strategy the config names.

The strategy class comes from the [STRATEGIES](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/registry.md) registry, and `trainer.training_strategy.params` reaches its constructor. A strategy supplies the base optimizer and scheduler through [BaseTrainingStrategy.get_base_configs](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/strategies/base_strategy.md). The function therefore logs a warning when `trainer.optimizer` or `trainer.scheduler` differs from its default.

A class without a concrete `rules` method predates the rule-based API and is deprecated. The function logs a warning. It builds an unregistered subclass with stubs for the abstract `rules` and `get_base_configs`, so that it can instantiate the class. It then mounts the instance through [LegacyStrategyAdapter](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/strategies/legacy.md). The adapter contributes no rules. Its `get_base_configs` raises `NotImplementedError` when the legacy class defines none. The training plan then falls back to the optimizer and the scheduler of the config.

Parameters

 * `cfg` (`Config`): The config. `trainer.training_strategy` names the strategy and holds its parameters.
 * `pl_module` (`pl.LightningModule`): The Lightning module the strategy attaches to.

Returns

 * `BaseTrainingStrategy | None`: The strategy, or `None` when the config names none.

### check_tensor_device

```python
def check_tensor_device(x: Tensor | list[Tensor], device: torch.device) -> bool:
```

Check whether a tensor, or every tensor of a list, is on a device.

> **Example**
> ```pycon
>>> import torch
>>> cpu = torch.device("cpu")
>>> check_tensor_device(torch.zeros(1), cpu)
True
>>> check_tensor_device([torch.zeros(1), 1.0], cpu)
False
```

Parameters

 * `x` (`Tensor | list[Tensor]`): The tensor, or a list or tuple of tensors.
 * `device` (`torch.device`): The device to compare with.

Returns

 * `bool`: `True` when the tensor is on `device`, or when every item of the sequence is a tensor on `device`. An empty sequence
   gives `True`.

Raises

 * `TypeError`: When `x` is neither a tensor nor a list or tuple.

### compute_losses

```python
def compute_losses(cfg: Config, losses: dict[str, dict[str, Tensor | tuple[Tensor, dict[str, Tensor]]]], device: torch.device) -> tuple[Tensor, dict[str, Tensor]]:
```

Sum the losses of every node into one total.

The total is a plain sum.
[BaseLoss.run](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/attached_modules/losses/base_loss.md)
already multiplies each loss by its `weight` from the config. The sub-losses carry no weight.

> **Example**
> ```pycon
>>> import torch
>>> from luxonis_train.config import Config
>>> bce = (torch.tensor(1.5), {"pos": torch.tensor(0.5)})
>>> total, logged = compute_losses(
...     Config(rich_logging=False),
...     {"head": {"bce": bce}},
...     torch.device("cpu"),
... )
>>> total.tolist()
[1.5]
>>> sorted(logged)
['loss', 'loss/head/bce', 'loss/head/bce/pos']
```

Parameters

 * `cfg` (`Config`): The config. `trainer.log_sub_losses` decides whether the sub-losses reach the logged dictionary.
 * `losses` (`dict[str, dict[str, Tensor | tuple[Tensor, dict[str, Tensor]]]]`): The losses of one step. The first key is the node identifier and the second key is the loss identifier. A value is the loss tensor, or a tuple of the loss tensor and its sub-losses.
 * `device` (`torch.device`): The device of the total.

Returns

 * `tuple[Tensor, dict[str, Tensor]]`: The total and the losses for logging. The total is a tensor of shape `[1]` on `device` that keeps its gradient graph. The dictionary holds `"loss/<node>/<loss>"` for every loss, `"loss/<node>/<loss>/<sub-loss>"` for every sub-loss when `trainer.log_sub_losses` is set, and `"loss"` for the total. The function detaches every logged tensor and moves it to the CPU.

### compute_visualization_buffer

```python
def compute_visualization_buffer(seq_buffer: list[dict[str, dict[str, Tensor]]], visualizations: dict[str, dict[str, Tensor]],
logged_idxs: list[int], max_log_images: int) -> dict[str, dict[str, Tensor]] | None:
```

Collect the images of a batch that the balanced logger skipped.

The buffer is a list of batches. Its fill level is the batch dimension of the first buffered entry only, not the sum over all entries. When that level reaches `max_log_images`, the function collects nothing more. Otherwise the function takes, from every visualization, the samples whose indices are not in `logged_idxs`, up to `max_log_images` minus the fill level.

> **Example**
> ```pycon
>>> import torch
>>> batch = {"head": {"boxes": torch.zeros(4, 3, 8, 8)}}
>>> extra = compute_visualization_buffer([], batch, [0, 2], 3)
>>> extra["head"]["boxes"].shape
torch.Size([2, 3, 8, 8])
>>> compute_visualization_buffer([extra], batch, [0, 2], 2) is None
True
```

Parameters

 * `seq_buffer` (`list[dict[str, dict[str, Tensor]]]`): The batches buffered so far. Each entry has the structure of
   `visualizations`.
 * `visualizations` (`dict[str, dict[str, Tensor]]`): The node identifier mapped to the visualizer identifier mapped to a batch of
   images of shape `[B, C, H, W]`. Must not be empty.
 * `logged_idxs` (`list[int]`): The batch indices that the balanced logger already logged.
 * `max_log_images` (`int`): The number of images to log in one epoch.

Returns

 * `dict[str, dict[str, Tensor]] | None`: The skipped samples, with the structure of `visualizations`. `None` when the first
   buffered entry is full, or when the balanced logger took every sample of the batch.

### get_main_metric

```python
def get_main_metric(cfg: Config) -> MainMetric | None:
```

Find the metric the config marks with `is_main_metric`.

The function scans the nodes and their metrics in config order, and the first match wins. The config validation allows one main
metric at most, and marks the first metric when none is set.

> **Example**
> ```pycon
>>> from luxonis_train.config import Config
>>> node = {"name": "ResNet", "metrics": [{"name": "ConfusionMatrix"}]}
>>> cfg = Config(rich_logging=False, model={"nodes": [node]})
>>> get_main_metric(cfg)
MainMetric(node_name='ResNet', metric_name='mcc')
```

Parameters

 * `cfg` (`Config`): The config.

Returns

 * `MainMetric | None`: The node identifier and the metric identifier, or `None` when no metric sets `is_main_metric`. A metric whose class name contains `"ConfusionMatrix"` gets the identifier `"mcc"`, whatever its alias.

### get_model_execution_order

```python
def get_model_execution_order(model: lxt.LuxonisLightningModule) -> list[str]:
```

List the names of the leaf modules with parameters, in run order.

The function registers a forward hook on every module that has parameters and no child modules. It runs the model on zero tensors with a batch size of 2 under `torch.no_grad`, and removes the hooks. The checkpoint stores the list, and [LuxonisLightningModule.load_checkpoint](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/luxonis_lightning.md) uses it to map the weights of an older checkpoint onto a changed module layout.

The function leaves the model in evaluation mode.

Parameters

 * `model` (`lxt.LuxonisLightningModule`): The model. Its `nodes.loader_input_shapes` gives the input shapes, and its `device` places the inputs.

Returns

 * `list[str]`: The module names, as `named_modules` reports them, in execution order. A module that runs more than once appears once for each run.

### log_balanced_class_images

```python
def log_balanced_class_images(tracker: LuxonisTrackerPL, nodes: Nodes, visualizations: dict[str, dict[str, Tensor]], labels:
Labels, cls_task_keys: list[str], class_log_counts: list[int], n_logged_images: int, max_log_images: int, mode: Literal['test',
'val'], current_epoch: int) -> tuple[int, list[int], list[int]]:
```

Log the images of a batch that keep the logged classes balanced.

The function selects a sample when one of its classes has the lowest count in `class_log_counts` at that moment. It then adds one to the count of every class of the sample. It never selects a sample without a present class. Finally, it logs the selected samples of every visualization with [log_sequential_images](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md).

Parameters

 * `tracker` (`LuxonisTrackerPL`): The tracker that receives the images.
 * `nodes` (`Nodes`): The node graph, used for the log names of the nodes.
 * `visualizations` (`dict[str, dict[str, Tensor]]`): The node identifier mapped to the visualizer identifier mapped to a batch of images of shape `[B, C, H, W]`. Must not be empty, because the function reads the batch size from its first entry.
 * `labels` (`Labels`): The labels of the batch.
 * `cls_task_keys` (`list[str]`): The label keys that hold multi-label classification targets of shape `[B, n_classes]`. The function concatenates the tensors along the class dimension, in this order. A class is present when its value is above 0.
 * `class_log_counts` (`list[int]`): How many selected samples held each class in this epoch. Its length is the total number of classes. The function updates it in place.
 * `n_logged_images` (`int`): How many images each node logged in this epoch before this batch.
 * `max_log_images` (`int`): The maximum number of images each node logs in one epoch.
 * `mode` (`Literal['test', 'val']`): The evaluation stage.
 * `current_epoch` (`int`): The current epoch, used as the step.

Returns

 * `tuple[int, list[int], list[int]]`: The image counter of the last node after this batch, as [log_sequential_images](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md) returns it, `class_log_counts` itself, and the batch indices of the selected samples.

### log_metric_artifacts

```python
def log_metric_artifacts(tracker: LuxonisTrackerPL, metric: BaseMetric, computed: Any, *, mode: Literal['test', 'val'],
formatted_node_name: str, metric_name: str, current_epoch: int):
```

Render and log the image artifacts of one metric.

The Lightning module calls it at the end of an evaluation epoch, after [BaseMetric.compute](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/attached_modules/metrics/base_metric.md) and before the metric resets. The module skips the call on the other processes and during the sanity check. The function logs every failure and continues, so a metric that cannot produce or upload a figure does not stop the run. When [BaseMetric.get_artifacts](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/attached_modules/metrics/base_metric.md) raises or returns something other than a dictionary, the function logs no artifact.

An artifact must be a tensor of shape `[C, H, W]`. The function logs a warning for any other artifact and skips it. The image goes to the tracker as an `[H, W, C]` array under the name that [metric_artifact_image_name](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md) builds, with `current_epoch` as the step.

Parameters

 * `tracker` (`LuxonisTrackerPL`): The tracker that receives the images.
 * `metric` (`BaseMetric`): The metric that produces the artifacts.
 * `computed` (`Any`): The result of [BaseMetric.compute](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/attached_modules/metrics/base_metric.md). The function hands it to [BaseMetric.get_artifacts](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/attached_modules/metrics/base_metric.md).
 * `mode` (`Literal['test', 'val']`): The evaluation stage.
 * `formatted_node_name` (`str`): The log name of the node.
 * `metric_name` (`str`): The identifier of the metric.
 * `current_epoch` (`int`): The current epoch, used as the step.

### log_sequential_images

```python
def log_sequential_images(tracker: LuxonisTrackerPL, nodes: Nodes, visualizations: dict[str, dict[str, Tensor]], n_logged_images:
int, max_log_images: int, mode: Literal['test', 'val'], current_epoch: int) -> int:
```

Log the first images of every visualization, in batch order.

For each node, a counter starts at `n_logged_images` and stops at `max_log_images`. Every visualizer of the node shares the counter, and the counter names the image. Once the counter reaches `max_log_images`, the rest of the visualizers of the node log nothing. The image goes to the tracker under `"<mode>/visualizations/<node>/<visualizer>/<counter>"`, as an `[H, W, C]` array, with `current_epoch` as the step. `<node>` is the log name from [Nodes.formatted_name](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md).

Parameters

 * `tracker` (`LuxonisTrackerPL`): The tracker that receives the images.
 * `nodes` (`Nodes`): The node graph, used for the log names of the nodes.
 * `visualizations` (`dict[str, dict[str, Tensor]]`): The node identifier mapped to the visualizer identifier mapped to a batch of images of shape `[B, C, H, W]`. Must not be empty.
 * `n_logged_images` (`int`): The counter value each node starts at.
 * `max_log_images` (`int`): The counter value at which a node stops.
 * `mode` (`Literal['test', 'val']`): The evaluation stage.
 * `current_epoch` (`int`): The current epoch, used as the step.

Returns

 * `int`: The counter of the last node after this batch.

### metric_artifact_image_name

```python
def metric_artifact_image_name(mode: Literal['test', 'val'], formatted_node_name: str, metric_name: str, artifact_name: str) ->
str:
```

Build the tracker image name of a metric artifact.

The name is `"<mode>/metrics/<node>/<metric>/<artifact>"`. The epoch is not part of it. For MLflow, `log_image` inserts the step as a path segment before the last one, as it does for the visualization images. [mlflow_image_key](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md) shows the final MLflow path. TensorBoard and Weights and Biases receive the name as it is.

> **Example**
> ```pycon
>>> metric_artifact_image_name("val", "head", "pr_curve", "curve")
'val/metrics/head/pr_curve/curve'
```

Parameters

 * `mode` (`Literal['test', 'val']`): The evaluation stage.
 * `formatted_node_name` (`str`): The log name of the node, see
   [Nodes.formatted_name](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/utils.md).
 * `metric_name` (`str`): The identifier of the metric.
 * `artifact_name` (`str`): The name of the artifact, as
   [BaseMetric.get_artifacts](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/attached_modules/metrics/base_metric.md)
   keys it.

Returns

 * `str`: The image name.

### mlflow_image_key

```python
def mlflow_image_key(name: str, step: int) -> str:
```

Return the MLflow artifact path of a logged image.

`LuxonisTracker.log_image` splits the caption off the name at the last `/` and puts the step between the two parts. This function
builds the same path.
[LuxonisLightningModule.get_mlflow_logging_keys](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/lightning/luxonis_lightning.md)
uses it to list the expected artifacts of a run without a tracker.

> **Example**
> ```pycon
>>> mlflow_image_key("val/metrics/head/pr_curve/curve", 7)
'val/metrics/head/pr_curve/7/curve.png'
```

Parameters

 * `name` (`str`): The image name that `log_image` receives. It must hold at least one `/`.
 * `step` (`int`): The step of the image. The Lightning module passes the epoch.

Returns

 * `str`: `"<base path>/<step>/<caption>.png"`.

Raises

 * `ValueError`: When `name` holds no `/`.

### postprocess_metrics

```python
def postprocess_metrics(name: str, values: Any, log_sub_metrics: bool = True) -> dict[str, Tensor]:
```

Flatten the result of [BaseMetric.compute](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/attached_modules/metrics/base_metric.md) into named values.

> **Example**
> ```pycon
>>> import torch
>>> result = (torch.tensor(0.5), {"map_50": torch.tensor(0.75)})
>>> out = postprocess_metrics("map", result)
>>> {k: v.item() for k, v in out.items()}
{'map': 0.5, 'map_50': 0.75}
```

```pycon
>>> out = postprocess_metrics("map", result, log_sub_metrics=False)
>>> list(out)
['map']
```

```pycon
>>> sub = {"map_50": torch.tensor(0.75)}
>>> postprocess_metrics("map", sub, log_sub_metrics=False)
{}
```

Parameters

 * `name` (`str`): The identifier of the metric.
 * `values` (`Any`): The computed result. A tensor is the main value. A tuple holds the main value and the sub-metrics. A
   dictionary holds only sub-metrics.
 * `log_sub_metrics` (`bool`): Keep the sub-metrics. When `False`, only the main value remains, and a dictionary result gives an
   empty dictionary.

Returns

 * `dict[str, Tensor]`: `name` mapped to the main value, plus each sub-metric under its own key. A dictionary result gives only
   the sub-metrics.

Raises

 * `ValueError`: When `values` has none of the three forms.

## Attributes

### A

### T
