Venket / src /verification /registry.py
venki101's picture
Add files using upload-large-folder tool
97b64b9 verified
Raw History Blame Contribute Delete
4.63 kB
"""Deterministic authorized-signer identity registry.
The base generation pipeline has no notion of "the correct signer for this
box" -- it just draws a random genuine signature for whichever box was
picked. Every verification case type (see :mod:`src.verification.case_types`)
hinges on comparing an *authorized* identity against an *actual* one, so this
module derives that ground truth purely from ``(master_seed, template_id,
box_id)`` via :func:`~src.utils.random_utils.derive_seed` -- no persisted
state, fully reproducible, consistent with the rest of the pipeline's
seeding philosophy.
"""
from __future__ import annotations
import random
import re
from src.signatures.signature_dataset import SignatureDataset
from src.templates.template_loader import TemplateLoader
from src.utils.names import generate_name
from src.utils.random_utils import derive_seed
# Signer groups auto-derived by SignatureDataset (CEDAR filenames, or no
# metadata.csv identity override) look like "signer_0014" and carry no real
# name -- those get a generated display name. Groups sourced from
# metadata.csv (e.g. "Priya Sharma") already *are* a real name.
_AUTO_GROUP_PATTERN = re.compile(r"^signer_\d+$")
class SignerIdentityRegistry:
"""Resolves ground-truth authorized signer identities for template boxes.
Example:
>>> registry = SignerIdentityRegistry(template_loader, signature_dataset, master_seed=1234)
>>> registry.authorized_signer_group("bank_doc_03", "sig1")
'signer_0014'
"""
def __init__(self, template_loader: TemplateLoader, signature_dataset: SignatureDataset, master_seed: int) -> None:
self.template_loader = template_loader
self.signature_dataset = signature_dataset
self.master_seed = master_seed
self._signer_groups = signature_dataset.signer_groups()
def authorized_signer_group(self, template_id: str, box_id: str) -> str:
"""Return the deterministic ground-truth authorized signer for one (template, box)."""
seed = derive_seed(self.master_seed, template_id, box_id, "authorized_signer")
idx = seed % len(self._signer_groups)
return self._signer_groups[idx]
def foreign_authorized(self, exclude_template_id: str) -> tuple[str, str, str]:
"""Return ``(signer_group, template_id, box_id)`` for a box on a *different* template.
Used for the "signer from a different form entirely" scenario: the
identity is a legitimately authorized signer, just not for this
document.
"""
templates = self.template_loader.discover()
candidate_ids = sorted(tid for tid in templates if tid != exclude_template_id)
if not candidate_ids:
# Only one template discovered; fall back to a different box on the same one.
candidate_ids = [exclude_template_id]
seed = derive_seed(self.master_seed, exclude_template_id, "foreign_template")
foreign_template_id = candidate_ids[seed % len(candidate_ids)]
boxes = templates[foreign_template_id].annotation.pages[0].signature_boxes
box_seed = derive_seed(self.master_seed, exclude_template_id, foreign_template_id, "foreign_box")
box = boxes[box_seed % len(boxes)]
return self.authorized_signer_group(foreign_template_id, box.id), foreign_template_id, box.id
def random_wrong_signer_group(self, rng: random.Random, exclude_group: str) -> str:
"""Return a uniformly random signer group other than ``exclude_group``."""
pool = [g for g in self._signer_groups if g != exclude_group]
return rng.choice(pool)
def display_name(self, signer_group: str) -> str:
"""Return a stable, human-readable display name for a signer group.
Groups sourced from ``dataset/metadata.csv`` (e.g. ``"Priya Sharma"``)
already carry a real name and are returned as-is. Anonymous
auto-derived groups (e.g. ``"signer_0014"``, from the bundled CEDAR
dataset) get a generated name, stable across an entire run so a
benchmark built from many samples reads as consistent identities
rather than a fresh random name every time.
"""
if not _AUTO_GROUP_PATTERN.match(signer_group):
return signer_group
seed = derive_seed(self.master_seed, signer_group, "display_name")
return generate_name(random.Random(seed))
def script_for(self, signer_group: str) -> str:
"""Return the handwriting script (e.g. ``"latin"``, ``"hindi"``, ``"bengali"``) for a signer group."""
return self.signature_dataset.script_for_group(signer_group)