hintauc 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hintauc/__init__.py +66 -0
- hintauc/auc.py +73 -0
- hintauc/cli.py +84 -0
- hintauc/hints.py +485 -0
- hintauc/longest_path.py +176 -0
- hintauc/metrics.py +305 -0
- hintauc-0.1.0.dist-info/METADATA +170 -0
- hintauc-0.1.0.dist-info/RECORD +12 -0
- hintauc-0.1.0.dist-info/WHEEL +5 -0
- hintauc-0.1.0.dist-info/entry_points.txt +2 -0
- hintauc-0.1.0.dist-info/licenses/LICENSE +21 -0
- hintauc-0.1.0.dist-info/top_level.txt +1 -0
hintauc/__init__.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""hintauc — Deterministic region-based hint generation (DHT) and Hint-AUC
|
|
2
|
+
evaluation for line-art colorization.
|
|
3
|
+
|
|
4
|
+
Library form of the method proposed in
|
|
5
|
+
"Hint-AUC: Deterministic Region-based Hint Generation for Line Art
|
|
6
|
+
Colorization Evaluation" (Madono, Mingcheng, Simo-Serra).
|
|
7
|
+
|
|
8
|
+
Quick start
|
|
9
|
+
-----------
|
|
10
|
+
>>> import hintauc
|
|
11
|
+
>>> hints = hintauc.generate_hints("illustration.png") # 1. hints
|
|
12
|
+
>>> color, mask = hints.at_ratio(0.10, hint_type="scribble") # top-10% regions
|
|
13
|
+
>>> hints.save("out/illustration") # canonical files
|
|
14
|
+
|
|
15
|
+
>>> ev = hintauc.Evaluator(metrics=("mse", "psnr", "ssim", "lpips"))
|
|
16
|
+
>>> scores = ev("colorized.png", "ground_truth.png") # 2. evaluation
|
|
17
|
+
>>> auc = hintauc.hint_auc({0.0: 0.44, 0.01: 0.33, 0.03: 0.27,
|
|
18
|
+
... 0.05: 0.24, 0.10: 0.20, 0.25: 0.15,
|
|
19
|
+
... 0.50: 0.12, 1.00: 0.10})
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .longest_path import geodesic_longest_path
|
|
23
|
+
from .hints import (
|
|
24
|
+
DEFAULT_HINT_SIZE,
|
|
25
|
+
FELZENSZWALB_PARAMS,
|
|
26
|
+
HintResult,
|
|
27
|
+
generate_hints,
|
|
28
|
+
region_ids,
|
|
29
|
+
segment_regions,
|
|
30
|
+
)
|
|
31
|
+
from .metrics import (
|
|
32
|
+
DEFAULT_METRICS,
|
|
33
|
+
LOWER_IS_BETTER,
|
|
34
|
+
Evaluator,
|
|
35
|
+
evaluate_dirs,
|
|
36
|
+
evaluate_pair,
|
|
37
|
+
)
|
|
38
|
+
from .auc import (
|
|
39
|
+
DEFAULT_ALPHAS,
|
|
40
|
+
evaluate_hint_curve,
|
|
41
|
+
hint_auc,
|
|
42
|
+
hint_auc_table,
|
|
43
|
+
trapz,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
__version__ = "0.1.0"
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"DEFAULT_ALPHAS",
|
|
50
|
+
"DEFAULT_HINT_SIZE",
|
|
51
|
+
"DEFAULT_METRICS",
|
|
52
|
+
"FELZENSZWALB_PARAMS",
|
|
53
|
+
"LOWER_IS_BETTER",
|
|
54
|
+
"Evaluator",
|
|
55
|
+
"HintResult",
|
|
56
|
+
"evaluate_dirs",
|
|
57
|
+
"evaluate_hint_curve",
|
|
58
|
+
"evaluate_pair",
|
|
59
|
+
"generate_hints",
|
|
60
|
+
"hint_auc",
|
|
61
|
+
"hint_auc_table",
|
|
62
|
+
"region_ids",
|
|
63
|
+
"segment_regions",
|
|
64
|
+
"trapz",
|
|
65
|
+
"__version__",
|
|
66
|
+
]
|
hintauc/auc.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Hint-AUC: integrate per-ratio metric scores over the hint-ratio grid.
|
|
2
|
+
|
|
3
|
+
Port of ``evaluation/calc_hint_auc.py``: plain trapezoidal integration
|
|
4
|
+
of metric(alpha) over alpha in [0, 1] (the alpha range has length 1, so the
|
|
5
|
+
integral equals the range-normalized value).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Dict, Mapping, Optional, Sequence
|
|
11
|
+
|
|
12
|
+
#: the paper's front-loaded hint-ratio grid
|
|
13
|
+
DEFAULT_ALPHAS = (0.00, 0.01, 0.03, 0.05, 0.10, 0.25, 0.50, 1.00)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def trapz(xs: Sequence[float], ys: Sequence[float]) -> float:
|
|
17
|
+
"""Trapezoidal rule (port of calc_hint_auc.trapz)."""
|
|
18
|
+
if len(xs) != len(ys) or len(xs) < 2:
|
|
19
|
+
raise ValueError("need >= 2 (x, y) points with matching lengths")
|
|
20
|
+
area = 0.0
|
|
21
|
+
for i in range(1, len(xs)):
|
|
22
|
+
area += (xs[i] - xs[i - 1]) * (ys[i] + ys[i - 1]) / 2.0
|
|
23
|
+
return area
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def hint_auc(scores_by_alpha: Mapping[float, float]) -> float:
|
|
27
|
+
"""Hint-AUC of one metric from ``{alpha: score}``.
|
|
28
|
+
|
|
29
|
+
>>> hint_auc({0.0: 0.44, 0.01: 0.33, ..., 1.0: 0.10})
|
|
30
|
+
"""
|
|
31
|
+
xs = sorted(scores_by_alpha)
|
|
32
|
+
ys = [scores_by_alpha[a] for a in xs]
|
|
33
|
+
return trapz(xs, ys)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def hint_auc_table(
|
|
37
|
+
metrics_by_alpha: Mapping[float, Mapping[str, float]],
|
|
38
|
+
metrics: Optional[Sequence[str]] = None,
|
|
39
|
+
) -> Dict[str, float]:
|
|
40
|
+
"""Hint-AUC per metric from ``{alpha: {metric: score}}``."""
|
|
41
|
+
alphas = sorted(metrics_by_alpha)
|
|
42
|
+
if metrics is None:
|
|
43
|
+
metrics = sorted({m for a in alphas for m in metrics_by_alpha[a]})
|
|
44
|
+
out = {}
|
|
45
|
+
for m in metrics:
|
|
46
|
+
out[m] = hint_auc({a: metrics_by_alpha[a][m] for a in alphas})
|
|
47
|
+
return out
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def evaluate_hint_curve(
|
|
51
|
+
preds_by_alpha: Mapping[float, str],
|
|
52
|
+
gt_dir: str,
|
|
53
|
+
evaluator=None,
|
|
54
|
+
metrics: Sequence[str] = ("mse", "psnr", "ssim"),
|
|
55
|
+
pairing: str = "sorted",
|
|
56
|
+
limit: int = 0,
|
|
57
|
+
) -> Dict[str, object]:
|
|
58
|
+
"""End-to-end Hint-AUC over per-ratio prediction directories.
|
|
59
|
+
|
|
60
|
+
``preds_by_alpha`` maps each hint ratio alpha to a directory of the
|
|
61
|
+
colorizations produced with hints at that ratio (paper grid:
|
|
62
|
+
:data:`DEFAULT_ALPHAS`). Returns per-alpha mean metrics and the
|
|
63
|
+
Hint-AUC of every metric.
|
|
64
|
+
"""
|
|
65
|
+
from .metrics import Evaluator, evaluate_dirs
|
|
66
|
+
|
|
67
|
+
ev = evaluator or Evaluator(metrics=metrics)
|
|
68
|
+
per_alpha: Dict[float, Dict[str, float]] = {}
|
|
69
|
+
for alpha in sorted(preds_by_alpha):
|
|
70
|
+
per_alpha[alpha] = evaluate_dirs(
|
|
71
|
+
preds_by_alpha[alpha], gt_dir, evaluator=ev,
|
|
72
|
+
pairing=pairing, limit=limit)
|
|
73
|
+
return {"per_alpha": per_alpha, "hint_auc": hint_auc_table(per_alpha)}
|
hintauc/cli.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Command-line interface: ``hintauc generate`` / ``hintauc eval``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main(argv=None):
|
|
12
|
+
p = argparse.ArgumentParser(
|
|
13
|
+
prog="hintauc",
|
|
14
|
+
description="Deterministic hint generation (DHT) and Hint-AUC evaluation")
|
|
15
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
16
|
+
|
|
17
|
+
g = sub.add_parser("generate", help="generate deterministic hints for an image")
|
|
18
|
+
g.add_argument("image", help="input image (ground-truth color image)")
|
|
19
|
+
g.add_argument("-o", "--out_stem", default=None,
|
|
20
|
+
help="output stem (default: <image without extension>)")
|
|
21
|
+
g.add_argument("--size", type=int, default=64, help="hint resolution (default 64)")
|
|
22
|
+
g.add_argument("--segmenter", default="felzenszwalb",
|
|
23
|
+
choices=["felzenszwalb", "slic", "quickshift"])
|
|
24
|
+
g.add_argument("--ratio", type=float, default=None,
|
|
25
|
+
help="also write hints masked to this hint ratio (0..1)")
|
|
26
|
+
g.add_argument("--hint_type", default="scribble", choices=["scribble", "dot"])
|
|
27
|
+
g.add_argument("--path_method", default="filfinder", choices=["filfinder", "geodesic"],
|
|
28
|
+
help="longest-path extraction: 'filfinder' (paper) or 'geodesic' (dependency-free, deterministic)")
|
|
29
|
+
g.add_argument("--tie_break", default="default", choices=["default", "stable"],
|
|
30
|
+
help="order of equal-area regions when selecting by --ratio: 'default' (paper: NumPy argsort) or 'stable' (ascending label; version-independent)")
|
|
31
|
+
g.add_argument("--dot_method", default="medoid", choices=["medoid", "mean", "nearest_mean"],
|
|
32
|
+
help="dot placement: 'mean' (paper: truncated mean of the path, may leave the region) or 'medoid' (in-region path pixel nearest to the mean)")
|
|
33
|
+
g.add_argument("-v", "--verbose", action="store_true")
|
|
34
|
+
|
|
35
|
+
e = sub.add_parser("eval", help="evaluate colorization(s) against ground truth")
|
|
36
|
+
e.add_argument("pred", help="predicted image or directory")
|
|
37
|
+
e.add_argument("gt", help="ground-truth image or directory")
|
|
38
|
+
e.add_argument("--metrics", nargs="+",
|
|
39
|
+
default=["mse", "psnr", "ssim"],
|
|
40
|
+
help="subset of: mse psnr ssim lpips openclip dino dreamsim")
|
|
41
|
+
e.add_argument("--pairing", default="sorted", choices=["sorted", "name"])
|
|
42
|
+
e.add_argument("--resize", type=int, default=256)
|
|
43
|
+
e.add_argument("--device", default=None)
|
|
44
|
+
e.add_argument("--limit", type=int, default=0)
|
|
45
|
+
|
|
46
|
+
args = p.parse_args(argv)
|
|
47
|
+
|
|
48
|
+
if args.cmd == "generate":
|
|
49
|
+
from .hints import generate_hints
|
|
50
|
+
import cv2
|
|
51
|
+
|
|
52
|
+
res = generate_hints(args.image, size=args.size,
|
|
53
|
+
segmenter=args.segmenter, verbose=args.verbose,
|
|
54
|
+
path_method=args.path_method, dot_method=args.dot_method)
|
|
55
|
+
stem = args.out_stem or os.path.splitext(args.image)[0]
|
|
56
|
+
paths = res.save(stem)
|
|
57
|
+
if args.ratio is not None:
|
|
58
|
+
color, mask = res.at_ratio(args.ratio, hint_type=args.hint_type, tie_break=args.tie_break)
|
|
59
|
+
pct = int(round(args.ratio * 100))
|
|
60
|
+
cv2.imwrite(f"{stem}_{args.hint_type}_col{res.size}_r{pct}.png", color)
|
|
61
|
+
cv2.imwrite(f"{stem}_{args.hint_type}_mask{res.size}_r{pct}.png", mask)
|
|
62
|
+
paths[f"ratio_{pct}"] = f"{stem}_{args.hint_type}_*{res.size}_r{pct}.png"
|
|
63
|
+
info = {"n_regions": res.n_regions(), "failed_regions": res.failed_regions,
|
|
64
|
+
"path_method": res.path_method, "dot_method": res.dot_method, "outputs": paths}
|
|
65
|
+
print(json.dumps(info, indent=1))
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
if args.cmd == "eval":
|
|
69
|
+
from .metrics import Evaluator, evaluate_dirs
|
|
70
|
+
|
|
71
|
+
ev = Evaluator(metrics=args.metrics, device=args.device, resize=args.resize)
|
|
72
|
+
if os.path.isdir(args.pred):
|
|
73
|
+
scores = evaluate_dirs(args.pred, args.gt, evaluator=ev,
|
|
74
|
+
pairing=args.pairing, limit=args.limit)
|
|
75
|
+
else:
|
|
76
|
+
scores = ev(args.pred, args.gt)
|
|
77
|
+
print(json.dumps(scores, indent=1))
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
return 1
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
sys.exit(main())
|