Download kit/verify.py from SharpMeow/ink-lens: direct link, hf CLI and curl.
- Browser
- Download file 8.16 kB
-
https://huggingface.co/spaces/SharpMeow/ink-lens/resolve/main/kit/verify.py
- Command line
-
hf download hf://spaces/SharpMeow/ink-lens/kit/verify.py
-
curl -L -o verify.py https://huggingface.co/spaces/SharpMeow/ink-lens/resolve/main/kit/verify.py
8.16 kB
| """Compare two ink maps from the same input: a reference (e.g. CPU) and a candidate (e.g. MPS). | |
| Acceptance follows the GENChase Apple GPU backend | |
| (https://github.com/ChaseHendrick/GENChase/blob/main/apps/validate/APPLE-GPU.md): | |
| agreement under a stated tolerance counts only if a deliberately wrong input, the | |
| control, is caught by the same comparison. Without a control the verdict says so. | |
| Default tolerance: villa PR #1865 reported CPU and MPS maps differing by at most 1 of | |
| 255 gray levels on 0.002% of pixels. The defaults (2 levels, at most 0.01% of pixels | |
| beyond that) leave margin above that report without accepting a different map. | |
| The maps are villa's uint8 TIFFs. Reading them needs numpy and tifffile (and | |
| imagecodecs for LZW); villa's environment has all three. | |
| """ | |
| import math | |
| DEFAULT_TOLERANCE = 2 | |
| DEFAULT_MAX_FRACTION = 1e-4 | |
| ROWS_PER_BLOCK = 1024 | |
| PASS = "pass" | |
| PASS_UNCONTROLLED = "pass-uncontrolled" | |
| FAIL = "fail" | |
| CONTROL_NOT_CAUGHT = "control-not-caught" | |
| # The extra that installs the map tools' dependencies (numpy, tifffile, imagecodecs, zarr, scipy; pyproject.toml). | |
| MAPS_INSTALL = 'python -m pip install "scrolls-workbench[maps]"' | |
| # The command running now, for install hints: (what the user typed, the kit command), such as | |
| # ("python -m kit auc", "auc"). kit.cli sets it while a command runs; None when kit is used as a library. | |
| RUNNING = None | |
| class VerifyError(Exception): | |
| pass | |
| def missing(module): | |
| """The VerifyError for a map tool that cannot import `module`: the command that needs it, the maps | |
| extra that installs it, and how to run the command inside villa's environment instead.""" | |
| typed, name = RUNNING if RUNNING else (None, None) | |
| need = f"{module} is required for {typed}" if typed else f"{module} is required" | |
| note = " (its zarr needs Python 3.11 or newer)" if module == "zarr" else "" | |
| return VerifyError(f"{need}. Install the maps extra{note}: {MAPS_INSTALL}, or run inside villa's environment: " | |
| f"uv run --project \"$VILLA/vesuvius\" --extra models python -m kit {name or 'COMMAND'} ...") | |
| def _numpy(): | |
| try: | |
| import numpy | |
| except ImportError as exc: | |
| raise missing("numpy") from exc | |
| return numpy | |
| def load_map(path): | |
| np = _numpy() | |
| try: | |
| import tifffile | |
| except ImportError as exc: | |
| raise missing("tifffile") from exc | |
| try: | |
| array = tifffile.imread(str(path)) | |
| except Exception as exc: # tifffile raises several types, e.g. for LZW without imagecodecs | |
| raise VerifyError(f"cannot read {path}: {exc}") from exc | |
| array = np.squeeze(array) | |
| if array.ndim != 2: | |
| raise VerifyError(f"{path}: expected a 2D map, got shape {array.shape}") | |
| return array | |
| def compare(reference, candidate, tolerance=DEFAULT_TOLERANCE): | |
| """Statistics of candidate minus reference, computed in row blocks to bound memory.""" | |
| np = _numpy() | |
| if reference.shape != candidate.shape: | |
| return {"shape_match": False, "reference_shape": list(reference.shape), | |
| "candidate_shape": list(candidate.shape)} | |
| n = reference.size | |
| max_abs = 0.0 | |
| sum_abs = 0.0 | |
| over = 0 | |
| sx = sy = sxx = syy = sxy = 0.0 | |
| for start in range(0, reference.shape[0], ROWS_PER_BLOCK): | |
| a = reference[start:start + ROWS_PER_BLOCK].astype(np.float64) | |
| b = candidate[start:start + ROWS_PER_BLOCK].astype(np.float64) | |
| d = np.abs(b - a) | |
| # NaN or inf in one map and not the other is a disagreement: d is then NaN or inf, and | |
| # max() and "d > tolerance" would both pass a NaN by. Equal values (inf and inf) or NaN in | |
| # both maps at the same pixel agree. | |
| same = (a == b) | (np.isnan(a) & np.isnan(b)) | |
| bad = ~(d <= tolerance) & ~same | |
| d = np.where(same, 0.0, d) | |
| if bad.any() and not np.isfinite(d[bad]).all(): | |
| max_abs = math.inf | |
| max_abs = max(max_abs, float(d.max())) | |
| sum_abs += float(d.sum()) | |
| over += int(bad.sum()) | |
| sx += float(a.sum()) | |
| sy += float(b.sum()) | |
| sxx += float((a * a).sum()) | |
| syy += float((b * b).sum()) | |
| sxy += float((a * b).sum()) | |
| cov = sxy - sx * sy / n | |
| var_a = sxx - sx * sx / n | |
| var_b = syy - sy * sy / n | |
| pearson = cov / math.sqrt(var_a * var_b) if var_a > 0 and var_b > 0 else None | |
| return { | |
| "shape_match": True, | |
| "shape": list(reference.shape), | |
| "pixels": n, | |
| "tolerance": tolerance, | |
| "max_abs_diff": max_abs, | |
| "mean_abs_diff": sum_abs / n, | |
| "pixels_over_tolerance": over, | |
| "fraction_over_tolerance": over / n, | |
| "pearson": pearson, | |
| } | |
| def agrees(stats, max_fraction=DEFAULT_MAX_FRACTION): | |
| return bool(stats.get("shape_match")) and stats["fraction_over_tolerance"] <= max_fraction | |
| def verdict(candidate_agrees, control_agrees=None): | |
| if not candidate_agrees: | |
| return FAIL | |
| if control_agrees is None: | |
| return PASS_UNCONTROLLED | |
| if control_agrees: | |
| return CONTROL_NOT_CAUGHT | |
| return PASS | |
| def verify_arrays(reference, candidate, control=None, tolerance=DEFAULT_TOLERANCE, | |
| max_fraction=DEFAULT_MAX_FRACTION): | |
| np = _numpy() | |
| if reference.dtype.kind == "f" and reference.size and not np.isfinite(reference).any(): | |
| # NaN at the same pixel counts as agreement, so an all-NaN candidate (both devices overflowing, | |
| # say) would agree with an all-NaN reference and any finite control would look caught | |
| raise VerifyError("the reference map has no finite values; a broken reference cannot show agreement") | |
| result = {"candidate": compare(reference, candidate, tolerance), "max_fraction": max_fraction} | |
| result["candidate_agrees"] = agrees(result["candidate"], max_fraction) | |
| control_agrees = None | |
| if control is not None: | |
| if control.shape != reference.shape: | |
| # A control of another shape would "disagree" by shape alone and turn any candidate | |
| # into a pass; it shows nothing about whether the comparison can fail. | |
| raise VerifyError(f"control shape {tuple(control.shape)} differs from the reference's " | |
| f"{tuple(reference.shape)}; the control must be the same map made wrongly") | |
| result["control"] = compare(reference, control, tolerance) | |
| control_agrees = agrees(result["control"], max_fraction) | |
| result["control_agrees"] = control_agrees | |
| result["verdict"] = verdict(result["candidate_agrees"], control_agrees) | |
| return result | |
| def verify_files(reference, candidate, control=None, tolerance=DEFAULT_TOLERANCE, | |
| max_fraction=DEFAULT_MAX_FRACTION): | |
| result = verify_arrays( | |
| load_map(reference), load_map(candidate), | |
| None if control is None else load_map(control), | |
| tolerance, max_fraction, | |
| ) | |
| result["files"] = {"reference": str(reference), "candidate": str(candidate), | |
| "control": None if control is None else str(control)} | |
| return result | |
| EXPLAIN = { | |
| PASS: "candidate agrees with the reference, and the control was caught", | |
| PASS_UNCONTROLLED: "candidate agrees, but no control was given, so this comparison was not shown able to fail", | |
| FAIL: "candidate does not agree with the reference", | |
| CONTROL_NOT_CAUGHT: "the control also agreed, so this comparison cannot tell maps apart; it proves nothing", | |
| } | |
| def format_result(result): | |
| def line(label, stats): | |
| if not stats.get("shape_match"): | |
| return f"{label}: shape mismatch {stats['reference_shape']} vs {stats['candidate_shape']}" | |
| pearson = "n/a" if stats["pearson"] is None else f"{stats['pearson']:.6f}" | |
| return (f"{label}: max |diff| {stats['max_abs_diff']:.0f}, mean {stats['mean_abs_diff']:.4f}, " | |
| f"{stats['fraction_over_tolerance']:.6%} of pixels over {stats['tolerance']}, pearson {pearson}") | |
| rows = [line("candidate", result["candidate"])] | |
| if "control" in result: | |
| rows.append(line("control ", result["control"])) | |
| rows.append(f"verdict: {result['verdict']} ({EXPLAIN[result['verdict']]})") | |
| return "\n".join(rows) | |