Skip to content

Commit 865935f

Browse files
authored
Merge branch 'main' into Custom-video-data-support-openvinotoolkit#1722-&-openvinotoolkit#1721
2 parents 5d75573 + 1a0e01a commit 865935f

File tree

17 files changed

+301
-90
lines changed

17 files changed

+301
-90
lines changed

.ci/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ ARG HTTP_PROXY
55
ARG HTTPS_PROXY
66
ARG NO_PROXY
77

8-
FROM nvidia/cuda:12.3.2-devel-ubuntu20.04 AS python_base_cuda
8+
FROM nvidia/cuda:12.4.0-devel-ubuntu20.04 AS python_base_cuda
99
LABEL maintainer="Anomalib Development Team"
1010

1111
# Setup proxies

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010

1111
### Changed
1212

13+
- 🔨Rename OptimalF1 to F1Max for consistency with the literature, by @samet-akcay in https://github.com/openvinotoolkit/anomalib/pull/1980
14+
- 🐞Update OptimalF1 score to use BinaryPrecisionRecallCurve and remove num_classes by @ashwinvaidya17 in https://github.com/openvinotoolkit/anomalib/pull/1972
15+
1316
### Deprecated
1417

1518
### Fixed
@@ -18,7 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1821

1922
**Full Changelog**:
2023

21-
## [v1.0.1] - Unreleased
24+
## [v1.0.1] - 2024-03-27
2225

2326
### Added
2427

configs/model/padim.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ model:
55
- layer1
66
- layer2
77
- layer3
8-
input_size: null
98
backbone: resnet18
109
pre_trained: true
1110
n_features: null

docs/source/markdown/guides/how_to/data/index.md

+8
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,19 @@ This section contains tutorials on how to fully utilize the data components of a
1313
Learn more about how to use `Folder` dataset to train anomalib models on your custom data.
1414
:::
1515

16+
:::{grid-item-card} {octicon}`table` Input tiling
17+
:link: ./input_tiling
18+
:link-type: doc
19+
20+
Learn more about how to use the tiler for input tiling.
21+
:::
22+
1623
::::
1724

