Repository: kornia/kornia

Issue:
Update configs to use `dataclasses`
We have some specific configs for some algorithms, will be nice to update them from using `dicts`/`TypedDict` to `dataclasses`. 

The idea here is to do it in a way that does not break things, so we should have an interface (to/from) between `dict` and the `dataclasses`.

Example of what we can explore for these methods
```python
>>> from dataclasses import dataclass, asdict
>>> @dataclass
... class A:
...     b: int
... 
>>> asdict(A(1))
{'b': 1}
>>> A(**asdict(A(1)))
A(b=1)
```
_Originally posted by @johnnv1 in https://github.com/kornia/kornia/pull/2092#discussion_r1049564760_

List of some configs to be replaced:
 - [ ] kornia.feature.adalam.core.AdalamConfig
 - [ ] kornia.contrib.face_detection.FaceDetector.config #2851
 - [ ] kornia.feature.keynet.KeyNet_conf - #2254
 - [ ] [kornia.feature.loftr.loftr.default_cfg](https://github.com/kornia/kornia/blob/2387a2d165a977f1646332d6cbe6b915a4806e37/kornia/feature/loftr/loftr.py#L25)
   - [ ] kornia.feature.loftr.loftr_module.fine_preprocess.FinePreprocess.config
   - [ ] kornia.feature.loftr.loftr_module.transformer.LocalFeatureTransformer.config
   - [ ] kornia.feature.loftr.utils.coarse_matching.CoarseMatching.config
   - [ ] kornia.feature.loftr.utils.supervision config
   - [ ] [kornia.feature.loftr.backbone.resnet_fpn config](https://github.com/kornia/kornia/blob/2387a2d165a977f1646332d6cbe6b915a4806e37/kornia/feature/loftr/backbone/resnet_fpn.py#L50)
 - [ ] kornia.feature.matching._get_default_fginn_params
 - [x] [kornia.feature.sold2.backbones.SOLD2Net.cfg](https://github.com/kornia/kornia/blob/2387a2d165a977f1646332d6cbe6b915a4806e37/kornia/feature/sold2/backbones.py#L377) - #2880
   - [x] [kornia.feature.sold2.sold2.default_cfg](https://github.com/kornia/kornia/blob/2387a2d165a977f1646332d6cbe6b915a4806e37/kornia/feature/sold2/sold2.py#L18)
   - [x] [kornia.feature.sold2.sold2_detector.default_cfg](https://github.com/kornia/kornia/blob/2387a2d165a977f1646332d6cbe6b915a4806e37/kornia/feature/sold2/sold2_detector.py#L17)
   - [ ] #2901
 - [ ] #2908 


Discussion:
@ducha-aiki can you help to triage importance in the feature module. Also to audit if we eventually have things to deprecate etc
@ johnnv1 Can I fix one of the files, for example, KeyNetConf, pull it, and if everything is okay, add more changes?
/assign Ikonsty
Go ahead, when the PR is ok for review you can ping me and @ducha-aiki for it 😄 


hey @johnnv1 can you assign this to me
@rajshukla1102  Go ahead, but if possible, open separate PRs for each feature
@johnnv1 Thank you! looking forward to completing this soon😊
I've been reviewing the list of potential contributions and noticed that many tasks are either already completed or no longer relevant. However, I've identified a couple of areas where I believe I could contribute effectively:

- kornia.feature.matching._get_default_fginn_params
- kornia.feature.adalam.core.AdalamConfig

Could you please confirm if these contributions are still needed? I would appreciate any additional guidance or suggestions you might have on how to proceed.

Looking forward to your feedback!
> Could you please confirm if these contributions are still needed?

Sure it's, place a PR for this, and if you have any questions you can ping me at kornia slack

For cases like `AdalamConfig` which is a typed dict within `NoRequired`, you will need to check if we will need to initialize it somehow or not. Based on this, you'll be able to define the types, and if necessary you'll need to add some `isinstance` checks in some places

aside from the typing, the asdict <-> config conversion as in the issue, should work fine

In #2880 the [kornia.feature.sold2.sold2_detector.default_cfg](https://github.com/kornia/kornia/blob/2387a2d165a977f1646332d6cbe6b915a4806e37/kornia/feature/sold2/sold2_detector.py#L17) config is refactored. I therefore also looked into [kornia.feature.sold2.sold2.default_cfg](https://github.com/kornia/kornia/blob/2387a2d165a977f1646332d6cbe6b915a4806e37/kornia/feature/sold2/sold2.py#L18):
- [kornia.feature.sold2.sold2.default_cfg](https://github.com/kornia/kornia/blob/2387a2d165a977f1646332d6cbe6b915a4806e37/kornia/feature/sold2/sold2.py#L18) uses the same `default_config` as in `SOLD2_detector` plus the `line_matcher_cfg`. Shall we put the `dataclass` definitions in a separate file (e.g. `sold2_dataclasses.py` in `utils`)? This way we could avoid repetition.
> Shall we put the `dataclass` definitions in a separate file (e.g. `sold2_dataclasses.py` in `utils`)? This way we could avoid repetition.

Yeap, the detector default config has the descriptor equals `False` different than other one. But you can unify it under a `structures.py` file in sold2 module ("kornia/feature/sold2/structures.py").





Resolving patch:
diff --git a/kornia/feature/sold2/sold2.py b/kornia/feature/sold2/sold2.py
--- a/kornia/feature/sold2/sold2.py
+++ b/kornia/feature/sold2/sold2.py
@@ -5,16 +5,16 @@
 
 from kornia.core import Module, Tensor, concatenate, pad, stack
 from kornia.core.check import KORNIA_CHECK_SHAPE
+from kornia.feature.sold2.structures import LineDetectorCfg
 from kornia.geometry.conversions import normalize_pixel_coordinates
 from kornia.utils import map_location_to_cpu
 
 from .backbones import SOLD2Net
-from .sold2_detector import LineDetectorCfg, LineSegmentDetectionModule, line_map_to_segments, prob_to_junctions
+from .sold2_detector import LineSegmentDetectionModule, line_map_to_segments, prob_to_junctions
 
 urls: Dict[str, str] = {}
 urls["wireframe"] = "http://cmp.felk.cvut.cz/~mishkdmy/models/sold2_wireframe.pth"
 
-
 default_cfg: Dict[str, Any] = {
     "backbone_cfg": {"input_channel": 1, "depth": 4, "num_stacks": 2, "num_blocks": 1, "num_classes": 5},
     "use_descriptor": True,
diff --git a/kornia/feature/sold2/sold2_detector.py b/kornia/feature/sold2/sold2_detector.py
--- a/kornia/feature/sold2/sold2_detector.py
+++ b/kornia/feature/sold2/sold2_detector.py
@@ -1,12 +1,12 @@
 import math
 import warnings
-from dataclasses import dataclass, field
 from typing import Any, Dict, Optional, Tuple
 
 import torch
 
 from kornia.core import Module, Tensor, concatenate, sin, stack, tensor, where, zeros
 from kornia.core.check import KORNIA_CHECK_SHAPE
+from kornia.feature.sold2.structures import DetectorCfg, HeatMapRefineCfg, JunctionRefineCfg, LineDetectorCfg
 from kornia.geometry.bbox import nms
 from kornia.utils import dataclass_to_dict, dict_to_dataclass, map_location_to_cpu, torch_meshgrid
 
@@ -16,58 +16,6 @@
 urls["wireframe"] = "https://www.polybox.ethz.ch/index.php/s/blOrW89gqSLoHOk/download"
 
 
-@dataclass
-class HeatMapRefineCfg:
-    mode: str = "local"
-    ratio: float = 0.2
-    valid_thresh: float = 0.001
-    num_blocks: int = 20
-    overlap_ratio: float = 0.5
-
-
-@dataclass
-class JunctionRefineCfg:
-    num_perturbs: int = 9
-    perturb_interval: float = 0.25
-
-
-@dataclass
-class LineDetectorCfg:
-    detect_thresh: float = 0.5
-    num_samples: int = 64
-    inlier_thresh: float = 0.99
-    use_candidate_suppression: bool = True
-    nms_dist_tolerance: float = 3.0
-    heatmap_low_thresh: float = 0.15
-    heatmap_high_thresh: float = 0.2
-    max_local_patch_radius: float = 3
-    lambda_radius: float = 2.0
-    use_heatmap_refinement: bool = True
-    heatmap_refine_cfg: HeatMapRefineCfg = field(default_factory=HeatMapRefineCfg)
-    use_junction_refinement: bool = True
-    junction_refine_cfg: JunctionRefineCfg = field(default_factory=JunctionRefineCfg)
-
-
-@dataclass
-class BackboneCfg:
-    input_channel: int = 1
-    depth: int = 4
-    num_stacks: int = 2
-    num_blocks: int = 1
-    num_classes: int = 5
-
-
-@dataclass
-class DetectorCfg:
-    backbone_cfg: BackboneCfg = field(default_factory=BackboneCfg)
-    use_descriptor: bool = False
-    grid_size: int = 8
-    keep_border_valid: bool = True
-    detection_thresh: float = 0.0153846  # = 1/65: threshold of junction detection
-    max_num_junctions: int = 500  # maximum number of junctions per image
-    line_detector_cfg: LineDetectorCfg = field(default_factory=LineDetectorCfg)
-
-
 class SOLD2_detector(Module):
     r"""Module, which detects line segments in an image.
 
@@ -93,8 +41,8 @@ def __init__(self, pretrained: bool = True, config: Optional[DetectorCfg] = None
         if isinstance(config, dict):
             warnings.warn(
                 "Usage of config as a plain dictionary is deprecated in favor of"
-                " `kornia.feature.sold2.sold2_detector.DetectorCfg`. The support of plain dictionaries"
-                "as config will be removed in kornia v0.8.0 (December 2024).",
+                "`kornia.features.sold2.structures.DetectorCfg`. The support of plain"
+                "dictionaries as config will be removed in kornia v0.8.0 (December 2024).",
                 category=DeprecationWarning,
                 stacklevel=2,
             )
diff --git a/kornia/feature/sold2/structures.py b/kornia/feature/sold2/structures.py
new file mode 100644
--- /dev/null
+++ b/kornia/feature/sold2/structures.py
@@ -0,0 +1,64 @@
+from dataclasses import dataclass, field
+
+
+@dataclass
+class HeatMapRefineCfg:
+    mode: str = "local"
+    ratio: float = 0.2
+    valid_thresh: float = 0.001
+    num_blocks: int = 20
+    overlap_ratio: float = 0.5
+
+
+@dataclass
+class JunctionRefineCfg:
+    num_perturbs: int = 9
+    perturb_interval: float = 0.25
+
+
+@dataclass
+class LineDetectorCfg:
+    detect_thresh: float = 0.5
+    num_samples: int = 64
+    inlier_thresh: float = 0.99
+    use_candidate_suppression: bool = True
+    nms_dist_tolerance: float = 3.0
+    heatmap_low_thresh: float = 0.15
+    heatmap_high_thresh: float = 0.2
+    max_local_patch_radius: float = 3
+    lambda_radius: float = 2.0
+    use_heatmap_refinement: bool = True
+    heatmap_refine_cfg: HeatMapRefineCfg = field(default_factory=HeatMapRefineCfg)
+    use_junction_refinement: bool = True
+    junction_refine_cfg: JunctionRefineCfg = field(default_factory=JunctionRefineCfg)
+
+
+@dataclass
+class LineMatcherCfg:
+    cross_check: bool = True
+    num_samples: int = 10
+    min_dist_pts: int = 8
+    top_k_candidates: int = 10
+    grid_size: int = 8
+    line_score: bool = False  # True to compute saliency on a line
+
+
+@dataclass
+class BackboneCfg:
+    input_channel: int = 1
+    depth: int = 4
+    num_stacks: int = 2
+    num_blocks: int = 1
+    num_classes: int = 5
+
+
+@dataclass
+class DetectorCfg:
+    backbone_cfg: BackboneCfg = field(default_factory=BackboneCfg)
+    use_descriptor: bool = False
+    grid_size: int = 8
+    keep_border_valid: bool = True
+    detection_thresh: float = 0.0153846  # = 1/65: threshold of junction detection
+    max_num_junctions: int = 500  # maximum number of junctions per image
+    line_detector_cfg: LineDetectorCfg = field(default_factory=LineDetectorCfg)
+    line_matcher_cfg: LineMatcherCfg = field(default_factory=LineMatcherCfg)


Test patch: