dikdimon commited on
Commit
2190bbe
·
verified ·
1 Parent(s): 46e02f4

Upload sd-webui-kohya-hiresfix-verold using SD-Hub

Browse files
sd-webui-kohya-hiresfix-verold/.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
sd-webui-kohya-hiresfix-verold/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+
2
+ *.pyc
3
+ config.yaml
sd-webui-kohya-hiresfix-verold/README.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Implementation Kohya Hires.fix for Auto1111 webui
2
+
3
+ #### Stop step - at which sampling step disable fix, increase at higher resolution
4
+ #### Depth - on which layer fix will be applied
5
+ #### Downsampling scale - decrease at higher resolution, helps preserve composition at very high resolutions
6
+ #### Upsampling scale - increasing can slightly improve quality at cost of VRAM
7
+
8
+ ## Source:
9
+ https://gist.github.com/kohya-ss/3f774da220df102548093a7abc8538ed
sd-webui-kohya-hiresfix-verold/config.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ enable: false
2
+ only_one_pass: true
3
+ d1: 3
4
+ d2: 4
5
+ s1: 0.15
6
+ s2: 0.3
7
+ scaler: bicubic
8
+ downscale: 0.5
9
+ upscale: 2
10
+ smooth_scaling: true
11
+ early_out: false
sd-webui-kohya-hiresfix-verold/scripts/__pycache__/khrfix.cpython-310.pyc ADDED
Binary file (42.9 kB). View file
 
sd-webui-kohya-hiresfix-verold/scripts/__pycache__/khrfix_olds.cpython-310.pyc ADDED
Binary file (5.72 kB). View file
 