1825
```{toctree}
1926
:caption: Data
2027
:hidden:
2128
2229
./custom_data
30+
./input_tiling
2331
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Input tiling
2+
3+
This tutorial will show you how to tile the input to a model, using the {py:class}`Tiler <anomalib.data.utils.tiler.Tiler>`.
4+
5+
```{warning}
6+
This tutorial assumes that you have already installed anomalib.
7+
If not, please refer to the [Installation](../../../../index.md#installation) section.
8+
```
9+
10+
```{warning}
11+
Only selected models support tiling.
12+
In the current version of Anomalib, these are:
13+
14+
- [Padim](../../reference/models/image/padim.md)
15+
- [Patchcore](../../reference/models/image/patchcore.md)
16+
- [Reverse Distillation](../../reference/models/image/reverse_distillation.md)
17+
- [STFPM](../../reference/models/image/stfpm.md)
18+
19+
```
20+
21+
## General tiling information
22+
23+
The general idea of input tiling is that the image is split into a rectangular grid of tiles as a pre-processing step, usually in order to reduce memory usage.
24+
By passing individual tiles to the model as input instead of full images, tiling reduces the model's input dimensions, while maintaining the effective input resolution of the images content-wise.
25+
26+
```{note}
27+
Tiler in Anomalib by default stacks the tiles batch-wise, so the memory consumption stays unchanged if the batch size is not reduced.
28+
```
29+
30+
The process of tiling is parametrized by four parameters `tile_size`, `stride`, `remove_border_count`, and `mode`.
31+
32+
- `tile_size` - determines the size of our tiles. Can be either a single number (square tiles) or a tuple.
33+
- `stride` - determines by how much we move in each direction when "cutting" the image into tiles. Can be either a single number (same step in both directions) or a tuple.
34+
- `remove_border_count` - how many pixels are removed at the border of the image before tiling (defaults to 0).
35+
- `mode` - what type of upscaling is used when the image isn't exactly divisible into tile-set specified by the parameters `tile_size` and `stride` (defaults to padding).
36+
37+
In most cases, we are only interested in the first two parameters - `tile_size` and `stride`. For the other two, refer to [Tiler implementation](../../reference/data/utils/tiling.md).
38+
39+
## Tiling setup
40+
41+
We can utilize the tiling in two ways. Either with the CLI or by using the API.
42+
In both cases, we need to use the {py:class}`TilerConfigurationCallback <anomalib.callbacks.TilerConfigurationCallback>`.
43+
This callback is responsible for assigning the tiler object to the model before the training starts.
44+
45+
```{note}
46+
Besides the arguments from {py:class}`Tiler <anomalib.data.utils.tiler.Tiler>`, {py:class}`TilerConfigurationCallback <anomalib.callbacks.TilerConfigurationCallback>` also has an additional `enable` argument, which must be set to `True` if we want the tiling to happen.
47+
```
48+
49+
::::{tab-set}
50+
51+
:::{tab-item} API
52+
53+
To use tiling from the API, we need to initialize the {py:class}`TilerConfigurationCallback <anomalib.callbacks.TilerConfigurationCallback>` and pass it to the engine:
54+
55+
```{code-block} python
56+
:lineno-start: 1
57+
:emphasize-lines: 12, 15
58+
# Import the required modules
59+
from anomalib.data import MVTec
60+
from anomalib.engine import Engine
61+
from anomalib.models import Padim
62+
from anomalib.callbacks import TilerConfigurationCallback
63+
64+
# Initialize the datamodule and model
65+
datamodule = MVTec(num_workers=0, image_size=(128, 128))
66+
model = Padim()
67+
68+
# prepare tiling configuration callback
69+
tiler_config_callback = TilerConfigurationCallback(enable=True, tile_size=[128, 64], stride=64)
70+
71+
# pass the tiling configuration callback to the engine
72+
engine = Engine(image_metrics=["AUROC"], pixel_metrics=["AUROC"], callbacks=[tiler_config_callback])
73+
74+
# train the model (tiling is seamlessly utilized in the background)
75+
engine.fit(datamodule=datamodule, model=model)
76+
```
77+
78+
:::
79+
80+
:::{tab-item} CLI
81+
82+
### Using CLI arguments
83+
84+
We can set the {py:class}`TilerConfigurationCallback <anomalib.callbacks.TilerConfigurationCallback>` and its init arguments directly from the CLI.
85+
86+
We pass it as trainer.callback, and then provide the parameters:
87+
88+
```{code-block} bash
89+
:emphasize-lines: 2, 3, 4, 5
90+
anomalib train --model Padim --data anomalib.data.MVTec
91+
--trainer.callbacks anomalib.callbacks.tiler_configuration.TilerConfigurationCallback
92+
--trainer.callbacks.enable True
93+
--trainer.callbacks.tile_size 128
94+
--trainer.callbacks.stride 64
95+
```
96+
97+
### Using config
98+
99+
For more advanced configuration, we can prepare the config file:
100+
101+
```{code-block} yaml
102+
:lineno-start: 1
103+
trainer.callbacks:
104+
class_path: anomalib.callbacks.tiler_configuration.TilerConfigurationCallback
105+
init_args:
106+
enable: True
107+
tile_size: [128, 256]
108+
stride: 64
109+
```
110+
111+
Then use the config from the CLI:
112+
113+
```{code-block} bash
114+
anomalib train --model Padim --data anomalib.data.MVTec --config config.yaml
115+
```
116+
117+
:::
118+
119+
::::

docs/source/markdown/guides/reference/callbacks/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
```{eval-rst}
44
.. automodule:: anomalib.callbacks
55
:members:
6-
:exclude-members: get_visualization_callbacks, TilerConfigurationCallback
6+
:exclude-members: get_visualization_callbacks
77
:show-inheritance:
88
```

src/anomalib/data/utils/tiler.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def tile(self, image: torch.Tensor, use_random_tiling: bool = False) -> torch.Te
378378
if self.input_h < self.tile_size_h or self.input_w < self.tile_size_w:
379379
msg = (
380380
f"One of the edges of the tile size {self.tile_size_h, self.tile_size_w} is larger than "
381-
f"that of the image {{self.input_h, self.input_w}}."
381+
f"that of the image {self.input_h, self.input_w}."
382382
)
383383
raise ValueError(
384384
msg,

src/anomalib/metrics/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# Copyright (C) 2022-2024 Intel Corporation
44
# SPDX-License-Identifier: Apache-2.0
55

6-
76
import importlib
87
import logging
98
from collections.abc import Callable
@@ -17,6 +16,7 @@
1716
from .aupro import AUPRO
1817
from .auroc import AUROC
1918
from .collection import AnomalibMetricCollection
19+
from .f1_max import F1Max
2020
from .f1_score import F1Score
2121
from .min_max import MinMax
2222
from .precision_recall_curve import BinaryPrecisionRecallCurve
@@ -30,6 +30,7 @@
3030
"AnomalyScoreDistribution",
3131
"BinaryPrecisionRecallCurve",
3232
"F1AdaptiveThreshold",
33+
"F1Max",
3334
"F1Score",
3435
"ManualThreshold",
3536
"MinMax",

src/anomalib/metrics/aupr.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66

77
import torch
88
from matplotlib.figure import Figure
9-
from torchmetrics import PrecisionRecallCurve
9+
from torchmetrics.classification import BinaryPrecisionRecallCurve
1010
from torchmetrics.utilities.compute import auc
1111
from torchmetrics.utilities.data import dim_zero_cat
1212

1313
from .plotting_utils import plot_figure
1414

1515

16-
class AUPR(PrecisionRecallCurve):
16+
class AUPR(BinaryPrecisionRecallCurve):
1717
"""Area under the PR curve.
1818
1919
This metric computes the area under the precision-recall curve.

src/anomalib/metrics/f1_max.py

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""Implementation of F1Max score based on TorchMetrics."""
2+
3+
# Copyright (C) 2022-2024 Intel Corporation
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
import logging
7+
8+
import torch
9+
from torchmetrics import Metric
10+
11+
from anomalib.metrics.precision_recall_curve import BinaryPrecisionRecallCurve
12+
13+
logger = logging.getLogger(__name__)
14+
15+
16+
class F1Max(Metric):
17+
"""F1Max Metric for Computing the Maximum F1 Score.
18+
19+
This class is designed to calculate the maximum F1 score from the precision-
20+
recall curve for binary classification tasks. The F1 score is a harmonic
21+
mean of precision and recall, offering a balance between these two metrics.
22+
The maximum F1 score (F1-Max) is particularly useful in scenarios where an
23+
optimal balance between precision and recall is desired, such as in
24+
imbalanced datasets or when both false positives and false negatives carry
25+
significant costs.
26+
27+
After computing the F1Max score, the class also identifies and stores the
28+
threshold that yields this maximum F1 score, which providing insight into
29+
the optimal point for the classification decision.
30+
31+
Args:
32+
**kwargs: Variable keyword arguments that can be passed to the parent class.
33+
34+
Attributes:
35+
full_state_update (bool): Indicates whether the metric requires updating
36+
the entire state. Set to False for this metric as it calculates the
37+
F1 score based on the current state without needing historical data.
38+
precision_recall_curve (BinaryPrecisionRecallCurve): Utility to compute
39+
precision and recall values across different thresholds.
40+
threshold (torch.Tensor): Stores the threshold value that results in the
41+
maximum F1 score.
42+
43+
Examples:
44+
>>> from anomalib.metrics import F1Max
45+
>>> import torch
46+
47+
>>> preds = torch.tensor([0.1, 0.4, 0.35, 0.8])
48+
>>> target = torch.tensor([0, 0, 1, 1])
49+
50+
>>> f1_max = F1Max()
51+
>>> f1_max.update(preds, target)
52+
53+
>>> optimal_f1_score = f1_max.compute()
54+
>>> print(f"Optimal F1 Score: {f1_max_score}")
55+
>>> print(f"Optimal Threshold: {f1_max.threshold}")
56+
57+
Note:
58+
- Use `update` method to input predictions and target labels.
59+
- Use `compute` method to calculate the maximum F1 score after all
60+
updates.
61+
- Use `reset` method to clear the current state and prepare for a new
62+
set of calculations.
63+
"""
64+
65+
full_state_update: bool = False
66+
67+
def __init__(self, **kwargs) -> None:
68+
super().__init__(**kwargs)
69+
70+
self.precision_recall_curve = BinaryPrecisionRecallCurve()
71+
72+
self.threshold: torch.Tensor
73+
74+
def update(self, preds: torch.Tensor, target: torch.Tensor, *args, **kwargs) -> None:
75+
"""Update the precision-recall curve metric."""
76+
del args, kwargs # These variables are not used.
77+
78+
self.precision_recall_curve.update(preds, target)
79+
80+
def compute(self) -> torch.Tensor:
81+
"""Compute the value of the optimal F1 score.
82+
83+
Compute the F1 scores while varying the threshold. Store the optimal
84+
threshold as attribute and return the maximum value of the F1 score.
85+
86+
Returns:
87+
Value of the F1 score at the optimal threshold.
88+
"""
89+
precision: torch.Tensor
90+
recall: torch.Tensor
91+
thresholds: torch.Tensor
92+
93+
precision, recall, thresholds = self.precision_recall_curve.compute()
94+
f1_score = (2 * precision * recall) / (precision + recall + 1e-10)
95+
self.threshold = thresholds[torch.argmax(f1_score)]
96+
return torch.max(f1_score)
97+
98+
def reset(self) -> None:
99+
"""Reset the metric."""
100+
self.precision_recall_curve.reset()

0 commit comments

Comments
 (0)