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
diffeval/runner/run.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Run one evaluation and write results.json."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from diffeval.aggregate.table import aggregate
|
|
6
|
+
from diffeval.registry.lookup import get
|
|
7
|
+
from diffeval.registry.tables import BACKENDS
|
|
8
|
+
from diffeval.runner.metric_device import metric_device
|
|
9
|
+
from diffeval.runner.options import chosen
|
|
10
|
+
from diffeval.runner.payload import payload
|
|
11
|
+
from diffeval.runner.seeds import score_seeds
|
|
12
|
+
from diffeval.runner.write_reports import write_reports
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_evaluation(options):
|
|
16
|
+
picked = chosen(options)
|
|
17
|
+
backend = get(BACKENDS, options.backend, "backend").from_config(options.backend_config)
|
|
18
|
+
backend.prepare()
|
|
19
|
+
device = metric_device(options.backend_config.get("device"))
|
|
20
|
+
out = Path(options.output_dir)
|
|
21
|
+
per_seed = score_seeds(backend, options.suite, picked, out, device)
|
|
22
|
+
body = payload(options, backend, device, picked["seeds"], per_seed, aggregate(per_seed))
|
|
23
|
+
write_reports(body, out)
|
|
24
|
+
return body
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Call compute and record an error if it raises."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _record(name, value, status, error, higher, protocol, details=None) -> dict:
|
|
5
|
+
return {"name": name, "value": value, "status": status, "error": error, "higher_is_better": higher, "details": details or {}, "protocol": protocol}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def safe_metric(metric, samples, context) -> dict:
|
|
9
|
+
try:
|
|
10
|
+
result = metric.compute(samples, context)
|
|
11
|
+
except Exception as exc:
|
|
12
|
+
higher = getattr(metric, "higher_is_better", None)
|
|
13
|
+
return _record(metric.name, None, "error", str(exc), higher, getattr(metric, "protocol", {}))
|
|
14
|
+
return _record(result.name, result.value, result.status, result.error, result.higher_is_better, result.protocol, result.details)
|
diffeval/runner/seeds.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Score every seed."""
|
|
2
|
+
|
|
3
|
+
from diffeval.metrics.skipped import skipped
|
|
4
|
+
from diffeval.registry.lookup import get
|
|
5
|
+
from diffeval.registry.tables import METRICS
|
|
6
|
+
from diffeval.runner.one_seed import one_seed
|
|
7
|
+
|
|
8
|
+
_PENDING = {"clip_score", "cmmd", "fid", "kid"}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _metric(name):
|
|
12
|
+
try:
|
|
13
|
+
return get(METRICS, name, "metric")()
|
|
14
|
+
except KeyError:
|
|
15
|
+
if name not in _PENDING:
|
|
16
|
+
raise
|
|
17
|
+
compute = lambda self, samples, context: skipped(name, "metric is not installed", None, {})
|
|
18
|
+
return type("Missing", (), {"name": name, "compute": compute, "higher_is_better": None, "protocol": {}})()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def score_seeds(backend, suite, picked, output_dir, device):
|
|
22
|
+
metrics = {name: _metric(name) for name in picked["metrics"]}
|
|
23
|
+
return [one_seed(backend, suite, seed, picked, metrics, output_dir, device) for seed in picked["seeds"]]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Write results.json."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def write_json(body: dict, output_dir: Path) -> Path:
|
|
8
|
+
path = Path(output_dir) / "results.json"
|
|
9
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
10
|
+
path.write_text(json.dumps(body, indent=2) + "\n", encoding="utf-8")
|
|
11
|
+
return path
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Write report.md and report.html."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from diffeval.report.html_page import html_page
|
|
6
|
+
from diffeval.report.markdown import markdown
|
|
7
|
+
from diffeval.runner.write_json import write_json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def write_reports(body: dict, output_dir) -> None:
|
|
11
|
+
write_json(body, output_dir)
|
|
12
|
+
folder = Path(output_dir)
|
|
13
|
+
(folder / "report.md").write_text(markdown(body), encoding="utf-8")
|
|
14
|
+
(folder / "report.html").write_text(html_page(body), encoding="utf-8")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Versions, GPU, and attention flags for a run."""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Platform, GPU, and attention flags."""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from diffeval.system.gpu import gpu
|
|
6
|
+
from diffeval.system.sdp_flags import sdp_flags
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def describe() -> dict:
|
|
10
|
+
return {"platform": platform.platform(), "gpu": gpu(), "torch_attention": sdp_flags()}
|
diffeval/system/gpu.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""CUDA device name, index, count, and memory."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def gpu_cuda(torch):
|
|
5
|
+
if not torch.cuda.is_available():
|
|
6
|
+
return None
|
|
7
|
+
index = torch.cuda.current_device()
|
|
8
|
+
props = torch.cuda.get_device_properties(index)
|
|
9
|
+
return {"name": props.name, "index": index, "count": torch.cuda.device_count(), "total_memory": props.total_memory}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""PyTorch scaled-dot-product attention flags."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def sdp_flags():
|
|
5
|
+
try:
|
|
6
|
+
import torch
|
|
7
|
+
except ImportError:
|
|
8
|
+
return None
|
|
9
|
+
cuda = torch.backends.cuda
|
|
10
|
+
return {
|
|
11
|
+
"flash_sdp": cuda.flash_sdp_enabled(),
|
|
12
|
+
"mem_efficient_sdp": cuda.mem_efficient_sdp_enabled(),
|
|
13
|
+
"math_sdp": cuda.math_sdp_enabled(),
|
|
14
|
+
"cudnn_sdp": cuda.cudnn_sdp_enabled(),
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Package versions recorded in results.json."""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from diffeval.system.version_of import version_of
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def versions() -> dict:
|
|
9
|
+
return {
|
|
10
|
+
"diffeval": version_of("diffeval"),
|
|
11
|
+
"python": platform.python_version(),
|
|
12
|
+
"numpy": version_of("numpy"),
|
|
13
|
+
"pillow": version_of("pillow"),
|
|
14
|
+
"pyyaml": version_of("pyyaml"),
|
|
15
|
+
"torch": version_of("torch"),
|
|
16
|
+
"diffusers": version_of("diffusers"),
|
|
17
|
+
"transformers": version_of("transformers"),
|
|
18
|
+
"accelerate": version_of("accelerate"),
|
|
19
|
+
"clean-fid": version_of("clean-fid"),
|
|
20
|
+
}
|
diffeval/tasks/core.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Register the bundled core suite."""
|
|
2
|
+
|
|
3
|
+
from diffeval.registry.register import register
|
|
4
|
+
from diffeval.registry.tables import TASKS
|
|
5
|
+
from diffeval.tasks.load_bundled import load_bundled
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Core:
|
|
9
|
+
description = "Four synthetic prompts for a CPU smoke run."
|
|
10
|
+
|
|
11
|
+
def load(self):
|
|
12
|
+
return load_bundled("core.yaml")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
register(TASKS, "core")(Core())
|
diffeval/tasks/errors.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Metric name list."""
|
|
2
|
+
|
|
3
|
+
from diffeval.tasks.errors import SuiteError
|
|
4
|
+
|
|
5
|
+
_DEFAULT = ("clip_score", "cmmd", "fid", "kid")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def metrics(data: dict) -> dict:
|
|
9
|
+
raw = data.get("metrics", list(_DEFAULT))
|
|
10
|
+
if not isinstance(raw, list) or not raw:
|
|
11
|
+
raise SuiteError("metrics must be a non-empty list")
|
|
12
|
+
if any(not isinstance(item, str) or not item for item in raw):
|
|
13
|
+
raise SuiteError("metric names must be non-empty strings")
|
|
14
|
+
return {"metrics": tuple(raw)}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""reference_dir, cmmd_ref_embed, and clean_fid_stats."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from diffeval.tasks.errors import SuiteError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _path(base: Path, value):
|
|
9
|
+
if value is None:
|
|
10
|
+
return None
|
|
11
|
+
if not isinstance(value, str) or not value:
|
|
12
|
+
raise SuiteError("path fields must be strings")
|
|
13
|
+
path = Path(value)
|
|
14
|
+
return path if path.is_absolute() else base / path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def paths(data: dict, base: Path) -> dict:
|
|
18
|
+
stats = data.get("clean_fid_stats")
|
|
19
|
+
if stats is not None and not isinstance(stats, dict):
|
|
20
|
+
raise SuiteError("clean_fid_stats must be a mapping")
|
|
21
|
+
return {
|
|
22
|
+
"reference_dir": _path(base, data.get("reference_dir")),
|
|
23
|
+
"cmmd_ref_embed": _path(base, data.get("cmmd_ref_embed")),
|
|
24
|
+
"clean_fid_stats": stats,
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Prompt list and duplicate ids, compared ignoring case."""
|
|
2
|
+
|
|
3
|
+
from diffeval.tasks.errors import SuiteError
|
|
4
|
+
from diffeval.tasks.one_prompt import one_prompt
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def prompts(data: dict) -> dict:
|
|
8
|
+
raw = data.get("prompts")
|
|
9
|
+
if not isinstance(raw, list) or not raw:
|
|
10
|
+
raise SuiteError("prompts must be a non-empty list")
|
|
11
|
+
built = tuple(one_prompt(item) for item in raw)
|
|
12
|
+
ids = [item.id.casefold() for item in built]
|
|
13
|
+
if len(ids) != len(set(ids)):
|
|
14
|
+
raise SuiteError("duplicate prompt id (ids are compared ignoring case)")
|
|
15
|
+
return {"prompts": built}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Non-empty list of distinct seeds."""
|
|
2
|
+
|
|
3
|
+
from diffeval.tasks.errors import SuiteError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def seeds(data: dict) -> dict:
|
|
7
|
+
raw = data.get("seeds")
|
|
8
|
+
if not isinstance(raw, list) or not raw:
|
|
9
|
+
raise SuiteError("seeds must be a non-empty list of ints")
|
|
10
|
+
if any(type(item) is not int for item in raw):
|
|
11
|
+
raise SuiteError("seeds must be ints")
|
|
12
|
+
if len(set(raw)) != len(raw):
|
|
13
|
+
raise SuiteError("seeds must not repeat")
|
|
14
|
+
return {"seeds": tuple(raw)}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Width and height, defaulting to 512."""
|
|
2
|
+
|
|
3
|
+
from diffeval.tasks.errors import SuiteError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def width_and_height(data: dict) -> dict:
|
|
7
|
+
width = data.get("width", 512)
|
|
8
|
+
height = data.get("height", 512)
|
|
9
|
+
bad = type(width) is not int or type(height) is not int
|
|
10
|
+
if bad or width < 1 or height < 1:
|
|
11
|
+
raise SuiteError("width and height must be positive ints")
|
|
12
|
+
return {"width": width, "height": height}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Suite name and description."""
|
|
2
|
+
|
|
3
|
+
from diffeval.tasks.errors import SuiteError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def name_and_description(data: dict) -> dict:
|
|
7
|
+
name = data.get("name")
|
|
8
|
+
description = data.get("description", "")
|
|
9
|
+
if not isinstance(name, str) or not name:
|
|
10
|
+
raise SuiteError("name must be a non-empty string")
|
|
11
|
+
if not isinstance(description, str):
|
|
12
|
+
raise SuiteError("description must be a string")
|
|
13
|
+
return {"name": name, "description": description}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Build a Suite from a mapping."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from diffeval.tasks.build_suite import build_suite
|
|
6
|
+
from diffeval.tasks.fields_metrics import metrics
|
|
7
|
+
from diffeval.tasks.fields_paths import paths
|
|
8
|
+
from diffeval.tasks.fields_prompts import prompts
|
|
9
|
+
from diffeval.tasks.fields_seeds import seeds
|
|
10
|
+
from diffeval.tasks.fields_size import width_and_height
|
|
11
|
+
from diffeval.tasks.fields_text import name_and_description
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def from_mapping(data: dict, source: str, base: Path):
|
|
15
|
+
fields = {"source": source}
|
|
16
|
+
fields.update(name_and_description(data))
|
|
17
|
+
fields.update(width_and_height(data))
|
|
18
|
+
fields.update(seeds(data))
|
|
19
|
+
fields.update(prompts(data))
|
|
20
|
+
fields.update(metrics(data))
|
|
21
|
+
fields.update(paths(data, base))
|
|
22
|
+
return build_suite(fields)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Load a suite from a filesystem YAML path."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from diffeval.tasks.from_mapping import from_mapping
|
|
6
|
+
from diffeval.tasks.read_yaml import read_yaml
|
|
7
|
+
from diffeval.types.suite import Suite
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_path(path) -> Suite:
|
|
11
|
+
file = Path(path)
|
|
12
|
+
return from_mapping(read_yaml(file), str(file), file.parent)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""One prompt mapping."""
|
|
2
|
+
|
|
3
|
+
from diffeval.tasks.errors import SuiteError
|
|
4
|
+
from diffeval.tasks.prompt_id import check_id
|
|
5
|
+
from diffeval.types.prompt import Prompt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def one_prompt(item: dict) -> Prompt:
|
|
9
|
+
if not isinstance(item, dict):
|
|
10
|
+
raise SuiteError("prompt must be a mapping")
|
|
11
|
+
text = item.get("prompt")
|
|
12
|
+
if not isinstance(text, str) or not text.strip():
|
|
13
|
+
raise SuiteError("prompt text must be a non-empty string")
|
|
14
|
+
negative = item.get("negative_prompt")
|
|
15
|
+
if negative is not None and not isinstance(negative, str):
|
|
16
|
+
raise SuiteError("negative_prompt must be a string")
|
|
17
|
+
return Prompt(check_id(item.get("id")), text, negative)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Prompt ids that are safe filenames."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from diffeval.tasks.errors import SuiteError
|
|
6
|
+
|
|
7
|
+
_ID = re.compile(r"[A-Za-z0-9_-]+")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_id(value: str) -> str:
|
|
11
|
+
if not isinstance(value, str) or _ID.fullmatch(value) is None:
|
|
12
|
+
raise SuiteError(f"bad prompt id {value!r}")
|
|
13
|
+
return value
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Read a YAML file to a mapping."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
from diffeval.tasks.errors import SuiteError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def read_yaml(path: Path) -> dict:
|
|
11
|
+
data = yaml.safe_load(Path(path).read_text(encoding="utf-8"))
|
|
12
|
+
if not isinstance(data, dict):
|
|
13
|
+
raise SuiteError(f"{path} must be a mapping")
|
|
14
|
+
return data
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Resolve a suite from a path or a registered name."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from diffeval.registry.lookup import get
|
|
6
|
+
from diffeval.registry.tables import TASKS
|
|
7
|
+
from diffeval.tasks.errors import SuiteError
|
|
8
|
+
from diffeval.tasks.load_path import load_path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def resolve(source: str):
|
|
12
|
+
path = Path(source)
|
|
13
|
+
if path.suffix in {".yaml", ".yml"}:
|
|
14
|
+
return load_path(path)
|
|
15
|
+
try:
|
|
16
|
+
return get(TASKS, source, "task").load()
|
|
17
|
+
except KeyError as exc:
|
|
18
|
+
raise SuiteError(str(exc)) from None
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
name: core
|
|
2
|
+
description: Synthetic smoke prompts. The drawings are not COCO.
|
|
3
|
+
seeds: [0, 1]
|
|
4
|
+
width: 512
|
|
5
|
+
height: 512
|
|
6
|
+
metrics: [clip_score, cmmd, fid, kid]
|
|
7
|
+
reference_dir: references/core
|
|
8
|
+
prompts:
|
|
9
|
+
- {id: red_cube, prompt: a red cube}
|
|
10
|
+
- {id: blue_sphere, prompt: a blue sphere}
|
|
11
|
+
- {id: green_triangle, prompt: a green triangle}
|
|
12
|
+
- {id: yellow_star, prompt: a yellow star}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Records shared by tasks, backends, and metrics."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Inputs a metric sees for one seed."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class EvalContext:
|
|
9
|
+
generated_dir: Path
|
|
10
|
+
reference_dir: Path | None
|
|
11
|
+
device: str
|
|
12
|
+
seed: int
|
|
13
|
+
clean_fid_stats: dict | None
|
|
14
|
+
cmmd_ref_embed: Path | None
|
|
15
|
+
batch_size: int
|
diffeval/types/prompt.py
ADDED
diffeval/types/result.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Finite value and status rules for a metric result."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def check(status: str, value: float | None, error: str | None) -> None:
|
|
7
|
+
if status == "ok":
|
|
8
|
+
finite = isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
9
|
+
if finite and math.isfinite(value) and error is None:
|
|
10
|
+
return
|
|
11
|
+
elif status in ("skipped", "error") and value is None and error:
|
|
12
|
+
return
|
|
13
|
+
raise ValueError(f"bad metric result status {status!r}")
|
diffeval/types/sample.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""One generated image and the request that produced it."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class Sample:
|
|
8
|
+
prompt_id: str
|
|
9
|
+
prompt: str
|
|
10
|
+
seed: int
|
|
11
|
+
width: int
|
|
12
|
+
height: int
|
|
13
|
+
image: object | None
|
|
14
|
+
path: str | None
|
|
15
|
+
negative_prompt: str | None = None
|
diffeval/types/suite.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""A loaded prompt and seed suite."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from diffeval.types.prompt import Prompt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class Suite:
|
|
11
|
+
name: str
|
|
12
|
+
description: str
|
|
13
|
+
prompts: tuple[Prompt, ...]
|
|
14
|
+
seeds: tuple[int, ...]
|
|
15
|
+
width: int
|
|
16
|
+
height: int
|
|
17
|
+
metrics: tuple[str, ...]
|
|
18
|
+
reference_dir: Path | None
|
|
19
|
+
clean_fid_stats: dict | None
|
|
20
|
+
cmmd_ref_embed: Path | None
|
|
21
|
+
source: str
|