ocr-foundry 0.2.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.
- khocr_gen/__init__.py +11 -0
- khocr_gen/_pil_compat.py +25 -0
- khocr_gen/_rust_accel.py +204 -0
- khocr_gen/augmentation.py +1193 -0
- khocr_gen/benchmark.py +888 -0
- khocr_gen/benchmark_cmd.py +103 -0
- khocr_gen/cli.py +246 -0
- khocr_gen/combine.py +214 -0
- khocr_gen/combine_cmd.py +125 -0
- khocr_gen/config.py +1474 -0
- khocr_gen/config_loader.py +157 -0
- khocr_gen/corpus.py +621 -0
- khocr_gen/data_generator.py +1160 -0
- khocr_gen/errors.py +33 -0
- khocr_gen/evaluate_cmd.py +50 -0
- khocr_gen/evaluation.py +259 -0
- khocr_gen/fonts.py +316 -0
- khocr_gen/generate.py +227 -0
- khocr_gen/layout.py +1028 -0
- khocr_gen/layout_cmd.py +371 -0
- khocr_gen/layout_degradation.py +128 -0
- khocr_gen/layout_mesh.py +200 -0
- khocr_gen/layout_shaping.py +264 -0
- khocr_gen/layout_transform.py +238 -0
- khocr_gen/layout_warp.py +473 -0
- khocr_gen/line_height.py +127 -0
- khocr_gen/lmdb_pack.py +132 -0
- khocr_gen/logging.py +25 -0
- khocr_gen/normalizer.py +221 -0
- khocr_gen/parallel.py +366 -0
- khocr_gen/provenance.py +328 -0
- khocr_gen/rendering.py +1607 -0
- khocr_gen/text_support.py +429 -0
- khocr_gen/verify.py +471 -0
- khocr_gen/viewer.py +172 -0
- ocr_foundry-0.2.0.dist-info/METADATA +174 -0
- ocr_foundry-0.2.0.dist-info/RECORD +40 -0
- ocr_foundry-0.2.0.dist-info/WHEEL +4 -0
- ocr_foundry-0.2.0.dist-info/entry_points.txt +3 -0
- ocr_foundry-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""YAML configuration loader for CLI subcommands.
|
|
2
|
+
|
|
3
|
+
Loading order (highest priority wins):
|
|
4
|
+
1. argparse built-in defaults (lowest)
|
|
5
|
+
2. YAML config file values (middle)
|
|
6
|
+
3. Explicit CLI flags (highest)
|
|
7
|
+
|
|
8
|
+
Keys in the YAML file may use either dashes or underscores;
|
|
9
|
+
both are normalized to underscores to match argparse `dest` names.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
_log = logging.getLogger("khocr_gen.config_loader")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_yaml_config(path: str | Path) -> dict[str, Any]:
|
|
23
|
+
"""Load a YAML file and return a flat `{dest: value}` dict.
|
|
24
|
+
|
|
25
|
+
Keys are normalized: hyphens -> underscores (matching argparse dest names).
|
|
26
|
+
Only top-level scalar / list values are supported; nested mappings are ignored with a warning.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
FileNotFoundError: If *path* does not exist.
|
|
30
|
+
ImportError: If PyYAML is not installed.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
import yaml # type: ignore[import-untyped]
|
|
34
|
+
except ImportError as exc:
|
|
35
|
+
raise ImportError(
|
|
36
|
+
"PyYAML is required to use --config (-c). Install it with: pip install pyyaml"
|
|
37
|
+
) from exc
|
|
38
|
+
|
|
39
|
+
path = Path(path)
|
|
40
|
+
if not path.exists():
|
|
41
|
+
raise FileNotFoundError(f"Config file not found: {path}")
|
|
42
|
+
|
|
43
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
44
|
+
raw = yaml.safe_load(fh)
|
|
45
|
+
|
|
46
|
+
if raw is None:
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
if not isinstance(raw, dict):
|
|
50
|
+
raise ValueError(f"Config file must be a YAML mapping (dict) at the top level: {path}")
|
|
51
|
+
|
|
52
|
+
result: dict[str, Any] = {}
|
|
53
|
+
for key, value in raw.items():
|
|
54
|
+
norm_key = str(key).replace("-", "_")
|
|
55
|
+
if isinstance(value, dict):
|
|
56
|
+
if norm_key in {
|
|
57
|
+
"script_intensity_scales", "script_intensity_scale",
|
|
58
|
+
"font_intensity_scales", "font_intensity_scale",
|
|
59
|
+
}:
|
|
60
|
+
result[norm_key] = value
|
|
61
|
+
continue
|
|
62
|
+
_log.warning(
|
|
63
|
+
"Config key %r is a nested mapping; only flat keys are supported, skipping.",
|
|
64
|
+
key,
|
|
65
|
+
)
|
|
66
|
+
continue
|
|
67
|
+
result[norm_key] = value
|
|
68
|
+
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _build_dest_aliases(parser: argparse.ArgumentParser) -> dict[str, str]:
|
|
73
|
+
"""Map option-string spellings to real `dest` names."""
|
|
74
|
+
aliases: dict[str, str] = {}
|
|
75
|
+
for action in parser._actions:
|
|
76
|
+
dest = action.dest
|
|
77
|
+
if not dest or dest == argparse.SUPPRESS:
|
|
78
|
+
continue
|
|
79
|
+
aliases[dest] = dest
|
|
80
|
+
for opt in action.option_strings:
|
|
81
|
+
if opt.startswith("--"):
|
|
82
|
+
aliases.setdefault(opt[2:].replace("-", "_"), dest)
|
|
83
|
+
aliases.setdefault("script_intensity_scales", "script_intensity_scale")
|
|
84
|
+
aliases.setdefault("font_intensity_scales", "font_intensity_scale")
|
|
85
|
+
return aliases
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _resolve_config_keys(parser: argparse.ArgumentParser, config: dict[str, Any]) -> dict[str, Any]:
|
|
89
|
+
"""Remap *config*'s keys to real argparse `dest` names where they differ."""
|
|
90
|
+
aliases = _build_dest_aliases(parser)
|
|
91
|
+
result: dict[str, Any] = {}
|
|
92
|
+
for k, v in config.items():
|
|
93
|
+
norm_k = str(k).replace("-", "_")
|
|
94
|
+
result[aliases.get(norm_k, norm_k)] = v
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def validate_config_keys(
|
|
99
|
+
parser: argparse.ArgumentParser,
|
|
100
|
+
config: dict[str, Any],
|
|
101
|
+
command: str,
|
|
102
|
+
strict: bool = False,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Validate that all keys in *config* correspond to registered arguments.
|
|
105
|
+
|
|
106
|
+
If *strict*, exit with code 2 on unrecognized keys; otherwise log a warning.
|
|
107
|
+
"""
|
|
108
|
+
valid_dests = {action.dest for action in parser._actions}
|
|
109
|
+
aliases = _build_dest_aliases(parser)
|
|
110
|
+
invalid_keys = [k for k in config if k not in valid_dests and k not in aliases]
|
|
111
|
+
if invalid_keys:
|
|
112
|
+
msg = (
|
|
113
|
+
f"[config] WARNING: Unrecognized keys in '{command}' configuration: "
|
|
114
|
+
f"{', '.join(sorted(invalid_keys))}"
|
|
115
|
+
)
|
|
116
|
+
if strict:
|
|
117
|
+
import sys
|
|
118
|
+
|
|
119
|
+
print(f"error: {msg}", file=sys.stderr)
|
|
120
|
+
sys.exit(2)
|
|
121
|
+
else:
|
|
122
|
+
_log.warning("%s", msg)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def apply_config_defaults(
|
|
126
|
+
parser: argparse.ArgumentParser,
|
|
127
|
+
config: dict[str, Any],
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Push *config* values into *parser* as low-priority defaults.
|
|
130
|
+
|
|
131
|
+
argparse guarantees that values set via `set_defaults` are overridden
|
|
132
|
+
by any matching CLI flag actually present on the command line.
|
|
133
|
+
"""
|
|
134
|
+
if config:
|
|
135
|
+
parser.set_defaults(**_resolve_config_keys(parser, config))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def resolve_config_path(
|
|
139
|
+
config_arg: str | None,
|
|
140
|
+
command: str,
|
|
141
|
+
cwd: str | Path | None = None,
|
|
142
|
+
) -> Path | None:
|
|
143
|
+
"""Resolve the config file to load.
|
|
144
|
+
|
|
145
|
+
Priority:
|
|
146
|
+
1. Explicit `--config` / `-c` value.
|
|
147
|
+
2. `configs/<command>.yml` relative to *cwd*.
|
|
148
|
+
"""
|
|
149
|
+
if config_arg is not None:
|
|
150
|
+
return Path(config_arg)
|
|
151
|
+
|
|
152
|
+
base = Path(cwd) if cwd is not None else Path.cwd()
|
|
153
|
+
candidate = base / "configs" / f"{command}.yml"
|
|
154
|
+
if candidate.exists():
|
|
155
|
+
return candidate
|
|
156
|
+
|
|
157
|
+
return None
|