sd-webui-kohya-hiresfix-verold/scripts/khrfix.py ADDED
@@ -0,0 +1,1406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # kohya_hires_fix_unified_v2.5.3_ultimate.py
2
+ # Версия: 2.5.3 (Ultimate: v19 Features + Old Math Fix)
3
+ # Совместимость: A1111 / modules.scripts API, PyTorch >= 1.12
4
+ #
5
+ # Изменения:
6
+ # - В основе лежит код версии 2.5.1 (v19) со всеми проверками безопасности и логами.
7
+ # - Внедрена логика "Старой математики" (float vs int) для резкости.
8
+ # - Внедрена логика "Старого одного прохода" (step vs flag).
9
+ # - Добавлены настройки совместимости в UI.
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import math
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List, Optional, Tuple
17
+
18
+ import gradio as gr
19
+ import torch
20
+ import torch.nn.functional as F
21
+ from omegaconf import DictConfig, OmegaConf
22
+ from modules import scripts, script_callbacks
23
+
24
+ CONFIG_PATH = Path(__file__).with_suffix(".yaml")
25
+ PRESETS_PATH = Path(__file__).with_name(Path(__file__).stem + ".presets.yaml")
26
+
27
+ # ---- Предустановленные разрешения ----
28
+
29
+ RESOLUTION_GROUPS = {
30
+ "Квадрат": [(1024, 1024)],
31
+ "Портрет": [(640, 1536), (768, 1344), (832, 1216), (896, 1152), (768, 1152)],
32
+ "Альбом": [(1536, 640), (1344, 768), (1216, 832), (1152, 896), (1024, 1536)],
33
+ }
34
+ RESOLUTION_CHOICES: List[str] = ["— не применять —"]
35
+ for group, dims in RESOLUTION_GROUPS.items():
36
+ for w, h in dims:
37
+ RESOLUTION_CHOICES.append(f"{group}: {w}x{h}")
38
+
39
+
40
+ def parse_resolution_label(label: str) -> Optional[Tuple[int, int]]:
41
+ if not label or label.startswith("—"):
42
+ return None
43
+ try:
44
+ _, wh = label.split(":")
45
+ w, h = wh.strip().lower().split("x")
46
+ return int(w), int(h)
47
+ except Exception:
48
+ return None
49
+
50
+
51
+ # ---- Вспомогательные утилиты ----
52
+
53
+ def _safe_mode(mode: str) -> str:
54
+ if mode == "nearest-exact":
55
+ return mode
56
+ if mode in {"bicubic", "bilinear", "nearest"}:
57
+ return mode
58
+ return "bilinear"
59
+
60
+
61
+ def _load_yaml(path: Path, default: dict) -> dict:
62
+ try:
63
+ return OmegaConf.to_container(OmegaConf.load(path), resolve=True) or default
64
+ except Exception:
65
+ return default
66
+
67
+
68
+ def _atomic_save_yaml(path: Path, data: dict) -> None:
69
+ try:
70
+ tmp = path.with_suffix(path.suffix + ".tmp")
71
+ OmegaConf.save(DictConfig(data), tmp)
72
+ tmp.replace(path)
73
+ except Exception as e:
74
+ print(f"[KohyaHiresFix] Failed to save config: {e}")
75
+
76
+
77
+ def _load_presets() -> Dict[str, dict]:
78
+ data = _load_yaml(PRESETS_PATH, {})
79
+ return {str(k): dict(v) for k, v in data.items()}
80
+
81
+
82
+ def _save_presets(presets: Dict[str, dict]) -> None:
83
+ _atomic_save_yaml(PRESETS_PATH, presets)
84
+
85
+
86
+ def _clamp(x: float, lo: float, hi: float) -> float:
87
+ return float(max(lo, min(hi, x)))
88
+
89
+
90
+ def _norm_mode_choice(value: str, default_: str = "auto") -> str:
91
+ try:
92
+ v = str(value).strip().lower()
93
+ except Exception:
94
+ v = ""
95
+ if v in {"true", "t", "1", "yes", "y"}:
96
+ return "true"
97
+ if v in {"false", "f", "0", "no", "n"}:
98
+ return "false"
99
+ if v in {"auto", "a", "авто"}:
100
+ return "auto"
101
+ return str(default_).strip().lower()
102
+
103
+
104
+ def _compute_adaptive_params(
105
+ width: int,
106
+ height: int,
107
+ profile: str,
108
+ base_s1: float,
109
+ base_s2: float,
110
+ base_d1: int,
111
+ base_d2: int,
112
+ base_down: float,
113
+ base_up: float,
114
+ keep_unitary_product: bool,
115
+ ) -> Tuple[float, float, int, int, float, float]:
116
+ rel_mpx = (max(1, int(width)) * max(1, int(height))) / float(1024 * 1024)
117
+ aspect = max(width, height) / float(min(width, height))
118
+
119
+ prof = (profile or "").strip().lower()
120
+ s1 = float(base_s1)
121
+ s2 = float(base_s2)
122
+ d1 = int(base_d1)
123
+ d2 = int(base_d2)
124
+ down = float(base_down)
125
+ up = float(base_up)
126
+
127
+ if prof.startswith("конс"):
128
+ s_add = -0.02
129
+ d_add = 0
130
+ elif prof.startswith("агре"):
131
+ s_add = 0.05
132
+ d_add = 1
133
+ else:
134
+ s_add = 0.0
135
+ d_add = 0
136
+
137
+ if rel_mpx >= 1.5:
138
+ s_add += 0.08
139
+ down -= 0.10
140
+ elif rel_mpx >= 1.1:
141
+ s_add += 0.05
142
+ down -= 0.05
143
+ elif rel_mpx <= 0.8:
144
+ s_add -= 0.02
145
+ down += 0.05
146
+
147
+ if aspect >= 1.6:
148
+ d_add += 1
149
+ elif aspect <= 1.1:
150
+ d_add -= 1
151
+
152
+ s1 = _clamp(s1 + s_add * 0.7, 0.0, 1.0)
153
+ s2 = _clamp(s2 + s_add, 0.0, 1.0)
154
+
155
+ d1 = max(1, d1 + d_add)
156
+ d2 = max(1, d2 + d_add)
157
+
158
+ down = _clamp(down, 0.1, 1.0)
159
+ if keep_unitary_product:
160
+ up = min(10.0, 1.0 / max(0.1, down))
161
+ else:
162
+ up = _clamp(up * (base_down / max(1e-6, down)), 1.0, 4.0)
163
+
164
+ return s1, s2, d1, d2, down, up
165
+
166
+
167
+ # ---- Класс пресета ----
168
+
169
+ class HiresPreset:
170
+ def __init__(self, **kwargs: Any) -> None:
171
+ self.category: str = "Общие"
172
+ self.algo_mode: str = "Enhanced (RU+)"
173
+
174
+ self.d1: int = 3
175
+ self.d2: int = 4
176
+ self.s1: float = 0.15
177
+ self.s2: float = 0.30
178
+
179
+ self.scaler: str = "bicubic"
180
+ self.downscale: float = 0.5
181
+ self.upscale: float = 2.0
182
+
183
+ self.smooth_scaling_enh: bool = True
184
+ self.smooth_scaling_legacy: bool = True
185
+
186
+ self.smoothing_curve: str = "Линейная"
187
+
188
+ self.early_out: bool = False
189
+
190
+ self.only_one_pass_enh: bool = True
191
+ self.only_one_pass_legacy: bool = True
192
+
193
+ self.depth_guard: bool = True
194
+ self.depth_clamp: bool = True
195
+ self.depth_pair_mode: str = "Авто (по алгоритму)" # 🆕 режим обработки пар d1/d2
196
+
197
+ self.keep_unitary_product: bool = False
198
+ self.align_corners_mode: str = "False"
199
+ self.recompute_scale_factor_mode: str = "False"
200
+ self.smoothing_mode: str = "Авто (по алгоритму)"
201
+ self.one_pass_mode: str = "Авто (по алгоритму)"
202
+
203
+ self.resolution_choice: str = RESOLUTION_CHOICES[0]
204
+ self.apply_resolution: bool = False
205
+ self.adaptive_by_resolution: bool = True
206
+ self.adaptive_profile: str = "Сбалансированный"
207
+
208
+ # 🆕 НОВЫЕ ФЛАГИ (из v2.5.2)
209
+ self.use_old_float_math: bool = True
210
+ self.use_old_onepass_logic: bool = True
211
+ self.legacy_force_align_false: bool = True
212
+ self.enhanced_safe_upscale_clamp: bool = True
213
+
214
+ # Legacy keys support
215
+ legacy_smooth = kwargs.pop("smooth_scaling", None)
216
+ legacy_one = kwargs.pop("only_one_pass", None)
217
+
218
+ for k, v in kwargs.items():
219
+ if hasattr(self, k):
220
+ setattr(self, k, v)
221
+
222
+ if legacy_smooth is not None:
223
+ self.smooth_scaling_enh = bool(legacy_smooth)
224
+ self.smooth_scaling_legacy = bool(legacy_smooth)
225
+ if legacy_one is not None:
226
+ self.only_one_pass_enh = bool(legacy_one)
227
+ self.only_one_pass_legacy = bool(legacy_one)
228
+
229
+ def to_dict(self) -> Dict[str, Any]:
230
+ return {
231
+ "category": self.category,
232
+ "algo_mode": self.algo_mode,
233
+ "d1": self.d1,
234
+ "d2": self.d2,
235
+ "s1": self.s1,
236
+ "s2": self.s2,
237
+ "scaler": self.scaler,
238
+ "downscale": self.downscale,
239
+ "upscale": self.upscale,
240
+ "smooth_scaling_enh": self.smooth_scaling_enh,
241
+ "smooth_scaling_legacy": self.smooth_scaling_legacy,
242
+ "smoothing_curve": self.smoothing_curve,
243
+ "early_out": self.early_out,
244
+ "only_one_pass_enh": self.only_one_pass_enh,
245
+ "only_one_pass_legacy": self.only_one_pass_legacy,
246
+ "depth_guard": self.depth_guard,
247
+ "depth_clamp": self.depth_clamp,
248
+ "depth_pair_mode": self.depth_pair_mode,
249
+ "keep_unitary_product": self.keep_unitary_product,
250
+ "align_corners_mode": self.align_corners_mode,
251
+ "recompute_scale_factor_mode": self.recompute_scale_factor_mode,
252
+ "smoothing_mode": self.smoothing_mode,
253
+ "one_pass_mode": self.one_pass_mode,
254
+ "resolution_choice": self.resolution_choice,
255
+ "apply_resolution": self.apply_resolution,
256
+ "adaptive_by_resolution": self.adaptive_by_resolution,
257
+ "adaptive_profile": self.adaptive_profile,
258
+ "use_old_float_math": self.use_old_float_math,
259
+ "use_old_onepass_logic": self.use_old_onepass_logic,
260
+ "legacy_force_align_false": self.legacy_force_align_false,
261
+ "enhanced_safe_upscale_clamp": self.enhanced_safe_upscale_clamp,
262
+ }
263
+
264
+ # КОНЕЦ ЧАСТИ 1
265
+
266
+ # НАЧАЛО ЧАСТИ 2
267
+
268
+ DEFAULT_PRESETS: Dict[str, Dict[str, Any]] = {
269
+ "XL · портрет (безопасный)": {
270
+ "category": "XL",
271
+ "algo_mode": "Enhanced (RU+)",
272
+ "resolution_choice": "Портрет: 832x1216",
273
+ "apply_resolution": True,
274
+ "adaptive_by_resolution": True,
275
+ "adaptive_profile": "Сбалансированный",
276
+ "d1": 3,
277
+ "s1": 0.18,
278
+ "d2": 5,
279
+ "s2": 0.32,
280
+ "scaler": "bicubic",
281
+ "downscale": 0.6,
282
+ "upscale": 1.8,
283
+ "smooth_scaling_enh": True,
284
+ "smooth_scaling_legacy": True,
285
+ "smoothing_curve": "Smoothstep",
286
+ "early_out": False,
287
+ "only_one_pass_enh": True,
288
+ "only_one_pass_legacy": True,
289
+ "keep_unitary_product": True,
290
+ "use_old_float_math": True,
291
+ "use_old_onepass_logic": True,
292
+ "legacy_force_align_false": True,
293
+ "enhanced_safe_upscale_clamp": True,
294
+ },
295
+ "SD15 · Legacy Old Style": {
296
+ "category": "SD15",
297
+ "algo_mode": "Legacy (Original)",
298
+ "resolution_choice": "— не применять —",
299
+ "apply_resolution": False,
300
+ "adaptive_by_resolution": False,
301
+ "d1": 3,
302
+ "s1": 0.15,
303
+ "d2": 4,
304
+ "s2": 0.30,
305
+ "scaler": "bicubic",
306
+ "downscale": 0.5,
307
+ "upscale": 2.0,
308
+ "smooth_scaling_enh": True,
309
+ "smooth_scaling_legacy": True,
310
+ "early_out": False,
311
+ "only_one_pass_enh": True,
312
+ "only_one_pass_legacy": True,
313
+ "use_old_float_math": True,
314
+ "use_old_onepass_logic": True,
315
+ "legacy_force_align_false": True,
316
+ "enhanced_safe_upscale_clamp": True,
317
+ },
318
+ }
319
+
320
+
321
+ class PresetManager:
322
+ def __init__(self) -> None:
323
+ self._cache: Dict[str, HiresPreset] = {}
324
+ self.reload()
325
+
326
+ def reload(self) -> None:
327
+ raw = _load_presets()
328
+ if raw is None:
329
+ raw = {}
330
+ if not raw:
331
+ raw = {name: pdata.copy() for name, pdata in DEFAULT_PRESETS.items()}
332
+
333
+ self._cache.clear()
334
+ for name, data in raw.items():
335
+ base = HiresPreset().to_dict()
336
+ if isinstance(data, dict):
337
+ base.update(data or {})
338
+ try:
339
+ self._cache[str(name)] = HiresPreset(**base)
340
+ except Exception:
341
+ continue
342
+
343
+ def _save(self) -> None:
344
+ raw = {name: preset.to_dict() for name, preset in self._cache.items()}
345
+ _save_presets(raw)
346
+
347
+ def names(self) -> List[str]:
348
+ return sorted(self._cache.keys())
349
+
350
+ def get(self, name: str) -> Optional[HiresPreset]:
351
+ return self._cache.get(name)
352
+
353
+ def upsert(self, name: str, preset: HiresPreset) -> None:
354
+ self._cache[name] = preset
355
+ self._save()
356
+
357
+ def delete(self, name: str) -> None:
358
+ if name in self._cache:
359
+ del self._cache[name]
360
+ self._save()
361
+
362
+ def categories(self) -> List[str]:
363
+ cats = {(p.category or "Общие") for p in self._cache.values()}
364
+ return sorted(cats) if cats else []
365
+
366
+ def names_for_category(self, category: Optional[str]) -> List[str]:
367
+ if not category or category == "Все":
368
+ return self.names()
369
+ cat = category or "Общие"
370
+ return sorted(
371
+ name for name, preset in self._cache.items()
372
+ if (preset.category or "Общие") == cat
373
+ )
374
+
375
+
376
+ class Scaler(torch.nn.Module):
377
+ def __init__(
378
+ self,
379
+ scale: float,
380
+ block: torch.nn.Module,
381
+ scaler: str,
382
+ align_mode: str = "false",
383
+ recompute_mode: str = "false",
384
+ use_old_float_math: bool = True, # 🆕 ФЛАГ
385
+ ) -> None:
386
+ super().__init__()
387
+ self.scale: float = float(scale)
388
+ self.block: torch.nn.Module = block
389
+ self.scaler: str = _safe_mode(scaler)
390
+ self.align_mode: str = _norm_mode_choice(align_mode, "false")
391
+ self.recompute_mode: str = _norm_mode_choice(recompute_mode, "false")
392
+ self.use_old_float_math: bool = use_old_float_math # 🆕
393
+
394
+ def forward(self, x: torch.Tensor, *args, **kwargs):
395
+ if self.scale == 1.0:
396
+ return self.block(x, *args, **kwargs)
397
+
398
+ align_corners = None
399
+ if self.scaler in {"bilinear", "bicubic", "linear", "trilinear"}:
400
+ if self.align_mode == "true":
401
+ align_corners = True
402
+ elif self.align_mode == "false":
403
+ align_corners = False
404
+ else:
405
+ align_corners = None
406
+
407
+ recompute_scale_factor = None
408
+ if self.recompute_mode == "true":
409
+ recompute_scale_factor = True
410
+ elif self.recompute_mode == "false":
411
+ recompute_scale_factor = False
412
+
413
+ # 🆕 ГИБРИДНАЯ ЛОГИКА
414
+ if self.use_old_float_math:
415
+ # ✅ OLD (Legacy) - Прямая передача float, без округления размеров
416
+ x_scaled = F.interpolate(
417
+ x,
418
+ scale_factor=self.scale,
419
+ mode=self.scaler,
420
+ align_corners=align_corners,
421
+ recompute_scale_factor=recompute_scale_factor,
422
+ )
423
+ else:
424
+ # ✅ NEW (Safe) - Округление до целых пикселей
425
+ h, w = x.shape[-2:]
426
+ new_h = max(1, int(h * self.scale))
427
+ new_w = max(1, int(w * self.scale))
428
+ scale_factor = (new_h / h, new_w / w)
429
+
430
+ x_scaled = F.interpolate(
431
+ x,
432
+ scale_factor=scale_factor,
433
+ mode=self.scaler,
434
+ align_corners=align_corners,
435
+ recompute_scale_factor=recompute_scale_factor,
436
+ )
437
+
438
+ out = self.block(x_scaled, *args, **kwargs)
439
+ return out
440
+
441
+ # КОНЕЦ ЧАСТИ 2
442
+
443
+ # НАЧАЛО ЧАСТИ 3
444
+
445
+ class KohyaHiresFix(scripts.Script):
446
+ def __init__(self) -> None:
447
+ super().__init__()
448
+ self.config: DictConfig = DictConfig(_load_yaml(CONFIG_PATH, {}))
449
+ self.disable: bool = False
450
+ self.step_limit: int = 0
451
+ self.infotext_fields = []
452
+ self._cb_registered: bool = False
453
+
454
+ # Debug features
455
+ self.debug_mode: bool = False
456
+ self.debug_log: List[str] = []
457
+ self.debug_log_max_size: int = 100
458
+
459
+ def title(self) -> str:
460
+ return "Kohya Hires.fix · Unified (Ult)"
461
+
462
+ def show(self, is_img2img: bool):
463
+ return scripts.AlwaysVisible
464
+
465
+ def _append_debug(self, msg: str) -> None:
466
+ self.debug_log.append(str(msg))
467
+ if len(self.debug_log) > self.debug_log_max_size:
468
+ self.debug_log = self.debug_log[-self.debug_log_max_size :]
469
+
470
+ def _save_config(self) -> None:
471
+ """Автосохранение последней конфигурации в YAML."""
472
+ try:
473
+ data = OmegaConf.to_container(self.config, resolve=True)
474
+ _atomic_save_yaml(CONFIG_PATH, data)
475
+ except Exception as e:
476
+ print(f"[KohyaHiresFix] Failed to autosave YAML config: {e}")
477
+
478
+ @staticmethod
479
+ def _unwrap_all(model) -> None:
480
+ if not model:
481
+ return
482
+ # Protection against None blocks
483
+ for i, b in enumerate(getattr(model, "input_blocks", [])):
484
+ if b is not None and isinstance(b, Scaler):
485
+ model.input_blocks[i] = b.block
486
+ for i, b in enumerate(getattr(model, "output_blocks", [])):
487
+ if b is not None and isinstance(b, Scaler):
488
+ model.output_blocks[i] = b.block
489
+
490
+ @staticmethod
491
+ def _map_output_index(model, in_idx: int, early_out: bool) -> Optional[int]:
492
+ outs = getattr(model, "output_blocks", None)
493
+ if not outs:
494
+ return None
495
+ n = len(outs)
496
+ if early_out:
497
+ return max(0, min(int(in_idx), n - 1))
498
+ mirror = (n - 1) - int(in_idx)
499
+ return max(0, min(mirror, n - 1))
500
+
501
+ def ui(self, is_img2img: bool):
502
+ self.infotext_fields = []
503
+ pm = PresetManager()
504
+ cfg = self.config
505
+
506
+ # Load defaults (с поддержкой новых флагов)
507
+ last_algo_mode = cfg.get("algo_mode", "Enhanced (RU+)")
508
+ last_resolution_choice = cfg.get("resolution_choice", RESOLUTION_CHOICES[0])
509
+ last_apply_resolution = cfg.get("apply_resolution", False)
510
+ last_adaptive_by_resolution = cfg.get("adaptive_by_resolution", True)
511
+ last_adaptive_profile = cfg.get("adaptive_profile", "Сбалансированный")
512
+
513
+ last_s1 = float(cfg.get("s1", 0.15))
514
+ last_s2 = float(cfg.get("s2", 0.30))
515
+ last_d1 = int(cfg.get("d1", 3))
516
+ last_d2 = int(cfg.get("d2", 4))
517
+ last_scaler = cfg.get("scaler", "bicubic")
518
+ last_downscale = float(cfg.get("downscale", 0.5))
519
+ last_upscale = float(cfg.get("upscale", 2.0))
520
+
521
+ # ФЛАГИ ПО УМОЛЧАНИЮ = True (OLD Logic)
522
+ last_use_old_float_math = cfg.get("use_old_float_math", True)
523
+ last_use_old_onepass_logic = cfg.get("use_old_onepass_logic", True)
524
+ last_legacy_force_align_false = cfg.get("legacy_force_align_false", True)
525
+ last_enh_safe_clamp = cfg.get("enhanced_safe_upscale_clamp", True)
526
+
527
+ # Legacy fallback logic
528
+ legacy_smooth = cfg.get("smooth_scaling", None)
529
+ last_smooth_enh = bool(legacy_smooth) if legacy_smooth is not None else cfg.get("smooth_scaling_enh", True)
530
+ last_smooth_leg = bool(legacy_smooth) if legacy_smooth is not None else cfg.get("smooth_scaling_legacy", True)
531
+
532
+ legacy_one = cfg.get("only_one_pass", None)
533
+ last_only_enh = bool(legacy_one) if legacy_one is not None else cfg.get("only_one_pass_enh", True)
534
+ last_only_leg = bool(legacy_one) if legacy_one is not None else cfg.get("only_one_pass_legacy", True)
535
+
536
+ last_depth_guard = cfg.get("depth_guard", True)
537
+ last_depth_clamp = cfg.get("depth_clamp", True) # 🆕
538
+ last_depth_pair_mode = cfg.get("depth_pair_mode", "Авто (по алгоритму)") # 🆕
539
+ last_smoothing_curve = cfg.get("smoothing_curve", "Линейная")
540
+ last_early_out = cfg.get("early_out", False)
541
+ last_keep1 = cfg.get("keep_unitary_product", False)
542
+ last_align_mode = cfg.get("align_corners_mode", "False")
543
+ last_recompute_mode = cfg.get("recompute_scale_factor_mode", "False")
544
+ last_smoothing_mode = cfg.get("smoothing_mode", "Авто (по алгоритму)")
545
+ last_one_pass_mode = cfg.get("one_pass_mode", "Авто (по алгоритму)")
546
+ last_simple_mode = cfg.get("simple_mode", True)
547
+ last_stop_preview_enabled = cfg.get("stop_preview_enabled", False)
548
+ last_stop_preview_steps = int(cfg.get("stop_preview_steps", 30))
549
+
550
+ is_enhanced = (last_algo_mode == "Enhanced (RU+)")
551
+
552
+ def _format_stop_preview_text(total_steps: int, s1_v: float, s2_v: float) -> str:
553
+ total = max(1, int(total_steps))
554
+ lines = [f"Всего шагов: **{total}**"]
555
+ def _line(label: str, ratio: float) -> str:
556
+ safe_ratio = max(0.0, float(ratio))
557
+ if safe_ratio <= 0: return f"{label}: вы��л"
558
+ stop_step = max(0, math.ceil(total * safe_ratio))
559
+ return f"{label}: стоп на шаге **{min(total, stop_step)}** (s={safe_ratio:.2f})"
560
+ lines.append(_line("Пара 1", s1_v))
561
+ lines.append(_line("Пара 2", s2_v))
562
+ return "\n".join(lines)
563
+
564
+ with gr.Accordion(label="Kohya Hires.fix", open=False):
565
+ with gr.Row():
566
+ enable = gr.Checkbox(label="Включить расширение", value=False)
567
+ algo_mode = gr.Radio(choices=["Enhanced (RU+)", "Legacy (Original)"], value=last_algo_mode, label="Алгоритм")
568
+ status_indicator = gr.Markdown("🔴 **Отключено**", elem_classes=["status-indicator"])
569
+
570
+ with gr.Row():
571
+ gr.Markdown("**⚡ Быстрые пресеты:**")
572
+ btn_quick_safe = gr.Button("🛡️ Безопасный", size="sm", variant="secondary")
573
+ btn_quick_balanced = gr.Button("⚖️ Сбалансированный", size="sm", variant="secondary")
574
+ btn_quick_aggressive = gr.Button("🔥 Агрессивный", size="sm", variant="secondary")
575
+
576
+ with gr.Row():
577
+ simple_mode = gr.Checkbox(label="Простой режим", value=last_simple_mode)
578
+
579
+ with gr.Group():
580
+ gr.Markdown("**Базовые параметры**")
581
+ with gr.Row():
582
+ s1 = gr.Slider(0.0, 1.0, step=0.01, label="Остановить (s1)", value=last_s1)
583
+ d1 = gr.Slider(1, 10, step=1, label="Глубина (d1)", value=last_d1)
584
+ with gr.Row():
585
+ s2 = gr.Slider(0.0, 1.0, step=0.01, label="Остановить (s2)", value=last_s2)
586
+ d2 = gr.Slider(1, 10, step=1, label="Глубина (d2)", value=last_d2)
587
+ with gr.Row():
588
+ stop_preview_toggle = gr.Checkbox(label="Визуализация остановки", value=last_stop_preview_enabled)
589
+ stop_preview_steps = gr.Slider(1, 200, step=1, label="Шаги семплера", value=last_stop_preview_steps, visible=last_stop_preview_enabled)
590
+ stop_preview_md = gr.Markdown(value=_format_stop_preview_text(last_stop_preview_steps, last_s1, last_s2) if last_stop_preview_enabled else "", visible=last_stop_preview_enabled)
591
+
592
+ with gr.Row():
593
+ depth_guard = gr.Checkbox(
594
+ label="Автокоррекция глубины (swap d1/d2)",
595
+ value=last_depth_guard,
596
+ info="Если включено — при d2 < d1 они меняются местами, чтобы не ломать карту блоков."
597
+ )
598
+ depth_clamp = gr.Checkbox(
599
+ label="Жёсткий clamp глубины",
600
+ value=last_depth_clamp,
601
+ info="ВКЛ: d1/d2 принудительно зажимаются в [0..max]. ВЫКЛ: если индекс вне диапазона — этот depth просто пропускается."
602
+ )
603
+
604
+ # 🆕 ГРУППА СОВМЕСТИМОСТИ
605
+ with gr.Group():
606
+ gr.Markdown("### 🛠️ Отладка и совместимость (Влияет на математику)")
607
+ with gr.Row():
608
+ use_old_float_math = gr.Checkbox(
609
+ label="🛠️ Использовать \"Старую математику\" (Float)",
610
+ value=last_use_old_float_math,
611
+ info="ВКЛ: передает scale_factor напрямую (OLD). ВЫКЛ: с округлением int() (NEW)"
612
+ )
613
+ use_old_onepass_logic = gr.Checkbox(
614
+ label="🛠️ Строгий режим \"Один проход\" (Old Logic)",
615
+ value=last_use_old_onepass_logic,
616
+ info="ВКЛ: запоминает номер шага (OLD). ВЫКЛ: использует флаг (NEW)"
617
+ )
618
+ with gr.Row():
619
+ legacy_force_align_false = gr.Checkbox(
620
+ label="Legacy: align_corners/recompute = False",
621
+ value=last_legacy_force_align_false,
622
+ info="При включении в Legacy всегда использует align_corners=False и recompute_scale_factor=False (как в старом скрипте)."
623
+ )
624
+ enhanced_safe_clamp = gr.Checkbox(
625
+ label="Enhanced: безопасный clamp Upscale",
626
+ value=last_enh_safe_clamp,
627
+ info="При включении ограничивает Upscale (1–4). При выключении ведёт себя как Legacy/olds."
628
+ )
629
+
630
+ with gr.Row():
631
+ scaler = gr.Dropdown(choices=["bicubic", "bilinear", "nearest", "nearest-exact"], label="Интерполяция", value=last_scaler)
632
+ downscale = gr.Slider(0.1, 1.0, step=0.05, label="Downscale", value=last_downscale)
633
+ upscale = gr.Slider(1.0, 4.0, step=0.1, label="Upscale", value=last_upscale)
634
+
635
+ with gr.Row():
636
+ early_out = gr.Checkbox(label="Early Out", value=last_early_out)
637
+ only_one_pass_enh = gr.Checkbox(
638
+ label="Только один проход (Enh)",
639
+ value=last_only_enh,
640
+ visible=is_enhanced,
641
+ info="Enhanced: применяет масштаб только до одного стоп-интервала; дальше denoiser идёт как обычно."
642
+ )
643
+ only_one_pass_legacy = gr.Checkbox(
644
+ label="Только один проход (Leg)",
645
+ value=last_only_leg,
646
+ visible=not is_enhanced,
647
+ info="Legacy: строгая логика как в old.py — запоминает шаг и после него больше не трогает блоки."
648
+ )
649
+ one_pass_mode_select = gr.Dropdown(
650
+ choices=["Авто (по алгоритму)", "Использовать Enhanced", "Использовать Legacy old"],
651
+ value=last_one_pass_mode,
652
+ label="Логика One Pass",
653
+ info="Авто: берёт only_one_pass_enh/legacy в зависимости от алгоритма. Остальные режимы принудительно используют соответствующую логику даже в другом алгоритме."
654
+ )
655
+
656
+ with gr.Row():
657
+ param_warnings = gr.Markdown("", elem_classes=["warning-box"])
658
+
659
+ # КОНЕЦ ЧАСТИ 3
660
+
661
+ # НАЧАЛО ЧАСТИ 4 (ИСПРАВЛЕННАЯ ПОЛНАЯ ВЕРСИЯ)
662
+
663
+ with gr.Group(visible=not last_simple_mode) as advanced_group:
664
+ with gr.Group():
665
+ gr.Markdown("**Параметры сглаживания и разрешения**")
666
+ with gr.Row():
667
+ smooth_scaling_enh = gr.Checkbox(
668
+ label="Плавное изменение (Enh)",
669
+ value=last_smooth_enh,
670
+ visible=is_enhanced,
671
+ info="Для Enhanced: плавно двигает Downscale/Upscale от начального значения до стоп-шага."
672
+ )
673
+ smooth_scaling_legacy = gr.Checkbox(
674
+ label="Плавное изменение (Leg)",
675
+ value=last_smooth_leg,
676
+ visible=not is_enhanced,
677
+ info="Для Legacy: аналогично, но повторяет поведение старого скрипта."
678
+ )
679
+ smoothing_mode_select = gr.Dropdown(
680
+ choices=["Авто (по алгоритму)", "Использовать Enhanced", "Использовать Legacy old"],
681
+ value=last_smoothing_mode,
682
+ label="Логика сглаживания",
683
+ info="Авто: берёт Enh/Leg в зависимости от выбранного алгоритма. Остальные пункты принудительно включают соответствующий флаг независимо от алгоритма."
684
+ )
685
+ smoothing_curve = gr.Dropdown(choices=["Линейная", "Smoothstep"], value=last_smoothing_curve, label="Кривая", visible=is_enhanced)
686
+ keep_unitary_product = gr.Checkbox(label="Сохранять масштаб=1", value=last_keep1, visible=is_enhanced)
687
+ with gr.Row():
688
+ resolution_choice = gr.Dropdown(choices=RESOLUTION_CHOICES, value=last_resolution_choice, label="Разрешение")
689
+ apply_resolution = gr.Checkbox(label="Применить к W/H", value=last_apply_resolution)
690
+ adaptive_by_resolution = gr.Checkbox(
691
+ label="Адаптация",
692
+ value=last_adaptive_by_resolution,
693
+ info="Если включено — автоматически подстраивает s1/s2/d1/d2 и Downscale/Upscale под конечное разрешение (только для Enhanced)."
694
+ )
695
+ adaptive_profile = gr.Dropdown(
696
+ choices=["Консервативный", "Сбалансированный", "Агрессивный"],
697
+ value=last_adaptive_profile,
698
+ label="Профиль",
699
+ visible=last_adaptive_by_resolution,
700
+ )
701
+ depth_pair_mode = gr.Dropdown( # 🆕 новый дропдаун
702
+ choices=[
703
+ "Авто (по алгоритму)",
704
+ "Последовательно (как olds.py)",
705
+ "Обе глубины одновременно",
706
+ "Только d1",
707
+ "Только d2",
708
+ ],
709
+ value=last_depth_pair_mode,
710
+ label="Режим обработки depth-пар",
711
+ info="Авто: Legacy → как в olds.py (по очереди и return), Enhanced → оба depth одновременно (как сейчас). "
712
+ "Остальные режимы принудительно задают поведение независимо от алгоритма."
713
+ )
714
+
715
+ with gr.Group():
716
+ gr.Markdown("**Интерполяция (Advanced)**")
717
+ with gr.Row():
718
+ align_corners_mode = gr.Dropdown(choices=["False", "True", "Авто"], value=last_align_mode, label="align_corners", visible=is_enhanced)
719
+ recompute_scale_factor_mode = gr.Dropdown(choices=["False", "True", "Авто"], value=last_recompute_mode, label="recompute_scale_factor", visible=is_enhanced)
720
+
721
+ with gr.Group():
722
+ gr.Markdown("**Пресеты / Импорт / Экспорт / Логи**")
723
+ with gr.Row():
724
+ preset_category_filter = gr.Dropdown(choices=["Все"] + pm.categories(), value="Все", label="Категория")
725
+ preset_select = gr.Dropdown(choices=pm.names_for_category(None), value=None, label="Выбрать пресет")
726
+ btn_save = gr.Button("Сохранить", variant="primary")
727
+ btn_load = gr.Button("Загрузить")
728
+ btn_delete = gr.Button("Удалить", variant="stop")
729
+ with gr.Row():
730
+ preset_name = gr.Textbox(placeholder="Имя пресета...", show_label=False)
731
+ preset_category_input = gr.Textbox(placeholder="Категория...", show_label=False)
732
+ preset_status = gr.Markdown("")
733
+ with gr.Row():
734
+ btn_export_config = gr.Button("Экспорт JSON")
735
+ btn_import_config = gr.Button("Импорт JSON")
736
+ config_json = gr.Textbox(label="JSON конфигурация", lines=3)
737
+ import_status = gr.Markdown("")
738
+ with gr.Row():
739
+ debug_mode = gr.Checkbox(label="Debug Mode (Log Steps)", value=False)
740
+ btn_clear_log = gr.Button("Clear Log")
741
+ debug_output = gr.Textbox(label="Debug Log", interactive=False, lines=5)
742
+
743
+ # --- Logic Connectors ---
744
+ def _validate_params(mode, d1_v, d2_v, s1_v, s2_v, down_v, up_v, keep1):
745
+ msgs = []
746
+
747
+ # s1 / s2
748
+ if s1_v <= 0 and s2_v <= 0:
749
+ msgs.append("⚠️ s1 и s2 = 0 — остановки по шагу не будут применяться (эффект минимум).")
750
+ elif s1_v > s2_v:
751
+ msgs.append("⚠️ s1 > s2 — вторая пара фактически не сработает (будет обрезана до s1).")
752
+
753
+ # d1 / d2
754
+ if d1_v < 1 or d2_v < 1:
755
+ msgs.append("⚠️ d1 и d2 должны быть ≥ 1.")
756
+ if d1_v > 10 or d2_v > 10:
757
+ msgs.append("ℹ️ d1/d2 > 10 — очень глубокий захват блоков, возможен перерасчёт лишних слоёв.")
758
+
759
+ # Downscale / Upscale
760
+ if down_v < 0.2:
761
+ msgs.append("⚠️ Downscale < 0.2 — слишком агрессивное уменьшение, возможны артефакты.")
762
+ if down_v > 0.9:
763
+ msgs.append("⚠️ Downscale > 0.9 — эффект hires.fix будет минимальным.")
764
+ if up_v > 3.0:
765
+ msgs.append("⚠️ Upscale > 3.0 — очень агрессивное увеличение, возможны мыло и шум.")
766
+
767
+ # Масштаб = 1
768
+ if keep1:
769
+ eff = float(down_v) * float(up_v)
770
+ if abs(eff - 1.0) > 0.15:
771
+ msgs.append(f"ℹ️ \"Сохранять масштаб=1\" включён, но down*up ≈ {eff:.2f} (отклонение от 1.0).")
772
+
773
+ mode_s = (mode or "").strip()
774
+ if "Legacy" in mode_s and up_v > 2.5:
775
+ msgs.append("ℹ️ В Legacy-режиме Upscale > 2.5 иногда даёт более жёсткий шум.")
776
+
777
+ if not msgs:
778
+ return "✅ Параметры корректны"
779
+ return "\n".join(msgs)
780
+
781
+ for p in [d1, d2, s1, s2, downscale, upscale, keep_unitary_product]:
782
+ p.change(
783
+ _validate_params,
784
+ inputs=[algo_mode, d1, d2, s1, s2, downscale, upscale, keep_unitary_product],
785
+ outputs=[param_warnings]
786
+ )
787
+
788
+ def _toggle_simple_mode(is_simple, mode):
789
+ is_enh = (mode == "Enhanced (RU+)")
790
+ return gr.update(visible=not is_simple), gr.update(visible=is_enh), gr.update(visible=is_enh)
791
+ simple_mode.change(_toggle_simple_mode, inputs=[simple_mode, algo_mode], outputs=[advanced_group, align_corners_mode, recompute_scale_factor_mode])
792
+
793
+ def _toggle_algo_vis(mode):
794
+ is_enh = (mode == "Enhanced (RU+)")
795
+ return (gr.update(visible=is_enh), gr.update(visible=not is_enh), gr.update(visible=is_enh),
796
+ gr.update(visible=is_enh), gr.update(visible=is_enh), gr.update(visible=is_enh))
797
+ algo_mode.change(_toggle_algo_vis, inputs=[algo_mode], outputs=[smooth_scaling_enh, smooth_scaling_legacy, smoothing_curve, keep_unitary_product, only_one_pass_enh, only_one_pass_legacy])
798
+
799
+ def _toggle_adaptive(adapt_enabled):
800
+ # Профиль имеет смысл только когда адаптация включена
801
+ return gr.update(visible=adapt_enabled)
802
+
803
+ adaptive_by_resolution.change(
804
+ _toggle_adaptive,
805
+ inputs=[adaptive_by_resolution],
806
+ outputs=[adaptive_profile]
807
+ )
808
+
809
+ def _update_status(enabled, mode):
810
+ return f"🟢 **Активен: {mode}**" if enabled else "🔴 **Отключено**"
811
+ enable.change(_update_status, [enable, algo_mode], [status_indicator])
812
+ algo_mode.change(_update_status, [enable, algo_mode], [status_indicator])
813
+
814
+ # Helper for presets list update
815
+ def _update_preset_list_for_category(cat: str):
816
+ pm.reload()
817
+ return gr.update(choices=pm.names_for_category(cat), value=None)
818
+ preset_category_filter.change(_update_preset_list_for_category, inputs=[preset_category_filter], outputs=[preset_select])
819
+
820
+ stop_preview_toggle.change(lambda e,t,s1,s2: (gr.update(visible=e), _format_stop_preview_text(t,s1,s2) if e else ""), inputs=[stop_preview_toggle, stop_preview_steps, s1, s2], outputs=[stop_preview_steps, stop_preview_md])
821
+
822
+ # --- Quick Presets Logic ---
823
+ def _apply_quick_preset(preset_type: str):
824
+ presets = {
825
+ "safe": {"s1": 0.15, "s2": 0.25, "d1": 3, "d2": 4, "down": 0.6, "up": 1.8},
826
+ "balanced": {"s1": 0.18, "s2": 0.32, "d1": 3, "d2": 5, "down": 0.5, "up": 2.0},
827
+ "aggressive": {"s1": 0.22, "s2": 0.38, "d1": 4, "d2": 6, "down": 0.4, "up": 2.5},
828
+ }
829
+ p = presets.get(preset_type, presets["balanced"])
830
+ return (p["s1"], p["s2"], p["d1"], p["d2"], p["down"], p["up"])
831
+
832
+ btn_quick_safe.click(lambda: _apply_quick_preset("safe"), outputs=[s1, s2, d1, d2, downscale, upscale])
833
+ btn_quick_balanced.click(lambda: _apply_quick_preset("balanced"), outputs=[s1, s2, d1, d2, downscale, upscale])
834
+ btn_quick_aggressive.click(lambda: _apply_quick_preset("aggressive"), outputs=[s1, s2, d1, d2, downscale, upscale])
835
+
836
+ # --- Save/Load Presets (FULL LOGIC RESTORED) ---
837
+ def _save_preset_cb(
838
+ name, cat_in, cat_filt,
839
+ mode, d1_v, d2_v, depth_guard_v, depth_clamp_v, depth_pair_mode_v,
840
+ s1_v, s2_v, scl, dw, up,
841
+ sm_enh, sm_leg, sm_sel, sm_c, eo,
842
+ one_enh, one_leg, one_sel,
843
+ k1, al, rc,
844
+ res, app, ad, ad_p,
845
+ legacy_align_false, enh_safe_clamp,
846
+ old_math, old_one
847
+ ):
848
+ name = (name or "").strip()
849
+ if not name:
850
+ return gr.update(), gr.update(), "⚠️ Имя?"
851
+
852
+ cat = (cat_in or "").strip() or (cat_filt if cat_filt != "Все" else "Общие")
853
+
854
+ base = HiresPreset().to_dict()
855
+ base.update({
856
+ "category": cat,
857
+ "algo_mode": mode,
858
+ "d1": int(d1_v),
859
+ "d2": int(d2_v),
860
+ "depth_guard": bool(depth_guard_v),
861
+ "depth_clamp": bool(depth_clamp_v),
862
+ "depth_pair_mode": str(depth_pair_mode_v),
863
+ "s1": float(s1_v),
864
+ "s2": float(s2_v),
865
+ "scaler": str(scl),
866
+ "downscale": float(dw),
867
+ "upscale": float(up),
868
+ "smooth_scaling_enh": bool(sm_enh),
869
+ "smooth_scaling_legacy": bool(sm_leg),
870
+ "smoothing_mode": str(sm_sel),
871
+ "smoothing_curve": str(sm_c),
872
+ "early_out": bool(eo),
873
+ "only_one_pass_enh": bool(one_enh),
874
+ "only_one_pass_legacy": bool(one_leg),
875
+ "one_pass_mode": str(one_sel),
876
+ "keep_unitary_product": bool(k1),
877
+ "align_corners_mode": str(al),
878
+ "recompute_scale_factor_mode": str(rc),
879
+ "resolution_choice": str(res),
880
+ "apply_resolution": bool(app),
881
+ "adaptive_by_resolution": bool(ad),
882
+ "adaptive_profile": str(ad_p),
883
+ "legacy_force_align_false": bool(legacy_align_false),
884
+ "enhanced_safe_upscale_clamp": bool(enh_safe_clamp),
885
+ "use_old_float_math": bool(old_math),
886
+ "use_old_onepass_logic": bool(old_one),
887
+ })
888
+ pm.upsert(name, HiresPreset(**base))
889
+ cats = ["Все"] + pm.categories()
890
+ return (
891
+ gr.update(choices=cats, value=cat),
892
+ gr.update(choices=pm.names_for_category(cat), value=name),
893
+ f"✅ Сохранён «{name}»."
894
+ )
895
+
896
+ btn_save.click(_save_preset_cb, inputs=[preset_name, preset_category_input, preset_category_filter, algo_mode, d1, d2, depth_guard, depth_clamp, depth_pair_mode, s1, s2, scaler, downscale, upscale, smooth_scaling_enh, smooth_scaling_legacy, smoothing_mode_select, smoothing_curve, early_out, only_one_pass_enh, only_one_pass_legacy, one_pass_mode_select, keep_unitary_product, align_corners_mode, recompute_scale_factor_mode, resolution_choice, apply_resolution, adaptive_by_resolution, adaptive_profile, legacy_force_align_false, enhanced_safe_clamp, use_old_float_math, use_old_onepass_logic], outputs=[preset_category_filter, preset_select, preset_status])
897
+
898
+ def _load_preset_cb(name):
899
+ p = pm.get(name)
900
+ if not p:
901
+ # 31 gr.update() для всех контролов + строка в preset_status
902
+ return (*[gr.update()]*31, "❌ Пресет не найден")
903
+
904
+ pd = p.to_dict()
905
+ return (
906
+ gr.update(value=pd.get("algo_mode", "Enhanced (RU+)")), # algo_mode
907
+ gr.update(value=pd.get("d1", 3)), # d1
908
+ gr.update(value=pd.get("d2", 4)), # d2
909
+ gr.update(value=pd.get("depth_guard", True)), # depth_guard
910
+ gr.update(value=pd.get("depth_clamp", True)), # depth_clamp
911
+ gr.update(value=pd.get("depth_pair_mode", "Авто (по алгоритму)")),
912
+ gr.update(value=pd.get("s1", 0.15)), # s1
913
+ gr.update(value=pd.get("s2", 0.30)), # s2
914
+ gr.update(value=pd.get("scaler", "bicubic")), # scaler
915
+ gr.update(value=pd.get("downscale", 0.5)), # downscale
916
+ gr.update(value=pd.get("upscale", 2.0)), # upscale
917
+ gr.update(value=pd.get("smooth_scaling_enh", True)),
918
+ gr.update(value=pd.get("smooth_scaling_legacy", True)),
919
+ gr.update(value=pd.get("smoothing_mode", "Авто (по алгоритму)")),
920
+ gr.update(value=pd.get("smoothing_curve", "Линейная")),
921
+ gr.update(value=pd.get("early_out", False)),
922
+ gr.update(value=pd.get("only_one_pass_enh", True)),
923
+ gr.update(value=pd.get("only_one_pass_legacy", True)),
924
+ gr.update(value=pd.get("one_pass_mode", "Авто (по алгоритму)")),
925
+ gr.update(value=pd.get("keep_unitary_product", False)),
926
+ gr.update(value=pd.get("align_corners_mode", "False")),
927
+ gr.update(value=pd.get("recompute_scale_factor_mode", "False")),
928
+ gr.update(value=pd.get("resolution_choice", RESOLUTION_CHOICES[0])),
929
+ gr.update(value=pd.get("apply_resolution", False)),
930
+ gr.update(value=pd.get("adaptive_by_resolution", True)),
931
+ gr.update(value=pd.get("adaptive_profile", "Сбалансированный")),
932
+ gr.update(value=pd.get("legacy_force_align_false", True)),
933
+ gr.update(value=pd.get("enhanced_safe_upscale_clamp", True)),
934
+ gr.update(value=pd.get("use_old_float_math", True)),
935
+ gr.update(value=pd.get("use_old_onepass_logic", True)),
936
+ gr.update(value=name), # preset_name
937
+ gr.update(value="✅ Loaded"), # preset_status
938
+ )
939
+
940
+ btn_load.click(_load_preset_cb, inputs=[preset_select], outputs=[algo_mode, d1, d2, depth_guard, depth_clamp, depth_pair_mode, s1, s2, scaler, downscale, upscale, smooth_scaling_enh, smooth_scaling_legacy, smoothing_mode_select, smoothing_curve, early_out, only_one_pass_enh, only_one_pass_legacy, one_pass_mode_select, keep_unitary_product, align_corners_mode, recompute_scale_factor_mode, resolution_choice, apply_resolution, adaptive_by_resolution, adaptive_profile, legacy_force_align_false, enhanced_safe_clamp, use_old_float_math, use_old_onepass_logic, preset_name, preset_status])
941
+
942
+ def _delete_preset_cb(name, cat_filt):
943
+ pm.delete(name)
944
+ cats = ["Все"] + pm.categories()
945
+ return gr.update(choices=cats, value=cat_filt), gr.update(choices=pm.names_for_category(cat_filt), value=None), f"🗑️ Удалён «{name}»."
946
+ btn_delete.click(_delete_preset_cb, inputs=[preset_select, preset_category_filter], outputs=[preset_category_filter, preset_select, preset_status])
947
+
948
+ def _clear_log():
949
+ self.debug_log.clear()
950
+ return ""
951
+
952
+ btn_clear_log.click(_clear_log, outputs=[debug_output])
953
+
954
+ # --- Export/Import Logic (FULL LOGIC RESTORED) ---
955
+ def _export_all_config(*params):
956
+ config = {
957
+ "version": "2.5.3",
958
+ "enable": params[0],
959
+ "simple_mode": params[1],
960
+ "algo_mode": params[2],
961
+ "only_one_pass_enh": params[3],
962
+ "only_one_pass_legacy": params[4],
963
+ "one_pass_mode": params[5],
964
+ "d1": params[6],
965
+ "d2": params[7],
966
+ "depth_guard": params[8],
967
+ "depth_clamp": params[9],
968
+ "depth_pair_mode": params[10],
969
+ "s1": params[11],
970
+ "s2": params[12],
971
+ "stop_preview_enabled": params[13],
972
+ "stop_preview_steps": params[14],
973
+ "scaler": params[15],
974
+ "downscale": params[16],
975
+ "upscale": params[17],
976
+ "smooth_scaling_enh": params[18],
977
+ "smooth_scaling_legacy": params[19],
978
+ "smoothing_mode": params[20],
979
+ "smoothing_curve": params[21],
980
+ "early_out": params[22],
981
+ "keep_unitary_product": params[23],
982
+ "align_corners_mode": params[24],
983
+ "recompute_scale_factor_mode": params[25],
984
+ "resolution_choice": params[26],
985
+ "apply_resolution": params[27],
986
+ "adaptive_by_resolution": params[28],
987
+ "adaptive_profile": params[29],
988
+ "legacy_force_align_false": params[30],
989
+ "enhanced_safe_upscale_clamp": params[31],
990
+ "use_old_float_math": params[32],
991
+ "use_old_onepass_logic": params[33],
992
+ }
993
+ return json.dumps(config, indent=2, ensure_ascii=False)
994
+
995
+ def _import_all_config(json_str):
996
+ try:
997
+ config = json.loads(json_str)
998
+
999
+ return (
1000
+ gr.update(value=config.get("enable", False)), # 0 enable
1001
+ gr.update(value=config.get("simple_mode", True)), # 1 simple_mode
1002
+ gr.update(value=config.get("algo_mode", "Enhanced (RU+)")), # 2 algo_mode
1003
+ gr.update(value=config.get("only_one_pass_enh", True)), # 3 only_one_pass_enh
1004
+ gr.update(value=config.get("only_one_pass_legacy", True)), # 4 only_one_pass_legacy
1005
+ gr.update(value=config.get("one_pass_mode", "Авто (по алгоритму)")), # 5 one_pass_mode_select
1006
+ gr.update(value=config.get("d1", 3)), # 6 d1
1007
+ gr.update(value=config.get("d2", 4)), # 7 d2
1008
+ gr.update(value=config.get("depth_guard", True)), # 8 depth_guard
1009
+ gr.update(value=config.get("depth_clamp", True)), # 9 depth_clamp
1010
+ gr.update(value=config.get("depth_pair_mode", "Авто (по алгоритму)")), # 10 depth_pair_mode
1011
+ gr.update(value=config.get("s1", 0.15)), # 11 s1
1012
+ gr.update(value=config.get("s2", 0.30)), # 12 s2
1013
+ gr.update(value=config.get("stop_preview_enabled", False)), # 13 stop_preview_enabled
1014
+ gr.update(value=int(config.get("stop_preview_steps", 30))), # 14 stop_preview_steps
1015
+ gr.update(value=config.get("scaler", "bicubic")), # 15 scaler
1016
+ gr.update(value=config.get("downscale", 0.5)), # 16 downscale
1017
+ gr.update(value=config.get("upscale", 2.0)), # 17 upscale
1018
+ gr.update(value=config.get("smooth_scaling_enh", True)), # 18 smooth_scaling_enh
1019
+ gr.update(value=config.get("smooth_scaling_legacy", True)), # 19 smooth_scaling_legacy
1020
+ gr.update(value=config.get("smoothing_mode", "Авто (по алгоритму)")), # 20 smoothing_mode_select
1021
+ gr.update(value=config.get("smoothing_curve", "Линейная")), # 21 smoothing_curve
1022
+ gr.update(value=config.get("early_out", False)), # 22 early_out
1023
+ gr.update(value=config.get("keep_unitary_product", False)), # 23 keep_unitary_product
1024
+ gr.update(value=config.get("align_corners_mode", "False")), # 24 align_corners_mode
1025
+ gr.update(value=config.get("recompute_scale_factor_mode", "False")), # 25 recompute_scale_factor_mode
1026
+ gr.update(value=config.get("resolution_choice", RESOLUTION_CHOICES[0])), # 26 resolution_choice
1027
+ gr.update(value=config.get("apply_resolution", False)), # 27 apply_resolution
1028
+ gr.update(value=config.get("adaptive_by_resolution", True)), # 28 adaptive_by_resolution
1029
+ gr.update(value=config.get("adaptive_profile", "Сбалансированный")), # 29 adaptive_profile
1030
+ gr.update(value=config.get("legacy_force_align_false", True)), # 30 legacy_force_align_false
1031
+ gr.update(value=config.get("enhanced_safe_upscale_clamp", True)), # 31 enhanced_safe_clamp
1032
+ gr.update(value=config.get("use_old_float_math", True)), # 32 use_old_float_math
1033
+ gr.update(value=config.get("use_old_onepass_logic", True)), # 33 use_old_onepass_logic
1034
+ "✅ Настройки импортированы", # 34 import_status
1035
+ )
1036
+ except Exception as e:
1037
+ return (
1038
+ *[gr.update()]*34,
1039
+ f"❌ Ошибка: {e}",
1040
+ )
1041
+
1042
+ all_params_list = [
1043
+ enable, simple_mode, algo_mode, only_one_pass_enh, only_one_pass_legacy, one_pass_mode_select,
1044
+ d1, d2, depth_guard, depth_clamp, depth_pair_mode, s1, s2, stop_preview_toggle, stop_preview_steps, scaler, downscale, upscale,
1045
+ smooth_scaling_enh, smooth_scaling_legacy, smoothing_mode_select, smoothing_curve, early_out,
1046
+ keep_unitary_product, align_corners_mode, recompute_scale_factor_mode, resolution_choice,
1047
+ apply_resolution, adaptive_by_resolution, adaptive_profile, legacy_force_align_false, enhanced_safe_clamp,
1048
+ use_old_float_math, use_old_onepass_logic
1049
+ ]
1050
+
1051
+ btn_export_config.click(_export_all_config, inputs=all_params_list, outputs=[config_json])
1052
+ btn_import_config.click(_import_all_config, inputs=[config_json], outputs=all_params_list + [import_status])
1053
+
1054
+ self.infotext_fields.append((enable, "DSHF_enabled"))
1055
+ for k, el in {
1056
+ "DSHF_mode": algo_mode, "DSHF_s1": s1, "DSHF_d1": d1, "DSHF_s2": s2, "DSHF_d2": d2,
1057
+ "DSHF_scaler": scaler, "DSHF_down": downscale, "DSHF_up": upscale, "DSHF_old_float": use_old_float_math
1058
+ }.items():
1059
+ self.infotext_fields.append((el, k))
1060
+
1061
+ return all_params_list + [debug_mode]
1062
+
1063
+ def process(
1064
+ self, p, enable, simple, algo_mode, only_one_pass_enh, only_one_pass_legacy, one_pass_mode_select,
1065
+ d1, d2, depth_guard, depth_clamp, depth_pair_mode, s1, s2, stop_preview_enabled, stop_preview_steps, scaler, downscale, upscale,
1066
+ smooth_scaling_enh, smooth_scaling_legacy, smoothing_mode_select, smoothing_curve, early_out,
1067
+ keep_unitary_product, align_ui, recompute_ui, res_choice, apply_res,
1068
+ adapt, adapt_prof, legacy_force_align_false, enhanced_safe_clamp,
1069
+ use_old_float_math, use_old_onepass_logic, # 🆕 HYBRID FLAGS
1070
+ debug_mode_val
1071
+ ):
1072
+ self.step_limit = 0
1073
+ self.debug_mode = debug_mode_val
1074
+ self.disable = False # ⬅︎ добавить
1075
+
1076
+ # ✅ ИСПРАВЛЕНИЕ 1: ПОЛНЫЙ CONFIG (Как в версии 19)
1077
+ self.config = DictConfig({
1078
+ "algo_mode": algo_mode,
1079
+ "simple_mode": simple,
1080
+ "s1": s1, "s2": s2,
1081
+ "d1": d1, "d2": d2,
1082
+ "depth_guard": depth_guard,
1083
+ "depth_clamp": depth_clamp, # 🆕
1084
+ "depth_pair_mode": depth_pair_mode,
1085
+ "stop_preview_enabled": stop_preview_enabled,
1086
+ "stop_preview_steps": stop_preview_steps,
1087
+ "scaler": scaler,
1088
+ "downscale": downscale,
1089
+ "upscale": upscale,
1090
+ "smooth_scaling_enh": smooth_scaling_enh,
1091
+ "smooth_scaling_legacy": smooth_scaling_legacy,
1092
+ "smoothing_mode": smoothing_mode_select,
1093
+ "smoothing_curve": smoothing_curve,
1094
+ "early_out": early_out,
1095
+ "only_one_pass_enh": only_one_pass_enh,
1096
+ "only_one_pass_legacy": only_one_pass_legacy,
1097
+ "one_pass_mode": one_pass_mode_select,
1098
+ "keep_unitary_product": keep_unitary_product,
1099
+ "align_corners_mode": align_ui,
1100
+ "recompute_scale_factor_mode": recompute_ui,
1101
+ "resolution_choice": res_choice,
1102
+ "apply_resolution": apply_res,
1103
+ "adaptive_by_resolution": adapt,
1104
+ "adaptive_profile": adapt_prof,
1105
+ "use_old_float_math": use_old_float_math, # 🆕
1106
+ "use_old_onepass_logic": use_old_onepass_logic, # 🆕
1107
+ "legacy_force_align_false": legacy_force_align_false,
1108
+ "enhanced_safe_upscale_clamp": enhanced_safe_clamp,
1109
+ "debug_mode": debug_mode_val
1110
+ })
1111
+
1112
+ self._save_config()
1113
+
1114
+ if not enable or self.disable:
1115
+ try: script_callbacks.remove_current_script_callbacks()
1116
+ except: pass
1117
+ self._cb_registered = False
1118
+ return
1119
+
1120
+ if apply_res:
1121
+ try:
1122
+ wh = parse_resolution_label(res_choice)
1123
+ if wh: p.width, p.height = wh
1124
+ except: pass
1125
+
1126
+ use_s1, use_s2 = _clamp(float(s1), 0.0, 1.0), _clamp(float(s2), 0.0, 1.0)
1127
+ use_d1, use_d2 = int(d1), int(d2)
1128
+ use_down, use_up = float(downscale), float(upscale)
1129
+
1130
+ # ⚙️ Адаптация по разрешению: ТОЛЬКО для Enhanced, в Legacy ведём себя как khrfix_olds
1131
+ if adapt and algo_mode != "Legacy (Original)":
1132
+ try:
1133
+ use_s1, use_s2, use_d1, use_d2, use_down, use_up = _compute_adaptive_params(
1134
+ int(getattr(p, "width", 1024)), int(getattr(p, "height", 1024)), adapt_prof,
1135
+ use_s1, use_s2, d1, d2, downscale, upscale, keep_unitary_product
1136
+ )
1137
+ except:
1138
+ pass
1139
+
1140
+ if use_s1 > use_s2: use_s2 = use_s1
1141
+
1142
+ model_container = getattr(p.sd_model, "model", None)
1143
+ if not model_container: return
1144
+ model = model_container.diffusion_model
1145
+
1146
+ inp_list = getattr(model, "input_blocks", [])
1147
+ out_list = getattr(model, "output_blocks", [])
1148
+ max_inp = len(inp_list) - 1
1149
+
1150
+ d1_idx = int(use_d1) - 1
1151
+ d2_idx = int(use_d2) - 1
1152
+ scaler_mode = _safe_mode(scaler)
1153
+
1154
+ if algo_mode == "Legacy (Original)" and legacy_force_align_false:
1155
+ # Строгий Legacy: как в старом скрипте — всегда False/False
1156
+ align_mode, recompute_mode = "false", "false"
1157
+ else:
1158
+ # Во всех остальных случаях — читаем из UI
1159
+ align_mode = _norm_mode_choice(align_ui, "auto")
1160
+ recompute_mode = _norm_mode_choice(recompute_ui, "auto")
1161
+
1162
+ # Select logic
1163
+ def _select_cross_mode(choice: str, enh_value: bool, legacy_value: bool) -> bool:
1164
+ sel = (choice or "").strip().lower()
1165
+ if sel.startswith("использовать legacy"): return bool(legacy_value)
1166
+ if sel.startswith("использовать enhanced"): return bool(enh_value)
1167
+ return bool(enh_value) if algo_mode == "Enhanced (RU+)" else bool(legacy_value)
1168
+
1169
+ use_smooth = _select_cross_mode(smoothing_mode_select, smooth_scaling_enh, smooth_scaling_legacy)
1170
+ use_one = _select_cross_mode(one_pass_mode_select, only_one_pass_enh, only_one_pass_legacy)
1171
+
1172
+ # 🛡️ DEPTH GUARD & MAPPING LOGIC
1173
+ mapping_notes = []
1174
+
1175
+ def _normalize_depth(idx, label):
1176
+ # Если clamp выключен — индексы вне диапазона просто выкидываем
1177
+ if not depth_clamp:
1178
+ if idx < 0 or idx > max_inp:
1179
+ mapping_notes.append(f"{label} out of range → skip (idx={idx}, max={max_inp})")
1180
+ return None
1181
+ return int(idx)
1182
+
1183
+ # Старое поведение: жёсткий clamp в [0..max_inp]
1184
+ clamped = max(0, min(int(idx), max_inp))
1185
+ if clamped != int(idx):
1186
+ mapping_notes.append(f"{label} clamped {idx} → {clamped}")
1187
+ return clamped
1188
+
1189
+ d1_idx = _normalize_depth(d1_idx, "d1")
1190
+ d2_idx = _normalize_depth(d2_idx, "d2")
1191
+
1192
+ # Если оба ушли в None — делать вообще нечего
1193
+ if d1_idx is None and d2_idx is None:
1194
+ if self.debug_mode:
1195
+ self._append_debug("Both d1 and d2 are out of range, skip callback.")
1196
+ return
1197
+
1198
+ # Если один из depth отвалился — работаем только со вторым
1199
+ if depth_guard and d1_idx is not None and d2_idx is not None and d2_idx < d1_idx:
1200
+ d1_idx, d2_idx = d2_idx, d1_idx
1201
+
1202
+ def _resolve_pair_mode(mode_str: str) -> str:
1203
+ ms = (mode_str or "").strip().lower()
1204
+ if ms.startswith("авто"):
1205
+ # Авто: Legacy → как olds.py, Enhanced → обе глубины
1206
+ return "sequential" if algo_mode == "Legacy (Original)" else "both"
1207
+ if "послед" in ms:
1208
+ return "sequential"
1209
+ if "оба" in ms or "обе" in ms:
1210
+ return "both"
1211
+ if "только d1" in ms:
1212
+ return "d1"
1213
+ if "только d2" in ms:
1214
+ return "d2"
1215
+ return "both"
1216
+
1217
+ resolved_pair_mode = _resolve_pair_mode(depth_pair_mode)
1218
+
1219
+ def _map_with_note(idx, tag: str):
1220
+ if idx is None:
1221
+ mapping_notes.append(f"{tag}: skipped (idx=None)")
1222
+ return None
1223
+ out_idx = KohyaHiresFix._map_output_index(model, idx, early_out)
1224
+ if out_idx is None:
1225
+ mapping_notes.append(f"{tag}: {idx} -> No Out")
1226
+ else:
1227
+ mapping_notes.append(f"{tag}: {idx} -> {out_idx}")
1228
+ return out_idx
1229
+
1230
+ _map_with_note(d1_idx, "d1")
1231
+ _map_with_note(d2_idx, "d2")
1232
+
1233
+ if self.debug_mode:
1234
+ self._append_debug(f"Mapping notes: {', '.join(mapping_notes)}")
1235
+
1236
+ def _apply_depth_pair(d_i: int, s_stop: float, current: int, total: int):
1237
+ if d_i is None or s_stop <= 0:
1238
+ return False # ничего не делали
1239
+
1240
+ if d_i >= len(model.input_blocks):
1241
+ return False
1242
+ out_i = KohyaHiresFix._map_output_index(model, d_i, early_out)
1243
+ if out_i is None or out_i >= len(model.output_blocks):
1244
+ return False
1245
+
1246
+ if model.input_blocks[d_i] is None or model.output_blocks[out_i] is None:
1247
+ return False
1248
+
1249
+ stop_step = total * s_stop
1250
+
1251
+ if current < stop_step:
1252
+ # Вешаем/обновляем Scaler
1253
+ if not isinstance(model.input_blocks[d_i], Scaler):
1254
+ model.input_blocks[d_i] = Scaler(
1255
+ use_down, model.input_blocks[d_i], scaler_mode,
1256
+ align_mode, recompute_mode,
1257
+ use_old_float_math=use_old_float_math
1258
+ )
1259
+ model.output_blocks[out_i] = Scaler(
1260
+ use_up, model.output_blocks[out_i], scaler_mode,
1261
+ align_mode, recompute_mode,
1262
+ use_old_float_math=use_old_float_math
1263
+ )
1264
+
1265
+ if use_smooth:
1266
+ ratio = float(max(0.0, min(1.0, current / stop_step)))
1267
+ if algo_mode == "Enhanced (RU+)" and smoothing_curve == "Smoothstep":
1268
+ ratio = ratio * ratio * (3.0 - 2.0 * ratio)
1269
+
1270
+ cur_down = min((1.0 - use_down) * ratio + use_down, 1.0)
1271
+ model.input_blocks[d_i].scale = cur_down
1272
+
1273
+ if algo_mode == "Enhanced (RU+)" and keep_unitary_product:
1274
+ cur_up = 1.0 / max(1e-6, cur_down)
1275
+ else:
1276
+ cur_up = use_up * (use_down / max(1e-6, cur_down))
1277
+ if algo_mode == "Enhanced (RU+)" and enhanced_safe_clamp:
1278
+ cur_up = _clamp(cur_up, 1.0, 4.0)
1279
+
1280
+ model.output_blocks[out_i].scale = cur_up
1281
+
1282
+ return True # что-то делали
1283
+
1284
+ else:
1285
+ # Вышли за stop_step — снимаем Scaler (если он есть)
1286
+ if isinstance(model.input_blocks[d_i], Scaler):
1287
+ model.input_blocks[d_i] = model.input_blocks[d_i].block
1288
+ if isinstance(model.output_blocks[out_i], Scaler):
1289
+ model.output_blocks[out_i] = model.output_blocks[out_i].block
1290
+ return False
1291
+
1292
+ def denoiser_callback(params: script_callbacks.CFGDenoiserParams):
1293
+ if self.disable:
1294
+ return
1295
+ try:
1296
+ total = max(1, int(params.total_sampling_steps))
1297
+ current = params.sampling_step
1298
+
1299
+ # ✅ Логирование шагов – здесь, внутри колбэка
1300
+
1301
+ if self.debug_mode:
1302
+ self._append_debug(f"Step {current}/{total}")
1303
+
1304
+ # ✅ ИСПРАВЛЕНИЕ 2: ПРОВЕРКА В НАЧАЛЕ (Start Check)
1305
+ if use_one:
1306
+ if use_old_onepass_logic:
1307
+ # OLD LOGIC: строгое сравнение с записанным шагом
1308
+ if current < self.step_limit:
1309
+ return
1310
+ else:
1311
+ # NEW LOGIC: проверка флага
1312
+ if self.step_limit == 1:
1313
+ return
1314
+
1315
+ # Формируем пары с учётом выбранного режима
1316
+ raw_pairs = []
1317
+ if resolved_pair_mode in ("both", "sequential", "d1", "d2"):
1318
+ if resolved_pair_mode in ("both", "sequential", "d1") and d1_idx is not None:
1319
+ raw_pairs.append(("p1", use_s1, d1_idx))
1320
+ if resolved_pair_mode in ("both", "sequential", "d2") and d2_idx is not None:
1321
+ raw_pairs.append(("p2", use_s2, d2_idx))
1322
+ else:
1323
+ # fallback — как "both"
1324
+ if d1_idx is not None:
1325
+ raw_pairs.append(("p1", use_s1, d1_idx))
1326
+ if d2_idx is not None:
1327
+ raw_pairs.append(("p2", use_s2, d2_idx))
1328
+
1329
+ # Для расчёта max_stop_s (нужно для new onepass logic)
1330
+ max_stop_s = max((s for _, s, _ in raw_pairs if s > 0), default=0.0)
1331
+
1332
+ if resolved_pair_mode == "sequential":
1333
+ # 🧠 Режим "как olds.py": обрабатываем пары по очереди, после первой успешной — return
1334
+ for tag, s_stop, d_i in raw_pairs:
1335
+ if s_stop <= 0:
1336
+ continue
1337
+
1338
+ did_work = _apply_depth_pair(d_i, s_stop, current, total)
1339
+
1340
+ # olds.py по факту тоже «останавливается» на первой сработавшей паре
1341
+ if did_work:
1342
+ # После первой активной пары — выходим из callback
1343
+ return
1344
+
1345
+ # Если до сюда дошли — ни одна пара не активна, но могли снять Scaler.
1346
+ # Ниже сработает onepass-логика.
1347
+
1348
+ else:
1349
+ # Режим "оба depth одновременно" (и прочие, кроме sequential)
1350
+ combined: Dict[int, float] = {}
1351
+ for _, s_stop, d_i in raw_pairs:
1352
+ if s_stop > 0 and d_i is not None:
1353
+ combined[d_i] = max(combined.get(d_i, 0.0), s_stop)
1354
+
1355
+ for d_i, s_stop in combined.items():
1356
+ _apply_depth_pair(d_i, s_stop, current, total)
1357
+
1358
+ # ✅ ИСПРАВЛЕНИЕ 3: УСТАНОВКА ЛИМИТА В КОНЦЕ (End Update)
1359
+ if use_one:
1360
+ if use_old_onepass_logic:
1361
+ # OLD WAY: обновляем ВСЕГДА, чтобы step_limit рос вместе с current
1362
+ # Это в точности копирует логику khrfix_olds
1363
+ self.step_limit = current
1364
+ else:
1365
+ # NEW WAY: ставим флаг "1" только когда эффект закончился
1366
+ if max_stop_s > 0 and current >= total * max_stop_s:
1367
+ self.step_limit = 1
1368
+
1369
+ except Exception as e:
1370
+ try:
1371
+ KohyaHiresFix._unwrap_all(model)
1372
+ except Exception:
1373
+ pass
1374
+
1375
+ print(f"[KohyaHiresFix] Error in callback: {e}")
1376
+ self.disable = True
1377
+
1378
+ # сразу отключаем все колбэки этого скрипта, чтобы не спамить ошибками
1379
+ try:
1380
+ script_callbacks.remove_current_script_callbacks()
1381
+ except Exception:
1382
+ pass
1383
+
1384
+ self._cb_registered = False
1385
+
1386
+ # 🔗 РЕГИСТРАЦИЯ CALLBACK'А (главное, чего не хватало)
1387
+ if not self._cb_registered:
1388
+ script_callbacks.on_cfg_denoiser(denoiser_callback)
1389
+ self._cb_registered = True
1390
+
1391
+ def postprocess(self, p, processed, *args):
1392
+ try:
1393
+ model_container = getattr(p.sd_model, "model", None)
1394
+ if model_container:
1395
+ KohyaHiresFix._unwrap_all(model_container.diffusion_model)
1396
+ finally:
1397
+ try:
1398
+ script_callbacks.remove_current_script_callbacks()
1399
+ except Exception:
1400
+ pass
1401
+ self._cb_registered = False
1402
+
1403
+ def process_batch(self, p, *args, **kwargs):
1404
+ self.step_limit = 0
1405
+
1406
+ # КОНЕЦ ЧАСТИ 4 (ФИНАЛ)
sd-webui-kohya-hiresfix-verold/scripts/khrfix.yaml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ algo_mode: Enhanced (RU+)
2
+ simple_mode: true
3
+ s1: 0.15
4
+ s2: 0.3
5
+ d1: 3
6
+ d2: 4
7
+ depth_guard: true
8
+ depth_clamp: true
9
+ depth_pair_mode: Авто (по алгоритму)
10
+ stop_preview_enabled: false
11
+ stop_preview_steps: 30
12
+ scaler: bicubic
13
+ downscale: 0.5
14
+ upscale: 2
15
+ smooth_scaling_enh: true
16
+ smooth_scaling_legacy: true
17
+ smoothing_mode: Авто (по алгоритму)
18
+ smoothing_curve: Линейная
19
+ early_out: false
20
+ only_one_pass_enh: true
21
+ only_one_pass_legacy: true
22
+ one_pass_mode: Авто (по алгоритму)
23
+ keep_unitary_product: false
24
+ align_corners_mode: 'False'
25
+ recompute_scale_factor_mode: 'False'
26
+ resolution_choice: — не применять —
27
+ apply_resolution: false
28
+ adaptive_by_resolution: true
29
+ adaptive_profile: Сбалансированный
30
+ use_old_float_math: true
31
+ use_old_onepass_logic: true
32
+ legacy_force_align_false: true
33
+ enhanced_safe_upscale_clamp: true
34
+ debug_mode: false
sd-webui-kohya-hiresfix-verold/scripts/khrfix_olds.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### https://gist.github.com/kohya-ss/3f774da220df102548093a7abc8538ed
2
+
3
+ from pathlib import Path
4
+ from omegaconf import DictConfig, OmegaConf
5
+ from modules import scripts, script_callbacks
6
+ import gradio as gr
7
+ import torch
8
+
9
+ CONFIG_PATH = Path(__file__).parent.resolve() / '../config.yaml'
10
+
11
+
12
+ class Scaler(torch.nn.Module):
13
+ def __init__(self, scale, block, scaler):
14
+ super().__init__()
15
+ self.scale = scale
16
+ self.block = block
17
+ self.scaler = scaler
18
+
19
+ def forward(self, x, *args):
20
+ x = torch.nn.functional.interpolate(x, scale_factor=self.scale, mode=self.scaler)
21
+ return self.block(x, *args)
22
+
23
+
24
+ class KohyaHiresFix(scripts.Script):
25
+ def __init__(self):
26
+ super().__init__()
27
+ try:
28
+ self.config: DictConfig = OmegaConf.load(CONFIG_PATH)
29
+ except Exception:
30
+ self.config = DictConfig({})
31
+ self.disable = False
32
+ self.step_limit = 0
33
+ self.infotext_fields = []
34
+
35
+ def title(self):
36
+ return "Kohya Hires.fix"
37
+
38
+ def show(self, is_img2img):
39
+ return scripts.AlwaysVisible
40
+
41
+ def ui(self, is_img2img):
42
+ with gr.Accordion(label='Kohya Hires.fix', open=False):
43
+ with gr.Row():
44
+ enable = gr.Checkbox(label='Enable extension', value=False)
45
+ with gr.Row():
46
+ s1 = gr.Slider(minimum=0, maximum=0.5, step=0.01, label="Stop at", value=self.config.get('s1', 0.15))
47
+ d1 = gr.Slider(minimum=1, maximum=10, step=1, label="Depth", value=self.config.get('d1', 3))
48
+ with gr.Row():
49
+ s2 = gr.Slider(minimum=0, maximum=0.5, step=0.01, label="Stop at", value=self.config.get('s2', 0.3))
50
+ d2 = gr.Slider(minimum=1, maximum=10, step=1, label="Depth", value=self.config.get('d2', 4))
51
+ with gr.Row():
52
+ scaler = gr.Dropdown(['bicubic', 'bilinear', 'nearest', 'nearest-exact'], label='Layer scaler',
53
+ value=self.config.get('scaler', 'bicubic'))
54
+ downscale = gr.Slider(minimum=0.1, maximum=1.0, step=0.05, label="Downsampling scale",
55
+ value=self.config.get('downscale', 0.5))
56
+ upscale = gr.Slider(minimum=1.0, maximum=4.0, step=0.1, label="Upsampling scale",
57
+ value=self.config.get('upscale', 2.0))
58
+ with gr.Row():
59
+ smooth_scaling = gr.Checkbox(label="Smooth scaling", value=self.config.get('smooth_scaling', True))
60
+ early_out = gr.Checkbox(label="Early upsampling", value=self.config.get('early_out', False))
61
+ only_one_pass = gr.Checkbox(label='Disable for additional passes',
62
+ value=self.config.get('only_one_pass', True))
63
+
64
+ ui = [enable, only_one_pass, d1, d2, s1, s2, scaler, downscale, upscale, smooth_scaling, early_out]
65
+ for elem in ui:
66
+ setattr(elem, "do_not_save_to_config", True)
67
+
68
+ parameters = {
69
+ 'DSHF_s1': s1,
70
+ 'DSHF_d1': d1,
71
+ 'DSHF_s2': s2,
72
+ 'DSHF_d2': d2,
73
+ 'DSHF_scaler': scaler,
74
+ 'DSHF_down': downscale,
75
+ 'DSHF_up': upscale,
76
+ 'DSHF_smooth': smooth_scaling,
77
+ 'DSHF_early': early_out,
78
+ 'DSHF_one': only_one_pass,
79
+ }
80
+ # using "DSHF_s1" as key to check if extension is enabled
81
+ self.infotext_fields.append((enable, lambda d: d.get('DSHF_s1', False)))
82
+ for k, element in parameters.items():
83
+ self.infotext_fields.append((element, k))
84
+
85
+ return ui
86
+
87
+ def process(self, p, enable, only_one_pass, d1, d2, s1, s2, scaler, downscale, upscale, smooth_scaling, early_out):
88
+ self.config = DictConfig({name: var for name, var in locals().items() if name not in ['self', 'p']})
89
+ if not enable or self.disable:
90
+ script_callbacks.remove_current_script_callbacks()
91
+ return
92
+ model = p.sd_model.model.diffusion_model
93
+ if s1 > s2: self.config.s2 = s1
94
+ self.p1 = (s1, d1 - 1)
95
+ self.p2 = (s2, d2 - 1)
96
+ self.step_limit = 0
97
+
98
+ def denoiser_callback(params: script_callbacks.CFGDenoiserParams):
99
+ if params.sampling_step < self.step_limit: return
100
+ for s, d in [self.p1, self.p2]:
101
+ out_d = d if self.config.early_out else -(d + 1)
102
+ if params.sampling_step < params.total_sampling_steps * s:
103
+ if not isinstance(model.input_blocks[d], Scaler):
104
+ model.input_blocks[d] = Scaler(self.config.downscale, model.input_blocks[d], self.config.scaler)
105
+ model.output_blocks[out_d] = Scaler(self.config.upscale, model.output_blocks[out_d], self.config.scaler)
106
+ elif self.config.smooth_scaling:
107
+ scale_ratio = params.sampling_step / (params.total_sampling_steps * s)
108
+ downscale = min((1 - self.config.downscale) * scale_ratio + self.config.downscale, 1.0)
109
+ model.input_blocks[d].scale = downscale
110
+ model.output_blocks[out_d].scale = self.config.upscale * (self.config.downscale / downscale)
111
+ return
112
+ elif isinstance(model.input_blocks[d], Scaler) and (self.p1[1] != self.p2[1] or s == self.p2[0]):
113
+ model.input_blocks[d] = model.input_blocks[d].block
114
+ model.output_blocks[out_d] = model.output_blocks[out_d].block
115
+ self.step_limit = params.sampling_step if self.config.only_one_pass else 0
116
+
117
+ script_callbacks.on_cfg_denoiser(denoiser_callback)
118
+
119
+ parameters = {
120
+ 'DSHF_s1': s1,
121
+ 'DSHF_d1': d1,
122
+ 'DSHF_s2': s2,
123
+ 'DSHF_d2': d2,
124
+ 'DSHF_scaler': scaler,
125
+ 'DSHF_down': downscale,
126
+ 'DSHF_up': upscale,
127
+ 'DSHF_smooth': smooth_scaling,
128
+ 'DSHF_early': early_out,
129
+ 'DSHF_one': only_one_pass,
130
+ }
131
+ for k, v in parameters.items():
132
+ p.extra_generation_params[k] = v
133
+
134
+ def postprocess(self, p, processed, *args):
135
+ for i, b in enumerate(p.sd_model.model.diffusion_model.input_blocks):
136
+ if isinstance(b, Scaler):
137
+ p.sd_model.model.diffusion_model.input_blocks[i] = b.block
138
+ for i, b in enumerate(p.sd_model.model.diffusion_model.output_blocks):
139
+ if isinstance(b, Scaler):
140
+ p.sd_model.model.diffusion_model.output_blocks[i] = b.block
141
+ OmegaConf.save(self.config, CONFIG_PATH)
142
+
143
+ def process_batch(self, p, *args, **kwargs):
144
+ self.step_limit = 0