feat(upscale): exact base_width/base_height on every upscaled item; fix matched-mode ctx keys and no-detection pairing
- Record the size of the generated image an upscale started from at all 10 sites (deferred, inline Florence2 + sentinel, inline SeedVR2, inline hires/model, and the dashboard upscale_runner paths via base_dims_of). - CMP_EXCLUDED_KEYS now ignores per-image fields (config_fp, base_fp, metrics, preview_of, florence2_*), so matched/one-axis pairing works on current manifests. - Compare pool skips Florence2 no-detection sentinels (they alias the base file). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b45fde0410
commit
2dbd42d35b
+8
-3
@@ -839,7 +839,7 @@ def run_florence2_step(
|
||||
|
||||
def build_florence2_manifest_entry(
|
||||
step_result, item, *, session_name, pipeline_name, upscale_id,
|
||||
upscaled_filename, current_index, hires_denoise
|
||||
upscaled_filename, current_index, hires_denoise, base_dims=None
|
||||
):
|
||||
"""Build the manifest entry dict for a successful Florence2 hi-res-fix.
|
||||
|
||||
@@ -876,17 +876,19 @@ def build_florence2_manifest_entry(
|
||||
"florence2_bbox": step_result.get("florence2_bbox"),
|
||||
"hires_denoise": hires_denoise,
|
||||
})
|
||||
if base_dims:
|
||||
base.update(base_dims)
|
||||
return base
|
||||
|
||||
|
||||
def build_florence2_no_detection_entry(step_result, item, *, sentinel_id, current_index):
|
||||
def build_florence2_no_detection_entry(step_result, item, *, sentinel_id, current_index, base_dims=None):
|
||||
"""Build a sentinel manifest entry for a Florence2 no-detection result.
|
||||
|
||||
No new image file is written — the entry points back at the source filename
|
||||
so the dashboard can render a "no detection" badge alongside the original.
|
||||
"""
|
||||
text = step_result.get("florence2_text_input", "")
|
||||
return {
|
||||
entry = {
|
||||
"id": sentinel_id,
|
||||
"gen_index": current_index,
|
||||
"filename": item.get("filename", ""),
|
||||
@@ -897,3 +899,6 @@ def build_florence2_no_detection_entry(step_result, item, *, sentinel_id, curren
|
||||
"florence2_model": step_result.get("florence2_model", ""),
|
||||
"note": f"Florence2 found no '{text}' in image",
|
||||
}
|
||||
if base_dims:
|
||||
entry.update(base_dims)
|
||||
return entry
|
||||
|
||||
@@ -633,6 +633,7 @@ def run_deferred_upscales(
|
||||
upscaled_meta["id"] = upscale_id
|
||||
upscaled_meta["filename"] = upscaled_filename
|
||||
upscaled_meta["upscaled"] = True
|
||||
upscaled_meta["base_width"], upscaled_meta["base_height"] = pipe_w, pipe_h
|
||||
upscaled_meta["upscale_pipeline"] = pipeline_name
|
||||
if job.get("config_fp"):
|
||||
from .config_utils import config_fingerprint as _cfp
|
||||
@@ -2112,6 +2113,7 @@ def run_generation_loop(
|
||||
)
|
||||
sentinel_meta.update({
|
||||
"id": sentinel_id,
|
||||
"base_width": w, "base_height": h,
|
||||
"base_fp": job_fp,
|
||||
"config_fp": config_fingerprint(conf, w, h, conf["seed"], pipeline_name),
|
||||
"upscaled": False,
|
||||
@@ -2156,6 +2158,7 @@ def run_generation_loop(
|
||||
"filename": upscaled_filename,
|
||||
"rejected": False,
|
||||
"upscaled": True,
|
||||
"base_width": w, "base_height": h,
|
||||
"upscale_source": "inline",
|
||||
"upscale_pipeline": pipeline_name,
|
||||
"upscale_mode": "florence2_hires",
|
||||
@@ -2230,6 +2233,7 @@ def run_generation_loop(
|
||||
"width": up_w, "height": up_h,
|
||||
"duration": sv_duration,
|
||||
"upscaled": True,
|
||||
"base_width": w, "base_height": h,
|
||||
"upscale_source": "inline",
|
||||
"upscale_pipeline": pipeline_name,
|
||||
"upscale_mode": "seedvr2",
|
||||
@@ -2395,6 +2399,7 @@ def run_generation_loop(
|
||||
)
|
||||
upscaled_meta["id"] = upscale_id
|
||||
upscaled_meta["upscaled"] = True
|
||||
upscaled_meta["base_width"], upscaled_meta["base_height"] = w, h
|
||||
upscaled_meta["upscale_pipeline"] = pipeline_name
|
||||
upscaled_meta["base_fp"] = job_fp
|
||||
upscaled_meta["config_fp"] = config_fingerprint(conf, w, h, conf["seed"], pipeline_name)
|
||||
|
||||
@@ -3,6 +3,7 @@ Manifest File Management
|
||||
Handles saving and merging manifest.json with user modifications
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -314,3 +315,14 @@ def append_items(manifest_path, items, remove_ids=(), session_name=None, default
|
||||
json.dump(data, f, indent=4)
|
||||
os.replace(tmp, manifest_path)
|
||||
return data
|
||||
|
||||
|
||||
def base_dims_of(item):
|
||||
"""{"base_width","base_height"} of the generated image an upscale of `item` starts from ({} if unknown)."""
|
||||
bw, bh = item.get("base_width"), item.get("base_height")
|
||||
if bw and bh:
|
||||
return {"base_width": bw, "base_height": bh}
|
||||
if item.get("upscaled"):
|
||||
return {} # legacy upscaled source: let normResolution estimate
|
||||
w, h = item.get("width"), item.get("height")
|
||||
return {"base_width": w, "base_height": h} if w and h else {}
|
||||
|
||||
@@ -307,7 +307,7 @@ function cmpBuildPool() {
|
||||
var src = processedData || [];
|
||||
for (var i = 0; i < src.length; i++) {
|
||||
var d = src[i];
|
||||
if (d && d.file && !d.rejected) out.push(d); // a reject is a decision already made
|
||||
if (d && d.file && !d.rejected && !d.florence2_no_detection) out.push(d); // a reject is a decision already made
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ var CMP_EXCLUDED_KEYS = new Set([
|
||||
'note', 'notes', 'positive', 'config_positive', 'negative', 'config_negative', 'seed',
|
||||
'width', 'height', 'base_width', 'base_height', 'model', 'lora', 'lora_expanded',
|
||||
'sampler', 'scheduler', 'cfg', 'steps', 'denoise', 'upscaled', 'source_file',
|
||||
'image_path', 'preview_path', 'video_path', 'hash', 'hires_denoise'
|
||||
'image_path', 'preview_path', 'video_path', 'hash', 'hires_denoise',
|
||||
// per-image outcomes / bookkeeping, not settings
|
||||
'config_fp', 'base_fp', 'metrics', 'preview_of', 'source_relpath',
|
||||
'florence2_bbox', 'florence2_detection_count', 'florence2_no_detection'
|
||||
]);
|
||||
var CMP_EXCLUDED_RE = /^upscale_|^hires_prompt_|_hash$|^worker|^_/;
|
||||
|
||||
@@ -89,6 +92,7 @@ function normResolution(d) {
|
||||
} else if (!d.upscaled) {
|
||||
k = d.width + 'x' + d.height;
|
||||
} else {
|
||||
// legacy manifests (before base_width/base_height): estimate from upscale_ratio
|
||||
var r = cmpProduct(d.upscale_ratio);
|
||||
if (r > 0 && d.width > 0 && d.height > 0) {
|
||||
k = (Math.round(d.width / r / 8) * 8) + 'x' + (Math.round(d.height / r / 8) * 8);
|
||||
|
||||
@@ -108,6 +108,7 @@ _GEN_ORCH_DEPS = {
|
||||
"load_existing_manifest": lambda *a, **kw: {},
|
||||
"save_manifest": lambda *a, **kw: None,
|
||||
"preserve_disk_comparisons": lambda *a, **kw: None,
|
||||
"base_dims_of": lambda item: {},
|
||||
"update_manifest_comparisons": lambda *a, **kw: {"comparisons": [], "added": 0, "removed": 0, "total": 0},
|
||||
},
|
||||
"model_loader": {
|
||||
|
||||
@@ -270,3 +270,18 @@ test('mergeComparisons dedupes by ts:a:b', () => {
|
||||
const m = R.mergeComparisons([{ a: 1, b: 2, ts: 5 }], [{ a: 1, b: 2, ts: 5 }, { a: 1, b: 2, ts: 6 }, { a: 2, b: 1, ts: 5 }]);
|
||||
assert.strictEqual(m.length, 3);
|
||||
});
|
||||
|
||||
// ---- wave 2 polish ----
|
||||
test('exact base dims win over a conflicting ratio guess; also on a non-upscaled item', () => {
|
||||
const d = { width: 1248, height: 1824, upscaled: true, upscale_ratio: 1.5, base_width: 832, base_height: 1216 };
|
||||
assert.strictEqual(R.normalizeAxis('resolution', d, {}).key, '832x1216');
|
||||
assert.strictEqual(R.normalizeAxis('resolution', { width: 100, height: 100, base_width: 64, base_height: 96 }, {}).key, '64x96');
|
||||
});
|
||||
test('per-image fields never make two items different settings (F2)', () => {
|
||||
const a = item({ config_fp: 'a', base_fp: 'x', metrics: { s: 1 }, preview_of: 5, source_relpath: 'p/a.png' });
|
||||
const b = item({ cfg: 5, config_fp: 'b', base_fp: 'y', metrics: { s: 2 }, preview_of: 6, source_relpath: 'p/b.png',
|
||||
florence2_bbox: [1, 2, 3, 4], florence2_detection_count: 2 });
|
||||
const df = R.axisDiff(a, b, { withCtxKeys: true });
|
||||
assert.strictEqual(df.ctxEqual, true, JSON.stringify(df.ctxKeys));
|
||||
assert.deepStrictEqual(df.axes, ['cfg']);
|
||||
});
|
||||
|
||||
@@ -143,3 +143,23 @@ def test_no_detection_entry_does_not_carry_upscale_metadata():
|
||||
# They don't claim to be an upscale output
|
||||
assert entry["upscaled"] is False
|
||||
assert "upscale_mode" not in entry
|
||||
|
||||
|
||||
def test_base_dims_kwarg_lands_in_both_builders():
|
||||
from florence2_hires import build_florence2_manifest_entry, build_florence2_no_detection_entry
|
||||
|
||||
step = {"status": "ok", "image_width": 1536, "image_height": 1536, "florence2_text_input": "face"}
|
||||
kw = dict(session_name="s", pipeline_name="P", upscale_id=1, upscaled_filename="f.webp",
|
||||
current_index=0, hires_denoise=0.5)
|
||||
item = {"id": 9, "file": "/view?x", "filename": "x.webp"}
|
||||
plain = build_florence2_manifest_entry(step, item, **kw)
|
||||
assert "base_width" not in plain and "base_height" not in plain
|
||||
dims = {"base_width": 1024, "base_height": 1024}
|
||||
withd = build_florence2_manifest_entry(step, item, base_dims=dims, **kw)
|
||||
assert withd["base_width"] == 1024 and withd["base_height"] == 1024
|
||||
assert {k: v for k, v in withd.items() if not k.startswith("base_")} == plain
|
||||
|
||||
s_plain = build_florence2_no_detection_entry(step, item, sentinel_id=3, current_index=0)
|
||||
assert "base_width" not in s_plain
|
||||
s_dims = build_florence2_no_detection_entry(step, item, sentinel_id=3, current_index=0, base_dims=dims)
|
||||
assert s_dims["base_width"] == 1024 and s_dims["florence2_no_detection"] is True
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Source guard: every place that writes an upscaled manifest item also records base_width/base_height."""
|
||||
import os
|
||||
import re
|
||||
|
||||
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
_WRITE = re.compile(r"""("upscaled": True|\["upscaled"\] = True)""")
|
||||
_SENTINEL = re.compile(r"\n\s*(?:sentinel_meta\.update\(|build_florence2_no_detection_entry\()")
|
||||
_BASE = ("base_width", "base_dims_of", "base_dims=")
|
||||
|
||||
|
||||
def _read(name):
|
||||
with open(os.path.join(_ROOT, name), encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def _blocks(text):
|
||||
"""Text from each upscaled-True write up to the manifest insert/append that follows it."""
|
||||
out = []
|
||||
for m in _WRITE.finditer(text):
|
||||
tail = text[m.end():]
|
||||
end = re.search(r"(items\"\]\.(insert|append)\()", tail)
|
||||
out.append((m.start(), text[max(0, m.start() - 1500): m.end() + (end.end() if end else 1500)]))
|
||||
return out
|
||||
|
||||
|
||||
def test_every_upscaled_write_records_base_dims():
|
||||
total = 0
|
||||
for name in ("generation_orchestrator.py", "upscale_runner.py"):
|
||||
text = _read(name)
|
||||
for pos, block in _blocks(text):
|
||||
total += 1
|
||||
line = text.count("\n", 0, pos) + 1
|
||||
assert any(tok in block for tok in _BASE), f"{name}:{line} writes an upscaled item without base dims"
|
||||
assert total >= 6 # 1 deferred + 3 inline + 2 dashboard (sanity: the regex still finds them)
|
||||
|
||||
|
||||
def test_sentinels_record_base_dims():
|
||||
for name in ("generation_orchestrator.py", "upscale_runner.py"):
|
||||
text = _read(name)
|
||||
for m in _SENTINEL.finditer(text):
|
||||
assert any(tok in text[m.end(): m.end() + 900] for tok in _BASE), f"{name}: sentinel without base dims"
|
||||
|
||||
|
||||
def test_base_dims_of_cases():
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location("_mu_real", os.path.join(_ROOT, "manifest_utils.py"))
|
||||
mu = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mu)
|
||||
f = mu.base_dims_of
|
||||
assert f({"width": 832, "height": 1216}) == {"base_width": 832, "base_height": 1216}
|
||||
assert f({"width": 1248, "height": 1824, "upscaled": True, "base_width": 832, "base_height": 1216}) == \
|
||||
{"base_width": 832, "base_height": 1216}
|
||||
assert f({"width": 1248, "height": 1824, "upscaled": True}) == {}
|
||||
assert f({}) == {}
|
||||
assert f({"width": 0, "height": 5}) == {}
|
||||
+5
-1
@@ -18,7 +18,7 @@ from PIL import Image as PILImage
|
||||
from .model_loader import load_checkpoint, load_vae_by_name
|
||||
from .batch_encoding import encode_prompt_with_combinators, encode_pair_cached
|
||||
from .image_generation import upscale_image, decode_latent_with_vae, create_image_metadata
|
||||
from .manifest_utils import save_manifest
|
||||
from .manifest_utils import save_manifest, base_dims_of
|
||||
from . import image_metrics
|
||||
|
||||
import comfy.model_management as mm
|
||||
@@ -346,6 +346,7 @@ def _run_upscale_thread(job, target_items, upscale_config, meta, manifest_data,
|
||||
"width": up_w, "height": up_h,
|
||||
"duration": round(sv_duration, 2),
|
||||
"upscaled": True,
|
||||
**base_dims_of(item),
|
||||
"upscale_source": "dashboard",
|
||||
"upscale_pipeline": pipeline_name,
|
||||
"upscale_mode": "seedvr2",
|
||||
@@ -420,6 +421,7 @@ def _run_upscale_thread(job, target_items, upscale_config, meta, manifest_data,
|
||||
step_result, item,
|
||||
sentinel_id=sentinel_id,
|
||||
current_index=len(manifest_data["items"]),
|
||||
base_dims=base_dims_of(item),
|
||||
)
|
||||
manifest_data["items"].insert(0, entry)
|
||||
upscale_combo_idx += 1
|
||||
@@ -448,6 +450,7 @@ def _run_upscale_thread(job, target_items, upscale_config, meta, manifest_data,
|
||||
upscaled_filename=upscaled_filename,
|
||||
current_index=len(manifest_data["items"]),
|
||||
hires_denoise=f2_config["hires_denoise"],
|
||||
base_dims=base_dims_of(item),
|
||||
)
|
||||
image_metrics.finalize(entry, result_pil, spec=item_metrics_spec)
|
||||
manifest_data["items"].insert(0, entry)
|
||||
@@ -561,6 +564,7 @@ def _run_upscale_thread(job, target_items, upscale_config, meta, manifest_data,
|
||||
"height": up_h,
|
||||
"duration": round(upscale_duration, 2),
|
||||
"upscaled": True,
|
||||
**base_dims_of(item),
|
||||
"upscale_source": "dashboard",
|
||||
"upscale_pipeline": pipeline_name,
|
||||
"upscale_mode": mode,
|
||||
|
||||
Reference in New Issue
Block a user