fix(manifest): keep per-item metrics written to disk when a generation run saves
metrics_runner computes metrics for an existing session straight to disk; a generation run's in-memory copy of those items had no metrics and would have wiped them on its next save. Disk wins for an item's metrics when the memory item has none (merge_manifest_user_changes, flush_batch_with_vae's disk sync, the remote VAE worker's disk sync). Fresh in-memory metrics are kept. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
add7fbb2cb
commit
e1936bfdad
@@ -1123,6 +1123,8 @@ def flush_batch_with_vae(pending_batch, vae, img_dir, existing_data, session_nam
|
||||
local_item["favorited"] = disk_item["favorited"]
|
||||
if "rejected" in disk_item:
|
||||
local_item["rejected"] = disk_item["rejected"]
|
||||
if "metrics" in disk_item and "metrics" not in local_item:
|
||||
local_item["metrics"] = disk_item["metrics"] # computed later for an existing session
|
||||
|
||||
except Exception as e:
|
||||
print(f"[GridTester] ⚠️ Error syncing with disk manifest: {e}")
|
||||
|
||||
@@ -185,6 +185,10 @@ def merge_manifest_user_changes(manifest_path, existing_data):
|
||||
if current_item.get("note"):
|
||||
item["note"] = current_item["note"]
|
||||
notes_preserved += 1
|
||||
# Per-image metrics computed for an existing session (metrics_runner) live only on
|
||||
# disk; a generation run's in-memory copy must not wipe them on its next save.
|
||||
if "metrics" in current_item and "metrics" not in item:
|
||||
item["metrics"] = current_item["metrics"]
|
||||
# else: This is a new item, no merge needed
|
||||
|
||||
# Log merge results if any user changes were preserved
|
||||
|
||||
@@ -262,6 +262,8 @@ class RemoteVAEDecodeWorker:
|
||||
local_item["favorited"] = disk_item["favorited"]
|
||||
if "rejected" in disk_item:
|
||||
local_item["rejected"] = disk_item["rejected"]
|
||||
if "metrics" in disk_item and "metrics" not in local_item:
|
||||
local_item["metrics"] = disk_item["metrics"] # computed later for an existing session
|
||||
|
||||
except Exception as e:
|
||||
print(f"[GridTester] ⚠️ Error syncing with disk manifest: {e}")
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"""A generation run's manifest save must not wipe per-image metrics that were computed
|
||||
for an existing session (metrics_runner writes them straight to disk while the run's
|
||||
in-memory copy of those items has no `metrics`). Disk wins for an item's metrics when
|
||||
the in-memory item has none; freshly computed in-memory metrics are kept.
|
||||
|
||||
Loads the real manifest_utils.py via importlib (tests/conftest.py registers a stub
|
||||
under the package name), like test_comparisons_manifest.py does.
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
|
||||
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
_spec = importlib.util.spec_from_file_location("manifest_utils_real_metrics", os.path.join(_ROOT, "manifest_utils.py"))
|
||||
mu = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(mu)
|
||||
|
||||
|
||||
def _item(i, **extra):
|
||||
d = {"id": i, "file": f"/view?filename=img_{i}.webp", "favorited": False, "rejected": False, "note": ""}
|
||||
d.update(extra)
|
||||
return d
|
||||
|
||||
|
||||
def test_disk_metrics_survive_a_stale_in_memory_save(tmp_path):
|
||||
path = str(tmp_path / "manifest.json")
|
||||
disk = {"session_name": "s", "meta": {}, "items": [
|
||||
_item(2, metrics={"sharpness": 12.5, "entropy": 7.1}),
|
||||
_item(1, metrics={"sharpness": 3.0}),
|
||||
]}
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(disk, f)
|
||||
|
||||
# the generation run's copy predates the metrics job: same items, no metrics, plus a new image
|
||||
stale = {"session_name": "s", "meta": {}, "items": [_item(3), _item(2), _item(1)]}
|
||||
mu.save_manifest(path, stale)
|
||||
|
||||
with open(path, encoding="utf-8") as f:
|
||||
after = json.load(f)
|
||||
by_id = {it["id"]: it for it in after["items"]}
|
||||
assert set(by_id) == {1, 2, 3}, "the new image is kept"
|
||||
assert by_id[2]["metrics"] == {"sharpness": 12.5, "entropy": 7.1}
|
||||
assert by_id[1]["metrics"] == {"sharpness": 3.0}
|
||||
assert "metrics" not in by_id[3]
|
||||
|
||||
|
||||
def test_fresh_in_memory_metrics_are_not_overwritten_by_disk(tmp_path):
|
||||
path = str(tmp_path / "manifest.json")
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump({"session_name": "s", "meta": {}, "items": [_item(1, metrics={"sharpness": 1.0})]}, f)
|
||||
fresh = {"session_name": "s", "meta": {}, "items": [_item(1, metrics={"sharpness": 99.0})]}
|
||||
mu.save_manifest(path, fresh)
|
||||
with open(path, encoding="utf-8") as f:
|
||||
after = json.load(f)
|
||||
assert after["items"][0]["metrics"] == {"sharpness": 99.0}
|
||||
Reference in New Issue
Block a user