Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

πŸ”¨ Refactor Engine args: Create workspace directory from API #1773

Merged
merged 52 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
39b44b0
Create anomalib path utils
samet-akcay Feb 23, 2024
adb2a90
Add name and category to datamodule and dataset
samet-akcay Feb 23, 2024
2702880
Add name and category to datamodule and dataset
samet-akcay Feb 23, 2024
0e51806
Rename new_dir to new_version_dir
samet-akcay Feb 23, 2024
6a1411b
Use convert_to_snake_case in get_models
samet-akcay Feb 23, 2024
07606c3
Remove result_dir arg from CLI
samet-akcay Feb 23, 2024
a35a1fd
add name property to anomaly module
samet-akcay Feb 23, 2024
e8d1ad1
Remove model checkpoint from get_callbacks function
samet-akcay Feb 23, 2024
c5b93fd
format cli.py
samet-akcay Feb 23, 2024
9a19939
Remove get_default_root_dir util function
samet-akcay Feb 23, 2024
628ad86
Remove project creation from update_config util function
samet-akcay Feb 23, 2024
ac1e4dd
Add workspace to engine
samet-akcay Feb 23, 2024
0646638
Add ModelCheckpoint back to checkpoint init
samet-akcay Feb 25, 2024
5c91a8b
Fix the following error: TypeError: unsupported operand type(s) for /…
samet-akcay Feb 26, 2024
252756c
Overwrite name propery in folder 3d dataset
samet-akcay Feb 26, 2024
d292715
Overwrite name propery in folder dataset
samet-akcay Feb 26, 2024
dbd5fc9
Update the example folder config
samet-akcay Feb 26, 2024
cae5534
Remove results_dir from tests
samet-akcay Feb 26, 2024
d3c0925
fix folder tests
samet-akcay Feb 26, 2024
21faf26
Default category is an empty string
samet-akcay Feb 26, 2024
fe9fbb5
Fix the synthetic data tests
samet-akcay Feb 26, 2024
b6ca717
Merge branch 'main' into refactor/engine-args
samet-akcay Feb 26, 2024
2209d87
Add name parameter to Folder initialization
samet-akcay Feb 27, 2024
26591a1
Merge branch 'refactor/engine-args' of github.com:samet-akcay/anomali…
samet-akcay Feb 27, 2024
6cbe81a
Fix notebook tests
samet-akcay Feb 27, 2024
d75e0ec
Add ModelCheckpoint to the list of anomalib callbacks and initialize …
samet-akcay Feb 28, 2024
47411f2
remove visualization from engine
samet-akcay Feb 28, 2024
ab1499c
Fix the image saving path
samet-akcay Feb 28, 2024
c168462
Revert show filename
samet-akcay Feb 28, 2024
f06113f
Resolve merge conflicts
samet-akcay Feb 28, 2024
fb8d464
resolve merge conflicts
samet-akcay Feb 28, 2024
6b72cfa
Partially address the cli tests
samet-akcay Feb 28, 2024
2d97c93
Setup workspace in each entrypoint
samet-akcay Feb 29, 2024
0d422bb
Merge main and resolve merge conflicts
samet-akcay Feb 29, 2024
06b975b
Add/remove missing/extra parts due to conflicts
samet-akcay Feb 29, 2024
bcb20ff
Update setup-workspace
samet-akcay Feb 29, 2024
e60ba59
update unit tests
ashwinvaidya17 Feb 29, 2024
a650df4
Merge pull request #7 from ashwinvaidya17/fix/update_tests
samet-akcay Feb 29, 2024
b4ea4ca
Merge branch 'main' of github.com:openvinotoolkit/anomalib into refac…
samet-akcay Feb 29, 2024
69112d7
Merge Ashwins solution
samet-akcay Feb 29, 2024
276385d
fix test
samet-akcay Feb 29, 2024
69c67b1
stash changes
ashwinvaidya17 Feb 29, 2024
e7ea8a5
Merge pull request #8 from ashwinvaidya17/fix/update_tests_2
samet-akcay Feb 29, 2024
41b1734
Merge main
samet-akcay Feb 29, 2024
24ebee5
update unit tests
ashwinvaidya17 Feb 29, 2024
801bd14
update unit tests
ashwinvaidya17 Feb 29, 2024
137d726
Merge pull request #9 from ashwinvaidya17/fix/update_tests_2
samet-akcay Feb 29, 2024
5029f04
fix path
ashwinvaidya17 Feb 29, 2024
e04b85d
Merge pull request #10 from ashwinvaidya17/fix/update_tests_2
samet-akcay Feb 29, 2024
63e357b
Fix ImageVisualizer tests
samet-akcay Feb 29, 2024
99895c9
Skip AiVAD test
ashwinvaidya17 Feb 29, 2024
86a5a2a
Merge pull request #11 from ashwinvaidya17/fix/update_tests_2
samet-akcay Feb 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configs/data/folder.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class_path: anomalib.data.Folder
init_args:
name: bottle
root: "datasets/MVTec/bottle"
normal_dir: "train/good"
abnormal_dir: "test/broken_large"
Expand Down
5 changes: 5 additions & 0 deletions notebooks/100_datamodules/103_folder.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"outputs": [],
"source": [
"folder_datamodule = Folder(\n",
" name=\"hazelnut_toy\",\n",
" root=dataset_root,\n",
" normal_dir=\"good\",\n",
" abnormal_dir=\"crack\",\n",
Expand Down Expand Up @@ -331,6 +332,7 @@
],
"source": [
"folder_dataset_classification_train = FolderDataset(\n",
" name=\"hazelnut_toy\",\n",
" normal_dir=dataset_root / \"good\",\n",
" abnormal_dir=dataset_root / \"crack\",\n",
" split=\"train\",\n",
Expand Down Expand Up @@ -476,6 +478,7 @@
"source": [
"# Folder Classification Test Set\n",
"folder_dataset_classification_test = FolderDataset(\n",
" name=\"hazelnut_toy\",\n",
" normal_dir=dataset_root / \"good\",\n",
" abnormal_dir=dataset_root / \"crack\",\n",
" split=\"test\",\n",
Expand Down Expand Up @@ -615,6 +618,7 @@
"source": [
"# Folder Segmentation Train Set\n",
"folder_dataset_segmentation_train = FolderDataset(\n",
" name=\"hazelnut_toy\",\n",
" normal_dir=dataset_root / \"good\",\n",
" abnormal_dir=dataset_root / \"crack\",\n",
" split=\"train\",\n",
Expand Down Expand Up @@ -727,6 +731,7 @@
"source": [
"# Folder Segmentation Test Set\n",
"folder_dataset_segmentation_test = FolderDataset(\n",
" name=\"hazelnut_toy\",\n",
" normal_dir=dataset_root / \"good\",\n",
" abnormal_dir=dataset_root / \"crack\",\n",
" split=\"test\",\n",
Expand Down
16 changes: 1 addition & 15 deletions src/anomalib/callbacks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .timer import TimerCallback

__all__ = [
"ModelCheckpoint",
"GraphLogger",
"LoadModelCallback",
"TilerConfigurationCallback",
Expand All @@ -43,21 +44,6 @@ def get_callbacks(config: DictConfig | ListConfig | Namespace) -> list[Callback]

callbacks: list[Callback] = []

samet-akcay marked this conversation as resolved.
Show resolved Hide resolved
monitor_metric = (
None if "early_stopping" not in config.model.init_args else config.model.init_args.early_stopping.metric
)
monitor_mode = "max" if "early_stopping" not in config.model.init_args else config.model.early_stopping.mode

checkpoint = ModelCheckpoint(
dirpath=Path(config.trainer.default_root_dir) / "weights" / "lightning",
filename="model",
monitor=monitor_metric,
mode=monitor_mode,
auto_insert_metric_name=False,
)

callbacks.extend([checkpoint, TimerCallback()])

if "ckpt_path" in config.trainer and config.ckpt_path is not None:
load_model = LoadModelCallback(config.ckpt_path)
callbacks.append(load_model)
Expand Down
13 changes: 12 additions & 1 deletion src/anomalib/callbacks/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,18 @@ def on_test_batch_end(
if result.file_name is None:
msg = "``save`` is set to ``True`` but file name is ``None``"
raise ValueError(msg)
save_image(image=result.image, root=self.root, filename=result.file_name)

# Get the filename to save the image.
# Filename is split based on the datamodule name and category.
# For example, if the filename is `MVTec/bottle/000.png`, then the
# filename is split based on `MVTec/bottle` and `000.png` is saved.
if trainer.datamodule is not None:
filename = str(result.file_name).split(
sep=f"{trainer.datamodule.name}/{trainer.datamodule.category}",
)[-1]
else:
filename = Path(result.file_name).name
save_image(image=result.image, root=self.root, filename=filename)
if self.show:
show_image(image=result.image, title=str(result.file_name))
if self.log:
Expand Down
28 changes: 4 additions & 24 deletions src/anomalib/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from anomalib.metrics.threshold import BaseThreshold
from anomalib.models import AnomalyModule
from anomalib.utils.config import update_config
from anomalib.utils.visualization.base import BaseVisualizer

except ImportError:
_LIGHTNING_AVAILABLE = False
Expand Down Expand Up @@ -143,15 +142,6 @@ def add_arguments_to_parser(self, parser: ArgumentParser) -> None:
from anomalib.callbacks.normalization import get_normalization_callback

parser.add_function_arguments(get_normalization_callback, "normalization")
# visualization takes task from the project
parser.add_argument(
"--visualization.visualizers",
type=BaseVisualizer | list[BaseVisualizer] | None,
default=None,
)
parser.add_argument("--visualization.save", type=bool, default=False)
parser.add_argument("--visualization.log", type=bool, default=False)
parser.add_argument("--visualization.show", type=bool, default=False)
parser.add_argument("--task", type=TaskType | str, default=TaskType.SEGMENTATION)
parser.add_argument("--metrics.image", type=list[str] | str | None, default=["F1Score", "AUROC"])
parser.add_argument("--metrics.pixel", type=list[str] | str | None, default=None, required=False)
Expand All @@ -160,13 +150,14 @@ def add_arguments_to_parser(self, parser: ArgumentParser) -> None:
if hasattr(parser, "subcommand") and parser.subcommand not in ("export", "predict"):
parser.link_arguments("task", "data.init_args.task")
parser.add_argument(
"--results_dir.path",
"--default_root_dir",
type=Path,
help="Path to save the results.",
default=Path("./results"),
)
parser.add_argument("--results_dir.unique", type=bool, help="Whether to create a unique folder.", default=False)
parser.link_arguments("results_dir.path", "trainer.default_root_dir")
parser.link_arguments("default_root_dir", "trainer.default_root_dir")
# TODO(ashwinvaidya17): Tiling should also be a category of its own
# CVS-122659

def add_trainer_arguments(self, parser: ArgumentParser, subcommand: str) -> None:
"""Add train arguments to the parser."""
Expand Down Expand Up @@ -329,7 +320,6 @@ def instantiate_engine(self) -> None:
"task": self._get(self.config_init, "task"),
"image_metrics": self._get(self.config_init, "metrics.image"),
"pixel_metrics": self._get(self.config_init, "metrics.pixel"),
**self._get_visualization_parameters(),
}
trainer_config = {**self._get(self.config_init, "trainer", default={}), **engine_args}
key = "callbacks"
Expand All @@ -348,16 +338,6 @@ def instantiate_engine(self) -> None:
trainer_config[key].extend(get_callbacks(self.config[self.subcommand]))
self.engine = Engine(**trainer_config)

def _get_visualization_parameters(self) -> dict[str, Any]:
"""Return visualization parameters."""
subcommand = self.config.subcommand
return {
"visualizers": self.config_init[subcommand].visualization.visualizers,
"save_image": self.config[subcommand].visualization.save,
"log_image": self.config[subcommand].visualization.log,
"show_image": self.config[subcommand].visualization.show,
}

def _run_subcommand(self) -> None:
"""Run subcommand depending on the subcommand.

Expand Down
16 changes: 16 additions & 0 deletions src/anomalib/data/base/datamodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,15 @@ def __init__(
self.test_data: AnomalibDataset

self._samples: DataFrame | None = None
self._category: str = ""

self._is_setup = False # flag to track if setup has been called from the trainer

@property
def name(self) -> str:
"""Name of the datamodule."""
return self.__class__.__name__

def setup(self, stage: str | None = None) -> None:
"""Set up train, validation and test data.

Expand Down Expand Up @@ -144,6 +150,16 @@ def _setup(self, _stage: str | None = None) -> None:
"""
raise NotImplementedError

@property
def category(self) -> str:
"""Get the category of the datamodule."""
return self._category
samet-akcay marked this conversation as resolved.
Show resolved Hide resolved

@category.setter
def category(self, category: str) -> None:
"""Set the category of the datamodule."""
self._category = category

def _create_test_split(self) -> None:
"""Obtain the test set based on the settings in the config."""
if self.test_data.has_normal:
Expand Down
22 changes: 22 additions & 0 deletions src/anomalib/data/base/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ def __init__(self, task: TaskType, transform: Transform | None = None) -> None:
self.task = task
self.transform = transform
self._samples: DataFrame | None = None
self._category: str | None = None

@property
def name(self) -> str:
"""Name of the dataset."""
class_name = self.__class__.__name__

# Remove the `_dataset` suffix from the class name
if class_name.endswith("Dataset"):
class_name = class_name[:-7]

return class_name

def __len__(self) -> int:
"""Get length of the dataset."""
Expand Down Expand Up @@ -113,6 +125,16 @@ def samples(self, samples: DataFrame) -> None:

self._samples = samples.sort_values(by="image_path", ignore_index=True)

@property
def category(self) -> str | None:
"""Get the category of the dataset."""
return self._category

@category.setter
def category(self, category: str) -> None:
"""Set the category of the dataset."""
self._category = category

@property
def has_normal(self) -> bool:
"""Check if the dataset contains any normal samples."""
Expand Down
24 changes: 24 additions & 0 deletions src/anomalib/data/depth/folder_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ class Folder3DDataset(AnomalibDepthDataset):
"""Folder dataset.

Args:
name (str): Name of the dataset.
task (TaskType): Task type. (``classification``, ``detection`` or ``segmentation``).
transform (Transform): Transforms that should be applied to the input images.
normal_dir (str | Path): Path to the directory containing normal images.
Expand Down Expand Up @@ -222,6 +223,7 @@ class Folder3DDataset(AnomalibDepthDataset):

def __init__(
self,
name: str,
task: TaskType,
normal_dir: str | Path,
root: str | Path | None = None,
Expand All @@ -237,6 +239,7 @@ def __init__(
) -> None:
super().__init__(task, transform)

self._name = name
self.split = split
self.root = root
self.normal_dir = normal_dir
Expand All @@ -261,11 +264,20 @@ def __init__(
extensions=self.extensions,
)

@property
def name(self) -> str:
"""Name of the dataset.

Folder3D dataset overrides the name property to provide a custom name.
"""
return self._name


class Folder3D(AnomalibDataModule):
"""Folder DataModule.

Args:
name (str): Name of the dataset. This is used to name the datamodule, especially when logging/saving.
normal_dir (str | Path): Name of the directory containing normal images.
root (str | Path | None): Path to the root folder containing normal and abnormal dirs.
Defaults to ``None``.
Expand Down Expand Up @@ -318,6 +330,7 @@ class Folder3D(AnomalibDataModule):

def __init__(
self,
name: str,
normal_dir: str | Path,
root: str | Path,
abnormal_dir: str | Path | None = None,
Expand Down Expand Up @@ -355,6 +368,7 @@ def __init__(
val_split_ratio=val_split_ratio,
seed=seed,
)
self._name = name
self.task = TaskType(task)
self.root = Path(root)
self.normal_dir = normal_dir
Expand All @@ -368,6 +382,7 @@ def __init__(

def _setup(self, _stage: str | None = None) -> None:
self.train_data = Folder3DDataset(
name=self.name,
task=self.task,
transform=self.train_transform,
split=Split.TRAIN,
Expand All @@ -383,6 +398,7 @@ def _setup(self, _stage: str | None = None) -> None:
)

self.test_data = Folder3DDataset(
name=self.name,
task=self.task,
transform=self.eval_transform,
split=Split.TEST,
Expand All @@ -396,3 +412,11 @@ def _setup(self, _stage: str | None = None) -> None:
mask_dir=self.mask_dir,
extensions=self.extensions,
)

@property
def name(self) -> str:
"""Name of the datamodule.

Folder3D datamodule overrides the name property to provide a custom name.
"""
return self._name
Loading
Loading