PV-PRISM / validate.py
XiaoShengpeng's picture
Release complete PV-PRISM code, weights and reference assets
1fde8ef verified
Raw History Blame Contribute Delete
5.58 kB
"""Verify the full released PV-PRISM against registered native predictions."""
import argparse
import gc
import json
import os
from pathlib import Path
import shutil
import site
import sys
import tempfile
import time
ROOT = Path(__file__).resolve().parent
sys.dont_write_bytecode = True
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--device", default="cuda:0")
parser.add_argument("--forbid-original", type=Path, required=True)
args = parser.parse_args()
target = ROOT / "validation/result.json"
if target.exists():
parser.error("Existing validation receipt must not be overwritten")
old = args.forbid_original.resolve()
# Installed third-party libraries are runtime dependencies, not experiment assets.
libraries = [Path(p).resolve() for p in site.getsitepackages()]
blocked = []
def audit(event, values):
if event == "open" and isinstance(values[0], (str, bytes, os.PathLike)):
path = Path(os.fsdecode(values[0])).resolve()
if path.is_relative_to(old) and not path.is_relative_to(ROOT) and not any(path.is_relative_to(p) for p in libraries):
blocked.append(str(path))
raise PermissionError("Original workspace read blocked: " + str(path))
sys.addaudithook(audit)
from runtime import PVPRISM, read
import numpy as np
import torch
config = read(ROOT / "configs/model.json")
result = dict(status="running", model="PV-PRISM", device=args.device, cases=[],
all_original_project_reads_denied=True, query_labels_shipped=False,
allowed_third_party_library_roots=[str(p) for p in libraries],
atol=2e-5, rtol=2e-5, started=time.time())
try:
for ds in ("development", "csb", "msps"):
fixture = config["datasets"][ds]["fixture"]
model = PVPRISM(ds, args.device)
begin = time.monotonic()
actual, meta = model.predict(ROOT / fixture["image"])
if ds == "development":
parts = []
for name in ("contact", "crack"):
with np.load(ROOT / fixture["expected"][name], allow_pickle=False) as z:
parts.append(z["probability"])
any_expected = z["any_probability"]
expected = np.stack(parts)
any_actual = model.last_any_probability.copy()
else:
with np.load(ROOT / fixture["expected"], allow_pickle=False) as z:
expected = z["probabilities"]
with tempfile.TemporaryDirectory(prefix="prism_anonymous_") as temporary:
anonymous = Path(temporary) / "unlabelled_query.png"
shutil.copy2(ROOT / fixture["image"], anonymous)
renamed, _ = model.predict(anonymous)
case = dict(dataset=ds, passed=bool(np.allclose(actual, expected, atol=2e-5, rtol=2e-5)),
max_probability_error=float(np.max(np.abs(actual-expected))),
argmax_equal=bool(np.array_equal(actual.argmax(0), expected.argmax(0))),
anonymous_name_equal=bool(np.array_equal(actual, renamed)),
shape=list(actual.shape), query_gt_used=meta["query_gt_used"],
elapsed_seconds=time.monotonic()-begin)
if ds == "development":
from unittest.mock import patch
with patch("prism.data.loading.read_labels", side_effect=AssertionError("Query GT access")):
query = model.engine.query(ROOT / fixture["image"])
case["query_gt_reader_denied"] = "target" not in query
case["threshold_equal"] = bool(np.array_equal(actual >= .5, expected >= .5))
case["any_bitwise_equal"] = bool(np.array_equal(any_actual[0], any_expected))
case["any_anonymous_equal"] = bool(np.array_equal(any_actual, model.last_any_probability))
case["passed"] = case["passed"] and case["any_bitwise_equal"] and case["any_anonymous_equal"] and case["threshold_equal"]
else:
query = model.engine.data.query(ROOT / fixture["image"])
try:
model.engine.data.target(query)
except ValueError:
case["query_gt_reader_denied"] = True
else:
case["query_gt_reader_denied"] = False
result["cases"].append(case)
print(json.dumps(case), flush=True)
if not case["passed"] or not case["anonymous_name_equal"] or not case["query_gt_reader_denied"]:
raise AssertionError(case)
del model
gc.collect()
torch.cuda.empty_cache()
if blocked:
raise AssertionError("Original workspace reads were attempted")
result.update(status="passed", blocked_access_attempts=blocked, finished=time.time())
except BaseException as error:
result.update(status="failed", error=repr(error), blocked_access_attempts=blocked, finished=time.time())
target.parent.mkdir(exist_ok=True)
target.write_text(json.dumps(result, ensure_ascii=False, indent=2)+"\n")
raise
target.parent.mkdir(exist_ok=True)
target.write_text(json.dumps(result, ensure_ascii=False, indent=2)+"\n")
print(json.dumps(dict(status=result["status"], cases=len(result["cases"]), receipt=str(target))), flush=True)
if __name__ == "__main__":
main()