diffeval 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffeval/__init__.py +7 -0
- diffeval/__main__.py +6 -0
- diffeval/aggregate/__init__.py +1 -0
- diffeval/aggregate/mean.py +7 -0
- diffeval/aggregate/names.py +11 -0
- diffeval/aggregate/row.py +26 -0
- diffeval/aggregate/sample_std.py +9 -0
- diffeval/aggregate/series.py +22 -0
- diffeval/aggregate/table.py +8 -0
- diffeval/backends/__init__.py +4 -0
- diffeval/backends/diffusers/__init__.py +1 -0
- diffeval/backends/diffusers/attention_apply.py +16 -0
- diffeval/backends/diffusers/attention_eager.py +6 -0
- diffeval/backends/diffusers/attention_name.py +15 -0
- diffeval/backends/diffusers/attention_sdpa.py +6 -0
- diffeval/backends/diffusers/attention_xformers.py +5 -0
- diffeval/backends/diffusers/backend.py +29 -0
- diffeval/backends/diffusers/call_kwargs.py +15 -0
- diffeval/backends/diffusers/coerce_dtype.py +10 -0
- diffeval/backends/diffusers/describe.py +6 -0
- diffeval/backends/diffusers/dtype_name.py +23 -0
- diffeval/backends/diffusers/from_config.py +17 -0
- diffeval/backends/diffusers/generate.py +18 -0
- diffeval/backends/diffusers/kwargs_generator.py +6 -0
- diffeval/backends/diffusers/kwargs_prompt.py +11 -0
- diffeval/backends/diffusers/kwargs_sampling.py +18 -0
- diffeval/backends/diffusers/kwargs_size.py +18 -0
- diffeval/backends/diffusers/load_kwargs.py +10 -0
- diffeval/backends/diffusers/load_pipe.py +23 -0
- diffeval/backends/diffusers/missing_dep.py +10 -0
- diffeval/backends/diffusers/one_batch.py +11 -0
- diffeval/backends/diffusers/pick_device.py +13 -0
- diffeval/backends/diffusers/presets.py +8 -0
- diffeval/backends/diffusers/sampling.py +18 -0
- diffeval/backends/dummy/__init__.py +1 -0
- diffeval/backends/dummy/backend.py +29 -0
- diffeval/backends/dummy/box.py +9 -0
- diffeval/backends/dummy/colors.py +10 -0
- diffeval/backends/dummy/describe.py +18 -0
- diffeval/backends/dummy/draw.py +16 -0
- diffeval/backends/dummy/from_config.py +14 -0
- diffeval/backends/dummy/generate.py +17 -0
- diffeval/backends/dummy/render.py +18 -0
- diffeval/backends/noop_prepare.py +5 -0
- diffeval/backends/protocol.py +15 -0
- diffeval/bench/__init__.py +1 -0
- diffeval/bench/cmmd_check.py +20 -0
- diffeval/bench/cmmd_samples.py +15 -0
- diffeval/bench/coco30k_cli.py +23 -0
- diffeval/bench/coco30k_export.py +19 -0
- diffeval/bench/coco30k_files.py +16 -0
- diffeval/bench/coco30k_images.py +21 -0
- diffeval/bench/coco30k_suite.py +16 -0
- diffeval/bench/fetch.py +12 -0
- diffeval/bench/published.py +10 -0
- diffeval/bench/table_cli.py +16 -0
- diffeval/bench/table_row.py +24 -0
- diffeval/cli/__init__.py +1 -0
- diffeval/cli/command_list.py +13 -0
- diffeval/cli/command_run.py +25 -0
- diffeval/cli/config.py +14 -0
- diffeval/cli/csv_ints.py +8 -0
- diffeval/cli/csv_names.py +8 -0
- diffeval/cli/main.py +12 -0
- diffeval/cli/output_dir.py +12 -0
- diffeval/cli/parser.py +17 -0
- diffeval/cli/print_paths.py +12 -0
- diffeval/cli/print_registered.py +6 -0
- diffeval/cli/run_parser_eval.py +14 -0
- diffeval/cli/run_parser_model.py +13 -0
- diffeval/metrics/__init__.py +7 -0
- diffeval/metrics/clip/__init__.py +1 -0
- diffeval/metrics/clip/features.py +14 -0
- diffeval/metrics/clip/formula.py +6 -0
- diffeval/metrics/clip/load.py +17 -0
- diffeval/metrics/clip/metric.py +20 -0
- diffeval/metrics/clip/protocol.py +10 -0
- diffeval/metrics/clip/sample_image.py +10 -0
- diffeval/metrics/clip/score.py +25 -0
- diffeval/metrics/cmmd/__init__.py +1 -0
- diffeval/metrics/cmmd/embed_batch.py +26 -0
- diffeval/metrics/cmmd/embed_paths.py +30 -0
- diffeval/metrics/cmmd/metric.py +20 -0
- diffeval/metrics/cmmd/mmd.py +23 -0
- diffeval/metrics/cmmd/protocol.py +11 -0
- diffeval/metrics/cmmd/resize.py +17 -0
- diffeval/metrics/cmmd/score.py +24 -0
- diffeval/metrics/count_warning.py +7 -0
- diffeval/metrics/distribution/__init__.py +1 -0
- diffeval/metrics/distribution/call.py +24 -0
- diffeval/metrics/distribution/kwargs_folder.py +16 -0
- diffeval/metrics/distribution/kwargs_stats.py +8 -0
- diffeval/metrics/distribution/protocol.py +12 -0
- diffeval/metrics/distribution/seed_numpy.py +12 -0
- diffeval/metrics/fid/__init__.py +1 -0
- diffeval/metrics/fid/guard.py +13 -0
- diffeval/metrics/fid/metric.py +20 -0
- diffeval/metrics/geneval/__init__.py +1 -0
- diffeval/metrics/geneval/metric.py +22 -0
- diffeval/metrics/geneval/protocol.py +5 -0
- diffeval/metrics/geneval/tags.py +3 -0
- diffeval/metrics/images.py +12 -0
- diffeval/metrics/kid/__init__.py +1 -0
- diffeval/metrics/kid/guard.py +3 -0
- diffeval/metrics/kid/metric.py +20 -0
- diffeval/metrics/ok.py +9 -0
- diffeval/metrics/protocol.py +17 -0
- diffeval/metrics/skipped.py +10 -0
- diffeval/plugins.py +7 -0
- diffeval/registry/__init__.py +1 -0
- diffeval/registry/lookup.py +9 -0
- diffeval/registry/names.py +5 -0
- diffeval/registry/register.py +11 -0
- diffeval/registry/tables.py +5 -0
- diffeval/registry/unregister.py +5 -0
- diffeval/report/__init__.py +1 -0
- diffeval/report/console.py +18 -0
- diffeval/report/console_widths.py +5 -0
- diffeval/report/direction.py +7 -0
- diffeval/report/html_figure.py +9 -0
- diffeval/report/html_grid.py +19 -0
- diffeval/report/html_page.py +15 -0
- diffeval/report/html_row.py +12 -0
- diffeval/report/markdown.py +11 -0
- diffeval/report/markdown_row.py +10 -0
- diffeval/report/number.py +7 -0
- diffeval/report/templates/page.html +35 -0
- diffeval/runner/__init__.py +1 -0
- diffeval/runner/generate_save.py +19 -0
- diffeval/runner/metric_device.py +16 -0
- diffeval/runner/one_seed.py +20 -0
- diffeval/runner/options.py +23 -0
- diffeval/runner/payload.py +26 -0
- diffeval/runner/relpath.py +7 -0
- diffeval/runner/requests.py +7 -0
- diffeval/runner/run.py +24 -0
- diffeval/runner/safe_metric.py +14 -0
- diffeval/runner/save_png.py +8 -0
- diffeval/runner/seeds.py +23 -0
- diffeval/runner/write_json.py +11 -0
- diffeval/runner/write_reports.py +14 -0
- diffeval/system/__init__.py +1 -0
- diffeval/system/describe.py +10 -0
- diffeval/system/gpu.py +11 -0
- diffeval/system/gpu_cuda.py +9 -0
- diffeval/system/gpu_mps.py +8 -0
- diffeval/system/sdp_flags.py +15 -0
- diffeval/system/version_of.py +10 -0
- diffeval/system/versions.py +20 -0
- diffeval/tasks/__init__.py +3 -0
- diffeval/tasks/build_suite.py +7 -0
- diffeval/tasks/core.py +15 -0
- diffeval/tasks/errors.py +5 -0
- diffeval/tasks/fields_metrics.py +14 -0
- diffeval/tasks/fields_paths.py +25 -0
- diffeval/tasks/fields_prompts.py +15 -0
- diffeval/tasks/fields_seeds.py +14 -0
- diffeval/tasks/fields_size.py +12 -0
- diffeval/tasks/fields_text.py +13 -0
- diffeval/tasks/from_mapping.py +22 -0
- diffeval/tasks/load_bundled.py +9 -0
- diffeval/tasks/load_path.py +12 -0
- diffeval/tasks/one_prompt.py +17 -0
- diffeval/tasks/prompt_id.py +13 -0
- diffeval/tasks/read_yaml.py +14 -0
- diffeval/tasks/resolve.py +18 -0
- diffeval/tasks/suites/core.yaml +12 -0
- diffeval/tasks/suites/references/core/blue_sphere.png +0 -0
- diffeval/tasks/suites/references/core/green_triangle.png +0 -0
- diffeval/tasks/suites/references/core/red_cube.png +0 -0
- diffeval/tasks/suites/references/core/yellow_star.png +0 -0
- diffeval/types/__init__.py +1 -0
- diffeval/types/context.py +15 -0
- diffeval/types/prompt.py +10 -0
- diffeval/types/request.py +13 -0
- diffeval/types/result.py +14 -0
- diffeval/types/result_rules.py +13 -0
- diffeval/types/sample.py +15 -0
- diffeval/types/suite.py +21 -0
- diffeval-0.1.0.dist-info/METADATA +378 -0
- diffeval-0.1.0.dist-info/RECORD +185 -0
- diffeval-0.1.0.dist-info/WHEEL +5 -0
- diffeval-0.1.0.dist-info/entry_points.txt +6 -0
- diffeval-0.1.0.dist-info/licenses/LICENSE +190 -0
- diffeval-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Reference folder or .npy against generated PNGs."""
|
|
2
|
+
|
|
3
|
+
from diffeval.metrics.cmmd.embed_batch import load_vision
|
|
4
|
+
from diffeval.metrics.cmmd.embed_paths import paired
|
|
5
|
+
from diffeval.metrics.cmmd.mmd import mmd
|
|
6
|
+
from diffeval.metrics.cmmd.protocol import PROTOCOL
|
|
7
|
+
from diffeval.metrics.count_warning import count_warning
|
|
8
|
+
from diffeval.metrics.ok import ok
|
|
9
|
+
from diffeval.metrics.skipped import skipped
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def score_cmmd(context):
|
|
13
|
+
if context.reference_dir is None and context.cmmd_ref_embed is None:
|
|
14
|
+
return skipped("cmmd", "reference set is missing", False, PROTOCOL)
|
|
15
|
+
loaded = load_vision(PROTOCOL["model"])
|
|
16
|
+
if loaded is None:
|
|
17
|
+
return skipped("cmmd", "metrics extra is not installed", False, PROTOCOL)
|
|
18
|
+
model, processor = loaded
|
|
19
|
+
model.to(context.device)
|
|
20
|
+
generated, reference = paired(context, model, processor)
|
|
21
|
+
if generated is None or reference is None:
|
|
22
|
+
return skipped("cmmd", "reference set is missing", False, PROTOCOL)
|
|
23
|
+
warning = count_warning(len(generated))
|
|
24
|
+
return ok("cmmd", float(mmd(reference, generated)), False, PROTOCOL, {} if warning is None else {"warning": warning})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""FID and KID."""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Call clean-fid for FID or KID."""
|
|
2
|
+
|
|
3
|
+
from diffeval.metrics.count_warning import count_warning
|
|
4
|
+
from diffeval.metrics.distribution.kwargs_folder import distribution_kwargs
|
|
5
|
+
from diffeval.metrics.distribution.seed_numpy import seeded
|
|
6
|
+
from diffeval.metrics.fid.guard import fid_ready
|
|
7
|
+
from diffeval.metrics.images import list_images
|
|
8
|
+
from diffeval.metrics.ok import ok
|
|
9
|
+
from diffeval.metrics.skipped import skipped
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def score_distribution(kind, context, protocol, model_name=None):
|
|
13
|
+
reason = fid_ready(context)
|
|
14
|
+
if reason:
|
|
15
|
+
return skipped(kind, reason, False, protocol)
|
|
16
|
+
try:
|
|
17
|
+
from cleanfid import fid as clean_fid
|
|
18
|
+
except ImportError:
|
|
19
|
+
return skipped(kind, "metrics extra is not installed", False, protocol)
|
|
20
|
+
fn = clean_fid.compute_fid if kind == "fid" else clean_fid.compute_kid
|
|
21
|
+
kwargs = distribution_kwargs(context, model_name)
|
|
22
|
+
value = seeded(context.seed, lambda: fn(**kwargs))
|
|
23
|
+
warning = count_warning(len(list_images(context.generated_dir)))
|
|
24
|
+
return ok(kind, float(value), False, protocol, {} if warning is None else {"warning": warning})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Folder-mode arguments for clean-fid."""
|
|
2
|
+
|
|
3
|
+
from diffeval.metrics.distribution.kwargs_stats import stats_kwargs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def folder_kwargs(reference, generated, device, batch_size, model_name=None) -> dict:
|
|
7
|
+
body = {"fdir1": str(reference), "fdir2": str(generated), "mode": "clean", "num_workers": 0, "batch_size": int(batch_size), "device": device, "verbose": False, "use_dataparallel": device == "cuda"}
|
|
8
|
+
if model_name:
|
|
9
|
+
body["model_name"] = model_name
|
|
10
|
+
return body
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def distribution_kwargs(context, model_name=None) -> dict:
|
|
14
|
+
if context.clean_fid_stats:
|
|
15
|
+
return stats_kwargs(context.generated_dir, context.clean_fid_stats, context.device, context.batch_size, model_name)
|
|
16
|
+
return folder_kwargs(context.reference_dir, context.generated_dir, context.device, context.batch_size, model_name)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Stats-mode arguments for clean-fid."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def stats_kwargs(generated, stats, device, batch_size, model_name=None) -> dict:
|
|
5
|
+
body = {"fdir1": str(generated), "dataset_name": stats["dataset_name"], "dataset_res": stats["dataset_res"], "dataset_split": stats["dataset_split"], "mode": "clean", "num_workers": 0, "batch_size": int(batch_size), "device": device, "verbose": False, "use_dataparallel": device == "cuda"}
|
|
6
|
+
if model_name:
|
|
7
|
+
body["model_name"] = model_name
|
|
8
|
+
return body
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Shared FID and KID protocol."""
|
|
2
|
+
|
|
3
|
+
PROTOCOL = {
|
|
4
|
+
"library": "clean-fid",
|
|
5
|
+
"mode": "clean",
|
|
6
|
+
"model": "inception_v3",
|
|
7
|
+
"resize": "clean-fid resizes each RGB image to 299x299 with PIL bicubic per channel in float and does not quantize back to uint8",
|
|
8
|
+
"pre_resize": "diffeval does not pre-resize",
|
|
9
|
+
"num_workers": 0,
|
|
10
|
+
"dataparallel": "only when the metric device is cuda",
|
|
11
|
+
"kid": "degree-3 polynomial, 100 subsets, subset size at most 1000",
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""FID metric."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Require two images and a reference or stats."""
|
|
2
|
+
|
|
3
|
+
from diffeval.metrics.images import list_images
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def fid_ready(context) -> str | None:
|
|
7
|
+
if context.reference_dir is None and not context.clean_fid_stats:
|
|
8
|
+
return "reference set is missing"
|
|
9
|
+
if len(list_images(context.generated_dir)) < 2:
|
|
10
|
+
return "fewer than two images"
|
|
11
|
+
if context.reference_dir is not None and not context.clean_fid_stats and len(list_images(context.reference_dir)) < 2:
|
|
12
|
+
return "fewer than two images"
|
|
13
|
+
return None
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Register fid."""
|
|
2
|
+
|
|
3
|
+
from diffeval.metrics.distribution.call import score_distribution
|
|
4
|
+
from diffeval.metrics.distribution.protocol import PROTOCOL
|
|
5
|
+
from diffeval.registry.register import register
|
|
6
|
+
from diffeval.registry.tables import METRICS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Fid:
|
|
10
|
+
name = "fid"
|
|
11
|
+
description = "FID via clean-fid, mode clean, Inception-v3."
|
|
12
|
+
higher_is_better = False
|
|
13
|
+
requires_references = True
|
|
14
|
+
protocol = PROTOCOL
|
|
15
|
+
|
|
16
|
+
def compute(self, samples, context):
|
|
17
|
+
return score_distribution("fid", context, self.protocol, "inception_v3")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
register(METRICS, "fid")(Fid)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""GenEval stub."""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Register geneval and return skipped."""
|
|
2
|
+
|
|
3
|
+
from diffeval.metrics.geneval.protocol import PROTOCOL
|
|
4
|
+
from diffeval.metrics.geneval.tags import TAGS
|
|
5
|
+
from diffeval.metrics.skipped import skipped
|
|
6
|
+
from diffeval.registry.register import register
|
|
7
|
+
from diffeval.registry.tables import METRICS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Geneval:
|
|
11
|
+
name = "geneval"
|
|
12
|
+
description = "GenEval v1 stub. The Mask2Former detector is not imported."
|
|
13
|
+
higher_is_better = True
|
|
14
|
+
requires_references = False
|
|
15
|
+
protocol = PROTOCOL
|
|
16
|
+
|
|
17
|
+
def compute(self, samples, context):
|
|
18
|
+
details = {"by_tag": {tag: None for tag in TAGS}}
|
|
19
|
+
return skipped(self.name, "GenEval detector is not in v1", True, self.protocol, details)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
register(METRICS, "geneval")(Geneval)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""List image files in a directory."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
_SUFFIXES = {".png", ".jpg", ".jpeg", ".webp", ".bmp"}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def list_images(folder) -> list[Path]:
|
|
9
|
+
root = Path(folder)
|
|
10
|
+
if not root.is_dir():
|
|
11
|
+
return []
|
|
12
|
+
return sorted(path for path in root.iterdir() if path.suffix.lower() in _SUFFIXES)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""KID metric."""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Register kid."""
|
|
2
|
+
|
|
3
|
+
from diffeval.metrics.distribution.call import score_distribution
|
|
4
|
+
from diffeval.metrics.distribution.protocol import PROTOCOL
|
|
5
|
+
from diffeval.registry.register import register
|
|
6
|
+
from diffeval.registry.tables import METRICS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Kid:
|
|
10
|
+
name = "kid"
|
|
11
|
+
description = "KID via clean-fid, mode clean, polynomial degree 3."
|
|
12
|
+
higher_is_better = False
|
|
13
|
+
requires_references = True
|
|
14
|
+
protocol = PROTOCOL
|
|
15
|
+
|
|
16
|
+
def compute(self, samples, context):
|
|
17
|
+
return score_distribution("kid", context, self.protocol)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
register(METRICS, "kid")(Kid)
|
diffeval/metrics/ok.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Build an ok MetricResult."""
|
|
2
|
+
|
|
3
|
+
from diffeval.types.result import MetricResult
|
|
4
|
+
from diffeval.types.result_rules import check
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ok(name: str, value: float, higher_is_better, protocol: dict, details: dict | None = None):
|
|
8
|
+
check("ok", value, None)
|
|
9
|
+
return MetricResult(name, value, "ok", None, higher_is_better, details or {}, dict(protocol))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Metric contract."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from diffeval.types.context import EvalContext
|
|
6
|
+
from diffeval.types.result import MetricResult
|
|
7
|
+
from diffeval.types.sample import Sample
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Metric(Protocol):
|
|
11
|
+
name: str
|
|
12
|
+
description: str
|
|
13
|
+
higher_is_better: bool | None
|
|
14
|
+
requires_references: bool
|
|
15
|
+
protocol: dict
|
|
16
|
+
|
|
17
|
+
def compute(self, samples: list[Sample], context: EvalContext) -> MetricResult: ...
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Build a skipped MetricResult."""
|
|
2
|
+
|
|
3
|
+
from diffeval.types.result import MetricResult
|
|
4
|
+
from diffeval.types.result_rules import check
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def skipped(name: str, error: str, higher_is_better, protocol: dict, details: dict | None = None):
|
|
8
|
+
check("skipped", None, error)
|
|
9
|
+
body = {} if details is None else details
|
|
10
|
+
return MetricResult(name, None, "skipped", error, higher_is_better, body, dict(protocol))
|
diffeval/plugins.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Task, metric, and backend registries."""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Register one object under one name."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def register(table: dict, name: str):
|
|
5
|
+
def decorator(obj):
|
|
6
|
+
if name in table and table[name] is not obj:
|
|
7
|
+
raise ValueError(f"{name} is already registered")
|
|
8
|
+
table[name] = obj
|
|
9
|
+
return obj
|
|
10
|
+
|
|
11
|
+
return decorator
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Console table, Markdown report, and HTML report."""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Plain text table of aggregate scores."""
|
|
2
|
+
|
|
3
|
+
from diffeval.report.console_widths import widths
|
|
4
|
+
from diffeval.report.direction import direction
|
|
5
|
+
from diffeval.report.number import number
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _cells(name, row) -> list[str]:
|
|
9
|
+
seeds = ",".join(number(item) for item in row["per_seed"])
|
|
10
|
+
return [name, row["status"], number(row["mean"]), number(row["std"]), seeds, direction(row.get("higher_is_better"))]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def console(body: dict) -> str:
|
|
14
|
+
header = ["metric", "status", "mean", "std", "per-seed", "direction"]
|
|
15
|
+
rows = [header, *[_cells(name, row) for name, row in body["aggregate"].items()]]
|
|
16
|
+
size = widths(rows)
|
|
17
|
+
lines = [" ".join(cell.ljust(size[i]) for i, cell in enumerate(row)) for row in rows]
|
|
18
|
+
return "\n".join(lines) + "\n"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""One escaped figure for one sample."""
|
|
2
|
+
|
|
3
|
+
from html import escape
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def html_figure(sample: dict) -> str:
|
|
7
|
+
src = escape(sample["path"], quote=True)
|
|
8
|
+
label = escape(f"seed {sample['seed']}")
|
|
9
|
+
return f'<figure><img src="{src}" alt="{label}"><figcaption>{label}</figcaption></figure>'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Image grids grouped by prompt."""
|
|
2
|
+
|
|
3
|
+
from html import escape
|
|
4
|
+
|
|
5
|
+
from diffeval.report.html_figure import html_figure
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _samples(body, prompt_id):
|
|
9
|
+
return [sample for seed in body["per_seed"] for sample in seed["samples"] if sample["prompt_id"] == prompt_id]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def html_grid(body: dict) -> str:
|
|
13
|
+
blocks = []
|
|
14
|
+
for prompt in body["suite"]["prompts"]:
|
|
15
|
+
figures = "".join(html_figure(sample) for sample in _samples(body, prompt["id"]))
|
|
16
|
+
heading = escape(prompt["id"])
|
|
17
|
+
text = escape(prompt["prompt"])
|
|
18
|
+
blocks.append(f'<section><h2>{heading}</h2><p>{text}</p>{figures}</section>')
|
|
19
|
+
return "\n".join(blocks)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Fill the static HTML template."""
|
|
2
|
+
|
|
3
|
+
from html import escape
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from diffeval.report.html_grid import html_grid
|
|
7
|
+
from diffeval.report.html_row import html_row
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def html_page(body: dict) -> str:
|
|
11
|
+
rows = "\n".join(html_row(name, row) for name, row in body["aggregate"].items())
|
|
12
|
+
template = (Path(__file__).parent / "templates" / "page.html").read_text(encoding="utf-8")
|
|
13
|
+
text = template.replace("{{TITLE}}", escape(str(body["suite"]["name"])))
|
|
14
|
+
text = text.replace("{{ROWS}}", rows)
|
|
15
|
+
return text.replace("{{GRID}}", html_grid(body))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""One escaped HTML table row."""
|
|
2
|
+
|
|
3
|
+
from html import escape
|
|
4
|
+
|
|
5
|
+
from diffeval.report.direction import direction
|
|
6
|
+
from diffeval.report.number import number
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def html_row(name: str, row: dict) -> str:
|
|
10
|
+
seeds = ", ".join(number(item) for item in row["per_seed"])
|
|
11
|
+
cells = (name, row["status"], number(row["mean"]), number(row["std"]), seeds, direction(row.get("higher_is_better")))
|
|
12
|
+
return "<tr>" + "".join(f"<td>{escape(cell)}</td>" for cell in cells) + "</tr>"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Markdown report for one run."""
|
|
2
|
+
|
|
3
|
+
from diffeval.report.markdown_row import markdown_row
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def markdown(body: dict) -> str:
|
|
7
|
+
header = "| metric | status | mean | std | per-seed | direction |"
|
|
8
|
+
rule = "| --- | --- | --- | --- | --- | --- |"
|
|
9
|
+
rows = [markdown_row(name, row) for name, row in body["aggregate"].items()]
|
|
10
|
+
title = f"# {body['suite']['name']}"
|
|
11
|
+
return "\n".join([title, "", header, rule, *rows, ""]) + "\n"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""One Markdown table row."""
|
|
2
|
+
|
|
3
|
+
from diffeval.report.direction import direction
|
|
4
|
+
from diffeval.report.number import number
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def markdown_row(name: str, row: dict) -> str:
|
|
8
|
+
seeds = ", ".join(number(item) for item in row["per_seed"])
|
|
9
|
+
cells = (name, row["status"], number(row["mean"]), number(row["std"]), seeds, direction(row.get("higher_is_better")))
|
|
10
|
+
return "| " + " | ".join(cells) + " |"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>{{TITLE}}</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: sans-serif; margin: 1.5rem; color: #222; }
|
|
8
|
+
.note { max-width: 40rem; }
|
|
9
|
+
.layout { display: flex; gap: 2rem; align-items: flex-start; flex-wrap: wrap; }
|
|
10
|
+
table { border-collapse: collapse; }
|
|
11
|
+
th, td { border: 1px solid #ccc; padding: 0.3rem 0.6rem; text-align: left; }
|
|
12
|
+
section { margin-bottom: 1.5rem; }
|
|
13
|
+
figure { display: inline-block; margin: 0.4rem; vertical-align: top; }
|
|
14
|
+
img { display: block; max-width: 12rem; height: auto; background: #eee; }
|
|
15
|
+
figcaption { font-size: 0.85rem; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<h1>{{TITLE}}</h1>
|
|
20
|
+
<p class="note">Metric numbers have to be checked against the published reference before they are compared to a paper. Scores have to be checked against the published reference implementation and dataset. The core suite is a smoke suite. The bundled drawings are not COCO.</p>
|
|
21
|
+
<div class="layout">
|
|
22
|
+
<table>
|
|
23
|
+
<thead>
|
|
24
|
+
<tr><th>metric</th><th>status</th><th>mean</th><th>std</th><th>per-seed</th><th>direction</th></tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody>
|
|
27
|
+
{{ROWS}}
|
|
28
|
+
</tbody>
|
|
29
|
+
</table>
|
|
30
|
+
<div class="grids">
|
|
31
|
+
{{GRID}}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Generate images, score them, and write results.json."""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Generate in chunks, save each PNG, and drop the pixels from memory."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from diffeval.runner.relpath import relpath
|
|
6
|
+
from diffeval.runner.save_png import save_png
|
|
7
|
+
|
|
8
|
+
CHUNK = 64
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_save(backend, requests, folder: Path, output_dir) -> list:
|
|
12
|
+
samples = []
|
|
13
|
+
for start in range(0, len(requests), CHUNK):
|
|
14
|
+
for sample in backend.generate(requests[start:start + CHUNK]):
|
|
15
|
+
path = folder / f"{sample.prompt_id}.png"
|
|
16
|
+
save_png(sample.image, path)
|
|
17
|
+
sample.path, sample.image = relpath(path, Path(output_dir)), None
|
|
18
|
+
samples.append(sample)
|
|
19
|
+
return samples
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Pick the device metrics use."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def metric_device(requested: str | None) -> str:
|
|
5
|
+
if requested:
|
|
6
|
+
return requested
|
|
7
|
+
try:
|
|
8
|
+
import torch
|
|
9
|
+
except ImportError:
|
|
10
|
+
return "cpu"
|
|
11
|
+
if torch.cuda.is_available():
|
|
12
|
+
return "cuda"
|
|
13
|
+
mps = getattr(torch.backends, "mps", None)
|
|
14
|
+
if mps is not None and mps.is_available():
|
|
15
|
+
return "mps"
|
|
16
|
+
return "cpu"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Generate, save, and score one seed."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from diffeval.runner.generate_save import generate_save
|
|
6
|
+
from diffeval.runner.requests import requests_for
|
|
7
|
+
from diffeval.runner.safe_metric import safe_metric
|
|
8
|
+
from diffeval.types.context import EvalContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _public(sample) -> dict:
|
|
12
|
+
return {"prompt_id": sample.prompt_id, "prompt": sample.prompt, "seed": sample.seed, "width": sample.width, "height": sample.height, "path": sample.path, "negative_prompt": sample.negative_prompt}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def one_seed(backend, suite, seed, picked, metrics, output_dir, device):
|
|
16
|
+
folder = Path(output_dir) / "images" / f"seed_{seed}"
|
|
17
|
+
samples = generate_save(backend, requests_for(suite, seed, picked["width"], picked["height"]), folder, output_dir)
|
|
18
|
+
context = EvalContext(folder, suite.reference_dir, device, seed, suite.clean_fid_stats, suite.cmmd_ref_embed, backend.describe()["batch_size"])
|
|
19
|
+
scored = [safe_metric(metrics[name], samples, context) for name in picked["metrics"]]
|
|
20
|
+
return {"seed": seed, "samples": [_public(item) for item in samples], "metrics": scored}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Options for one evaluation run."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from diffeval.types.suite import Suite
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class RunOptions:
|
|
11
|
+
suite: Suite
|
|
12
|
+
backend: str
|
|
13
|
+
backend_config: dict
|
|
14
|
+
output_dir: Path
|
|
15
|
+
seeds: tuple[int, ...] | None = None
|
|
16
|
+
metrics: tuple[str, ...] | None = None
|
|
17
|
+
width: int | None = None
|
|
18
|
+
height: int | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def chosen(options) -> dict:
|
|
22
|
+
suite = options.suite
|
|
23
|
+
return {"seeds": options.seeds or suite.seeds, "metrics": options.metrics or suite.metrics, "width": options.width or suite.width, "height": options.height or suite.height}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Assemble the results object."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
from diffeval.system.describe import describe as system_info
|
|
6
|
+
from diffeval.system.versions import versions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _text(path):
|
|
10
|
+
return None if path is None else str(path)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _protocols(per_seed):
|
|
14
|
+
found = {}
|
|
15
|
+
for seed in per_seed:
|
|
16
|
+
for metric in seed["metrics"]:
|
|
17
|
+
found.setdefault(metric["name"], metric.pop("protocol", {}))
|
|
18
|
+
return found
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def payload(options, backend, device, seeds, per_seed, aggregate) -> dict:
|
|
22
|
+
suite = options.suite
|
|
23
|
+
prompts = [{"id": item.id, "prompt": item.prompt, "negative_prompt": item.negative_prompt} for item in suite.prompts]
|
|
24
|
+
info = {"name": suite.name, "description": suite.description, "source": suite.source, "width": options.width or suite.width, "height": options.height or suite.height, "prompts": prompts, "reference_dir": _text(suite.reference_dir), "clean_fid_stats": suite.clean_fid_stats, "cmmd_ref_embed": _text(suite.cmmd_ref_embed)}
|
|
25
|
+
stamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
26
|
+
return {"schema_version": 1, "created_at": stamp, "suite": info, "backend": backend.describe(), "metric_device": device, "seeds": list(seeds), "versions": versions(), "system": system_info(), "metric_protocols": _protocols(per_seed), "per_seed": per_seed, "aggregate": aggregate, "outputs": ["results.json", "report.md", "report.html"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Generation requests for one seed."""
|
|
2
|
+
|
|
3
|
+
from diffeval.types.request import GenerationRequest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def requests_for(suite, seed: int, width: int, height: int) -> list:
|
|
7
|
+
return [GenerationRequest(item.id, item.prompt, seed, width, height, item.negative_prompt) for item in suite.prompts]
|