diff --git a/.gitattributes b/.gitattributes index 026d20568c625f9e96e94a407b783daad78aab8e..7886d4a7615b073e46ff46c23caa1af6a4da2bcd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4974,3 +4974,74 @@ unified_pipeline/data/raw/signet/train/059_forg/03_0104059.PNG filter=lfs diff=l unified_pipeline/data/raw/signet/train/059_forg/03_0125059.PNG filter=lfs diff=lfs merge=lfs -text unified_pipeline/data/raw/signet/train/059_forg/04_0104059.PNG filter=lfs diff=lfs merge=lfs -text unified_pipeline/data/raw/signet/train/059_forg/04_0125059.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/01_0111060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/01_0121060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/01_0126060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/02_0111060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/02_0121060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/02_0126060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/03_0111060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/03_0121060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/03_0126060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/04_0111060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/04_0121060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/060_forg/04_0126060.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/01_0116062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/01_0201062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/02_0116062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/02_0201062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/03_0109062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/03_0116062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/03_0201062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/04_0109062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/04_0116062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/062_forg/04_0201062.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/063_forg/01_0104063.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/063_forg/01_0119063.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/063_forg/02_0104063.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/063_forg/02_0119063.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/063_forg/03_0104063.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/063_forg/04_0104063.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/064_forg/01_0105064.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/064_forg/01_0203064.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/064_forg/02_0105064.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/064_forg/02_0203064.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/064_forg/03_0105064.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/064_forg/03_0203064.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/064_forg/04_0105064.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/064_forg/04_0203064.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/065_forg/01_0118065.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/065_forg/01_0206065.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/065_forg/02_0118065.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/065_forg/03_0118065.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/065_forg/03_0206065.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/065_forg/04_0118065.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/065_forg/04_0206065.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/066_forg/01_0212066.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/066_forg/02_0212066.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/066_forg/03_0101066.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/066_forg/03_0212066.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/066_forg/04_0212066.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/067_forg/01_0205067.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/067_forg/01_0212067.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/067_forg/02_0205067.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/067_forg/02_0212067.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/067_forg/03_0205067.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/067_forg/03_0212067.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/067_forg/04_0205067.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/067_forg/04_0212067.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/068_forg/03_0113068.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/068_forg/04_0113068.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/01_0106069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/01_0108069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/01_0111069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/02_0106069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/02_0108069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/02_0111069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/03_0106069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/03_0108069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/03_0111069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/04_0106069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/04_0108069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/data/raw/signet/train/069_forg/04_0111069.PNG filter=lfs diff=lfs merge=lfs -text +unified_pipeline/docs/unified_pipeline_Metrics_Thresholds_Formulas.pptx filter=lfs diff=lfs merge=lfs -text diff --git a/unified_pipeline/README.md b/unified_pipeline/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d46871eaf2eb1beabe6a3479ecbf3bf13355358b --- /dev/null +++ b/unified_pipeline/README.md @@ -0,0 +1,844 @@ +# unified_pipeline + +A single, self-contained synthetic signed-bank-document generator, built +against a spec deck (`Metrics_Thresholds_Formulas` / +`Synthetic_Test_Data_Combined.pptx`). It combines two template families -- +9 real scanned bank-document photographs with hand-measured signature-box +annotations, and 15 procedurally (matplotlib-)drawn documents with +cosmetic-field realism (SWIFT/NRIC/UEN/amounts) -- behind one config-driven +(Pydantic + YAML) generation engine, a 12-case-type reject-code taxonomy, +a shared signature dataset pool (CEDAR, SigNet, BHSig260 Hindi/Bengali), a +richer Albumentations-backed augmentation set, and a 119-test pytest suite. + +**Start here if you're reviewing this:** `data/showcase_dataset/` -- 24 +pre-generated cases covering all 12 case types, each rendered once on a +scanned template and once on a procedural template, with +`SHOWCASE_INDEX.csv` as a one-page overview, a `before_after/` folder +holding every case's metrics preview grouped together (see "Before/after +degradation preview" below), and `reference_signers_index.json` mapping +every name/role that appears in the dataset back to their signer identity +and reference signature image(s) (see "Reference-signer index" below). No +need to run anything to see what the pipeline produces. +`docs/unified_pipeline_Metrics_Thresholds_Formulas.pptx` walks through the +same metrics/thresholds/formulas with worked examples and images. + +## This repo is fully self-contained + +**Nothing here points outside this folder.** Unzip it anywhere and +`python main.py` / `pytest` just work -- no sibling checkouts, no external +dataset downloads. See "Datasets" below for exactly what's bundled and what +was left out (and why). + +## Design notes + +A few structural choices worth calling out up front: + +- **Per-case output folders** (`case_NNN.../`) hold the rendered document, + the exact reference signature used (copied in, not just referenced by + path), and the ground truth together -- a flat `sample_NNNNNN.png/.json` + layout only makes sense at ML-training scale where nobody opens + individual cases; for a reviewed dataset, self-contained per-case folders + are the better fit. +- **Ground-truth schema** is deep and fully populated (geometry/placement/ + rendering/augmentation audit trail), with top-level convenience fields + (`case_id`, `expected_verdict`, `expected_reject_code`) layered on top -- + so a reader doesn't need to know the nested `verification.verdict` shape + to answer "what should a verifier conclude here." +- **Case generation** runs off one data table (`CASE_TABLE`) plus one + generic engine, rather than 12 separate hardcoded functions duplicating + the same logic. +- **Config system** is Pydantic + YAML (validated, typed) -- catches bad + values before a long batch run fails partway through. +- **Templates** are two genuinely complementary families (9 real scanned + photos, 15 programmatically-drawn documents) kept separate but unified + under one id space. `template_provider: both` is the **default** (see + "Template providers" below), so a plain `python main.py` shows both. +- **Difficulty formula** is identical across both template families -- + same weights/tiers/normalization; only the *reference constants* differ + (see "Difficulty calibration profiles"), and that's a config choice, not + a code fork. +- **Difficulty CLI** takes a specific numeric target (`--target-difficulty + 0.55`), not a coarse tier, since the score itself is already continuous. + See "Difficulty-score targeting" below. + +## Datasets ("use all the dataset") + +`SignatureDataset` (`src/signatures/signature_dataset.py`) indexes every +source into one pool under `data/raw/`, each record tagged with +`source_type` (genuine/forged) and `script` (latin/hindi/bengali): + +| Source | Path | Signer group prefix | Bundled | Notes | +|---|---|---|---|---| +| CEDAR | `data/raw/cedar/{genuine,forged}` | `signer_NNNN` | Full (1320 + 1320) | Used for the scanned templates. | +| SigNet | `data/raw/signet/{train,test}` | `signet_NNNN` | Full (2149 files) | Primary Latin-script source. | +| BHSig260-Hindi | `data/raw/bhsig260_hindi` | `bhsig260_hindi_NNNN` | Capped 8 genuine + 8 forged / writer, 160 writers (2560 files; full archive is ~8600 files / 2.3GB) | | +| BHSig260-Bengali | `data/raw/bhsig260_bengali` | `bhsig260_bengali_NNNN` | Capped 8+8 / writer, 100 writers (1600 files; full archive is ~5400 files / 1.5GB) | | +| GPDS | -- | `gpds_NNNN` | **Not bundled** | Never wired into the live generation path (present in the original dataset archive, never validated). `SignatureDataset` still supports it (`gpds_dir` config) if you have the archive and want to point at it. | + +**Dropped: an 11-file named-signer demo overlay** (`dataset/hindi_bengali/` ++ `metadata.csv`, mapping a handful of files to hand-picked names like +"Priya Sharma"). It added no signature diversity beyond what BHSig260 +already provides at ~260x the scale (260 writers vs. 7) -- its only effect +was a few ground-truth JSONs showing a real name instead of an +auto-generated one. `SignatureDataset`'s `metadata.csv` support itself is +still fully functional generic code (see `test_metadata_csv_overrides_signer_identity_and_script` +in `tests/test_discovery.py`), just not exercised by bundled data anymore. + +Total bundled dataset: **~1.7GB**. Any source can be omitted by pointing +its config path at a location that doesn't exist -- `SignatureDataset` +skips missing sources gracefully rather than failing (this is how GPDS +support stays in the code without being bundled). + +## Output structure + +Every generated case gets its own folder: + +``` +output/case_000001/ + case_000001_document.png # the rendered document + case_000001_reference_signature.png # copy of the exact signature image used + case_000001_companion_signature.png # (case types that officially test a 2nd signer) + case_000001__signature.png # (any OTHER box on a multi-box template -- see below) + case_000001_visualization.png # (--save-visualization: geometry/metrics overlay) + case_000001_ground_truth.json # full metadata +output/before_after/ + case_000001_before_after.png # (--save-before-after: clean vs. final side by side) + case_000002_before_after.png # grouped in one folder, not scattered per-case -- + ... # easier to flip through in a row +output/dataset_summary.csv # one row per case: template/type/verdict/difficulty +output/generation_summary.json # batch totals +output/resolved_config.yaml # the fully-resolved config that produced this run +``` + +Case-type batches (`quality.case_type` set) get `_` appended to the +folder name, e.g. `case_000001_3b/`. + +**Every signature box on a multi-box template gets signed, always.** If a +template has more than one signature box but the sample's case type doesn't +officially test the extra one (e.g. `1a`/`2a`/generic samples on a 2-box +template), that box still gets filled with its own correct, genuine +signature rather than being left blank with no explanation -- tracked in +`metadata["additional_signatures"]`. This never overrides a case type's own +deliberate blank/wrong target (a `2a` case's target box still correctly +stays blank with `NO_SIGNATURE`; only its *unrelated* second box gets the +extra fill). See "Multi-signature-box logic, audited" below. + +## Template providers ("works with both template approaches") + +Set via `paths.template_provider` in config or `--set paths.template_provider=...`: + +- `both` (**default**) -- both pools merged into one id space (`bank_doc_*` + `proc_doc_*`), so a plain `python main.py` shows both template families. +- `scanned` -- the 9 real scanned bank-document images only. +- `procedural` -- the 15 matplotlib-drawn documents only (`proc_doc_01`..`proc_doc_15`). + +`paths.excluded_template_ids` (default `[]`) can pull any scanned template +out of the pool without touching the source image file -- exclusion is +id-stable (doesn't renumber/shift any other template's id). + +Every ground-truth JSON records `template_kind` (`"scanned"` / `"procedural"`) +at the top level regardless of mode. + +Both families live under one `templates/` folder: + +``` +templates/ + scanned/ + guarantee/ bank_doc_01.png, bank_doc_02.png, bank_doc_03.png + loan_forms/ bank_doc_04.png, bank_doc_05.png, bank_doc_06.png + mandate/ bank_doc_07.png, bank_doc_08.png, bank_doc_09.png + procedural/ proc_doc_01.png .. proc_doc_15.png +``` + +`templates/scanned/` holds the 9 real scanned photographs (`paths.image_dir`); +`templates/procedural/` holds the 15 procedurally-drawn documents, rendered +once and cached as PNGs the same way a scanned template is a fixed image +(`paths.cache_dir`) -- delete it and it's regenerated automatically on the +next run, from the drawing code in `src/templates/procedural_template_provider.py`. + +## Difficulty calibration profiles + +Real signature ink occupies a different proportion of the page on the two +template families, so procedural samples use different smallness/faintness/ +low_texture reference constants (`smallness_reference_area_ratio=0.003`, +`faintness_dark_ref=165`, `faintness_light_ref=220`, +`low_texture_reference_stddev=46` -- see +`src/config/schema.py::DifficultyWeights`) than scanned samples, which keep +the spec deck's literal generic values (`0.04` / `90` / `210` / `60`) -- +still an accurate fit for scanned templates' own measured population. + +`faintness_dark_ref` and `low_texture_reference_stddev` were refit +2026-08-11 against a fresh 60-sample natural draw of real procedural output. +The prior `faintness_dark_ref` (184) sat *above* the real median +`mean_dark_intensity` (180.6), which floored `faintness` to exactly `0.000` +for every sample measured -- a 20%-weighted component contributing nothing, +discriminating nothing, for the whole procedural family. Re-anchored to the +real 10th percentile (~164, rounded to 165): median sample faintness is now +~0.29, only the darkest ~13% floor to 0, spread runs to ~0.79 for the +lightest observed ink. `low_texture_reference_stddev` had never been split +from the deck-literal default at all, despite the same underlying asymmetry +existing there too (procedural's vector-rendered ink is naturally less +textured than scanned's real-photograph ink) -- anchored to the real 90th +percentile `gray_stddev` (~46.5, rounded to 46), the same treatment already +given to `smallness_reference_area_ratio`. Verified: re-measured 60 fresh +procedural samples post-fix -- faintness floors to 0 for only 8/60 (was +60/60), low_texture floors to 0 for only 7/60; full 119-test suite still +passes. + +This is applied automatically, **per sample**, by `GeneratorContext` +(`src/generator.py::_difficulty_analyzer_for()`), keyed on each sample's own +`template_kind()` -- not a config profile you have to remember to select. +Previously this only applied if the whole run used +`--config configs/procedural_templates.yaml`, which meant `template_provider: +both` (or the plain CLI default) silently scored every procedural sample +against the deck-literal, too-aggressive constants regardless. Fixed: each +sample now gets the constants that actually fit its template family, even +within a mixed `both` batch. + +If you explicitly set any of the 3 reference constants yourself -- a CLI +`--set difficulty.smallness_reference_area_ratio=...` override, or a whole +profile like `configs/procedural_templates.yaml` that sets them itself -- +that choice is respected as-is and applied to every sample regardless of +template kind; the automatic per-kind default only fills in fields you +didn't already decide for yourself +(`DifficultyWeights.model_fields_set` is how this is detected). + +## Placement calibration + +Signatures are also pasted at a different scale per template family, same +per-sample-keyed pattern as difficulty calibration above +(`GeneratorContext._placement_engine_for()`): + +- **`min_signature_height_px`**: `24px -> 28px` (shared default -- see + `src/config/schema.py::PlacementConfig`, moved off a `placement_engine.py` + module constant so it's config-overridable like every other placement + knob). Measured 2026-08-11 across 20 real placements per candidate into a + genuine 18px "sign here" line: 24px -> 15.1% fully-opaque ink, 28px -> + 16.8%, with a modest overflow cost (median 4% at 28px vs. 0% at 24px); + 32px was tested and rejected (20%+ median overflow -- escalates much + faster than the legibility gain). +- **`placement.scale_factor`**: `1.15` (procedural only; scanned keeps the + shipped `{min: 0.78, max: 0.95}` range unchanged). Procedural's boxes + (200x80px) have real headroom scanned's cramped real "sign here" lines + don't -- 1.1 already gave a meaningful legibility gain (opaque-ink + fraction 0.238 -> 0.280) with zero overflow; 1.3 starts trading further + gains for real overflow (6-7%). Scanned was tested too (0.9 through 1.2) + and showed **no change at all** in that range -- `min_signature_height_px` + is the actual binding constraint there, not `scale_factor`, so bumping + scanned's scale_factor would have been a no-op. + +**A real bug surfaced while implementing this**: the difficulty-calibration +pattern detects genuine user overrides via `model_fields_set`, which works +because `configs/default.yaml` never explicitly sets the difficulty +reference constants. It does explicitly set `placement.scale_factor` +though (to its own baseline range) -- so applying the same pattern +unmodified would have silently defeated this recalibration on every single +run, with or without a real override. Fixed by comparing the resolved +value against the known shipped baseline directly instead of relying on +field-presence tracking. + +Pasting bigger also organically fixed a gap flagged in the earlier +recalibration work: `smallness_reference_area_ratio=0.003` previously sat +above even the best 10% of real procedural samples (floor ~0.4, never +reaching 0). Re-measured post-placement-fix: real median +`signature_area_ratio` moved 0.00095 -> 0.00180, and `smallness` now +resolves with genuine spread on its own (8/50 samples reach 0, median +0.398) -- no further change to the reference constant was needed. + +## Difficulty-score targeting + +```bash +python main.py --count 5 --target-difficulty 0.55 +python main.py --count 5 --target-difficulty 0.55 --difficulty-tolerance 0.03 --difficulty-max-attempts 8 +``` + +A retry-loop (`case_generation.py::generate_case_at_difficulty()`) built on +this pipeline's own CLI conventions (argparse, `--set key=value`): +since the difficulty score is already a continuous `[0, 1]` value (not a +bucket), you ask for a specific number (`0.55`), not a tier. Retries (`--difficulty-max-attempts`, default 5) with fresh but +still fully deterministic randomness (`seed_manager.python_rng` derived +from `f"{sample_index}_difficulty_retry{attempt}"`) until the score lands +within `--difficulty-tolerance` (default 0.05) of the target; if no +attempt matches, the closest one is kept and `ground_truth.json`'s +`difficulty_targeting.matched` is `false` so this is never silently +misreported. See `GeneratorContext.run_sample_targeting_difficulty()`. + +## Before/after degradation preview + +```bash +python main.py --count 5 --save-before-after +``` + +Builds a clean-vs-final side-by-side comparison combined with a metrics +overlay into one image. Writes +`before_after/_before_after.png` per sample -- grouped in one +shared folder rather than scattered one per case folder, so every sample's +preview can be flipped through in a row without opening each case folder +individually: + +- **BEFORE (left)** -- the clean render (signature placed, before any + document-level augmentation), annotated with the "what was actually + measured" overlay: the target box (red), the actual + signature bbox (yellow), its detected ink polygon (green), and any + cosmetic field boxes (cyan = actual, orange = target cell), plus a + legend, a **difficulty breakdown** (`value x weight = contribution` per + component, e.g. `misplacement: 0.464 x 0.35 = 0.162`), and an + **augmentation-applied** panel listing every effect this sample rolled -- + ON with its strength, or off. +- **AFTER (right)** -- the final look, labeled with a one-line summary of + which effects actually fired (`"AFTER - 2 effect(s): contrast, + scanner_noise"`). + +Every case in `data/showcase_dataset/` has one of these pre-generated. +Distinct from the existing `--save-visualization` (a single matplotlib-based +multi-panel figure -- crop, ink mask, bar chart -- on the FINAL image only, +no before/after comparison); the two can be used together. See +`src/visualization/before_after_preview.py`. + +## Cosmetic (non-scored) fields + +`document.enable_cosmetic_fields: true` (**on by default**) fills in +name/company-name and, on procedural templates, the fuller SWIFT/NRIC/UEN/ +amount set too -- never read by `DifficultyAnalyzer` or any scored metric. + +- **Procedural templates**: full field set at exact coordinates (known + from the drawing spec) -- see `src/cosmetics/cosmetic_fields.py`. +- **Scanned templates**: a best-effort name/company-name field on 7 of the + 9 templates, found via the same CV line-detection technique already used + for signature-box auto-detection (search a hand-picked region for the + blank line/table-cell, draw text against it) -- see + `src/cosmetics/scanned_cosmetic_fields.py`. `bank_doc_01`/`bank_doc_02` + are stock photos with fabricated content already baked into the image + (bracketed `[PLACEHOLDER ...]` text, or an already-fully-filled specimen + letter) and are left as-is -- there's no genuine blank field to fill + without inpainting over existing print. + +## Template exclusion (mechanism exists, nothing excluded by default) + +`paths.excluded_template_ids` (config list, default `[]`) can pull a +scanned template out of the pool without touching the source image file -- +exclusion is id-stable (`TemplateLoader(excluded_ids=...)`), so pulling one +id never renumbers/shifts any other template's id. Every discovered +template is included by default. + +## Scanned cosmetic fields: field semantics, not just position + +`bank_doc_09`'s "Company Name" table cell (under `ENTITY DETAILS`, next to +`Registration Number`) was pasting the *signer's own personal name* into +it (e.g. "Nancy Ivanov" under a column literally headed "Company Name") -- +the position was correct, but the wrong *kind* of text. Every other +scanned field really is a personal-name field ("Print Name:", "Name of +Account Holder:", "Mr./Ms. ___") and correctly gets the signer's name; +`company_name` is the one exception and now generates an actual company +name (`generate_company_name()`, seeded, same generator procedural +templates already used) instead. See `src/cosmetics/scanned_cosmetic_fields.py`'s +`_GENERATED_FIELD_TEXT` dispatch table. + +### Cosmetic-field coordinates are pre-measured and cached, like signature boxes + +`signature_boxes` in `annotations/.json` have always been +hand-measured once and just read at generation time -- no per-sample image +analysis. Cosmetic-field coordinates (where the "Name"/"Company Name" text +gets drawn on a scanned template) now work exactly the same way, via a new +`cosmetic_fields` array on the same annotation file: + +```json +"cosmetic_fields": [ + {"field": "company_name", "mode": "below_line", "line_x": 74, "line_y": 164, "line_w": 303} +] +``` + +**How each coordinate is measured** (`scripts/detect_cosmetic_field_coordinates.py`): + +1. A generous *search region* is hand-picked per field, once, by eye (a + `(x0, y0, x1, y1)` rectangle expressed as fractions of the page's + width/height -- see `FIELD_REGION_RECIPES` in that script). This step + only needs to be roughly right, not precise. +2. Within that region, the *same* classical-CV horizontal-line detector + already used to auto-detect signature boxes when no manual annotation + exists (`src/templates/box_detector.py`: binarize, morphological + opening with a wide horizontal kernel to isolate long thin dark runs, + merge nearby fragments) finds the actual blank underline or table + row-divider pixel-exactly. Deterministic given the same image, so it + only ever needs to run once per template. +3. The result (`field`, `above_line`/`below_line` mode, and the detected + line's exact `x`/`y`/`width`) is written into the template's + `annotations/.json`, alongside `signature_boxes`. + +Run it manually (`python scripts/detect_cosmetic_field_coordinates.py +[--force]`) after adding a new scanned template or if a template image +changes -- it is **not** run as part of normal generation. At generation +time, `fill_scanned_cosmetic_fields()` (`src/cosmetics/scanned_cosmetic_fields.py`) +just reads `template.annotation.pages[N].cosmetic_fields` directly, the +same way signature-box placement already reads `signature_boxes` -- one +measurement, reused on every sample, instead of re-running CV detection +per call. + +### One "Print Name:" field per signatory, not one per template + +`bank_doc_03`, `04`, `05`, `06`, and `08` each have **two** signature +boxes, and (confirmed by visually cropping every candidate line in each +template) each box's signatory has their *own* "Print Name:"-style line -- +not one field shared by both parties. The original single-field-per-template +recipe only ever measured one of the two, so the second signatory's name +was always left blank regardless of what filled the first. Each field now +carries an optional `box_id`, tying it to the `signature_boxes[].id` whose +signer's name belongs on that line: + +```json +"cosmetic_fields": [ + {"field": "name_sig1", "mode": "above_line", "line_x": 606, "line_y": 464, "line_w": 136, "box_id": "sig1"}, + {"field": "name_sig2", "mode": "above_line", "line_x": 828, "line_y": 464, "line_w": 136, "box_id": "sig2"} +] +``` + +Fields with no `box_id` (e.g. `bank_doc_09`'s `company_name`) are +entity-level, not tied to one signer, and are unaffected. + +Because a companion/additional signer's identity usually isn't resolved +until *after* the primary box's cosmetic fields are already filled (see +`_run_case_sample`/`_run_generic_sample` in `src/generator.py`), +`_maybe_fill_cosmetic_fields()` only ever fills fields with no `box_id` or +tied to the box it's given; a second pass, `_fill_box_cosmetic_field()`, +fills the companion/extra box's own field once that signer's display name +is known -- so both parties' names land on their own lines, matching +whichever signature actually got pasted into their box. + +While narrowing `bank_doc_03`'s search region to find its second field, a +related pre-existing bug surfaced: its *first* field had been landing on +the "By:" line instead of "Print Name:" (both lines sit in the same +generous search region, and `_find_line_in_region()`'s "pick the widest +line" heuristic happened to prefer "By:"). Fixed by narrowing that +template's region to the exact "Print Name:" line's y-band. + +## Legible ink on short signature lines + +A real "sign here" line on a scanned bank form is often only ~15-25px tall +at this pipeline's working resolution (e.g. `bank_doc_08`'s "Signature of +Bank Manager:" line is 22px tall) -- placing a signature stamp from a +typical few-hundred-pixel-tall dataset source crop into a box that short +forces a large downscale, sometimes 10-20x. `cv2.resize`'s `INTER_AREA` +area-averages a multi-pixel-wide source stroke down into a sub-pixel-wide +destination stroke, which is the geometrically correct way to downscale -- +but it also dilutes that stroke's alpha well below fully opaque across most +of its footprint. A signature that was solidly inked in its source crop +could come out mostly under 30-40% opacity: a faint gray smudge rather than +a smaller-but-still-dark signature (real signatures squeezed onto a short +line stay dark, just compact). + +`PlacementEngine.place()` (`src/placement/placement_engine.py`) now applies +a gamma correction to the placed alpha channel after any real downscale -- +every partially-transparent pixel gets boosted non-linearly (alpha^gamma, +gamma<1), which is what actually restores legibility (a plain linear +rescale matching the *source's own* percentile was tried first and wasn't +enough: even the source crop's own mid-tone pixels are already somewhat +soft/anti-aliased from the original scan). Gamma self-scales with +`final_scale` (the actual resize factor) -- a sample that barely shrank +gets ~no correction (gamma near 1, alpha^1 = alpha); one squeezed 10-20x +onto a short line gets the strongest correction (gamma floor 0.18). +Applied to alpha only, never RGB, so ink *color* is unaffected -- this +makes thin/faint strokes fully opaque sooner, not darker in hue. Regression +test: `test_aggressive_downscale_onto_a_short_box_keeps_ink_dark` in +`tests/test_placement_engine.py`. + +## Post-render ink detection: not fooled by the template's own printed content + +`GeometryAnalyzer` (`src/analysis/geometry_analyzer.py`) redetects each +sample's signature from the rendered pixels alone (deliberately -- a real +verifier only ever sees the final scanned document, not ground-truth +placement coordinates). Four contamination sources were found and fixed +across three review passes -- the first two via a user-reported case +(`bank_doc_08`'s Bank Manager signature, a genuine PASS, whose metrics +preview showed a bounding box/polygon spanning almost the entire printed +line instead of just the ink), the third via a broader anomaly scan across +the whole showcase dataset after a follow-up "the metrics still look kinda +off" report, the fourth via the interactive UI's degradation lever +surfacing a sample that visually looked heavily degraded yet scored "easy": + +1. **The printed "sign here" rule line itself.** A pasted signature very + often sits directly on top of, and touches, that line -- 8-connectivity + then merges the line and the ink into ONE component, whose bounding box + spans the line's full width. `_strip_printed_rule_line_pixels()` runs a + *narrow, absolute-width* (20px, not proportional to the crop -- a + signature crossing the middle of the line splits it into two shorter + sub-segments, and a proportional kernel could exceed either half's + length) horizontal morphological opening to isolate straight + single-row runs, then only subtracts ones whose overall connected-component + height stays <=3px (so a genuinely tall block of ink -- thick + handwriting, or a filled test fixture -- survives; only truly thin, + line-like structures get erased). +2. **Printed field labels near the box** (e.g. "Bank Manager:"). A tight + detection pass expands slightly past the annotated box edge, and on a + short box that expansion can reach into adjacent label text. Fixed by + optionally diffing against the clean, unsigned template image + (`analyze(..., baseline_image_rgb=...)`) -- pixels already dark in the + baseline (permanent printed content) are never counted as new ink. The + baseline is known in advance the same way a real production pipeline + knows its own form layouts, not this run's specific signature + placement, so this isn't using ground truth the way a real verifier + couldn't. Originally skipped automatically when the sample underwent a + page-level geometric transform (`perspective`/`page_tilt`/`crumple`) + that would misalign a pixel-for-pixel diff -- see contamination source + 4 below for why that gap itself needed fixing. + +Net effect on the reported case: `box_coverage_pct` dropped from a +contamination-inflated 56% to an accurate ~10%, and the detected polygon +now tightly traces just the actual ink. + +3. **A printed caption below a deliberately BLANK target box** (2a/5a case + types -- `_BLANK_CASE_TYPES`). The widened retry pass (`analyze()`'s + second, more generous search) exists to chase a signature that was + deliberately misplaced far from its own box -- but a 2a/5a target box + has no signature at all, so there's nothing to chase, and on a sample + where `baseline_image_rgb` had to be omitted (a geometric transform was + applied, so a pixel diff would misalign), the widened search had no + baseline to exclude already-printed content with. Caught a printed + role-caption ("1. SIGNATURE: AUTHORIZED REPRESENTATIVE *") sitting just + below the blank box and reported it as a wildly-misplaced signature + (`distance_from_box_center_px` ~90-130px, `margin_utilization` >2). + Fixed with `analyze(..., expect_blank=True)`, passed whenever the + caller already knows from ground truth that this target has no + signature (`target_rgba is None` in `_run_case_sample`) -- skips the + widened retry entirely, so a genuinely blank box just reports + `detected: false` and zeroed metrics, exactly as it should. + +4. **Whole blocks of printed text, whenever `perspective`/`page_tilt`/ + `crumple` actually fired.** Found via the interactive UI's degradation + lever: a heavily rotated/noisy "Explore from clean" preview scored + 16% ("easy") despite looking obviously harder to read than the clean + original (48%, "medium"). Root cause: contamination source 2's + baseline-diff exclusion was skipped entirely whenever a geometric + transform fired (a rotated page no longer lines up pixel-for-pixel with + the un-rotated baseline template), so the ONLY defense against printed + content near the box was gone -- and `_strip_printed_rule_line_pixels()` + only catches thin rule *lines*, not printed paragraph *text*. On one + measured sample, a `page_tilt` of well under 1 degree was enough for a + nearby paragraph heading to rotate into the search box's expanded + region and get counted wholesale as "ink": detected pixels jumped from + 545 (the real signature) to 13,993, `mean_dark_intensity` dropped from + 175 to 107 (consistent with picking up dense black print), and the + artificially large, well-centered "blob" read as LESS misplaced and + LESS small than the real, small, genuine signature -- the opposite of + what a harder-to-read page should score. Fixed at the source instead of + working around it: `AugmentationEngine.apply_to_document()` now accepts + an optional `baseline_image`, and whenever perspective/page_tilt/crumple + actually apply, the identical transform (same warp matrix / same + `map_x`,`map_y` remap field) is also applied to the baseline companion + image, so it stays pixel-aligned with the final document and baseline + exclusion keeps working instead of being skipped. Verified in isolation: + `page_tilt` alone at maximum strength now detects 552 ink pixels (vs. + 545 with no tilt at all) -- effectively fixed. + + **A separate, smaller contamination source was found (not fixed) while + verifying #4 above:** forcing `scanner_noise`/`salt_pepper`/`dust` on + simultaneously with everything else -- as the lever's "Explore from + clean (all effects)" mode deliberately does -- inflates detected ink + pixels through a *different* mechanism: the geometry pipeline scores a + signature using the convex hull/bounding box of *every* surviving "ink" + pixel, so even a few scattered noise specks near opposite corners of the + search box balloon that box to span nearly the whole region, regardless + of how little total dark area they cover (measured: ink pixel *count* + only roughly doubled, but IoU-with-box jumped from 0.11 to 0.82). A + median-blur denoise pre-pass was prototyped as a fix and rejected: a + kernel strong enough to meaningfully suppress noise at extreme, + all-effects-maxed strength (5x5) also cut a genuinely clean sample's + real ink-pixel count by ~60% (403 -> 157) -- real pen strokes are only a + few pixels wide, the same order of magnitude as the noise being + filtered, so the fix would trade real detection accuracy for a mostly + synthetic problem. A shape/cluster-aware alternative (discard ink + components far from the "main" cluster) was also rejected: it directly + conflicts with this same section's existing, deliberate design (the + hull is meant to *bridge* a genuine flourish far from the main strokes, + not discard it) and would need real testing against genuine faint/thin + signatures to avoid new false negatives -- a bigger investment than + warranted, since at realistic (non-maxed) strengths the effect is mild + (measured: only ~8% ink-pixel inflation at strength 0.3, vs. 25x for the + page_tilt bug above). Left as a documented limitation of the "Explore + from clean (all effects)" lever mode specifically -- which stacks all 14 + effects simultaneously, something real generation never does (each + effect fires independently/probabilistically) -- rather than changing + detection logic; the lever now shows an explicit warning above 25% in + that mode instead of a silently-misleading score. + +## Placement floor: 28px, not the deck's 16px -- resolved by measurement, not forced + +A full line-by-line audit against `Metrics_Thresholds_Formulas.pptx`'s +"Threshold reference sheet" slide found every constant in this pipeline +matches exactly except one: `min_signature_height_px` +(`src/config/schema.py::PlacementConfig`, formerly a +`placement_engine.py` module constant) is 24px-then-28px in code vs. the +deck's 16px. Rather than silently override it to match the deck, or leave +it unexplained, this was resolved empirically: a real signature was placed +into a genuine short 18px "sign here" line (the deck's own "real lines are +12-20px tall" range) at each floor value and the result measured directly. + +| Floor | Placed height | Fully-opaque ink (alpha > 200/255) | +|---|---|---| +| 16px (deck) | 16px | 12.3% | +| 24px (first pass) | 22px | 15.1% | +| 28px (current, see "Placement calibration" above) | -- | 16.8% | + +Each step up measurably preserves more solid, legible ink -- `cv2.INTER_AREA` +dilutes a multi-pixel source stroke's alpha the smaller the downscale +target gets, and 16px sits deepest in that dilution zone (the same artifact +already described in this file's placement-scaling comments). 32px was +also tested (see "Placement calibration" above) and rejected -- overflow +past 28px escalates much faster than the legibility gain. The constant's +own definition cites this measurement directly, the same way +`DifficultyWeights`' smallness/faintness constants document their own +deliberate deviation from the deck's generic values. + +This was one finding from a full line-by-line audit (every formula/ +threshold/case-type in the deck checked against this codebase) -- 52 of 53 +checks matched exactly; this was the one documented deviation, resolved by +measurement rather than left silently unexplained. + +## The ink polygon on the preview tints the real ink, not the hull + +Several rendering styles were tried this session, in order, each fixing +the previous one's complaint: + +1. **Hull, outline only** (the original design). A convex hull "bridges" + gaps between separate ink blobs (letters, a flourish far from the main + strokes) with straight lines -- the bridging edges visibly don't hug + the ink wherever they cross a gap. Reported: "the polygon doesn't even + cover the signature." +2. **Hull, translucent fill.** Fixed #1 (the ink is always inside the + tinted region), but a filled hull shades in every gap it bridges too. + Reported, correctly: "shouldn't [it] only cover surround the signature + not whitespaces." +3. **Hull outline + `cv2.approxPolyDP` simplification** (a vertex-count + reduction on the hull polygon). Still visibly failed to "hug" a + wide/gappy signature on direct re-inspection, since it's still + fundamentally the same convex hull -- a known tradeoff of hull-based + rendering that just isn't obvious on a small, compact signature. +4. **`display_contours`, translucent fill** (current, final). A + purely-visual field on `GeometryResult`: one real boundary contour per + disjoint ink blob, via `cv2.findContours` on the exact mask used for + scoring (mildly dilated first, on a copy, just to merge one letter's + own stroke fragments into a single blob -- nowhere near enough to + bridge a real gap between separate letters). Filling each blob's own + contour tints only actual ink, never the whitespace between strokes, + and -- unlike an *outline* of these same contours (tried and rejected + earlier: for a dense cursive/Devanagari signature it visually resembled + a second overlaid signature) -- a fill has no separate line art to be + confused with handwriting. + +`signature_polygon_px`/`convex_hull_px` in `ground_truth.json` remain the +convex hull (with the `approxPolyDP` simplification from step 3 kept, as a +minor genuine improvement to the scoring polygon's vertex count) -- every +coverage metric (`box_coverage_pct`/IoU/`containment_pct`/`margin_utilization`) +still comes from the hull, unchanged. `display_contours` is additive and +explicitly documented as "never used in scoring." Both +`before_after_preview.py` and `visualizer.py` (`--save-visualization`) +fill `display_contours` when present, falling back to filling the hull for +any geometry dict that predates this field. This was a deliberate choice, +made after comparing several rendering styles directly -- prioritizing an +accurate, non-misleading preview over hull-only visual simplicity. + +## Metrics preview shows every signed box, not just the scored one + +A multi-box template's companion/additional-signature boxes are genuinely +rendered with real ink but aren't part of a sample's *scored* target +(geometry/difficulty stay keyed to the one target box the rest of the +pipeline scores) -- previously the before/after metrics preview only ever +overlaid that one box, leaving a second signature elsewhere on the page +unexplained. `compute_extra_signature_overlays()` (`src/generator.py`) +re-runs `GeometryAnalyzer` (view-only, never for scoring) on every other +signed box for the preview, and `save_before_after_preview()`'s +`extra_boxes` param draws them in a distinct purple/pink palette, labeled +by role (e.g. "Account Holder (other box)") instead of full re-scoring. + +## Reference-signer index + +`scripts/generate_showcase_dataset.py` also writes +`data/showcase_dataset/reference_signers_index.json` -- every distinct +name/role that appears anywhere in the dataset (as a primary signer, +companion, or additional/other-box signer), with which signer identity +backs it and where its ink actually comes from: + +```json +{ + "signer_group": "signet_0009", + "display_name": "John Thompson", + "script": "latin", + "roles": ["Bank Manager"], + "appears_in": [{"case_id": "case_001_1a_scanned", "role": "Bank Manager", "as": "primary", "template": "bank_doc_08"}], + "reference_images": ["data/raw/signet/train/009/009_24.PNG"] +} +``` + +`signer_group` is the pipeline's internal, deterministic identity key +(`src/verification/registry.py::SignerIdentityRegistry`) -- `display_name` +is the human-readable name derived from it, stable across every case that +identity appears in. `appears_in` distinguishes *how* that identity was +used in each case (`"primary"` = the case's own target signer, `"companion"` += the officially-tested second signer for `1c`/`4a`/`4b`/`5a`/`5b`, +`"additional"` = an unrelated-but-still-genuine extra box fill -- see +"Every signature box on a multi-box template gets signed" above). +`reference_images` collects every distinct source image path used for that +identity across the whole dataset (not just one case), each already copied +into its case folder too (`*_reference_signature.png` / +`*_companion_signature.png` / `*__signature.png`) so nothing here +depends on `data/raw/` actually being present to inspect a specific case. + +## Multi-signature-box logic, audited (and a real gap fixed) + +Every case type that needs two signature boxes (`1c`, `4a`, `4b`, `5a`, +`5b` -- see `CASE_TABLE`'s `fill_companion` flag) was verified end-to-end +across both scanned and procedural 2-box templates: template selection +correctly restricts to templates with >=2 boxes, the companion box is +always filled with its own correct authorized signer, and the target box +gets exactly what its case type specifies (`1c`: also correct; `4a`: the +*companion's* signer, in the wrong box, same form; `4b`: a signer +authorized on a *different* template entirely; `5a`: left blank; `5b`: an +unrelated wrong signer) -- confirmed both from the ground-truth JSON +(`expected_signer_group` vs `actual_signer_group` vs the companion's +group) and from rendered images. + +**Gap found and fixed:** case types that *don't* officially test a second +signer (`1a`, `1b`, `1d`, `2a`, `2b`, `3a`, `3b` -- `fill_companion=False`) +and plain generic (no `quality.case_type` at all) samples were leaving any +*extra* box on a multi-box template completely blank, with no verdict or +reject_code explaining why -- it just looked like a broken render. Fixed +in both `_run_generic_sample` and `_run_case_sample` +(`src/generator.py`): any signature box beyond the one the sample is +actually scored/tested against now gets filled with its own correct +authorized signer too, recorded under the new `additional_signatures` +metadata field (list of `{box_id, role, signature_id, signer_group, +source_type, script, ref_path}`) and copied into the case folder as +`...__signature.png`. This only ever *adds* a properly-signed box +-- it never overrides a case type's own deliberate blank/wrong-signer +target (verified: a `2a` case's target box is still correctly blank with +`NO_SIGNATURE`, while its unrelated second box is now properly signed +instead of mysteriously empty). + +One real annotation bug found and fixed in the process: `bank_doc_09`'s +bundled signature-box annotation (`annotations/bank_doc_09.json`, +inherited as-is from an earlier revision) placed both boxes 27px too +high -- inside the paragraph text above the "First/Second Authorised +Signatory" line instead of on it. Found by rendering a sample and looking +at where the ink actually landed (not visible from the annotation JSON +alone), confirmed against the template's real line position via the same +CV line-detector `scanned_cosmetic_fields.py` uses, and corrected. + +## A bug worth knowing about (fixed): reference signature images + +An earlier version of the per-case output copied the reference signature +file with `shutil.copy2` under a `*_reference_signature.png` name. For +CEDAR/SigNet sources (already `.png`) that's fine; for **BHSig260 sources +(`.tif` files)** it silently produced a file containing real TIFF bytes +under a `.png` name -- any standards-compliant image viewer correctly +refuses to open it. Fixed by decoding with Pillow and re-saving as a real +PNG (`src/generator.py::_export_reference_signature`) -- every reference/ +companion signature image is now guaranteed to actually be what its +extension claims. Regression test: `tests/test_scanned_cosmetic_fields.py::test_export_reference_signature_produces_a_real_png_from_a_tiff_source`. + +## Usage + +```bash +pip install -r requirements.txt + +# The flagship demo -- all 12 case types x both template families, 24 cases +python scripts/generate_showcase_dataset.py +# (already generated once into data/showcase_dataset/ -- rerun to regenerate) + +# Default: both template pools, deck-literal difficulty constants +python main.py --count 20 --seed 1 + +# Procedural only, recalibrated difficulty constants +python main.py --config configs/procedural_templates.yaml --count 20 --seed 1 + +# Scanned only +python main.py --set paths.template_provider=scanned --count 20 --seed 1 + +# A specific case type (see src/verification/case_types.py for all 12) +python main.py --config configs/procedural_templates.yaml --set quality.case_type=3b --count 10 + +# Request a specific difficulty score, with a before/after preview +python main.py --count 5 --target-difficulty 0.55 --save-before-after +``` + +## Interactive UI + +```bash +streamlit run streamlit_app.py +``` + +A local browser UI (`streamlit_app.py`) for one-off, customizable document +creation -- pick template provider/id (picking a specific template shows a +live preview of its blank form, with its document type and signature-box +roles, right below the dropdown, before you even generate anything), case +type (with its own description/verdict shown inline), a natural or +targeted difficulty (as a plain percentage, not a raw `0.0-1.0` score), a +**difficulty calibration** toggle (see below), signer name/role overrides, +cosmetic fields on/off, and a seed, then generate and immediately see the +rendered document, verdict, and a plain difficulty percentage (the +easy/medium/hard tier is kept in the ground-truth data for case-type +validation, but isn't the headline UI number anymore -- a caption under +the percentage shows it for anyone who wants it), and (organized into tabs +to keep the page scannable): +- **Difficulty & degradation** -- what actually built the difficulty score + (a labeled progress bar per weighted component -- + misplacement/whitespace/faintness/smallness/low texture, each with a + one-line plain-English explanation) and which degradation effects + actually, randomly fired for this specific sample (not just which were + possible). Difficulty calibration is now automatic per sample (see + "Difficulty calibration profiles" above) -- procedural samples get + `smallness_reference_area_ratio=0.003` / `faintness_dark_ref/light_ref= + 165/220` / `low_texture_reference_stddev=46` and scanned samples keep the + deck-literal `0.04` / `90` / `210` / `60`, without needing to opt in. The + sidebar's **"Force deck-literal reference for ALL templates"** checkbox + (off by default) is only there to deliberately turn this OFF -- forcing + every sample, including procedural ones, back onto the deck-literal + constants that peg smallness/faintness near their ceiling for almost any + real signature (real signature area is only ~0.2-1.5% of the page vs. the + deck's 4% "normal"), e.g. to compare + against pre-recalibration scores. +- **Interactive lever** -- drag a slider to watch the same clean render + pass through progressively stronger document degradation live, without + re-running the whole pipeline. Three modes: *match this document* + (default) scales only the effects that actually fired, together, from + 0% (clean) to 100% (this document's real per-effect strengths -- so it + closely matches the Before/After tab, modulo the lever using its own + seed key for random-variant choices like stain shape/shadow corner); + *adjust individually* gives one slider per document-level effect, + defaulted to what actually fired (0% if it didn't), so you can push any + single effect harder/weaker/on independently of the rest; *explore from + clean* ignores what actually fired entirely and forces all 14 effects on + together at one shared strength, starting from clean -- a generic + worst-case stress-test (this is what the original version of the lever + always did, kept as its own mode). Both the clean and degraded preview + now show a live `difficulty_score`, freshly re-detected from the + pictured image with the same `GeometryAnalyzer`/`DifficultyAnalyzer` the + real pipeline uses -- it updates as you drag, but is computed + independently of (and can legitimately differ from) the "Difficulty" + badge at the top of the page, which reflects the actual generated + document. The lever only covers the 14 whole-page effects + (`apply_to_document()`) -- the 6 signature-only effects (rotation, + stroke thickness, ink bleed/fading, blur) run earlier, on the signature + snippet alone before it's pasted onto the page, so they're already baked + into "Clean" as plain pixels and can't be toggled here; a warning banner + surfaces exactly which of them fired (if any) for the current sample, so + the gap is visible rather than silent. +- **Before / After (real)** -- the saved before/after preview PNG, with a + note that BEFORE's colored boxes are a measurement diagnostic + (target/detected/cosmetic-field boxes) and AFTER deliberately has no + overlay, since it's meant to show what a real reviewer/scanner would see. +- **Ground truth & export** -- the full ground-truth JSON, the equivalent + `python main.py --set ...` CLI command (every control is a thin wrapper + over a config override the CLI already accepts, so anything built in the + UI is exactly reproducible from the command line), and "Save this case", + which writes the document/ground-truth/reference-signature into + `data/ui_generated//`, in the same per-case-folder shape as + `output/`/`data/showcase_dataset/`. + +## Tests + +```bash +python -m pytest -q # 119 passed, ~2-3 min +``` + +Covers formula correctness, placement, augmentation, validation, all 12 +case types, end-to-end reproducibility, `ProceduralTemplateProvider`, +`CompositeTemplateProvider`, the additive signature sources, and the two +difficulty-calibration profiles. diff --git a/unified_pipeline/data/raw/signet/train/060_forg/01_0111060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/01_0111060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..2923194c92f1b36c393230d613f617977a54d758 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/01_0111060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31496c6d68074637ca36a0c6bb346d6678551afe2803f339bef367091574d9a7 +size 114775 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/01_0121060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/01_0121060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..65235adad9e32bd482fd8e1b0c521343dcec53ee --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/01_0121060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acb9e77c2a4aeaf4600cf5907ce5413344f13f00459048c45e9c9b42e7c71819 +size 188570 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/01_0126060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/01_0126060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..97803de24f1bcfd7dcbd5d246212019db49c6559 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/01_0126060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96cb14811ad8ed05325c923286559d627054d2f615b809d562c1b676207ce7fe +size 259747 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/02_0111060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/02_0111060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..797f82b9b1c914432055fd50d95a6fe5c98315be --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/02_0111060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55f2e683a222502f51fec263ea8fb00c98b91389a03f0406877b47b3dc0fb3b3 +size 113149 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/02_0121060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/02_0121060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..ce44aec24597bab321cb7058f7e58c86b49d1169 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/02_0121060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5858e02eaeda90aca615c8dafd425737f2f319e393cd506278b757271b98b40c +size 173025 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/02_0126060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/02_0126060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d8a008d26e9a591bf0895056cea4e0d804f717e8 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/02_0126060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8cffe194d22603bd6d68ea0d9945f5b72890e2ab67675956ee9729ebf078ce6 +size 265293 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/03_0111060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/03_0111060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e2b5511b67dfe544464eb8f808ad877aeaceb7df --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/03_0111060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3da30dd830c67ba350d57cbd8cc6d50749eee11a92d4476d285d959e8740e8ca +size 150390 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/03_0121060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/03_0121060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..adf5395c46704e7421e9c8bd2f0df669bd5982f2 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/03_0121060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3e667c430d9eb878e2ced5e7b6ff47c4f9381368866511cdd61386e60b96025 +size 222449 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/03_0126060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/03_0126060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d63ad4a697fdafa685a2c04fb8f00c3e8aa0c0ff --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/03_0126060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db6d7545a5e2f3ae6e9c311daa04bfabf735946f576aa657a410e6712cff2f2b +size 261001 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/04_0111060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/04_0111060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..80ef1b946f707024c18a359a2cb0a117f24b28f0 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/04_0111060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbbefd8f0241a8f134103438159de09e0a1a55af6f6514447cb87f19724b8d1a +size 174948 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/04_0121060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/04_0121060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6833c43d16f6bf50c87cce8d2562dcc274e70a05 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/04_0121060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00295e55830320743ad5decd91912eb28002d44ea3fe53085ac1369091a05c63 +size 181664 diff --git a/unified_pipeline/data/raw/signet/train/060_forg/04_0126060.PNG b/unified_pipeline/data/raw/signet/train/060_forg/04_0126060.PNG new file mode 100644 index 0000000000000000000000000000000000000000..96ab96a387ccdef439b085d607d8a1bd6bf6e3f2 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/060_forg/04_0126060.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da9aedadd72a45fbd1b1b39d3c0ffdb5ca1865f26fbd7e07d3f50ec9ea78c609 +size 275826 diff --git a/unified_pipeline/data/raw/signet/train/061/01_061.png b/unified_pipeline/data/raw/signet/train/061/01_061.png new file mode 100644 index 0000000000000000000000000000000000000000..fd84d6f3a68d441f8cd371d0f2bb1fe415a4d6f8 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/01_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24ee1233423aee1b1fd0b9b188aaf9d7d35b5d98707c435502d1b0916cc70e0e +size 52658 diff --git a/unified_pipeline/data/raw/signet/train/061/02_061.png b/unified_pipeline/data/raw/signet/train/061/02_061.png new file mode 100644 index 0000000000000000000000000000000000000000..35a89f570573b5bebc242cd516e42975710b45a2 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/02_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bf7f2a6433b6ffc93c089b98941ec593593d6475e5f1fda714934bc1bfd7ec6 +size 42297 diff --git a/unified_pipeline/data/raw/signet/train/061/03_061.png b/unified_pipeline/data/raw/signet/train/061/03_061.png new file mode 100644 index 0000000000000000000000000000000000000000..f80d9a4c94f320b1f33b6fc4f4f15bef8775e548 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/03_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98c63f6e32e34d1abd37d25225710c8981a2ccc163b511a889538b6a6804e251 +size 39937 diff --git a/unified_pipeline/data/raw/signet/train/061/04_061.png b/unified_pipeline/data/raw/signet/train/061/04_061.png new file mode 100644 index 0000000000000000000000000000000000000000..3ba7580db4d595381ddc1d3740114197e23fa8e4 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/04_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d11c2fec2f7f08c96e4c0b7e27baaca873bf664b29a9fd4cf812bef7cf9fb005 +size 44135 diff --git a/unified_pipeline/data/raw/signet/train/061/05_061.png b/unified_pipeline/data/raw/signet/train/061/05_061.png new file mode 100644 index 0000000000000000000000000000000000000000..b292b12789c6e1dab1019fd0379347b6300ab8d4 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/05_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc96513a511a90aac1078032ff7870c58e31c4ea0a14f490a97d971b327a51d1 +size 39328 diff --git a/unified_pipeline/data/raw/signet/train/061/06_061.png b/unified_pipeline/data/raw/signet/train/061/06_061.png new file mode 100644 index 0000000000000000000000000000000000000000..d5c42128481b543338f3b92d401f6d49ca50a920 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/06_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:919b0121c9b55cbb1fb24f2d35d5b2f6935b19d16b718d6c8e1a9fc0b9f46beb +size 41589 diff --git a/unified_pipeline/data/raw/signet/train/061/07_061.png b/unified_pipeline/data/raw/signet/train/061/07_061.png new file mode 100644 index 0000000000000000000000000000000000000000..22a39e77f17ebaf7b9784de3da919f39eca86551 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/07_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9ec3552b8a9de49728f7177beaf6505095ce327407cd6b4fdf08edbffe7295c +size 41685 diff --git a/unified_pipeline/data/raw/signet/train/061/08_061.png b/unified_pipeline/data/raw/signet/train/061/08_061.png new file mode 100644 index 0000000000000000000000000000000000000000..d2896c9af634fc2839a9c581f3ed06f10feb5ef2 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/08_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4811dd029b7f5829fc89437eea6675968abda9d4d87e3e004362d5e4ff08ec8 +size 42423 diff --git a/unified_pipeline/data/raw/signet/train/061/09_061.png b/unified_pipeline/data/raw/signet/train/061/09_061.png new file mode 100644 index 0000000000000000000000000000000000000000..73bb75f69fe5185703b3b92aa2b36a3b0c380f91 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/09_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bc5b17d930fd24c32c63e704db711582678be8d4ea916313720ea23463c3489 +size 38677 diff --git a/unified_pipeline/data/raw/signet/train/061/10_061.png b/unified_pipeline/data/raw/signet/train/061/10_061.png new file mode 100644 index 0000000000000000000000000000000000000000..a38a51a692a832c29bcdd09bbeedf401a23a8ec7 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/10_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b722e8c703ce21729273cd65f9d9a77980108f9e68fc03d6ea151ce4cee46813 +size 50695 diff --git a/unified_pipeline/data/raw/signet/train/061/11_061.png b/unified_pipeline/data/raw/signet/train/061/11_061.png new file mode 100644 index 0000000000000000000000000000000000000000..9b8d66c1b857302b705a4604b1e5f16103d12e29 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/11_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64837fc5a63a915211e1487485e3b1903bf06cb64f80b31fc6ca0ca9c23bb295 +size 57143 diff --git a/unified_pipeline/data/raw/signet/train/061/12_061.png b/unified_pipeline/data/raw/signet/train/061/12_061.png new file mode 100644 index 0000000000000000000000000000000000000000..5a8e246d973bfc030457f66ba660c4e142e85218 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/061/12_061.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:469a35b7d78e91d335d83285b80e1be153a173122175e397ccd77fce3173ff03 +size 58711 diff --git a/unified_pipeline/data/raw/signet/train/061_forg/01_0102061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/01_0102061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..52647d06347e101d999322d8f4181a083a2b3030 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/01_0102061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/01_0112061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/01_0112061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..26d149b2158c1d886ee5126fb653699c59506ddd Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/01_0112061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/01_0206061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/01_0206061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..03c4d3ea50d1b9deaa8cfa4e504c03d917da3c3e Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/01_0206061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/02_0102061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/02_0102061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..464d41fe1af2accf4831c55937ff240db7855617 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/02_0102061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/02_0112061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/02_0112061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..92047f8152034526ca55d355835ba38dd1c98333 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/02_0112061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/02_0206061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/02_0206061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9299ac0e918f2187eba1539aada726e05e95ede1 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/02_0206061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/03_0102061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/03_0102061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..5306918e2fdb39a8182e5bbcd8bb91618e88d65c Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/03_0102061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/03_0112061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/03_0112061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..680ae4ecbb5f9e19578a977117adf54845c278d8 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/03_0112061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/03_0206061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/03_0206061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c27981d06f9a58e1270076bd113272e0680f639f Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/03_0206061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/04_0102061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/04_0102061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..407c84be9d09c0eb046fd07b934c0b180a6ae713 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/04_0102061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/04_0112061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/04_0112061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..8bea6456f72efc1f4d2e4a9fb4945d27f384f559 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/04_0112061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/061_forg/04_0206061.PNG b/unified_pipeline/data/raw/signet/train/061_forg/04_0206061.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d9871e7cae719bc1c317b56a32cba249601e839f Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/061_forg/04_0206061.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/062/01_062.png b/unified_pipeline/data/raw/signet/train/062/01_062.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5893b9eca7e4468510bb303dd0ff688c5774f3 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/01_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97958e22de9d091e0b6263b948feac01b67ec3cf2c4ee0be68270151844e05cf +size 125276 diff --git a/unified_pipeline/data/raw/signet/train/062/02_062.png b/unified_pipeline/data/raw/signet/train/062/02_062.png new file mode 100644 index 0000000000000000000000000000000000000000..fadb91203be7561f9622bba139bb51425ee3b104 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/02_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8a50bdc4694dffdf4a09a2e5cabfda7d90761ea86a4858309d22f5c67b0bb95 +size 84641 diff --git a/unified_pipeline/data/raw/signet/train/062/03_062.png b/unified_pipeline/data/raw/signet/train/062/03_062.png new file mode 100644 index 0000000000000000000000000000000000000000..00cdd68d7612e2164b5e7096ef8732e570c09c40 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/03_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea4d90d03a1424880f7f2b9dbdb06fdbe0b8fdc6b6dc54e95a24a3f05bba983b +size 74699 diff --git a/unified_pipeline/data/raw/signet/train/062/04_062.png b/unified_pipeline/data/raw/signet/train/062/04_062.png new file mode 100644 index 0000000000000000000000000000000000000000..e406c12ef7c926325c423d0ef8e2069b36398d15 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/04_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ea4adf0785b2208c85eb4cb5eb0eb2c6787b8b20dab2525ffccab352a0bf6a9 +size 92700 diff --git a/unified_pipeline/data/raw/signet/train/062/05_062.png b/unified_pipeline/data/raw/signet/train/062/05_062.png new file mode 100644 index 0000000000000000000000000000000000000000..2e7bf0b809e006acae3bfaddb978e63c423eb460 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/05_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a8d66677e02eee1eae7684de2c94fe90995ed35d43c0b3303d72d78c7362888 +size 105248 diff --git a/unified_pipeline/data/raw/signet/train/062/06_062.png b/unified_pipeline/data/raw/signet/train/062/06_062.png new file mode 100644 index 0000000000000000000000000000000000000000..f252e73526cdb8d9b9ee0d45d3bb29967b61d5d1 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/06_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ad82c164dab8580c29076421598dd092088d80372917e0989acfa317ec58cbd +size 65928 diff --git a/unified_pipeline/data/raw/signet/train/062/07_062.png b/unified_pipeline/data/raw/signet/train/062/07_062.png new file mode 100644 index 0000000000000000000000000000000000000000..774a5d19ab28d5b43e3704d64cb0268f5b513293 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/07_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7527248c5455a852be985e0e9e359fd2b792cc0860b5591d96d4a660047cdc0 +size 148647 diff --git a/unified_pipeline/data/raw/signet/train/062/08_062.png b/unified_pipeline/data/raw/signet/train/062/08_062.png new file mode 100644 index 0000000000000000000000000000000000000000..95c64b84f7e5b6cd41519f9c5597b919ccf64ef1 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/08_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9978a128b81c80d1edded76f2bb8f3a7181c9f618efd3db0a12dae50d26f430 +size 99966 diff --git a/unified_pipeline/data/raw/signet/train/062/09_062.png b/unified_pipeline/data/raw/signet/train/062/09_062.png new file mode 100644 index 0000000000000000000000000000000000000000..d83947b7d5f3a9228ffd7b51f4ec1078bb600146 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/09_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fb5aaa7821705ed7e3584e50c7b3d2171b59b5a725dacb0314c63857bcdda81 +size 79131 diff --git a/unified_pipeline/data/raw/signet/train/062/10_062.png b/unified_pipeline/data/raw/signet/train/062/10_062.png new file mode 100644 index 0000000000000000000000000000000000000000..b99828f775ba68df6ef7a2fbfd4cf063963c22e5 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/10_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10beb290f0adcaa7c7ee286ad16b5763b5e5e1cfb2e5cc4df1bf688a9f747091 +size 131298 diff --git a/unified_pipeline/data/raw/signet/train/062/11_062.png b/unified_pipeline/data/raw/signet/train/062/11_062.png new file mode 100644 index 0000000000000000000000000000000000000000..65e601946ad7b3566d84a83f4ba4f90e53fb5732 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/11_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6e748df181d9b76fa67a884ff5457dd2e66a3bcf5edd8773dc86ca10a6433a2 +size 181329 diff --git a/unified_pipeline/data/raw/signet/train/062/12_062.png b/unified_pipeline/data/raw/signet/train/062/12_062.png new file mode 100644 index 0000000000000000000000000000000000000000..021e400d474621c6673febc8ae9fb4d70a185e64 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062/12_062.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64069b6c5bd978e5b93a7694283c9264b4976245f73a8d6bdc343857cc99d085 +size 136436 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/01_0109062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/01_0109062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..dcd5396b3fb8514bbc06869483584cde2580d1de Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/062_forg/01_0109062.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/062_forg/01_0116062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/01_0116062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..b33c764f424b4efeed3510410944b7db9750d0e0 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/01_0116062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3867ca22df070d8b4d51acba0f925717e6741420b0da56199c6f98734f810771 +size 130456 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/01_0201062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/01_0201062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..76cab8211e4689493fc23eaf813b340140fc8706 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/01_0201062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fc8e844114c4c51f12427f64063bc154d7cda89a251b4d60f03b8beb9bc04ef +size 125871 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/02_0109062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/02_0109062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f6242c4e208d56a96e5dde9f240f08db7634bc86 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/062_forg/02_0109062.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/062_forg/02_0116062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/02_0116062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..3af36584e1c1b246fc6a5bde6ef3cf51529a18a5 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/02_0116062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9c9eba67078f44f89587406b228bb821cad91e423665b1ae5cdb9fd52c2a4fb +size 108808 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/02_0201062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/02_0201062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..1811cd86a5ffa68dd636062c496f0ade12217678 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/02_0201062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ebf6278d7c965fddf7ffddd02a8b833c3e11711b5f0fb57acbd5a4c1a2db6cf +size 151589 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/03_0109062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/03_0109062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..56ee046954530b15d15e6681b47b538d8065e761 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/03_0109062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5acf3329f89c9c7c66743599177b603b26c764d4dbf25c0d02ac04189c929a7 +size 166588 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/03_0116062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/03_0116062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..3934c3f19de6ca1bc393027b004488edf40fa16a --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/03_0116062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ec2f34f7dfcebb89a6e1666dfe2a0c7fdc226870099cbc48374b7a15c6b59c5 +size 220327 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/03_0201062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/03_0201062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..66773bd6b47ae33c5ba4746d8805a61638d1d051 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/03_0201062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1f5771ff89df972fab52d439712b57965cbacaedc7acf727a4ee9a31712d8d9 +size 180016 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/04_0109062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/04_0109062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d34bdbd60bd5bbee94f9cdc3479220f79fda80bc --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/04_0109062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d60ee19edf721ada8a2b5cbe3bdc6893b004fb8d64da25719570ebce2b67aad7 +size 114665 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/04_0116062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/04_0116062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..2b62ba6927fa6746fc443ed90775f6016a83e339 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/04_0116062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e57bfef3274dc46fb678e2a4a1e229e497f9aa04e5b9a5732a9d5c80b29ee737 +size 242422 diff --git a/unified_pipeline/data/raw/signet/train/062_forg/04_0201062.PNG b/unified_pipeline/data/raw/signet/train/062_forg/04_0201062.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e44711e22ec07afb2d757eb55c37a9730c11e114 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/062_forg/04_0201062.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6cd7ad1a457346b47742cf3c92a48e8e0bd5119da5b6422d3e5b09d63e05db1 +size 174463 diff --git a/unified_pipeline/data/raw/signet/train/063/01_063.png b/unified_pipeline/data/raw/signet/train/063/01_063.png new file mode 100644 index 0000000000000000000000000000000000000000..fb9b055ac5f6221702b4ccd1dead3658d2a381c5 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/01_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a50cd81cefcde879289a41394dfc2dfb792c2727fb6b0dacae8d0169925b2e1 +size 145347 diff --git a/unified_pipeline/data/raw/signet/train/063/02_063.png b/unified_pipeline/data/raw/signet/train/063/02_063.png new file mode 100644 index 0000000000000000000000000000000000000000..be08220ce9d26bf56c23710fe1247bf71e4dde7f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/02_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4cafb8d442c91f64ae404d390194dc999f62c73dce84731f89993d785f663d8 +size 104709 diff --git a/unified_pipeline/data/raw/signet/train/063/03_063.png b/unified_pipeline/data/raw/signet/train/063/03_063.png new file mode 100644 index 0000000000000000000000000000000000000000..67e5662630e10c846f97ce949a67ab4afdafd728 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/03_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a826c0539c8e7c949b86e257b8ec3dffdc196acba696a4e867ffd14e1cd3a8c7 +size 96811 diff --git a/unified_pipeline/data/raw/signet/train/063/04_063.png b/unified_pipeline/data/raw/signet/train/063/04_063.png new file mode 100644 index 0000000000000000000000000000000000000000..0e3459777ab0f7528a9f39828e14294bcde229af --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/04_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfcf8dc4656ede6a984365206612f79e12b4c365cd43df31af8205c677fe9372 +size 99871 diff --git a/unified_pipeline/data/raw/signet/train/063/05_063.png b/unified_pipeline/data/raw/signet/train/063/05_063.png new file mode 100644 index 0000000000000000000000000000000000000000..836dc1957ff7f572d1dab0124b4b7a31669e935f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/05_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35b6a3ded574ac4a342c24f153894261f04c07b3d913b936d7d0c67501bf0b1f +size 93301 diff --git a/unified_pipeline/data/raw/signet/train/063/06_063.png b/unified_pipeline/data/raw/signet/train/063/06_063.png new file mode 100644 index 0000000000000000000000000000000000000000..58d980c4e3877c61a9c3eb438300b45e55d58fb0 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/06_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72c6989aa3099a0fec784fe32cb0861f4cd29de87b983370047c03611a8705b0 +size 94749 diff --git a/unified_pipeline/data/raw/signet/train/063/07_063.png b/unified_pipeline/data/raw/signet/train/063/07_063.png new file mode 100644 index 0000000000000000000000000000000000000000..b2bc9931329016fc1879c79876caaafb3ed7bd18 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/07_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70545131e8db5be69916edcc2117b429dc2d908d84008ef08f3f9984d3e8486c +size 96887 diff --git a/unified_pipeline/data/raw/signet/train/063/08_063.png b/unified_pipeline/data/raw/signet/train/063/08_063.png new file mode 100644 index 0000000000000000000000000000000000000000..7d86ab570bd4956ad417fe7ef6ba56ca003ecbbd --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/08_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f83b1d906d722f79d59bea98cabb2ce4c9d90979b5f5cf9241997423c239a43e +size 136710 diff --git a/unified_pipeline/data/raw/signet/train/063/09_063.png b/unified_pipeline/data/raw/signet/train/063/09_063.png new file mode 100644 index 0000000000000000000000000000000000000000..e057a47570f9c021059a8677d440e7488eca49d7 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/09_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c775076e78b7ccc3b5e265ffad294be915811b6b9e154fe2b0e408c32ee1701 +size 87395 diff --git a/unified_pipeline/data/raw/signet/train/063/10_063.png b/unified_pipeline/data/raw/signet/train/063/10_063.png new file mode 100644 index 0000000000000000000000000000000000000000..b2e78dcb67f7c082ff6b71b88dcb4eb3195ecb08 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/10_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ddb4bce224b61a5f63114397e56cbfae8f888fbf898f02dc87f8441253932739 +size 126540 diff --git a/unified_pipeline/data/raw/signet/train/063/11_063.png b/unified_pipeline/data/raw/signet/train/063/11_063.png new file mode 100644 index 0000000000000000000000000000000000000000..862e452d609adb3d1a7386bca72c15e7372b1d29 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/11_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ea6580cbe1feb3baa165ec7a755ce98dcc7608d83a5fada16345e9e13d97dbb +size 117714 diff --git a/unified_pipeline/data/raw/signet/train/063/12_063.png b/unified_pipeline/data/raw/signet/train/063/12_063.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8e4d1facbf91f0621dfe6ec3b81ff29c3e965f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063/12_063.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3c366e6217d272734646329df88971c1830386fb3f4dfc283fd76c61bed17ba +size 100698 diff --git a/unified_pipeline/data/raw/signet/train/063_forg/01_0104063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/01_0104063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..595f3281d7927533adef8eb3f46ccc7c874e29a8 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063_forg/01_0104063.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97b433d2987f6aad53fefbcd39e6214d0b398f29543c437b1bb860a26743bee7 +size 118184 diff --git a/unified_pipeline/data/raw/signet/train/063_forg/01_0108063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/01_0108063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..323af89734e25057d03bd1165d4473eabb8e277c Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/063_forg/01_0108063.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/063_forg/01_0119063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/01_0119063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f4c6f7ce0257d96458c07b0ebced17aa1888dcdb --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063_forg/01_0119063.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:114d9fe3bb8822a77d1f3ed4e3e2c2998b193d9e6046ca22e80539e365d9718b +size 118306 diff --git a/unified_pipeline/data/raw/signet/train/063_forg/02_0104063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/02_0104063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..fe38bdc9038b6ad5f6313c0cc3da7544a6a6c8e0 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063_forg/02_0104063.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d00a25b748edad145bc7c26ccd8245095ac0fc2aa65d834244f4d7574d3b6f0b +size 115398 diff --git a/unified_pipeline/data/raw/signet/train/063_forg/02_0108063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/02_0108063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7eaf9812cc3cb713b501a138f09c82f5f86501d8 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/063_forg/02_0108063.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/063_forg/02_0119063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/02_0119063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..bd9ce7a60fb14db3d1205e2477e16c531efa520e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063_forg/02_0119063.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf3d21673051ac8dfcf59cb958bc630bb551c9dc5be9da9a27ffd8c80e5ce999 +size 108693 diff --git a/unified_pipeline/data/raw/signet/train/063_forg/03_0104063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/03_0104063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..4ab4e99f8ba16afa2a58c677cfbc0e17c2eb3b82 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063_forg/03_0104063.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1fbc97506d2c6000638f0aad72036c2691d115f3818206f6bff4c74a54bcc0c +size 179357 diff --git a/unified_pipeline/data/raw/signet/train/063_forg/03_0108063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/03_0108063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..b104491ca9b8750b5c246eb3083eb4b2bc3f73df Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/063_forg/03_0108063.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/063_forg/03_0119063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/03_0119063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..3226d1d1e319e10c9babb9fd5caab1a10679b519 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/063_forg/03_0119063.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/063_forg/04_0104063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/04_0104063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..92904b5e201d25f4adea7df3c7da5ba161126551 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/063_forg/04_0104063.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c338fe3529697d867aa2971d76b6a6bbe212d96b4db3a70d1bee56cb6d092dc1 +size 165823 diff --git a/unified_pipeline/data/raw/signet/train/063_forg/04_0108063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/04_0108063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..ee27217b81c60af43c6490ec369ce77a129345b7 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/063_forg/04_0108063.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/063_forg/04_0119063.PNG b/unified_pipeline/data/raw/signet/train/063_forg/04_0119063.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c9bc36ea4ae0f1118ff1b20d22577fea443b70f9 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/063_forg/04_0119063.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/064/01_064.png b/unified_pipeline/data/raw/signet/train/064/01_064.png new file mode 100644 index 0000000000000000000000000000000000000000..eabaab616570876468da79e2c8fa5cf900402fbf --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/01_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f819cf912fe4f835278b5374e7287c69f9499b91e8279d5c93b85535f63d5ff +size 111695 diff --git a/unified_pipeline/data/raw/signet/train/064/02_064.png b/unified_pipeline/data/raw/signet/train/064/02_064.png new file mode 100644 index 0000000000000000000000000000000000000000..56152405ed284f1b1bc6c421271aa3d17b5f9020 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/02_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92f61bbfc1d6c9320e50d5072a1329021b08b5ceb2aec975b6368a11796ef4b9 +size 137309 diff --git a/unified_pipeline/data/raw/signet/train/064/03_064.png b/unified_pipeline/data/raw/signet/train/064/03_064.png new file mode 100644 index 0000000000000000000000000000000000000000..28966337c976b8171b60a4064249d0efc7792627 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/03_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a105c8de86bc97b24f3d1417627f9ba0ae291b1252000694841a4331d6ec608f +size 95059 diff --git a/unified_pipeline/data/raw/signet/train/064/04_064.png b/unified_pipeline/data/raw/signet/train/064/04_064.png new file mode 100644 index 0000000000000000000000000000000000000000..784944a54cb063c9a37c8e919a0bb9659d2a1a5b --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/04_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1565329fbbf85b97c70ab1fdef68b96baee3ed3bd5c40d89aed0cdae83967b08 +size 117107 diff --git a/unified_pipeline/data/raw/signet/train/064/05_064.png b/unified_pipeline/data/raw/signet/train/064/05_064.png new file mode 100644 index 0000000000000000000000000000000000000000..28f15717c39178a4cbb1eee17c03963adbf70888 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/05_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca538999f6f935e71816009447cf63cf55514513a31cffd177880769d6dbe74a +size 102338 diff --git a/unified_pipeline/data/raw/signet/train/064/06_064.png b/unified_pipeline/data/raw/signet/train/064/06_064.png new file mode 100644 index 0000000000000000000000000000000000000000..44cb98920235f114bb703e6a523ce5a1d4cf5fa9 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/06_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1797d45839a67f2c874242210771c06f12202d0bfd5477a07b46a01911964a76 +size 109975 diff --git a/unified_pipeline/data/raw/signet/train/064/07_064.png b/unified_pipeline/data/raw/signet/train/064/07_064.png new file mode 100644 index 0000000000000000000000000000000000000000..3a6151bb23ba80950349afc717af70740379bfe2 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/07_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55d44b18ecbc217a7e8cdbe00867a0fb371b81c21814668ba1067f0c55088ce7 +size 135125 diff --git a/unified_pipeline/data/raw/signet/train/064/08_064.png b/unified_pipeline/data/raw/signet/train/064/08_064.png new file mode 100644 index 0000000000000000000000000000000000000000..bbf25eded7c0031ce23af80b3a057e0811c79d48 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/08_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46103a62a2590fa286f33f9102a112b3cfd4940956fdc1a0a91ff31488cb8391 +size 137880 diff --git a/unified_pipeline/data/raw/signet/train/064/09_064.png b/unified_pipeline/data/raw/signet/train/064/09_064.png new file mode 100644 index 0000000000000000000000000000000000000000..5e89bdd5f09887a37f4739d63493c7ebe0d84cf4 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/09_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b42aff30400a795e9aca6956035d560a4e56bd0e75ceb5af915d7fb659165214 +size 159196 diff --git a/unified_pipeline/data/raw/signet/train/064/10_064.png b/unified_pipeline/data/raw/signet/train/064/10_064.png new file mode 100644 index 0000000000000000000000000000000000000000..8258e8d9ef69147857477e8c31a3c6930430c9e4 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/10_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c3751fb4ea72de8c525c09ee63511b9948aa9caaa877852be0c3fa8fc4ca9ea +size 231825 diff --git a/unified_pipeline/data/raw/signet/train/064/11_064.png b/unified_pipeline/data/raw/signet/train/064/11_064.png new file mode 100644 index 0000000000000000000000000000000000000000..ad25bb121d06b6b48f072011033ad78881661a90 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/11_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bef37108e6d8323ff89eae6ac41b0c40571289555ee40ec267f045efe9657388 +size 255897 diff --git a/unified_pipeline/data/raw/signet/train/064/12_064.png b/unified_pipeline/data/raw/signet/train/064/12_064.png new file mode 100644 index 0000000000000000000000000000000000000000..ec18ffe41067e09b43491f78f2b2ebe6c9fb4645 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064/12_064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b33c7529c036b87bad9d770c05445e076859677bc62e707aaa340897b4fbd79 +size 175751 diff --git a/unified_pipeline/data/raw/signet/train/064_forg/01_0105064.PNG b/unified_pipeline/data/raw/signet/train/064_forg/01_0105064.PNG new file mode 100644 index 0000000000000000000000000000000000000000..75c2ecdb4fce7ed31768935eb25cb8172437c4b3 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064_forg/01_0105064.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f0f3053fa332fc6bfd4dd34e0204c39fd2bd36997c6bc81d9bfa05305905ba1 +size 119352 diff --git a/unified_pipeline/data/raw/signet/train/064_forg/01_0203064.PNG b/unified_pipeline/data/raw/signet/train/064_forg/01_0203064.PNG new file mode 100644 index 0000000000000000000000000000000000000000..07152d7429068797b734e6c6bce2eea84fc2dbd9 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064_forg/01_0203064.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:941179338135cc7d54a73ccdb18994e0c35ce7c28aacc84bc586fde720cb9a50 +size 250433 diff --git a/unified_pipeline/data/raw/signet/train/064_forg/02_0105064.PNG b/unified_pipeline/data/raw/signet/train/064_forg/02_0105064.PNG new file mode 100644 index 0000000000000000000000000000000000000000..b745b7175b34f805d8d26c1f5a5c68d18798dc19 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064_forg/02_0105064.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88860c4b65bffc4f35fba0ca7abb0a29f1808231d18d42b576dd234cb8a414f1 +size 129641 diff --git a/unified_pipeline/data/raw/signet/train/064_forg/02_0203064.PNG b/unified_pipeline/data/raw/signet/train/064_forg/02_0203064.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7c8028afd69b9394993bfe43ede21339d44f8097 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064_forg/02_0203064.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d151e50c51d8a306c57dcd8adf861e3a098ab121e547ceaf75002d78b25c2923 +size 226858 diff --git a/unified_pipeline/data/raw/signet/train/064_forg/03_0105064.PNG b/unified_pipeline/data/raw/signet/train/064_forg/03_0105064.PNG new file mode 100644 index 0000000000000000000000000000000000000000..97532d4a09ecc72a0f7f27ce5396c2f47026a3cc --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064_forg/03_0105064.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dee590b6eb4e0c5e5c8c11d35298f352276e35a4f18c797764f02de7d8e5be6 +size 125880 diff --git a/unified_pipeline/data/raw/signet/train/064_forg/03_0203064.PNG b/unified_pipeline/data/raw/signet/train/064_forg/03_0203064.PNG new file mode 100644 index 0000000000000000000000000000000000000000..11c31d57388f6cb2244911101ce56a4dbf62d879 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064_forg/03_0203064.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98075135e9d68c9657d07b7beaee875e59cbffc046b242ae9456a3235b17df71 +size 254397 diff --git a/unified_pipeline/data/raw/signet/train/064_forg/04_0105064.PNG b/unified_pipeline/data/raw/signet/train/064_forg/04_0105064.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d8842cd344261800b856e68b9e2c4cd096a8cc31 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064_forg/04_0105064.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:672c683662763869526799603037a337ba2b4a0e28b9409e77e405509dac0ce4 +size 143897 diff --git a/unified_pipeline/data/raw/signet/train/064_forg/04_0203064.PNG b/unified_pipeline/data/raw/signet/train/064_forg/04_0203064.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e7034a8e790a2b1fdaa60c389ba472039f44775f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/064_forg/04_0203064.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d59522502b1919b999e1ccae0010be27c5d27f7c5d2a1ada8518b91548369ca +size 243915 diff --git a/unified_pipeline/data/raw/signet/train/065/01_065.png b/unified_pipeline/data/raw/signet/train/065/01_065.png new file mode 100644 index 0000000000000000000000000000000000000000..0e721d286a47f0afa537063e22361e308238378e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/01_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3eddc1e5db66e1572d2b855bb173163145e4558901b33e667654b4bcca0a521a +size 178193 diff --git a/unified_pipeline/data/raw/signet/train/065/02_065.png b/unified_pipeline/data/raw/signet/train/065/02_065.png new file mode 100644 index 0000000000000000000000000000000000000000..4a247070dc332d71bf68961ff0cee6eb552b1289 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/02_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:beb69b87a707b142d24c2bc605bbca5740a1be3a56a46187772ef5fe5f6d4949 +size 136482 diff --git a/unified_pipeline/data/raw/signet/train/065/03_065.png b/unified_pipeline/data/raw/signet/train/065/03_065.png new file mode 100644 index 0000000000000000000000000000000000000000..3afe37156729fbcd9d73bacde7fef798a0b49e70 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/03_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44e4861dc4777f1347b92705673f538b6392b8f13b8fe34914ce1a878c558395 +size 125902 diff --git a/unified_pipeline/data/raw/signet/train/065/04_065.png b/unified_pipeline/data/raw/signet/train/065/04_065.png new file mode 100644 index 0000000000000000000000000000000000000000..9a88d5b5202d1a36dc84697af59af64d37db8ec1 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/04_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:714a8c08e043f37370696640637a6a89846e5b1dc486b44755a96c410d49c430 +size 135229 diff --git a/unified_pipeline/data/raw/signet/train/065/05_065.png b/unified_pipeline/data/raw/signet/train/065/05_065.png new file mode 100644 index 0000000000000000000000000000000000000000..e8cc3a266f4c370ca9b972e6139f11f6e05751ca --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/05_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:324d293d707a171b541df5f16945dda22d4b8a0fad559ba4bd95722a465772af +size 118711 diff --git a/unified_pipeline/data/raw/signet/train/065/06_065.png b/unified_pipeline/data/raw/signet/train/065/06_065.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc769dd102fdd37f9f6905a9bfee1e8ac3fb25a --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/06_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95e3725bc5f16cd999fc2b774ce744f56f50f4528ef525d20984bc9f5b3edf98 +size 111503 diff --git a/unified_pipeline/data/raw/signet/train/065/07_065.png b/unified_pipeline/data/raw/signet/train/065/07_065.png new file mode 100644 index 0000000000000000000000000000000000000000..c3ec47cc8edac821f2e1ec2e125f343aa7de8d0e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/07_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e6266e827e69113a11132c548e92a3b66c7bbfc2775685710e65b52756e6449 +size 165158 diff --git a/unified_pipeline/data/raw/signet/train/065/08_065.png b/unified_pipeline/data/raw/signet/train/065/08_065.png new file mode 100644 index 0000000000000000000000000000000000000000..e675237ac26daffbe8a3ac245bd562c77aeaf6c1 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/08_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:970ad9612898595b0a447521f41778c24cdddc2db108fd3f1953c9fe97a638e9 +size 137940 diff --git a/unified_pipeline/data/raw/signet/train/065/09_065.png b/unified_pipeline/data/raw/signet/train/065/09_065.png new file mode 100644 index 0000000000000000000000000000000000000000..387f3320712c29887fb8fd0dad407b142994bb96 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/09_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27e542adfed56c821b2c9bda25a7645f1f98b020ab0466b728f880fddb20f0a6 +size 143678 diff --git a/unified_pipeline/data/raw/signet/train/065/10_065.png b/unified_pipeline/data/raw/signet/train/065/10_065.png new file mode 100644 index 0000000000000000000000000000000000000000..d58ec314eed599f4d49230c3d0484b96cb1348f2 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/10_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65e9c53d72e1a8976f99011048744bd6fbb660db0ff7f2ecb26c1e44122524e8 +size 200151 diff --git a/unified_pipeline/data/raw/signet/train/065/11_065.png b/unified_pipeline/data/raw/signet/train/065/11_065.png new file mode 100644 index 0000000000000000000000000000000000000000..4a8c857eec512e88f5f4f57ad60d5a8d9ee8ea2e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/11_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cff17fd88c6d76652401e03175a399e5acc1601bf3625b2efc25648296114e78 +size 198535 diff --git a/unified_pipeline/data/raw/signet/train/065/12_065.png b/unified_pipeline/data/raw/signet/train/065/12_065.png new file mode 100644 index 0000000000000000000000000000000000000000..853356edde80a7fc6bbc07f7426a1ddaba0acb4f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065/12_065.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff638916821896070dcf4886e4819a92b45839b2c2b2378a42f993b0cdb10fe5 +size 186577 diff --git a/unified_pipeline/data/raw/signet/train/065_forg/01_0118065.PNG b/unified_pipeline/data/raw/signet/train/065_forg/01_0118065.PNG new file mode 100644 index 0000000000000000000000000000000000000000..697f9de64ee4d548af5de717b14558555ee174b8 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065_forg/01_0118065.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e854d8cb3a2c033ed6ad830ecaed391f2f76d70b0afb0d41af4513e023d3635e +size 190428 diff --git a/unified_pipeline/data/raw/signet/train/065_forg/01_0206065.PNG b/unified_pipeline/data/raw/signet/train/065_forg/01_0206065.PNG new file mode 100644 index 0000000000000000000000000000000000000000..268896df36fbf97818ed33344a4315c0ac7b5faf --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065_forg/01_0206065.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a98ec2798427773525dcf8ec9105c0e457f94092c2b0f5fe7c974c6e2417ee3c +size 103880 diff --git a/unified_pipeline/data/raw/signet/train/065_forg/02_0118065.PNG b/unified_pipeline/data/raw/signet/train/065_forg/02_0118065.PNG new file mode 100644 index 0000000000000000000000000000000000000000..8047bcc39a2dcb592f52f2eb0aa84016d2db4dab --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065_forg/02_0118065.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9824f6c3568170378947064f4ada951826582a46656347eee00f9fab8291b118 +size 161096 diff --git a/unified_pipeline/data/raw/signet/train/065_forg/02_0206065.PNG b/unified_pipeline/data/raw/signet/train/065_forg/02_0206065.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c4dc4340f5d15ca53ca4701a529e1fe5842ef010 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/065_forg/02_0206065.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/065_forg/03_0118065.PNG b/unified_pipeline/data/raw/signet/train/065_forg/03_0118065.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c743509220d73b503fa9daec0ed694b72d357480 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065_forg/03_0118065.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e89cbfbf77fc5a0cab1c5075d5cdb150d2e0c7ae04305d48f6434cd197a21d4 +size 201570 diff --git a/unified_pipeline/data/raw/signet/train/065_forg/03_0206065.PNG b/unified_pipeline/data/raw/signet/train/065_forg/03_0206065.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7343d4dc717f0f1dd424a3dca591457efbe3573c --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065_forg/03_0206065.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f790696fc1fafede03d2aa15d17101607d00fe859943b67526e11771c6721076 +size 114384 diff --git a/unified_pipeline/data/raw/signet/train/065_forg/04_0118065.PNG b/unified_pipeline/data/raw/signet/train/065_forg/04_0118065.PNG new file mode 100644 index 0000000000000000000000000000000000000000..36455083ad5420fe7b5a705423d569546a1303db --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065_forg/04_0118065.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a45fc73ec169e05d4893f7034699d2b540112e041c47432ba3ddcc8f9e3df28 +size 219644 diff --git a/unified_pipeline/data/raw/signet/train/065_forg/04_0206065.PNG b/unified_pipeline/data/raw/signet/train/065_forg/04_0206065.PNG new file mode 100644 index 0000000000000000000000000000000000000000..2b04060635a84edd8970476192e888103ce55119 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/065_forg/04_0206065.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a2e0a95f80f9d28ea96a8377fc59700fb53f954162c64cd8b17c0cfab3e746a +size 123116 diff --git a/unified_pipeline/data/raw/signet/train/066/01_066.png b/unified_pipeline/data/raw/signet/train/066/01_066.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4c0fb18fb552d0320adbf31346b39bf7bb634e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/01_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a780e828c2e1f83fe1e3ab11183d5bf2b6e1d716e4c5badbbe267492a1021eb9 +size 68802 diff --git a/unified_pipeline/data/raw/signet/train/066/02_066.png b/unified_pipeline/data/raw/signet/train/066/02_066.png new file mode 100644 index 0000000000000000000000000000000000000000..faf13abff0a1be1f5cc730bf53f2d9f430c5e476 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/02_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4510e398ae1faacbe0158ad45c9ead8c40dc9fea8077fd5b0bbdfe7b50f2c93 +size 63093 diff --git a/unified_pipeline/data/raw/signet/train/066/03_066.png b/unified_pipeline/data/raw/signet/train/066/03_066.png new file mode 100644 index 0000000000000000000000000000000000000000..4747010ebd40f015bcea695309b2553d7c40693a --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/03_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12b8ecdea8d0b5dcfde1a4b60b3bcafbc85f9f89951a7d3a6ec50b4282dd95af +size 83394 diff --git a/unified_pipeline/data/raw/signet/train/066/04_066.png b/unified_pipeline/data/raw/signet/train/066/04_066.png new file mode 100644 index 0000000000000000000000000000000000000000..2df6c44762e44f7f611211b489f82deff7057d5d --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/04_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:025004d8922112c153752b086ae59cbf9919d1a4b836f9aa797afacd0cfe78be +size 76847 diff --git a/unified_pipeline/data/raw/signet/train/066/05_066.png b/unified_pipeline/data/raw/signet/train/066/05_066.png new file mode 100644 index 0000000000000000000000000000000000000000..c7fea0ea059926702b12c61cbad7e474f9d4f558 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/05_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2771d764bfeeef391a16874ab370264a6fbf6a7685d19b4776dc71bedc23e616 +size 57351 diff --git a/unified_pipeline/data/raw/signet/train/066/06_066.png b/unified_pipeline/data/raw/signet/train/066/06_066.png new file mode 100644 index 0000000000000000000000000000000000000000..b3949ddcf98b7a0bfe108a98857635c652e142be --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/06_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0280b267dcae413b69559e47a01cf4397a4239d677627f862ff74e873bbb0050 +size 67209 diff --git a/unified_pipeline/data/raw/signet/train/066/07_066.png b/unified_pipeline/data/raw/signet/train/066/07_066.png new file mode 100644 index 0000000000000000000000000000000000000000..d407b306d33d4714448243d41523ace9d607ee57 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/07_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c208cd8bc2a4c7b15c2cf7b53542b7c03f00bd279fc78feecd2a5d0ed721cea +size 93481 diff --git a/unified_pipeline/data/raw/signet/train/066/08_066.png b/unified_pipeline/data/raw/signet/train/066/08_066.png new file mode 100644 index 0000000000000000000000000000000000000000..cc4432b6ffb4281e134beb2b8f8718341ca98886 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/08_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe4f65c08421c278e057a964d1b4fd353d2980f786edc6271dc04631a4cef043 +size 81731 diff --git a/unified_pipeline/data/raw/signet/train/066/09_066.png b/unified_pipeline/data/raw/signet/train/066/09_066.png new file mode 100644 index 0000000000000000000000000000000000000000..785c86215f9bfd5f335a78b7255688b204f7881b --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/09_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77280c36dcfeb7e6986eb3e62cba5ab2de87301a1d2d132850e78ac8ada9a34c +size 81669 diff --git a/unified_pipeline/data/raw/signet/train/066/10_066.png b/unified_pipeline/data/raw/signet/train/066/10_066.png new file mode 100644 index 0000000000000000000000000000000000000000..c14ed6c139b0083acf10f30dc03608fe1f7be33c --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/10_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:492067cd964c849e58e7b5ba596a7c87bf9e10436fe71b72ef4983005e51e9ab +size 99849 diff --git a/unified_pipeline/data/raw/signet/train/066/11_066.png b/unified_pipeline/data/raw/signet/train/066/11_066.png new file mode 100644 index 0000000000000000000000000000000000000000..80b3908f798c30ba3988e8393f154da1e63c34bb --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/11_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed61a3a99e612d2f01a24855e2a33924b05fffe2692504155e6267a67286c2f0 +size 103012 diff --git a/unified_pipeline/data/raw/signet/train/066/12_066.png b/unified_pipeline/data/raw/signet/train/066/12_066.png new file mode 100644 index 0000000000000000000000000000000000000000..9f62b11a2c3c51d505f0b4044319e31b68d7f950 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066/12_066.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c105283587d8790130ddacbf977a8087fc1df145d2d40d4a3302a8fdb5b1ebf +size 102501 diff --git a/unified_pipeline/data/raw/signet/train/066_forg/01_0101066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/01_0101066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..3cae9a6836e3b53e1908896fc2e56bb61351535d Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/01_0101066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/01_0127066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/01_0127066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..78f0d5e4378c4c22c61e50773634878ed4bc8fb6 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/01_0127066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/01_0211066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/01_0211066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e2f1e9bbcf7798fc67321346712434cb41832421 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/01_0211066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/01_0212066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/01_0212066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..1baf2f813e6f17c52806247f39bd8a8585f92812 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066_forg/01_0212066.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:855c540238b30f8086b9cfef204f8a39f11f64b72ac2991ee1e7d344b32a725c +size 188480 diff --git a/unified_pipeline/data/raw/signet/train/066_forg/02_0101066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/02_0101066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..2681b330539e54b82e7441dbcd45a3f4b7d1e99d Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/02_0101066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/02_0127066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/02_0127066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..39a44b56cbce8bf6814ecc9175ae0f7d2d368d3d Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/02_0127066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/02_0211066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/02_0211066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..ce6ca34cf9d19bdf8c7089c2bcad02e07b54468d Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/02_0211066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/02_0212066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/02_0212066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f98a8da98925eba6c93e69ed7f0501ca73063a91 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066_forg/02_0212066.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f3ad2081fa972d857fd237af741daebac7ee6b61f54e7f5f8796323a7902656 +size 181778 diff --git a/unified_pipeline/data/raw/signet/train/066_forg/03_0101066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/03_0101066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..1b85c5382157d2d58b0d65d60bd3aad777b9e643 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066_forg/03_0101066.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:817d33525a2cd5a5da26b1896ac89d9e93df2f48bca210db5ca234fe7cd12b44 +size 115868 diff --git a/unified_pipeline/data/raw/signet/train/066_forg/03_0127066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/03_0127066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..660c5093d2913c8cd010af5f2acc2ce28a2abed8 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/03_0127066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/03_0211066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/03_0211066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..3a6250fc21c82a7e9a3a85b353cbafa54e77516c Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/03_0211066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/03_0212066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/03_0212066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..48d1c3494f6c0d4446aec6c567b2581bb7d2e15f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066_forg/03_0212066.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d11bddb0f9579f27383322eda70f23569f18420847dd53a55b04a2c85af3837e +size 186203 diff --git a/unified_pipeline/data/raw/signet/train/066_forg/04_0101066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/04_0101066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f10bb01ca6e5717213ec33563f26f5e05adccff2 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/04_0101066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/04_0127066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/04_0127066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..553e2df93971071ba47070db8e46d4b22806b7b1 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/04_0127066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/04_0211066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/04_0211066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..af24303707a082910ef23347447d4004fed08d6c Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/066_forg/04_0211066.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/066_forg/04_0212066.PNG b/unified_pipeline/data/raw/signet/train/066_forg/04_0212066.PNG new file mode 100644 index 0000000000000000000000000000000000000000..fbe38747034bb59cf115fec7f5a5f28201a984e2 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/066_forg/04_0212066.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2deb5e79a479c0f32952ae42b6e153b3040c94aea2b781d8f7670039ded2bbc6 +size 249979 diff --git a/unified_pipeline/data/raw/signet/train/067/01_067.png b/unified_pipeline/data/raw/signet/train/067/01_067.png new file mode 100644 index 0000000000000000000000000000000000000000..96f80642359e117f0f50078f43b858aed8bc9eb0 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/01_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b0cb78ca9a06a75ee513d9153eba20f32f421e638c00971e6688c43a8861c43 +size 156408 diff --git a/unified_pipeline/data/raw/signet/train/067/02_067.png b/unified_pipeline/data/raw/signet/train/067/02_067.png new file mode 100644 index 0000000000000000000000000000000000000000..0432f9822219fa649a4244440fcc74974ecbf538 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/02_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:807db93e5548d46ecd6d741339745e85c43a000da32e2c9407f1966e6d3dcfb2 +size 115439 diff --git a/unified_pipeline/data/raw/signet/train/067/03_067.png b/unified_pipeline/data/raw/signet/train/067/03_067.png new file mode 100644 index 0000000000000000000000000000000000000000..54f0f31fae89101d7b4b92b05a657ca73843665c --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/03_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0696b13009fd8706d8c86e97823d7f0b93bc209ad840882582c84bda8779bdab +size 100202 diff --git a/unified_pipeline/data/raw/signet/train/067/04_067.png b/unified_pipeline/data/raw/signet/train/067/04_067.png new file mode 100644 index 0000000000000000000000000000000000000000..e5237785473ed0be9b15f6315723656d6e75eabd --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/04_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3582bd85e0adf44af85e7db7433dac86b80e414d3534a87ff809f7197369b0da +size 100481 diff --git a/unified_pipeline/data/raw/signet/train/067/05_067.png b/unified_pipeline/data/raw/signet/train/067/05_067.png new file mode 100644 index 0000000000000000000000000000000000000000..21a8b58dfa0f9f2233f2a6c2c76fbb1f9c77621f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/05_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c98cb4c89f6c4076f8fbf91f218fd376d97d6fed118d2489264cea1794b29b4c +size 103526 diff --git a/unified_pipeline/data/raw/signet/train/067/06_067.png b/unified_pipeline/data/raw/signet/train/067/06_067.png new file mode 100644 index 0000000000000000000000000000000000000000..444c65e842b344ed7bd9893e5ca0ceba274de8b3 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/06_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b87842fd07bc8e88b5e1efa05f0ddf350763fee3c9fcc0586a9fb083027655b +size 87867 diff --git a/unified_pipeline/data/raw/signet/train/067/07_067.png b/unified_pipeline/data/raw/signet/train/067/07_067.png new file mode 100644 index 0000000000000000000000000000000000000000..3c66fd45a92a0e38c6af211c092c928280606428 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/07_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09462e8689cce3972e1201d7810142ab49aa070bc0f2acec3cc263b0ea31dc35 +size 141076 diff --git a/unified_pipeline/data/raw/signet/train/067/08_067.png b/unified_pipeline/data/raw/signet/train/067/08_067.png new file mode 100644 index 0000000000000000000000000000000000000000..0805b37b0b49420038b23ae5d32d1236f718fc64 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/08_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cfb18b9fbc13ad3176357925500f0b6f228ddcb53c186e7a10c2025553f3505 +size 131897 diff --git a/unified_pipeline/data/raw/signet/train/067/09_067.png b/unified_pipeline/data/raw/signet/train/067/09_067.png new file mode 100644 index 0000000000000000000000000000000000000000..b9f4fb47425264704c6eb0da74ca7f2e6f62957e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/09_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b64c3854664ca1a1d36538aa40f9e8fa496552d066bf9214da998479b64e49f +size 110282 diff --git a/unified_pipeline/data/raw/signet/train/067/10_067.png b/unified_pipeline/data/raw/signet/train/067/10_067.png new file mode 100644 index 0000000000000000000000000000000000000000..3fae3b4ffdb1e4491ab4a699b17fdafa2e36ff0b --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/10_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:939c5fb1f61fff7b282942ef6293f7d100ae6f28c338320c96870215a61266d0 +size 167084 diff --git a/unified_pipeline/data/raw/signet/train/067/11_067.png b/unified_pipeline/data/raw/signet/train/067/11_067.png new file mode 100644 index 0000000000000000000000000000000000000000..c9e7c9c6a81cbefeb0ea714c9c3e31bc28649e30 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/11_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47a8f1ba889c275900334ac39c0d054fcaa03b01609c9daaf52332e90e8c1df3 +size 178861 diff --git a/unified_pipeline/data/raw/signet/train/067/12_067.png b/unified_pipeline/data/raw/signet/train/067/12_067.png new file mode 100644 index 0000000000000000000000000000000000000000..c3d4633d05f2b9a6d60512838378aef4ba18f1ab --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067/12_067.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17ce307bb0738fbb87e0d7c9900b48775d687e69e79932421d902d84df693d03 +size 208672 diff --git a/unified_pipeline/data/raw/signet/train/067_forg/01_0205067.PNG b/unified_pipeline/data/raw/signet/train/067_forg/01_0205067.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c67cc8d3ab7a5ee3edb738f967a8a59d21a10afb --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067_forg/01_0205067.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c76245894677424f0b3b09f70328dc3f445a318060b893cf53151617b0c3571b +size 188086 diff --git a/unified_pipeline/data/raw/signet/train/067_forg/01_0212067.PNG b/unified_pipeline/data/raw/signet/train/067_forg/01_0212067.PNG new file mode 100644 index 0000000000000000000000000000000000000000..78eeb643713856aca138557b1f50d01f5fe427f3 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067_forg/01_0212067.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7232018d5574c4459758bbc544b32782d17a92ede48d79f57b07e15442dfb8dd +size 249768 diff --git a/unified_pipeline/data/raw/signet/train/067_forg/02_0205067.PNG b/unified_pipeline/data/raw/signet/train/067_forg/02_0205067.PNG new file mode 100644 index 0000000000000000000000000000000000000000..a15bbec3993467d60eb572d3caa71fad4da3a345 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067_forg/02_0205067.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5010670e2c2d4515600c20dcd9e00a4bfe76a2281709e4fa16650650ea3fd73f +size 160358 diff --git a/unified_pipeline/data/raw/signet/train/067_forg/02_0212067.PNG b/unified_pipeline/data/raw/signet/train/067_forg/02_0212067.PNG new file mode 100644 index 0000000000000000000000000000000000000000..50ce79b1d04364b620f9038fb7ece7804bf37ea1 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067_forg/02_0212067.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa955f590b0a0035fbb589183c68a08387fd31f4d5584b354ffdfd6b1c1dd5cd +size 216096 diff --git a/unified_pipeline/data/raw/signet/train/067_forg/03_0205067.PNG b/unified_pipeline/data/raw/signet/train/067_forg/03_0205067.PNG new file mode 100644 index 0000000000000000000000000000000000000000..cf458786c5d991d1bc8ef9a8289db3ee90a8e312 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067_forg/03_0205067.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0f000bb5a7b63756801e4f013a301614379b3b5624ff0d42f85d2e807506eed +size 153984 diff --git a/unified_pipeline/data/raw/signet/train/067_forg/03_0212067.PNG b/unified_pipeline/data/raw/signet/train/067_forg/03_0212067.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d7f2ec10b8406692fb27a6e9d85ee89e1cfd1331 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067_forg/03_0212067.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11a07cad85aae3968ff73eb039d49ec1c8fa77fc9f64e44099bf73689cdf96a6 +size 277678 diff --git a/unified_pipeline/data/raw/signet/train/067_forg/04_0205067.PNG b/unified_pipeline/data/raw/signet/train/067_forg/04_0205067.PNG new file mode 100644 index 0000000000000000000000000000000000000000..da713ee33b9b7c19c968e19677b6779c83c93ef8 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067_forg/04_0205067.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fffce34ed098bf8e7eae521f9db90e06eb30f74461ba0e533a18993867e1f4de +size 135488 diff --git a/unified_pipeline/data/raw/signet/train/067_forg/04_0212067.PNG b/unified_pipeline/data/raw/signet/train/067_forg/04_0212067.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9a7f88dacd367a9720a20ccbb55d51670a64667c --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/067_forg/04_0212067.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3595c60d146fe26cdd51d45921b31729522613e113539c07757767053c7743e0 +size 304809 diff --git a/unified_pipeline/data/raw/signet/train/068/01_068.png b/unified_pipeline/data/raw/signet/train/068/01_068.png new file mode 100644 index 0000000000000000000000000000000000000000..9900b69be6279d0382eac12059752eac16787af4 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/01_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13fc519c28e6b7e2cc037688e18774cffe017bd7de299f44255052703d25606b +size 77154 diff --git a/unified_pipeline/data/raw/signet/train/068/02_068.png b/unified_pipeline/data/raw/signet/train/068/02_068.png new file mode 100644 index 0000000000000000000000000000000000000000..df7c70b1dbcf3a1f61b139abb869725895e4efc0 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/02_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d762e12addc4bd965e48286b05dc664a1f6f54cc12db7f0191cad234206d7c9 +size 68175 diff --git a/unified_pipeline/data/raw/signet/train/068/03_068.png b/unified_pipeline/data/raw/signet/train/068/03_068.png new file mode 100644 index 0000000000000000000000000000000000000000..fba08f2ff884662f79991af9b60522202017df7e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/03_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:369e09d4083f3d2dfe62064aa35d7a974a20d2a1fff8ff78f9b5fa2efb9c70c7 +size 70900 diff --git a/unified_pipeline/data/raw/signet/train/068/04_068.png b/unified_pipeline/data/raw/signet/train/068/04_068.png new file mode 100644 index 0000000000000000000000000000000000000000..0321d63aa6c9b8ec81c714d77b61f413d295bd29 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/04_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d9d14b7f9152c61f6d6f976045e163c9505b1018bd0d645e064f740c2ad1a53 +size 79350 diff --git a/unified_pipeline/data/raw/signet/train/068/05_068.png b/unified_pipeline/data/raw/signet/train/068/05_068.png new file mode 100644 index 0000000000000000000000000000000000000000..63e7ff521e9f959c76563805b48651ffcb26d239 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/05_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca0f1bd833acc189c9460b7bff1caa5fccc3a069ce4bc9b0c1595024e837d724 +size 64255 diff --git a/unified_pipeline/data/raw/signet/train/068/06_068.png b/unified_pipeline/data/raw/signet/train/068/06_068.png new file mode 100644 index 0000000000000000000000000000000000000000..5c38d6336602f0f823dbda0a585a838f31eeb215 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/06_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afbe9f5aa6aa54b96b5b06059f90cf634f0fc3e3c7aaec9ec65339f9dadb3f83 +size 70109 diff --git a/unified_pipeline/data/raw/signet/train/068/07_068.png b/unified_pipeline/data/raw/signet/train/068/07_068.png new file mode 100644 index 0000000000000000000000000000000000000000..265150207833f79a2ec032329105ad1089339cc6 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/07_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a239c2cfe12f927fa8838aa1dfa7786c95ff152d10094dcfbe61ef58b8ac2977 +size 94332 diff --git a/unified_pipeline/data/raw/signet/train/068/08_068.png b/unified_pipeline/data/raw/signet/train/068/08_068.png new file mode 100644 index 0000000000000000000000000000000000000000..95fa63ed23661aca7288ee300e352238b26b3a53 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/08_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2482d057f6fcc78d968e3951a6a38e74e71614ac7648877ba6dd5f947d2d253c +size 70570 diff --git a/unified_pipeline/data/raw/signet/train/068/09_068.png b/unified_pipeline/data/raw/signet/train/068/09_068.png new file mode 100644 index 0000000000000000000000000000000000000000..d0eea758ae4133c9bd569f8a44134ca0e86e2a30 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/09_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b1d90f8712a6fda39c5a5d6273197432748eda9aaa59e315d9574e43c69805e +size 68414 diff --git a/unified_pipeline/data/raw/signet/train/068/10_068.png b/unified_pipeline/data/raw/signet/train/068/10_068.png new file mode 100644 index 0000000000000000000000000000000000000000..7adc143e178dc78f2e98eb901a4c74ac338d0bda --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/10_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5d2e1b7b1e5f0523a5886fc99edaf11277914f0018dfa7dee5e66eee37ec607 +size 89779 diff --git a/unified_pipeline/data/raw/signet/train/068/11_068.png b/unified_pipeline/data/raw/signet/train/068/11_068.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9082a8f10891a8ed7bde4aa8e8f5274d2dc793 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/11_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:941916a0f01a148127417b758aea67bcfb5fa853dcda08304f3d33bcad1e2dfe +size 95691 diff --git a/unified_pipeline/data/raw/signet/train/068/12_068.png b/unified_pipeline/data/raw/signet/train/068/12_068.png new file mode 100644 index 0000000000000000000000000000000000000000..f2962c0c4f41df8fe124cb713d2f2e93c8ecc617 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068/12_068.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5def7ad0988c25054f3c2e7aba9805bf5ea836661818ecd36d55ea5aee32b46 +size 90897 diff --git a/unified_pipeline/data/raw/signet/train/068_forg/01_0113068.PNG b/unified_pipeline/data/raw/signet/train/068_forg/01_0113068.PNG new file mode 100644 index 0000000000000000000000000000000000000000..db75ff81490ed74afa1272c158fe8334644f2e0c Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/068_forg/01_0113068.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/068_forg/01_0124068.PNG b/unified_pipeline/data/raw/signet/train/068_forg/01_0124068.PNG new file mode 100644 index 0000000000000000000000000000000000000000..58477aea3f4fa853e962ca59b9689dab3c5cd535 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/068_forg/01_0124068.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/068_forg/02_0113068.PNG b/unified_pipeline/data/raw/signet/train/068_forg/02_0113068.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6da1b17afde7480a03962d81dabe78e9f0b1a008 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/068_forg/02_0113068.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/068_forg/02_0124068.PNG b/unified_pipeline/data/raw/signet/train/068_forg/02_0124068.PNG new file mode 100644 index 0000000000000000000000000000000000000000..df2923854289ff02c0edd73905fcd59ab1dd7817 Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/068_forg/02_0124068.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/068_forg/03_0113068.PNG b/unified_pipeline/data/raw/signet/train/068_forg/03_0113068.PNG new file mode 100644 index 0000000000000000000000000000000000000000..698d1972a6d927156eaaf94f21db561a6a3fca67 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068_forg/03_0113068.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ec2391185af1d02b464edc36524fccb56a1254f318ec4b4673cd6461d3ee22c +size 147808 diff --git a/unified_pipeline/data/raw/signet/train/068_forg/03_0124068.PNG b/unified_pipeline/data/raw/signet/train/068_forg/03_0124068.PNG new file mode 100644 index 0000000000000000000000000000000000000000..8d68ec19189aed9ec446d30fbf6832d417af215f Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/068_forg/03_0124068.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/068_forg/04_0113068.PNG b/unified_pipeline/data/raw/signet/train/068_forg/04_0113068.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7eb84455524ee6802a01903a7a3ad3a73f51342c --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/068_forg/04_0113068.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7dfe55f7cf88b5586c05b26b13599e5d7f25c048ec60e631f4dc2023f3c8d15 +size 150497 diff --git a/unified_pipeline/data/raw/signet/train/068_forg/04_0124068.PNG b/unified_pipeline/data/raw/signet/train/068_forg/04_0124068.PNG new file mode 100644 index 0000000000000000000000000000000000000000..ea44a9fb4752d2fa612fb7048d14daed5b346a4f Binary files /dev/null and b/unified_pipeline/data/raw/signet/train/068_forg/04_0124068.PNG differ diff --git a/unified_pipeline/data/raw/signet/train/069/01_069.png b/unified_pipeline/data/raw/signet/train/069/01_069.png new file mode 100644 index 0000000000000000000000000000000000000000..ef0edcdeb03efe5e72f6d894a6c2762d669bab14 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/01_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e4a90e5ebbb2fad781cbc74f386c667c33f6837e39d4c220889a17b72b6c30c +size 300071 diff --git a/unified_pipeline/data/raw/signet/train/069/02_069.png b/unified_pipeline/data/raw/signet/train/069/02_069.png new file mode 100644 index 0000000000000000000000000000000000000000..5dcf7b0abfd01174549a49173f10fac8851ad7c9 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/02_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd7d0ddaeaa934f4c69d6d12fb0934fee101d6e4de3055c936bc95804cfb6a5b +size 279261 diff --git a/unified_pipeline/data/raw/signet/train/069/03_069.png b/unified_pipeline/data/raw/signet/train/069/03_069.png new file mode 100644 index 0000000000000000000000000000000000000000..42be9946a871a4f968238d43463dd6f757142f0b --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/03_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3688ab5118411162a7d76e7ec0129f55599f0ce6f77dcbd350b4ec3b8f25e188 +size 333965 diff --git a/unified_pipeline/data/raw/signet/train/069/04_069.png b/unified_pipeline/data/raw/signet/train/069/04_069.png new file mode 100644 index 0000000000000000000000000000000000000000..a9c646db96ef470926eb2d07042fc01f7c233a03 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/04_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e326eccd733df2f10a0c58fef4297d88fb9dcbe8c61ccacfed9e977e3cd8482 +size 294737 diff --git a/unified_pipeline/data/raw/signet/train/069/05_069.png b/unified_pipeline/data/raw/signet/train/069/05_069.png new file mode 100644 index 0000000000000000000000000000000000000000..d13dfda2e072c65126111f80f969499124d816f9 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/05_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ee3825c4bac7fc2ec4e0f993eb150cec6cb0f8f1e21f70644cbe4dec9f014b6 +size 212483 diff --git a/unified_pipeline/data/raw/signet/train/069/06_069.png b/unified_pipeline/data/raw/signet/train/069/06_069.png new file mode 100644 index 0000000000000000000000000000000000000000..4b0e64ba70e23c687e6f800fdb60930ae328ce13 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/06_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79688e57adaa64d2b4ebd6dfaf17ec9ed0be1b53f940755f65213c1378773d6b +size 255544 diff --git a/unified_pipeline/data/raw/signet/train/069/07_069.png b/unified_pipeline/data/raw/signet/train/069/07_069.png new file mode 100644 index 0000000000000000000000000000000000000000..7aa41ef754b5718528a2a11c0889b61d620ed8f0 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/07_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77f39a6321f4c1065e3bb94e8b2f06c66831125f2d17edb02be53d92a13a5f49 +size 333215 diff --git a/unified_pipeline/data/raw/signet/train/069/08_069.png b/unified_pipeline/data/raw/signet/train/069/08_069.png new file mode 100644 index 0000000000000000000000000000000000000000..a746ff57b6b681e94a95d2b85c8f5ffbbe494c0c --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/08_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95f061bea94acde199aa5b2a57ee61a1800ef14577114f19b94c745e487fb015 +size 285363 diff --git a/unified_pipeline/data/raw/signet/train/069/09_069.png b/unified_pipeline/data/raw/signet/train/069/09_069.png new file mode 100644 index 0000000000000000000000000000000000000000..90e0fc4e02fcf0734fdf611cffcca2051586d855 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/09_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:339e4ca4996d3ae5c0d0b4649ab5d73dfc70147b27b5c153e6bcc591fa86264b +size 277019 diff --git a/unified_pipeline/data/raw/signet/train/069/10_069.png b/unified_pipeline/data/raw/signet/train/069/10_069.png new file mode 100644 index 0000000000000000000000000000000000000000..35b56423854f41ce03c86580fda9ebe6e180a072 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/10_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a542f8c4df11cd1d6a69fbd3644b2bf7c77675fefa979bb70e16ceb66b2dba83 +size 311023 diff --git a/unified_pipeline/data/raw/signet/train/069/11_069.png b/unified_pipeline/data/raw/signet/train/069/11_069.png new file mode 100644 index 0000000000000000000000000000000000000000..54bb0bc3c5cd46efde798b4e0c586b8bfa93af0e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/11_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:895f5d0e677544b247ff483880c4e9b82825ca04957f079ea90265435fee3962 +size 348348 diff --git a/unified_pipeline/data/raw/signet/train/069/12_069.png b/unified_pipeline/data/raw/signet/train/069/12_069.png new file mode 100644 index 0000000000000000000000000000000000000000..5b06924d127dac9372c3fc011f2404ce6234fb2f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069/12_069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1be2529b56dbefba75b7dfcf8ad7808942dbeb174c7266749b3e4d9de965b44b +size 297990 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/01_0106069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/01_0106069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9f230c0d00e6e8aea3b9a9ea4179921be0131c58 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/01_0106069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7496e1a00abf89d988953c946bf004947ccd7bf76b3f6582e060f7b6228766e +size 225982 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/01_0108069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/01_0108069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e407bf22d19b47a945b8fc3d2f286e402bd096d1 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/01_0108069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cd7bb520ed9b3c50b12252d97a29bcfd83727f6e0450b08cf14caa1d297485c +size 201643 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/01_0111069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/01_0111069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..5e11227d2844f29d6179feb2e6ebbfb96ff62c49 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/01_0111069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e1cf8bfb6ecb7fc4f722838e9a8956702169837e29d4d2fc329d84ee4211c39 +size 165724 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/02_0106069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/02_0106069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..634d5c880458c8710397d6a3a49fc8011dbe393f --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/02_0106069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7c884ea7ad6cc7c5e2d8c0221fa89ce2f4cf4e05b099df0031f9bb1d4729d5d +size 201958 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/02_0108069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/02_0108069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..8972ffd3eed02381475480a12b9faeeb478e6316 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/02_0108069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b728a10664eb9398165e3bd7c8f6a9e5c09404d51783ce6f8ddce6ff0ff54f2d +size 212717 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/02_0111069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/02_0111069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..352edf0d2c384d3f796f6de63d33cd6f8ab1f2ff --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/02_0111069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59276008a604a1452698eb45ca5b61d1cc2290b1533e06ff4e5a9021245947c5 +size 158327 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/03_0106069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/03_0106069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..3dd4c83199d5f63335425002f4ba0a30bc43dcdf --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/03_0106069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d0fd73ababfd06fa2329bd1f3e8fd89d546c3c35828ac0547132dcfff3aca9f +size 226276 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/03_0108069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/03_0108069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..a0a79ca2a85a62ff49f427d96d2e2db3f79834dd --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/03_0108069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a412a4cea1b7b84259fbfa47936f4a4aa42d2df5d1954eeed7aac480fadd71a +size 251974 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/03_0111069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/03_0111069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..125a1c305cac86c5fdf3d30ccb2653f517c1302e --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/03_0111069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e96eff4ff1badd213407683fb1670c60117a4aa365235f2bd4faf488dbe8276d +size 182676 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/04_0106069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/04_0106069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..1e1db76f52c306471b735060faadedead6a4c908 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/04_0106069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e05af9cb11b47ca98436a8f5e9931c02493a1b10a1732dadbb4155f458d02db6 +size 203974 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/04_0108069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/04_0108069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..5c6102b6a4c0798956e1d2ccef52f7be6d484f95 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/04_0108069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc975c8dc7aa4f1a602ef65fb5c597ba9b10c89ee8398824e53370bdcfd54220 +size 255984 diff --git a/unified_pipeline/data/raw/signet/train/069_forg/04_0111069.PNG b/unified_pipeline/data/raw/signet/train/069_forg/04_0111069.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9f5a7aaf86938b252772a66047f3c0a8fec673f2 --- /dev/null +++ b/unified_pipeline/data/raw/signet/train/069_forg/04_0111069.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3404f9db5e36d0401d34807a4b75f9009731c69fd5719160155a043052e164fa +size 184846 diff --git a/unified_pipeline/data/showcase_dataset/SHOWCASE_INDEX.csv b/unified_pipeline/data/showcase_dataset/SHOWCASE_INDEX.csv new file mode 100644 index 0000000000000000000000000000000000000000..2948e5379f16e4fc75bc4d3f6873d4d450010662 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/SHOWCASE_INDEX.csv @@ -0,0 +1,25 @@ +case_id,case_type,description,template_provider,template_id,verdict,reject_code,signer_name,signer_script,difficulty_tier,difficulty_score,validation_passed +case_001_1a_scanned,1a,Correct signer signs normally,scanned,bank_doc_08,PASS,,John Thompson,latin,hard,0.7126,True +case_002_1a_procedural,1a,Correct signer signs normally,procedural,proc_doc_04,PASS,,Sandra Harris,hindi,easy,0.3262,True +case_003_1b_scanned,1b,"Correct signer, natural pen variation applied",scanned,bank_doc_09,PASS,,Nancy Ivanov,bengali,hard,0.7614,True +case_004_1b_procedural,1b,"Correct signer, natural pen variation applied",procedural,proc_doc_04,PASS,,Donald Ali,latin,hard,0.7185,True +case_005_1c_scanned,1c,"Multi-signer form, both sign correctly",scanned,bank_doc_04,PASS,,Patricia Baker,hindi,medium,0.523,True +case_006_1c_procedural,1c,"Multi-signer form, both sign correctly",procedural,proc_doc_07,PASS,,Edward Williams,bengali,medium,0.6441,True +case_007_1d_scanned,1d,"Correct signer, scan degraded to test robustness",scanned,bank_doc_09,PASS,,Hans Anderson,hindi,hard,0.7572,True +case_008_1d_procedural,1d,"Correct signer, scan degraded to test robustness",procedural,proc_doc_15,PASS,,Scott Wright,hindi,medium,0.4664,True +case_009_2a_scanned,2a,Box left completely blank,scanned,bank_doc_07,REJECT,NO_SIGNATURE,Scott Martinez,latin,hard,0.7592,True +case_010_2a_procedural,2a,Box left completely blank,procedural,proc_doc_06,REJECT,NO_SIGNATURE,David Adams,bengali,hard,1.0,True +case_011_2b_scanned,2b,"Printed/typed name only, no handwriting",scanned,bank_doc_08,REJECT,NO_SIGNATURE,Emily Davis,hindi,medium,0.4785,True +case_012_2b_procedural,2b,"Printed/typed name only, no handwriting",procedural,proc_doc_04,REJECT,NO_SIGNATURE,Mei Young,latin,easy,0.3214,True +case_013_3a_scanned,3a,Wrong real person signs the box,scanned,bank_doc_05,REJECT,IDENTITY_MISMATCH,Margaret Mitchell,latin,hard,0.7691,True +case_014_3a_procedural,3a,Wrong real person signs the box,procedural,proc_doc_12,REJECT,IDENTITY_MISMATCH,Samantha Adams,bengali,easy,0.305,True +case_015_3b_scanned,3b,Skilled-forgery sample used,scanned,bank_doc_06,REJECT,IDENTITY_MISMATCH,Linda Nguyen,hindi,hard,0.666,True +case_016_3b_procedural,3b,Skilled-forgery sample used,procedural,proc_doc_12,REJECT,IDENTITY_MISMATCH,Fatima Lee,bengali,medium,0.5829,True +case_017_4a_scanned,4a,"Authorized signer, but wrong role's box",scanned,bank_doc_09,REJECT,ROLE_MISMATCH,Sandra Harris,latin,medium,0.6322,True +case_018_4a_procedural,4a,"Authorized signer, but wrong role's box",procedural,proc_doc_11,REJECT,ROLE_MISMATCH,Emily Jones,bengali,medium,0.6246,True +case_019_4b_scanned,4b,Signer from a different form entirely,scanned,bank_doc_08,REJECT,ROLE_MISMATCH,Stephanie Martinez,hindi,hard,0.6823,True +case_020_4b_procedural,4b,Signer from a different form entirely,procedural,proc_doc_09,REJECT,ROLE_MISMATCH,Amanda Suzuki,bengali,easy,0.2546,True +case_021_5a_scanned,5a,Required signer's box left empty,scanned,bank_doc_05,REJECT,REQUIRED_SIGNER_ABSENT,Carlos Wilson,latin,hard,1.0,True +case_022_5a_procedural,5a,Required signer's box left empty,procedural,proc_doc_09,REJECT,REQUIRED_SIGNER_ABSENT,Jennifer White,latin,hard,1.0,True +case_023_5b_scanned,5b,Someone else signs in the required signer's place,scanned,bank_doc_09,REJECT,REQUIRED_SIGNER_ABSENT,Stephen King,bengali,hard,0.7202,True +case_024_5b_procedural,5b,Someone else signs in the required signer's place,procedural,proc_doc_11,REJECT,REQUIRED_SIGNER_ABSENT,Noah Hill,latin,medium,0.629,True diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_001_1a_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_001_1a_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..40886eebba057bdcc9ed833bf30653011a29dd4f --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_001_1a_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:438790b07e3c6bba608ccc8195352c4f4f00c915164a780dc3ad77dc821671b2 +size 6538562 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_002_1a_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_002_1a_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..35a92b18221382464a008adb4e9b900d4813ee14 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_002_1a_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71e784f4009cccadf7c3ba1e7b60a4e5e2ef5dd25946674a1378596c562ede62 +size 14542387 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_003_1b_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_003_1b_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..d8f746d8b1f12c3394efc06688a65f54a39d739d --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_003_1b_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7006ffef5e6fff570fd9f64b6e9e027fd2b06663f4996515feb60de9c9adf5e8 +size 2273775 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_004_1b_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_004_1b_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..78011cc20eb76d66ba2019e1b1242df51a257466 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_004_1b_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bde10294fd4221a43cbaa5a202acf6f0391c0a590a25c5708a58e4c3ba951bfb +size 10047014 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_005_1c_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_005_1c_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..eef6ab62be80f09ca8d1edf00ed50aece04b6428 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_005_1c_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3689b4ec4e75c7fa5630f6fa8cfec1047b640701330ef6dac3b9b74f6df9c6d +size 2205661 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_006_1c_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_006_1c_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..1854331f93ed36b5305d59dd9dab188189aa6ede --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_006_1c_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70087e0745f497031ab7902029296ffb31f62bed9024f2cfe6ca81dc49aa1508 +size 11561453 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_007_1d_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_007_1d_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..b2fbe5b0d1934468ddc1f80a78f5150f62f54783 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_007_1d_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a21ad7224fbd775db9bb5ff7d2894c1fc68f04236d33c58af2128e019d771e0e +size 2441962 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_008_1d_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_008_1d_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..96dde6cab8377c3072202de5cb5fa30e26155e71 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_008_1d_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e3a1cc9d68b4cb5486f755dd267e783a249f9a5d187d2ad45087039ee5b81ab +size 10514261 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_009_2a_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_009_2a_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..c4b280c38d841a77f9c5169680b087ab7b19e76b --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_009_2a_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe3bac094cad8f367845a70a0b3303bcf91f08bc972100b69c9f328472592ae0 +size 5490361 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_010_2a_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_010_2a_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..ec50da80bae8e250460101a866b3ff2f3362cc59 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_010_2a_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fd7e47675895356f1120c595a931071bb4d463dcebf17bfafcf5a1d69f4fa4e +size 10486449 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_011_2b_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_011_2b_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..44004c98c5acbcab8b986014eed136989cfae566 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_011_2b_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98985ac86d2f1e35b6bc3482366873a5d44149d1bfda324a371757459ab2ae8f +size 4833764 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_012_2b_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_012_2b_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..e5a76ae9b81cfaf290e0597a2c3a104ce6e52569 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_012_2b_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e325adc568ce02c831c5b18080442321f8286e8e7fcc3ec3057c4a5d3dca2ea +size 14611407 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_013_3a_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_013_3a_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..cfcc2528f5bfe1c6b345545a5b9bfb2305b777d9 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_013_3a_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9220bf74587d65b68b6fc18b6b077b6e435678ad41415809c2720e0b7658173f +size 2695849 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_014_3a_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_014_3a_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..1b427498d2862a55e131705add7519060cd745e5 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_014_3a_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b400c32fbd1aada2d5325cc144eb842a77a33b4627a9f9eaec49cfe8a61be480 +size 13723714 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_015_3b_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_015_3b_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..89ee8c5b2e220d7fa4b06f10239c6572f5a1dbe3 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_015_3b_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa79d4334ccb7f3d13c1bd13e35fa088bc89ade6484beea74a362348547c4439 +size 2493011 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_016_3b_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_016_3b_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..b18c5f6efb61f131552c80a82183bc07bc810a10 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_016_3b_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66e32311ebfa6f20eaa03d4bfe0f547249cf65a48eb0c5b727b8e5b0fb35edae +size 9375343 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_017_4a_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_017_4a_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..2fa97e73191ffa8af74650e0b806b5e19a7396c8 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_017_4a_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7dcb0b4602d646bbe475c684d53007cebf130b19a988b3b873872684a1736d5 +size 2234315 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_018_4a_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_018_4a_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..0edaf589080ad7c28c9c4ad1c8824d7dbddd3a5d --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_018_4a_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:459d4922ff7e0b49d8d4d73d40c7570b8a7b1999939b3c6181b2126106e1e63f +size 14847354 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_019_4b_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_019_4b_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..f8e816a50a903803342e0f3c2e76012a96da8324 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_019_4b_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8c595858a7fa648dd206a38c75e9c389975227d20fbccc84304c1add1bb4a26 +size 6393379 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_020_4b_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_020_4b_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..82c793fc6a61d96d597464be7dc358d3854da6bd --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_020_4b_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb734cc03a3fe5b042d0f863d6bae249a5037b062e5ee551142698335926ff72 +size 14367620 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_021_5a_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_021_5a_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..cb77b301caf2730cb4bac358a84e8c58fd43fa36 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_021_5a_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed75e5c0a1ea124b45eb5ed0e0e0b78da568204dc6275b897dcc964a0db0ddfa +size 2770024 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_022_5a_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_022_5a_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..3beb6e77103c21845d04b8f939ae6e7436fc9b4b --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_022_5a_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:799b069a886f3926565f6059c46e045bbcfd13dd2ca630236fe0d5f094c19547 +size 14672598 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_023_5b_scanned_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_023_5b_scanned_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..0ad58ed0b89dffcb2e5ab756fb2c9bea340d23ee --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_023_5b_scanned_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f65dad793faee3d02779b6653b6963ca7e2607b69bcceeeaa7f19f4e081ee818 +size 2195975 diff --git a/unified_pipeline/data/showcase_dataset/before_after/case_024_5b_procedural_before_after.png b/unified_pipeline/data/showcase_dataset/before_after/case_024_5b_procedural_before_after.png new file mode 100644 index 0000000000000000000000000000000000000000..97340dd198aff44663249e8771375efc584257b8 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/before_after/case_024_5b_procedural_before_after.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e870ee82332e07ff69c0b894b010813019b658833953086eaae73d176463bb64 +size 14873267 diff --git a/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..70cfd908101b5c7c4b3490da3126068d9384f34c --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4693cb94125c2015007a486ac00416530184091040f21d72869ed4a875e8e3b +size 3309922 diff --git a/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..7f53dd591ef0eac75fc496a307787a4dba4aea38 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_ground_truth.json @@ -0,0 +1,753 @@ +{ + "case_id": "case_001_1a_scanned", + "expected_verdict": "PASS", + "expected_reject_code": null, + "document_id": "case_000001", + "template": "bank_doc_08", + "template_kind": "scanned", + "document_type": "bank_account_verification_mandate", + "page": 1, + "page_size": [ + 1024, + 1536 + ], + "dpi": 300, + "signer": { + "name": "John Thompson", + "role": "Bank Manager" + }, + "signature_source": { + "signature_id": "signet_000698", + "path": "data/raw/signet/train/009/009_24.PNG", + "signer_group": "signet_0009", + "source_type": "genuine", + "script": "latin" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -3.39, + "scale_factor": 0.9073, + "anchor_point": "center", + "margin_target_box": [ + 685.0, + 568.0, + 935.0, + 590.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8419, + "blur_sigma": 0.531, + "feather_px": 0.8655, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.7126, + "tier": "hard", + "misplacement": 0.8976, + "whitespace": 0.5126, + "faintness": 0.6502, + "smallness": 0.986, + "low_texture": 0.1797 + }, + "quality": { + "degraded": false, + "case_type": "1a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "1a", + "verdict": "PASS", + "reject_code": null, + "target_box_id": "sig2", + "expected_signer_group": "signet_0009", + "expected_signer_script": "latin", + "actual_signer_group": "signet_0009", + "actual_source_type": "genuine", + "actual_signer_script": "latin", + "companion": null + }, + "augmentation": { + "rotate_deg": -3.39, + "scale_factor": 0.9073, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.0, + "blur_sigma": 0.531, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3946862778, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 2522813244, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1506400430, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3568125756, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 4101928056, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 1480471534, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4174271986, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 2595317075, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3298199712, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1046792379, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 775489957, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 4176617545, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3548083946, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 1812057404, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 2338949290, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2608462890, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4015077707, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2048276598, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1109886136, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 2866917226, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 685.0, + 568.0, + 935.0, + 590.0 + ], + "signature_bbox_px": [ + 793.0, + 572.0, + 831.0, + 595.0 + ], + "target_box": [ + 685.0, + 568.0, + 935.0, + 590.0 + ], + "actual_box": [ + 793.0, + 572.0, + 831.0, + 595.0 + ], + "search_box_px": [ + 666.25, + 562.0, + 953.75, + 596.0 + ], + "signature_polygon_px": [ + [ + 830, + 574 + ], + [ + 828, + 587 + ], + [ + 826, + 589 + ], + [ + 803, + 594 + ], + [ + 793, + 588 + ], + [ + 797, + 574 + ], + [ + 827, + 572 + ] + ], + "convex_hull_px": [ + [ + 830, + 574 + ], + [ + 828, + 587 + ], + [ + 826, + 589 + ], + [ + 803, + 594 + ], + [ + 793, + 588 + ], + [ + 797, + 574 + ], + [ + 827, + 572 + ] + ], + "display_contours": [ + [ + [ + 824.0, + 586.0 + ], + [ + 823.0, + 587.0 + ], + [ + 820.0, + 587.0 + ], + [ + 819.0, + 588.0 + ], + [ + 819.0, + 589.0 + ], + [ + 820.0, + 590.0 + ], + [ + 826.0, + 590.0 + ], + [ + 829.0, + 587.0 + ], + [ + 828.0, + 586.0 + ] + ], + [ + [ + 821.0, + 571.0 + ], + [ + 820.0, + 572.0 + ], + [ + 816.0, + 572.0 + ], + [ + 815.0, + 573.0 + ], + [ + 814.0, + 573.0 + ], + [ + 813.0, + 574.0 + ], + [ + 812.0, + 574.0 + ], + [ + 811.0, + 575.0 + ], + [ + 809.0, + 575.0 + ], + [ + 808.0, + 576.0 + ], + [ + 807.0, + 575.0 + ], + [ + 807.0, + 574.0 + ], + [ + 805.0, + 572.0 + ], + [ + 803.0, + 572.0 + ], + [ + 802.0, + 573.0 + ], + [ + 801.0, + 573.0 + ], + [ + 800.0, + 574.0 + ], + [ + 799.0, + 574.0 + ], + [ + 798.0, + 573.0 + ], + [ + 797.0, + 573.0 + ], + [ + 795.0, + 575.0 + ], + [ + 795.0, + 582.0 + ], + [ + 794.0, + 583.0 + ], + [ + 794.0, + 584.0 + ], + [ + 795.0, + 585.0 + ], + [ + 794.0, + 586.0 + ], + [ + 793.0, + 586.0 + ], + [ + 792.0, + 587.0 + ], + [ + 792.0, + 588.0 + ], + [ + 793.0, + 589.0 + ], + [ + 797.0, + 589.0 + ], + [ + 799.0, + 587.0 + ], + [ + 798.0, + 586.0 + ], + [ + 798.0, + 585.0 + ], + [ + 799.0, + 584.0 + ], + [ + 801.0, + 584.0 + ], + [ + 802.0, + 585.0 + ], + [ + 809.0, + 585.0 + ], + [ + 810.0, + 586.0 + ], + [ + 809.0, + 587.0 + ], + [ + 807.0, + 587.0 + ], + [ + 806.0, + 588.0 + ], + [ + 806.0, + 589.0 + ], + [ + 805.0, + 590.0 + ], + [ + 804.0, + 589.0 + ], + [ + 803.0, + 589.0 + ], + [ + 802.0, + 590.0 + ], + [ + 802.0, + 591.0 + ], + [ + 801.0, + 592.0 + ], + [ + 801.0, + 593.0 + ], + [ + 803.0, + 595.0 + ], + [ + 805.0, + 595.0 + ], + [ + 806.0, + 594.0 + ], + [ + 810.0, + 594.0 + ], + [ + 811.0, + 593.0 + ], + [ + 815.0, + 593.0 + ], + [ + 817.0, + 591.0 + ], + [ + 816.0, + 590.0 + ], + [ + 813.0, + 590.0 + ], + [ + 812.0, + 589.0 + ], + [ + 814.0, + 587.0 + ], + [ + 813.0, + 586.0 + ], + [ + 812.0, + 586.0 + ], + [ + 811.0, + 585.0 + ], + [ + 812.0, + 584.0 + ], + [ + 823.0, + 584.0 + ], + [ + 825.0, + 582.0 + ], + [ + 826.0, + 582.0 + ], + [ + 828.0, + 580.0 + ], + [ + 828.0, + 577.0 + ], + [ + 831.0, + 574.0 + ], + [ + 829.0, + 572.0 + ], + [ + 828.0, + 572.0 + ], + [ + 827.0, + 571.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 811.62, + 583.08 + ], + "size": [ + 37.72, + 21.08 + ], + "angle_deg": -3.18 + }, + "centroid_px": [ + 811.53, + 580.73 + ], + "orientation_deg": -10.01, + "ink_pixels": 426, + "detected": true + }, + "metrics": { + "box_coverage_pct": 10.334, + "containment_pct": 91.894, + "ink_fill_ratio": 0.4874, + "polygon_overlap_proxy": 0.1024, + "distance_from_box_center_px": 2.3, + "margin_utilization": 0.1992, + "mean_dark_intensity": 168.02, + "gray_stddev": 49.22, + "signature_area_ratio": 0.00056 + }, + "provenance": { + "master_seed": 1001, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "name_sig2": { + "actual_box": [ + 752.0, + 445.0, + 902.0, + 464.0 + ], + "target_box": null + }, + "name_sig1": { + "actual_box": [ + 312.0, + 370.0, + 455.0, + 389.0 + ], + "target_box": null + } + } + }, + "additional_signatures": [ + { + "box_id": "sig1", + "role": "Account Holder", + "signature_id": "bhsig260_hindi_001688", + "signer_group": "bhsig260_hindi_0005", + "source_type": "genuine", + "script": "hindi", + "ref_path": "data/raw/bhsig260_hindi/5/H-S-5-G-01.tif" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..9dcde0e842c05e38af6277b0ef282af54a8249e3 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36a8ba86150c7c6fe1a726bd600ad369bce90633afac2e588f9436e10f3a0fde +size 148109 diff --git a/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_sig1_signature.png b/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_sig1_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..ab52821af4beb4e55e479de72e69be5c5f00912a --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_001_1a_scanned/case_001_1a_scanned_sig1_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b9711e8493d16a832906a309f43fe67866c5a5d0888b63f22a72f4ed90ccb42 +size 3372 diff --git a/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..ec475e993442f58765dcfbe3340c7d6bb1c44419 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce0048f5569c2181ee41dc27fb623e9b299bdd128b0d4c1db95c606b525fa887 +size 7162431 diff --git a/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..30714584b34a78153140b73b4805e60124c1ca29 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_ground_truth.json @@ -0,0 +1,2211 @@ +{ + "case_id": "case_002_1a_procedural", + "expected_verdict": "PASS", + "expected_reject_code": null, + "document_id": "case_000001", + "template": "proc_doc_04", + "template_kind": "procedural", + "document_type": "Loan Agreement", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Sandra Harris", + "role": "Primary Debtor" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_001112", + "path": "data/raw/bhsig260_hindi/17/H-S-017-G-01.tif", + "signer_group": "bhsig260_hindi_0017", + "source_type": "genuine", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 2.906, + "scale_factor": 0.7836, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": true, + "misplacement_dx": 45.41, + "misplacement_dy": 19.67 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.9195, + "blur_sigma": 0.1567, + "feather_px": 0.7287, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.3262, + "tier": "easy", + "misplacement": 0.4741, + "whitespace": 0.7948, + "faintness": 0.0, + "smallness": 0.0, + "low_texture": 0.0133 + }, + "quality": { + "degraded": false, + "case_type": "1a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "1a", + "verdict": "PASS", + "reject_code": null, + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_hindi_0017", + "expected_signer_script": "hindi", + "actual_signer_group": "bhsig260_hindi_0017", + "actual_source_type": "genuine", + "actual_signer_script": "hindi", + "companion": null + }, + "augmentation": { + "rotate_deg": 2.906, + "scale_factor": 0.7836, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": -1.718, + "discoloration_strength": 0.2, + "blur_sigma": 0.1567, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 2735628727, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 3517569097, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2853144402, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2743603819, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2447381597, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.3, + "seed": 3057068250, + "params": { + "sigma": 0.53 + } + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 264120302, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 1255156382, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2018338688, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 88638159, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1560934515, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 1239212653, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 468501382, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 1041611606, + "params": { + "resolved_tone": "yellow" + } + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 2858039758, + "params": { + "tilt_deg": -1.718 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1254585474, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1534191909, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 1474551045, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 945154117, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 3023542570, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 85.0, + 1908.0, + 315.0, + 1966.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 85.0, + 1908.0, + 315.0, + 1966.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 314, + 1965 + ], + [ + 89, + 1965 + ], + [ + 85, + 1937 + ], + [ + 173, + 1908 + ], + [ + 284, + 1908 + ], + [ + 314, + 1944 + ] + ], + "convex_hull_px": [ + [ + 314, + 1965 + ], + [ + 89, + 1965 + ], + [ + 85, + 1937 + ], + [ + 173, + 1908 + ], + [ + 284, + 1908 + ], + [ + 314, + 1944 + ] + ], + "display_contours": [ + [ + [ + 89.0, + 1964.0 + ], + [ + 88.0, + 1965.0 + ], + [ + 93.0, + 1965.0 + ], + [ + 92.0, + 1964.0 + ] + ], + [ + [ + 218.0, + 1961.0 + ], + [ + 217.0, + 1962.0 + ], + [ + 217.0, + 1965.0 + ], + [ + 221.0, + 1965.0 + ], + [ + 222.0, + 1964.0 + ], + [ + 222.0, + 1962.0 + ], + [ + 221.0, + 1961.0 + ] + ], + [ + [ + 297.0, + 1960.0 + ], + [ + 296.0, + 1961.0 + ], + [ + 296.0, + 1965.0 + ], + [ + 314.0, + 1965.0 + ], + [ + 314.0, + 1964.0 + ], + [ + 313.0, + 1963.0 + ], + [ + 313.0, + 1962.0 + ], + [ + 312.0, + 1961.0 + ], + [ + 309.0, + 1961.0 + ], + [ + 307.0, + 1963.0 + ], + [ + 305.0, + 1961.0 + ], + [ + 300.0, + 1961.0 + ], + [ + 299.0, + 1960.0 + ] + ], + [ + [ + 286.0, + 1960.0 + ], + [ + 284.0, + 1962.0 + ], + [ + 284.0, + 1963.0 + ], + [ + 283.0, + 1964.0 + ], + [ + 283.0, + 1965.0 + ], + [ + 292.0, + 1965.0 + ], + [ + 292.0, + 1963.0 + ], + [ + 291.0, + 1962.0 + ], + [ + 291.0, + 1961.0 + ], + [ + 290.0, + 1960.0 + ] + ], + [ + [ + 258.0, + 1959.0 + ], + [ + 257.0, + 1960.0 + ], + [ + 257.0, + 1965.0 + ], + [ + 280.0, + 1965.0 + ], + [ + 280.0, + 1961.0 + ], + [ + 279.0, + 1960.0 + ], + [ + 274.0, + 1960.0 + ], + [ + 273.0, + 1961.0 + ], + [ + 273.0, + 1962.0 + ], + [ + 272.0, + 1963.0 + ], + [ + 269.0, + 1960.0 + ], + [ + 265.0, + 1960.0 + ], + [ + 264.0, + 1961.0 + ], + [ + 264.0, + 1964.0 + ], + [ + 263.0, + 1965.0 + ], + [ + 262.0, + 1964.0 + ], + [ + 262.0, + 1961.0 + ], + [ + 260.0, + 1959.0 + ] + ], + [ + [ + 231.0, + 1958.0 + ], + [ + 230.0, + 1959.0 + ], + [ + 230.0, + 1965.0 + ], + [ + 254.0, + 1965.0 + ], + [ + 254.0, + 1964.0 + ], + [ + 255.0, + 1963.0 + ], + [ + 255.0, + 1962.0 + ], + [ + 252.0, + 1959.0 + ], + [ + 244.0, + 1959.0 + ], + [ + 243.0, + 1960.0 + ], + [ + 243.0, + 1961.0 + ], + [ + 242.0, + 1962.0 + ], + [ + 239.0, + 1959.0 + ], + [ + 235.0, + 1959.0 + ], + [ + 234.0, + 1958.0 + ] + ], + [ + [ + 206.0, + 1958.0 + ], + [ + 205.0, + 1959.0 + ], + [ + 205.0, + 1965.0 + ], + [ + 215.0, + 1965.0 + ], + [ + 215.0, + 1964.0 + ], + [ + 213.0, + 1962.0 + ], + [ + 216.0, + 1959.0 + ], + [ + 215.0, + 1958.0 + ] + ], + [ + [ + 185.0, + 1957.0 + ], + [ + 184.0, + 1958.0 + ], + [ + 184.0, + 1962.0 + ], + [ + 183.0, + 1963.0 + ], + [ + 183.0, + 1965.0 + ], + [ + 201.0, + 1965.0 + ], + [ + 201.0, + 1964.0 + ], + [ + 203.0, + 1962.0 + ], + [ + 203.0, + 1961.0 + ], + [ + 200.0, + 1958.0 + ], + [ + 198.0, + 1958.0 + ], + [ + 197.0, + 1957.0 + ], + [ + 192.0, + 1957.0 + ], + [ + 191.0, + 1958.0 + ], + [ + 191.0, + 1964.0 + ], + [ + 190.0, + 1965.0 + ], + [ + 189.0, + 1964.0 + ], + [ + 189.0, + 1958.0 + ], + [ + 188.0, + 1957.0 + ] + ], + [ + [ + 103.0, + 1955.0 + ], + [ + 101.0, + 1957.0 + ], + [ + 101.0, + 1961.0 + ], + [ + 104.0, + 1964.0 + ], + [ + 106.0, + 1964.0 + ], + [ + 107.0, + 1965.0 + ], + [ + 119.0, + 1965.0 + ], + [ + 120.0, + 1964.0 + ], + [ + 121.0, + 1965.0 + ], + [ + 126.0, + 1965.0 + ], + [ + 126.0, + 1960.0 + ], + [ + 127.0, + 1959.0 + ], + [ + 130.0, + 1959.0 + ], + [ + 131.0, + 1960.0 + ], + [ + 130.0, + 1961.0 + ], + [ + 129.0, + 1961.0 + ], + [ + 128.0, + 1962.0 + ], + [ + 128.0, + 1964.0 + ], + [ + 129.0, + 1965.0 + ], + [ + 163.0, + 1965.0 + ], + [ + 163.0, + 1963.0 + ], + [ + 162.0, + 1962.0 + ], + [ + 162.0, + 1961.0 + ], + [ + 163.0, + 1960.0 + ], + [ + 166.0, + 1960.0 + ], + [ + 167.0, + 1961.0 + ], + [ + 166.0, + 1962.0 + ], + [ + 166.0, + 1965.0 + ], + [ + 172.0, + 1965.0 + ], + [ + 172.0, + 1961.0 + ], + [ + 173.0, + 1960.0 + ], + [ + 174.0, + 1960.0 + ], + [ + 175.0, + 1959.0 + ], + [ + 176.0, + 1960.0 + ], + [ + 176.0, + 1962.0 + ], + [ + 175.0, + 1963.0 + ], + [ + 175.0, + 1965.0 + ], + [ + 181.0, + 1965.0 + ], + [ + 181.0, + 1958.0 + ], + [ + 180.0, + 1957.0 + ], + [ + 177.0, + 1957.0 + ], + [ + 176.0, + 1958.0 + ], + [ + 175.0, + 1957.0 + ], + [ + 166.0, + 1957.0 + ], + [ + 165.0, + 1956.0 + ], + [ + 163.0, + 1956.0 + ], + [ + 161.0, + 1958.0 + ], + [ + 159.0, + 1956.0 + ], + [ + 155.0, + 1956.0 + ], + [ + 154.0, + 1957.0 + ], + [ + 154.0, + 1958.0 + ], + [ + 153.0, + 1959.0 + ], + [ + 153.0, + 1961.0 + ], + [ + 152.0, + 1962.0 + ], + [ + 152.0, + 1963.0 + ], + [ + 151.0, + 1964.0 + ], + [ + 150.0, + 1963.0 + ], + [ + 150.0, + 1957.0 + ], + [ + 149.0, + 1956.0 + ], + [ + 146.0, + 1956.0 + ], + [ + 145.0, + 1957.0 + ], + [ + 145.0, + 1959.0 + ], + [ + 144.0, + 1960.0 + ], + [ + 143.0, + 1959.0 + ], + [ + 143.0, + 1958.0 + ], + [ + 141.0, + 1956.0 + ], + [ + 137.0, + 1956.0 + ], + [ + 136.0, + 1957.0 + ], + [ + 136.0, + 1962.0 + ], + [ + 135.0, + 1963.0 + ], + [ + 134.0, + 1962.0 + ], + [ + 133.0, + 1962.0 + ], + [ + 132.0, + 1961.0 + ], + [ + 132.0, + 1960.0 + ], + [ + 134.0, + 1958.0 + ], + [ + 134.0, + 1957.0 + ], + [ + 133.0, + 1956.0 + ], + [ + 132.0, + 1956.0 + ], + [ + 131.0, + 1955.0 + ], + [ + 125.0, + 1955.0 + ], + [ + 124.0, + 1956.0 + ], + [ + 123.0, + 1956.0 + ], + [ + 121.0, + 1958.0 + ], + [ + 121.0, + 1960.0 + ], + [ + 120.0, + 1961.0 + ], + [ + 119.0, + 1960.0 + ], + [ + 119.0, + 1956.0 + ], + [ + 118.0, + 1955.0 + ], + [ + 115.0, + 1955.0 + ], + [ + 114.0, + 1956.0 + ], + [ + 114.0, + 1962.0 + ], + [ + 113.0, + 1963.0 + ], + [ + 111.0, + 1961.0 + ], + [ + 110.0, + 1961.0 + ], + [ + 109.0, + 1960.0 + ], + [ + 112.0, + 1957.0 + ], + [ + 112.0, + 1956.0 + ], + [ + 111.0, + 1955.0 + ] + ], + [ + [ + 267.0, + 1919.0 + ], + [ + 266.0, + 1920.0 + ], + [ + 266.0, + 1921.0 + ], + [ + 263.0, + 1924.0 + ], + [ + 263.0, + 1929.0 + ], + [ + 266.0, + 1932.0 + ], + [ + 269.0, + 1932.0 + ], + [ + 272.0, + 1929.0 + ], + [ + 272.0, + 1926.0 + ], + [ + 273.0, + 1925.0 + ], + [ + 275.0, + 1927.0 + ], + [ + 275.0, + 1930.0 + ], + [ + 276.0, + 1931.0 + ], + [ + 277.0, + 1931.0 + ], + [ + 278.0, + 1930.0 + ], + [ + 279.0, + 1930.0 + ], + [ + 280.0, + 1931.0 + ], + [ + 280.0, + 1932.0 + ], + [ + 281.0, + 1933.0 + ], + [ + 282.0, + 1933.0 + ], + [ + 284.0, + 1931.0 + ], + [ + 284.0, + 1924.0 + ], + [ + 283.0, + 1923.0 + ], + [ + 283.0, + 1922.0 + ], + [ + 284.0, + 1921.0 + ], + [ + 284.0, + 1920.0 + ], + [ + 283.0, + 1919.0 + ], + [ + 274.0, + 1919.0 + ], + [ + 272.0, + 1921.0 + ], + [ + 270.0, + 1919.0 + ] + ], + [ + [ + 245.0, + 1915.0 + ], + [ + 243.0, + 1917.0 + ], + [ + 243.0, + 1920.0 + ], + [ + 245.0, + 1922.0 + ], + [ + 244.0, + 1923.0 + ], + [ + 244.0, + 1926.0 + ], + [ + 246.0, + 1928.0 + ], + [ + 251.0, + 1928.0 + ], + [ + 254.0, + 1931.0 + ], + [ + 256.0, + 1929.0 + ], + [ + 257.0, + 1929.0 + ], + [ + 258.0, + 1930.0 + ], + [ + 260.0, + 1928.0 + ], + [ + 260.0, + 1927.0 + ], + [ + 261.0, + 1926.0 + ], + [ + 261.0, + 1920.0 + ], + [ + 260.0, + 1919.0 + ], + [ + 257.0, + 1919.0 + ], + [ + 256.0, + 1920.0 + ], + [ + 255.0, + 1920.0 + ], + [ + 254.0, + 1919.0 + ], + [ + 254.0, + 1918.0 + ], + [ + 253.0, + 1917.0 + ], + [ + 252.0, + 1917.0 + ], + [ + 250.0, + 1915.0 + ] + ], + [ + [ + 200.0, + 1909.0 + ], + [ + 199.0, + 1910.0 + ], + [ + 199.0, + 1912.0 + ], + [ + 201.0, + 1914.0 + ], + [ + 202.0, + 1914.0 + ], + [ + 205.0, + 1911.0 + ], + [ + 203.0, + 1909.0 + ] + ], + [ + [ + 281.0, + 1907.0 + ], + [ + 280.0, + 1908.0 + ], + [ + 279.0, + 1908.0 + ], + [ + 275.0, + 1912.0 + ], + [ + 275.0, + 1913.0 + ], + [ + 274.0, + 1914.0 + ], + [ + 274.0, + 1916.0 + ], + [ + 275.0, + 1917.0 + ], + [ + 277.0, + 1917.0 + ], + [ + 279.0, + 1915.0 + ], + [ + 279.0, + 1914.0 + ], + [ + 280.0, + 1913.0 + ], + [ + 282.0, + 1913.0 + ], + [ + 285.0, + 1910.0 + ], + [ + 285.0, + 1908.0 + ], + [ + 284.0, + 1907.0 + ] + ], + [ + [ + 173.0, + 1907.0 + ], + [ + 172.0, + 1908.0 + ], + [ + 169.0, + 1908.0 + ], + [ + 168.0, + 1909.0 + ], + [ + 167.0, + 1909.0 + ], + [ + 166.0, + 1910.0 + ], + [ + 165.0, + 1910.0 + ], + [ + 159.0, + 1916.0 + ], + [ + 158.0, + 1916.0 + ], + [ + 156.0, + 1918.0 + ], + [ + 156.0, + 1920.0 + ], + [ + 157.0, + 1921.0 + ], + [ + 158.0, + 1921.0 + ], + [ + 159.0, + 1922.0 + ], + [ + 159.0, + 1925.0 + ], + [ + 158.0, + 1926.0 + ], + [ + 158.0, + 1929.0 + ], + [ + 159.0, + 1930.0 + ], + [ + 159.0, + 1931.0 + ], + [ + 160.0, + 1932.0 + ], + [ + 161.0, + 1932.0 + ], + [ + 163.0, + 1930.0 + ], + [ + 163.0, + 1922.0 + ], + [ + 164.0, + 1921.0 + ], + [ + 168.0, + 1921.0 + ], + [ + 169.0, + 1922.0 + ], + [ + 169.0, + 1924.0 + ], + [ + 170.0, + 1925.0 + ], + [ + 170.0, + 1927.0 + ], + [ + 171.0, + 1928.0 + ], + [ + 171.0, + 1930.0 + ], + [ + 168.0, + 1933.0 + ], + [ + 168.0, + 1935.0 + ], + [ + 169.0, + 1936.0 + ], + [ + 169.0, + 1937.0 + ], + [ + 168.0, + 1938.0 + ], + [ + 137.0, + 1938.0 + ], + [ + 136.0, + 1937.0 + ], + [ + 104.0, + 1937.0 + ], + [ + 103.0, + 1936.0 + ], + [ + 85.0, + 1936.0 + ], + [ + 85.0, + 1940.0 + ], + [ + 106.0, + 1940.0 + ], + [ + 107.0, + 1941.0 + ], + [ + 138.0, + 1941.0 + ], + [ + 139.0, + 1942.0 + ], + [ + 173.0, + 1942.0 + ], + [ + 174.0, + 1943.0 + ], + [ + 206.0, + 1943.0 + ], + [ + 207.0, + 1944.0 + ], + [ + 240.0, + 1944.0 + ], + [ + 241.0, + 1945.0 + ], + [ + 271.0, + 1945.0 + ], + [ + 272.0, + 1946.0 + ], + [ + 274.0, + 1946.0 + ], + [ + 275.0, + 1945.0 + ], + [ + 276.0, + 1945.0 + ], + [ + 277.0, + 1946.0 + ], + [ + 307.0, + 1946.0 + ], + [ + 308.0, + 1947.0 + ], + [ + 314.0, + 1947.0 + ], + [ + 314.0, + 1943.0 + ], + [ + 305.0, + 1943.0 + ], + [ + 304.0, + 1942.0 + ], + [ + 270.0, + 1942.0 + ], + [ + 269.0, + 1941.0 + ], + [ + 237.0, + 1941.0 + ], + [ + 236.0, + 1940.0 + ], + [ + 203.0, + 1940.0 + ], + [ + 202.0, + 1939.0 + ], + [ + 176.0, + 1939.0 + ], + [ + 174.0, + 1937.0 + ], + [ + 175.0, + 1936.0 + ], + [ + 175.0, + 1935.0 + ], + [ + 174.0, + 1934.0 + ], + [ + 174.0, + 1933.0 + ], + [ + 176.0, + 1931.0 + ], + [ + 177.0, + 1931.0 + ], + [ + 179.0, + 1933.0 + ], + [ + 182.0, + 1930.0 + ], + [ + 182.0, + 1921.0 + ], + [ + 183.0, + 1920.0 + ], + [ + 184.0, + 1920.0 + ], + [ + 186.0, + 1922.0 + ], + [ + 186.0, + 1924.0 + ], + [ + 185.0, + 1925.0 + ], + [ + 185.0, + 1926.0 + ], + [ + 186.0, + 1927.0 + ], + [ + 185.0, + 1928.0 + ], + [ + 185.0, + 1929.0 + ], + [ + 186.0, + 1930.0 + ], + [ + 187.0, + 1930.0 + ], + [ + 188.0, + 1931.0 + ], + [ + 189.0, + 1931.0 + ], + [ + 190.0, + 1930.0 + ], + [ + 191.0, + 1930.0 + ], + [ + 193.0, + 1928.0 + ], + [ + 192.0, + 1927.0 + ], + [ + 193.0, + 1926.0 + ], + [ + 195.0, + 1926.0 + ], + [ + 196.0, + 1927.0 + ], + [ + 195.0, + 1928.0 + ], + [ + 195.0, + 1929.0 + ], + [ + 194.0, + 1930.0 + ], + [ + 194.0, + 1932.0 + ], + [ + 196.0, + 1934.0 + ], + [ + 199.0, + 1931.0 + ], + [ + 199.0, + 1928.0 + ], + [ + 198.0, + 1927.0 + ], + [ + 198.0, + 1926.0 + ], + [ + 199.0, + 1925.0 + ], + [ + 200.0, + 1925.0 + ], + [ + 202.0, + 1927.0 + ], + [ + 201.0, + 1928.0 + ], + [ + 201.0, + 1929.0 + ], + [ + 202.0, + 1930.0 + ], + [ + 206.0, + 1930.0 + ], + [ + 208.0, + 1932.0 + ], + [ + 210.0, + 1930.0 + ], + [ + 210.0, + 1929.0 + ], + [ + 211.0, + 1928.0 + ], + [ + 210.0, + 1927.0 + ], + [ + 211.0, + 1926.0 + ], + [ + 212.0, + 1926.0 + ], + [ + 213.0, + 1927.0 + ], + [ + 213.0, + 1931.0 + ], + [ + 214.0, + 1932.0 + ], + [ + 218.0, + 1928.0 + ], + [ + 220.0, + 1930.0 + ], + [ + 220.0, + 1934.0 + ], + [ + 221.0, + 1935.0 + ], + [ + 224.0, + 1932.0 + ], + [ + 224.0, + 1918.0 + ], + [ + 222.0, + 1916.0 + ], + [ + 180.0, + 1916.0 + ], + [ + 179.0, + 1915.0 + ], + [ + 180.0, + 1914.0 + ], + [ + 180.0, + 1913.0 + ], + [ + 181.0, + 1912.0 + ], + [ + 181.0, + 1911.0 + ], + [ + 180.0, + 1910.0 + ], + [ + 180.0, + 1909.0 + ], + [ + 179.0, + 1908.0 + ], + [ + 177.0, + 1908.0 + ], + [ + 176.0, + 1907.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 199.5, + 1936.5 + ], + "size": [ + 57.0, + 229.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 202.79, + 1940.25 + ], + "orientation_deg": -2.36, + "ink_pixels": 2737, + "detected": true + }, + "metrics": { + "box_coverage_pct": 58.552, + "containment_pct": 83.788, + "ink_fill_ratio": 0.2052, + "polygon_overlap_proxy": 0.5259, + "distance_from_box_center_px": 20.45, + "margin_utilization": 1.1466, + "mean_dark_intensity": 138.11, + "gray_stddev": 59.2, + "signature_area_ratio": 0.00357 + }, + "provenance": { + "master_seed": 1002, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 426, + 527 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1005, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 887, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + }, + "additional_signatures": [ + { + "box_id": "sig2", + "role": "Corporate Guarantor", + "signature_id": "bhsig260_bengali_000426", + "signer_group": "bhsig260_bengali_0032", + "source_type": "genuine", + "script": "bengali", + "ref_path": "data/raw/bhsig260_bengali/32/B-S-32-G-03.tif" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..3c2f1a5d4cd00c5a03f5948c4ef7e3ca9c4498c6 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edcf6d991b878d16697d4f7123d939aa1c8838679b7deed57d8faa5527a20aba +size 3887 diff --git a/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_sig2_signature.png b/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_sig2_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..2103266d6ef1a502995562f86541b4428fc642cc --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_002_1a_procedural/case_002_1a_procedural_sig2_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:096f260df1d69afc098d9627584fd69c5899dcb4a204a3a351e22550f01becfb +size 5788 diff --git a/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..f829a4a654701bde9668e1b761a5b56b191db540 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c862c1bc638e5f663bd23d0fb8414d2267a8b885453ac85adfafe045b6421c49 +size 671197 diff --git a/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..6aba41f372901ebb52135b0050383f716ba58a1d --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_ground_truth.json @@ -0,0 +1,1090 @@ +{ + "case_id": "case_003_1b_scanned", + "expected_verdict": "PASS", + "expected_reject_code": null, + "document_id": "case_000001", + "template": "bank_doc_09", + "template_kind": "scanned", + "document_type": "corporate_account_opening_mandate", + "page": 1, + "page_size": [ + 687, + 1024 + ], + "dpi": 300, + "signer": { + "name": "Nancy Ivanov", + "role": "Second Authorised Signatory" + }, + "signature_source": { + "signature_id": "bhsig260_bengali_001134", + "path": "data/raw/bhsig260_bengali/72/B-S-72-G-07.tif", + "signer_group": "bhsig260_bengali_0072", + "source_type": "genuine", + "script": "bengali" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 2.38, + "scale_factor": 0.9418, + "anchor_point": "center", + "margin_target_box": [ + 350.0, + 797.0, + 575.0, + 832.0 + ], + "misplaced": true, + "misplacement_dx": 27.08, + "misplacement_dy": 17.14 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.7941, + "blur_sigma": 0.1953, + "feather_px": 0.6772, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.7614, + "tier": "hard", + "misplacement": 0.911, + "whitespace": 0.6717, + "faintness": 0.7274, + "smallness": 0.951, + "low_texture": 0.2005 + }, + "quality": { + "degraded": false, + "case_type": "1b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "1b", + "verdict": "PASS", + "reject_code": null, + "target_box_id": "sig2", + "expected_signer_group": "bhsig260_bengali_0072", + "expected_signer_script": "bengali", + "actual_signer_group": "bhsig260_bengali_0072", + "actual_source_type": "genuine", + "actual_signer_script": "bengali", + "companion": null + }, + "augmentation": { + "rotate_deg": 2.38, + "scale_factor": 0.9418, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.0, + "blur_sigma": 0.1953, + "jpeg_quality": 69, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 2377408784, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 2652967198, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1327249589, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3047952843, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1909478467, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 1214869271, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 123729075, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 2471577092, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2637059529, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 73113111, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2441497709, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 1505035684, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3961626024, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 3663729009, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 36954855, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1572858198, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1300845687, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2020296847, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1469041678, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 887209718, + "params": { + "quality": 69 + } + } + } + }, + "geometry": { + "box_px": [ + 350.0, + 797.0, + 575.0, + 832.0 + ], + "signature_bbox_px": [ + 444.0, + 823.0, + 536.0, + 838.0 + ], + "target_box": [ + 350.0, + 797.0, + 575.0, + 832.0 + ], + "actual_box": [ + 444.0, + 823.0, + 536.0, + 838.0 + ], + "search_box_px": [ + 333.125, + 791.0, + 591.875, + 838.0 + ], + "signature_polygon_px": [ + [ + 535, + 824 + ], + [ + 527, + 837 + ], + [ + 452, + 837 + ], + [ + 444, + 830 + ], + [ + 444, + 827 + ], + [ + 454, + 823 + ] + ], + "convex_hull_px": [ + [ + 535, + 824 + ], + [ + 527, + 837 + ], + [ + 452, + 837 + ], + [ + 444, + 830 + ], + [ + 444, + 827 + ], + [ + 454, + 823 + ] + ], + "display_contours": [ + [ + [ + 514.0, + 835.0 + ], + [ + 513.0, + 836.0 + ], + [ + 513.0, + 837.0 + ], + [ + 515.0, + 837.0 + ], + [ + 516.0, + 836.0 + ], + [ + 515.0, + 835.0 + ] + ], + [ + [ + 490.0, + 834.0 + ], + [ + 489.0, + 835.0 + ], + [ + 489.0, + 836.0 + ], + [ + 488.0, + 837.0 + ], + [ + 491.0, + 837.0 + ], + [ + 492.0, + 836.0 + ], + [ + 493.0, + 837.0 + ], + [ + 495.0, + 837.0 + ], + [ + 496.0, + 836.0 + ], + [ + 496.0, + 835.0 + ], + [ + 495.0, + 834.0 + ], + [ + 493.0, + 834.0 + ], + [ + 492.0, + 835.0 + ], + [ + 491.0, + 834.0 + ] + ], + [ + [ + 513.0, + 822.0 + ], + [ + 512.0, + 823.0 + ], + [ + 505.0, + 823.0 + ], + [ + 504.0, + 824.0 + ], + [ + 504.0, + 825.0 + ], + [ + 505.0, + 826.0 + ], + [ + 508.0, + 826.0 + ], + [ + 509.0, + 827.0 + ], + [ + 509.0, + 830.0 + ], + [ + 508.0, + 831.0 + ], + [ + 508.0, + 832.0 + ], + [ + 509.0, + 833.0 + ], + [ + 513.0, + 833.0 + ], + [ + 514.0, + 832.0 + ], + [ + 515.0, + 832.0 + ], + [ + 516.0, + 833.0 + ], + [ + 518.0, + 831.0 + ], + [ + 517.0, + 830.0 + ], + [ + 518.0, + 829.0 + ], + [ + 521.0, + 829.0 + ], + [ + 522.0, + 830.0 + ], + [ + 522.0, + 831.0 + ], + [ + 524.0, + 833.0 + ], + [ + 523.0, + 834.0 + ], + [ + 520.0, + 834.0 + ], + [ + 519.0, + 835.0 + ], + [ + 519.0, + 837.0 + ], + [ + 528.0, + 837.0 + ], + [ + 527.0, + 836.0 + ], + [ + 527.0, + 835.0 + ], + [ + 526.0, + 834.0 + ], + [ + 525.0, + 834.0 + ], + [ + 524.0, + 833.0 + ], + [ + 526.0, + 831.0 + ], + [ + 526.0, + 827.0 + ], + [ + 525.0, + 826.0 + ], + [ + 525.0, + 825.0 + ], + [ + 526.0, + 824.0 + ], + [ + 528.0, + 826.0 + ], + [ + 529.0, + 826.0 + ], + [ + 530.0, + 825.0 + ], + [ + 531.0, + 825.0 + ], + [ + 532.0, + 826.0 + ], + [ + 534.0, + 826.0 + ], + [ + 536.0, + 824.0 + ], + [ + 535.0, + 823.0 + ], + [ + 532.0, + 823.0 + ], + [ + 531.0, + 824.0 + ], + [ + 530.0, + 823.0 + ], + [ + 528.0, + 823.0 + ], + [ + 527.0, + 824.0 + ], + [ + 526.0, + 824.0 + ], + [ + 525.0, + 823.0 + ], + [ + 522.0, + 823.0 + ], + [ + 521.0, + 824.0 + ], + [ + 521.0, + 825.0 + ], + [ + 520.0, + 826.0 + ], + [ + 518.0, + 826.0 + ], + [ + 517.0, + 825.0 + ], + [ + 519.0, + 823.0 + ], + [ + 518.0, + 822.0 + ] + ], + [ + [ + 454.0, + 822.0 + ], + [ + 453.0, + 823.0 + ], + [ + 453.0, + 824.0 + ], + [ + 454.0, + 825.0 + ], + [ + 453.0, + 826.0 + ], + [ + 444.0, + 826.0 + ], + [ + 443.0, + 827.0 + ], + [ + 443.0, + 830.0 + ], + [ + 444.0, + 831.0 + ], + [ + 445.0, + 830.0 + ], + [ + 446.0, + 831.0 + ], + [ + 446.0, + 832.0 + ], + [ + 447.0, + 833.0 + ], + [ + 450.0, + 833.0 + ], + [ + 453.0, + 830.0 + ], + [ + 454.0, + 831.0 + ], + [ + 456.0, + 829.0 + ], + [ + 457.0, + 830.0 + ], + [ + 457.0, + 832.0 + ], + [ + 458.0, + 833.0 + ], + [ + 457.0, + 834.0 + ], + [ + 454.0, + 834.0 + ], + [ + 451.0, + 837.0 + ], + [ + 456.0, + 837.0 + ], + [ + 458.0, + 835.0 + ], + [ + 457.0, + 834.0 + ], + [ + 460.0, + 831.0 + ], + [ + 460.0, + 829.0 + ], + [ + 459.0, + 828.0 + ], + [ + 460.0, + 827.0 + ], + [ + 461.0, + 827.0 + ], + [ + 462.0, + 828.0 + ], + [ + 462.0, + 832.0 + ], + [ + 463.0, + 833.0 + ], + [ + 465.0, + 831.0 + ], + [ + 466.0, + 831.0 + ], + [ + 468.0, + 833.0 + ], + [ + 469.0, + 832.0 + ], + [ + 470.0, + 832.0 + ], + [ + 471.0, + 833.0 + ], + [ + 473.0, + 831.0 + ], + [ + 475.0, + 833.0 + ], + [ + 475.0, + 834.0 + ], + [ + 474.0, + 835.0 + ], + [ + 474.0, + 837.0 + ], + [ + 483.0, + 837.0 + ], + [ + 482.0, + 836.0 + ], + [ + 482.0, + 835.0 + ], + [ + 481.0, + 834.0 + ], + [ + 480.0, + 834.0 + ], + [ + 479.0, + 833.0 + ], + [ + 484.0, + 828.0 + ], + [ + 485.0, + 828.0 + ], + [ + 486.0, + 829.0 + ], + [ + 486.0, + 831.0 + ], + [ + 487.0, + 832.0 + ], + [ + 488.0, + 831.0 + ], + [ + 489.0, + 831.0 + ], + [ + 490.0, + 832.0 + ], + [ + 492.0, + 830.0 + ], + [ + 493.0, + 830.0 + ], + [ + 495.0, + 832.0 + ], + [ + 497.0, + 830.0 + ], + [ + 497.0, + 826.0 + ], + [ + 498.0, + 825.0 + ], + [ + 496.0, + 823.0 + ], + [ + 494.0, + 823.0 + ], + [ + 493.0, + 824.0 + ], + [ + 492.0, + 823.0 + ], + [ + 488.0, + 823.0 + ], + [ + 486.0, + 825.0 + ], + [ + 481.0, + 825.0 + ], + [ + 480.0, + 824.0 + ], + [ + 479.0, + 824.0 + ], + [ + 478.0, + 825.0 + ], + [ + 477.0, + 825.0 + ], + [ + 476.0, + 824.0 + ], + [ + 472.0, + 824.0 + ], + [ + 471.0, + 825.0 + ], + [ + 471.0, + 826.0 + ], + [ + 470.0, + 827.0 + ], + [ + 469.0, + 826.0 + ], + [ + 469.0, + 825.0 + ], + [ + 468.0, + 824.0 + ], + [ + 464.0, + 824.0 + ], + [ + 463.0, + 823.0 + ], + [ + 460.0, + 823.0 + ], + [ + 458.0, + 825.0 + ], + [ + 455.0, + 822.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 489.5, + 830.0 + ], + "size": [ + 14.0, + 91.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 485.89, + 828.84 + ], + "orientation_deg": -0.09, + "ink_pixels": 453, + "detected": true + }, + "metrics": { + "box_coverage_pct": 9.353, + "containment_pct": 64.978, + "ink_fill_ratio": 0.3283, + "polygon_overlap_proxy": 0.089, + "distance_from_box_center_px": 27.44, + "margin_utilization": 0.6667, + "mean_dark_intensity": 177.29, + "gray_stddev": 47.97, + "signature_area_ratio": 0.00196 + }, + "provenance": { + "master_seed": 1003, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "company_name": { + "actual_box": [ + 80.0, + 169.0, + 277.0, + 183.0 + ], + "target_box": null + } + } + }, + "additional_signatures": [ + { + "box_id": "sig1", + "role": "First Authorised Signatory", + "signature_id": "signet_001659", + "signer_group": "signet_0049", + "source_type": "genuine", + "script": "latin", + "ref_path": "data/raw/signet/train/049/11_049.png" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..d474e74dc6609de5f4a1de865883303e0c6d7b46 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1423535c4164fb6f1d4ae2048299516a2b6b360811bf2a54a1e3f02e590bd98a +size 5675 diff --git a/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_sig1_signature.png b/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_sig1_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..a0da5562176d25666b4f7895b126a5a3d192e387 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_003_1b_scanned/case_003_1b_scanned_sig1_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6887fdc1ba121cf88b9939a4496eac257b95372c10ca0865107a4c8f9048541c +size 179206 diff --git a/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..fb56f368d8d426267834fa4ecf9ad2db1a2a0d3a --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64a8269c4ec3a174c10d606a53843a768faf632314577c4d878188da13ac535e +size 2270634 diff --git a/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..3d42e3d852136f9de52ccd0d6f73300710bceba9 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_ground_truth.json @@ -0,0 +1,785 @@ +{ + "case_id": "case_004_1b_procedural", + "expected_verdict": "PASS", + "expected_reject_code": null, + "document_id": "case_000001", + "template": "proc_doc_04", + "template_kind": "procedural", + "document_type": "Loan Agreement", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Donald Ali", + "role": "Primary Debtor" + }, + "signature_source": { + "signature_id": "sig_001585", + "path": "data/raw/cedar/genuine/original_20_10.png", + "signer_group": "signer_0020", + "source_type": "genuine", + "script": "latin" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 1.069, + "scale_factor": 0.791, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8653, + "blur_sigma": 0.3959, + "feather_px": 0.6447, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.7185, + "tier": "hard", + "misplacement": 0.9767, + "whitespace": 0.6466, + "faintness": 0.2736, + "smallness": 0.93, + "low_texture": 0.5308 + }, + "quality": { + "degraded": false, + "case_type": "1b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "1b", + "verdict": "PASS", + "reject_code": null, + "target_box_id": "sig1", + "expected_signer_group": "signer_0020", + "expected_signer_script": "latin", + "actual_signer_group": "signer_0020", + "actual_source_type": "genuine", + "actual_signer_script": "latin", + "companion": null + }, + "augmentation": { + "rotate_deg": 1.069, + "scale_factor": 0.791, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 2.737, + "discoloration_strength": 0.0, + "blur_sigma": 0.3959, + "jpeg_quality": 41, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 1487541828, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 3699110109, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3996808421, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 4286035151, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 567283435, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.3, + "seed": 529657621, + "params": { + "sigma": 0.53 + } + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 697207773, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 198515825, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 910198265, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3207403976, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2501817233, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 1736761613, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3678401168, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 1709460556, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 717092111, + "params": { + "tilt_deg": 2.737 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 461098773, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 999349552, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 3773836948, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 4075665065, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 3820724574, + "params": { + "quality": 41 + } + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 231.0, + 1947.0, + 273.0, + 1966.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 231.0, + 1947.0, + 273.0, + 1966.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 272, + 1958 + ], + [ + 270, + 1960 + ], + [ + 247, + 1965 + ], + [ + 233, + 1965 + ], + [ + 231, + 1961 + ], + [ + 233, + 1952 + ], + [ + 259, + 1947 + ] + ], + "convex_hull_px": [ + [ + 272, + 1958 + ], + [ + 270, + 1960 + ], + [ + 247, + 1965 + ], + [ + 233, + 1965 + ], + [ + 231, + 1961 + ], + [ + 233, + 1952 + ], + [ + 259, + 1947 + ] + ], + "display_contours": [ + [ + [ + 259.0, + 1946.0 + ], + [ + 258.0, + 1947.0 + ], + [ + 258.0, + 1949.0 + ], + [ + 259.0, + 1950.0 + ], + [ + 259.0, + 1952.0 + ], + [ + 258.0, + 1953.0 + ], + [ + 257.0, + 1953.0 + ], + [ + 256.0, + 1954.0 + ], + [ + 255.0, + 1954.0 + ], + [ + 254.0, + 1953.0 + ], + [ + 250.0, + 1953.0 + ], + [ + 249.0, + 1954.0 + ], + [ + 248.0, + 1954.0 + ], + [ + 247.0, + 1955.0 + ], + [ + 246.0, + 1955.0 + ], + [ + 245.0, + 1954.0 + ], + [ + 246.0, + 1953.0 + ], + [ + 245.0, + 1952.0 + ], + [ + 244.0, + 1952.0 + ], + [ + 243.0, + 1953.0 + ], + [ + 243.0, + 1954.0 + ], + [ + 244.0, + 1955.0 + ], + [ + 244.0, + 1958.0 + ], + [ + 243.0, + 1959.0 + ], + [ + 242.0, + 1958.0 + ], + [ + 242.0, + 1957.0 + ], + [ + 241.0, + 1956.0 + ], + [ + 240.0, + 1956.0 + ], + [ + 239.0, + 1957.0 + ], + [ + 237.0, + 1957.0 + ], + [ + 236.0, + 1956.0 + ], + [ + 236.0, + 1952.0 + ], + [ + 235.0, + 1951.0 + ], + [ + 233.0, + 1951.0 + ], + [ + 232.0, + 1952.0 + ], + [ + 232.0, + 1954.0 + ], + [ + 231.0, + 1955.0 + ], + [ + 231.0, + 1956.0 + ], + [ + 230.0, + 1957.0 + ], + [ + 230.0, + 1961.0 + ], + [ + 231.0, + 1962.0 + ], + [ + 231.0, + 1964.0 + ], + [ + 232.0, + 1965.0 + ], + [ + 252.0, + 1965.0 + ], + [ + 253.0, + 1964.0 + ], + [ + 255.0, + 1964.0 + ], + [ + 256.0, + 1963.0 + ], + [ + 260.0, + 1963.0 + ], + [ + 261.0, + 1962.0 + ], + [ + 264.0, + 1962.0 + ], + [ + 265.0, + 1961.0 + ], + [ + 270.0, + 1961.0 + ], + [ + 273.0, + 1958.0 + ], + [ + 272.0, + 1957.0 + ], + [ + 269.0, + 1957.0 + ], + [ + 268.0, + 1958.0 + ], + [ + 263.0, + 1958.0 + ], + [ + 262.0, + 1957.0 + ], + [ + 263.0, + 1956.0 + ], + [ + 263.0, + 1951.0 + ], + [ + 262.0, + 1950.0 + ], + [ + 262.0, + 1948.0 + ], + [ + 260.0, + 1946.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 250.9, + 1956.4 + ], + "size": [ + 40.83, + 15.41 + ], + "angle_deg": -10.89 + }, + "centroid_px": [ + 248.46, + 1958.59 + ], + "orientation_deg": -6.41, + "ink_pixels": 282, + "detected": true + }, + "metrics": { + "box_coverage_pct": 2.348, + "containment_pct": 73.794, + "ink_fill_ratio": 0.3534, + "polygon_overlap_proxy": 0.0233, + "distance_from_box_center_px": 61.95, + "margin_utilization": 0.7883, + "mean_dark_intensity": 193.85, + "gray_stddev": 28.15, + "signature_area_ratio": 0.00021 + }, + "provenance": { + "master_seed": 1004, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 417, + 527 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1004, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 887, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + }, + "additional_signatures": [ + { + "box_id": "sig2", + "role": "Corporate Guarantor", + "signature_id": "bhsig260_hindi_000525", + "signer_group": "bhsig260_hindi_0128", + "source_type": "genuine", + "script": "hindi", + "ref_path": "data/raw/bhsig260_hindi/128/H-S-128-G-06.tif" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..d5539aa2e0630e22783541e968afb52e5f0e520c --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35f09a88605e625c5b25cf00375890f94e9daa1cbe1b61e873ffb43a51fadb2f +size 93787 diff --git a/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_sig2_signature.png b/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_sig2_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..ff5a3f8c5aa4ed2c77c0dfd2cce979c61e9c6087 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_004_1b_procedural/case_004_1b_procedural_sig2_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e45285f0594a69b6c94632848c59ef4072d9d35946d1bfdd280dd4e560200d2 +size 4092 diff --git a/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..d259c141301a3abde697ddda3365bedbe649d135 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20996b5d26b2319d7cfa21fd0be40a62dc5098e5a1964c18565e491160160f39 +size 129977 diff --git a/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..2b4ce7d0932df44f08a9d7ca6d8d729eef205da5 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32a5ad1bad50f47674744648865662fbf367c2634d89117458f79131b2c20256 +size 853743 diff --git a/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..a7005e82cc347cc5b986a53b799c7d129582a841 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_ground_truth.json @@ -0,0 +1,1790 @@ +{ + "case_id": "case_005_1c_scanned", + "expected_verdict": "PASS", + "expected_reject_code": null, + "document_id": "case_000001", + "template": "bank_doc_04", + "template_kind": "scanned", + "document_type": "corporate_bank_loan_agreement", + "page": 1, + "page_size": [ + 1024, + 559 + ], + "dpi": 300, + "signer": { + "name": "Patricia Baker", + "role": "Borrower (Authorized Signatory)" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_001999", + "path": "data/raw/bhsig260_hindi/67/H-S-67-G-08.tif", + "signer_group": "bhsig260_hindi_0067", + "source_type": "genuine", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -3.628, + "scale_factor": 0.9109, + "anchor_point": "center", + "margin_target_box": [ + 580.0, + 435.0, + 750.0, + 475.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.7643, + "blur_sigma": 0.2396, + "feather_px": 0.8338, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.523, + "tier": "medium", + "misplacement": 0.2734, + "whitespace": 0.862, + "faintness": 0.6743, + "smallness": 0.5775, + "low_texture": 0.3345 + }, + "quality": { + "degraded": false, + "case_type": "1c", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "1c", + "verdict": "PASS", + "reject_code": null, + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_hindi_0067", + "expected_signer_script": "hindi", + "actual_signer_group": "bhsig260_hindi_0067", + "actual_source_type": "genuine", + "actual_signer_script": "hindi", + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "signet_0006", + "script": "latin", + "matches_expected": true, + "ref_path": "data\\raw\\signet\\train\\006\\006_04.PNG" + } + }, + "augmentation": { + "rotate_deg": -3.628, + "scale_factor": 0.9109, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "corner", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.553, + "discoloration_strength": 0.0, + "blur_sigma": 0.2396, + "jpeg_quality": 82, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 2660003074, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 4270498797, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 490902205, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1437887694, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3726677478, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 302964519, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2990746393, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 1483812202, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3663749923, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3785198786, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2405869330, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 3946425504, + "params": { + "resolved_type": "corner", + "corner": "top_left" + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2181472543, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 2741880652, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 17342459, + "params": { + "tilt_deg": 0.553 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2849766421, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 624921754, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2250445862, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2817536729, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 2202020226, + "params": { + "quality": 82 + } + } + } + }, + "geometry": { + "box_px": [ + 580.0, + 435.0, + 750.0, + 475.0 + ], + "signature_bbox_px": [ + 567.0, + 429.0, + 753.0, + 481.0 + ], + "target_box": [ + 580.0, + 435.0, + 750.0, + 475.0 + ], + "actual_box": [ + 567.0, + 429.0, + 753.0, + 481.0 + ], + "search_box_px": [ + 567.25, + 429.0, + 762.75, + 481.0 + ], + "signature_polygon_px": [ + [ + 752, + 480 + ], + [ + 567, + 480 + ], + [ + 570, + 429 + ], + [ + 752, + 429 + ] + ], + "convex_hull_px": [ + [ + 752, + 480 + ], + [ + 567, + 480 + ], + [ + 570, + 429 + ], + [ + 752, + 429 + ] + ], + "display_contours": [ + [ + [ + 582.0, + 474.0 + ], + [ + 581.0, + 475.0 + ], + [ + 581.0, + 478.0 + ], + [ + 580.0, + 479.0 + ], + [ + 579.0, + 478.0 + ], + [ + 576.0, + 478.0 + ], + [ + 575.0, + 479.0 + ], + [ + 574.0, + 478.0 + ], + [ + 571.0, + 478.0 + ], + [ + 570.0, + 479.0 + ], + [ + 569.0, + 478.0 + ], + [ + 567.0, + 478.0 + ], + [ + 567.0, + 480.0 + ], + [ + 605.0, + 480.0 + ], + [ + 605.0, + 477.0 + ], + [ + 604.0, + 476.0 + ], + [ + 599.0, + 476.0 + ], + [ + 598.0, + 475.0 + ], + [ + 597.0, + 475.0 + ], + [ + 596.0, + 476.0 + ], + [ + 595.0, + 475.0 + ], + [ + 594.0, + 475.0 + ], + [ + 593.0, + 476.0 + ], + [ + 591.0, + 476.0 + ], + [ + 590.0, + 475.0 + ], + [ + 588.0, + 475.0 + ], + [ + 587.0, + 474.0 + ], + [ + 586.0, + 474.0 + ], + [ + 585.0, + 475.0 + ], + [ + 584.0, + 474.0 + ] + ], + [ + [ + 664.0, + 467.0 + ], + [ + 663.0, + 468.0 + ], + [ + 663.0, + 473.0 + ], + [ + 664.0, + 474.0 + ], + [ + 664.0, + 478.0 + ], + [ + 665.0, + 479.0 + ], + [ + 669.0, + 479.0 + ], + [ + 671.0, + 477.0 + ], + [ + 672.0, + 477.0 + ], + [ + 674.0, + 479.0 + ], + [ + 678.0, + 479.0 + ], + [ + 680.0, + 477.0 + ], + [ + 681.0, + 478.0 + ], + [ + 682.0, + 478.0 + ], + [ + 683.0, + 477.0 + ], + [ + 684.0, + 477.0 + ], + [ + 686.0, + 479.0 + ], + [ + 688.0, + 477.0 + ], + [ + 690.0, + 479.0 + ], + [ + 692.0, + 479.0 + ], + [ + 693.0, + 478.0 + ], + [ + 696.0, + 478.0 + ], + [ + 698.0, + 476.0 + ], + [ + 698.0, + 475.0 + ], + [ + 697.0, + 474.0 + ], + [ + 697.0, + 473.0 + ], + [ + 700.0, + 470.0 + ], + [ + 699.0, + 469.0 + ], + [ + 689.0, + 469.0 + ], + [ + 688.0, + 470.0 + ], + [ + 687.0, + 470.0 + ], + [ + 686.0, + 469.0 + ], + [ + 684.0, + 469.0 + ], + [ + 682.0, + 467.0 + ], + [ + 681.0, + 467.0 + ], + [ + 680.0, + 468.0 + ], + [ + 680.0, + 469.0 + ], + [ + 679.0, + 470.0 + ], + [ + 678.0, + 469.0 + ], + [ + 672.0, + 469.0 + ], + [ + 670.0, + 467.0 + ] + ], + [ + [ + 639.0, + 467.0 + ], + [ + 637.0, + 469.0 + ], + [ + 633.0, + 469.0 + ], + [ + 632.0, + 468.0 + ], + [ + 630.0, + 468.0 + ], + [ + 628.0, + 470.0 + ], + [ + 627.0, + 470.0 + ], + [ + 626.0, + 469.0 + ], + [ + 625.0, + 469.0 + ], + [ + 624.0, + 470.0 + ], + [ + 622.0, + 470.0 + ], + [ + 620.0, + 468.0 + ], + [ + 619.0, + 468.0 + ], + [ + 618.0, + 469.0 + ], + [ + 618.0, + 470.0 + ], + [ + 619.0, + 471.0 + ], + [ + 617.0, + 473.0 + ], + [ + 616.0, + 472.0 + ], + [ + 616.0, + 469.0 + ], + [ + 615.0, + 468.0 + ], + [ + 614.0, + 468.0 + ], + [ + 613.0, + 469.0 + ], + [ + 613.0, + 470.0 + ], + [ + 612.0, + 471.0 + ], + [ + 612.0, + 472.0 + ], + [ + 613.0, + 473.0 + ], + [ + 613.0, + 478.0 + ], + [ + 614.0, + 479.0 + ], + [ + 616.0, + 477.0 + ], + [ + 616.0, + 475.0 + ], + [ + 617.0, + 474.0 + ], + [ + 618.0, + 475.0 + ], + [ + 619.0, + 475.0 + ], + [ + 620.0, + 474.0 + ], + [ + 621.0, + 475.0 + ], + [ + 621.0, + 477.0 + ], + [ + 623.0, + 479.0 + ], + [ + 624.0, + 478.0 + ], + [ + 625.0, + 479.0 + ], + [ + 627.0, + 479.0 + ], + [ + 629.0, + 477.0 + ], + [ + 631.0, + 479.0 + ], + [ + 632.0, + 478.0 + ], + [ + 633.0, + 478.0 + ], + [ + 634.0, + 479.0 + ], + [ + 636.0, + 477.0 + ], + [ + 635.0, + 476.0 + ], + [ + 635.0, + 475.0 + ], + [ + 636.0, + 474.0 + ], + [ + 636.0, + 473.0 + ], + [ + 637.0, + 472.0 + ], + [ + 638.0, + 473.0 + ], + [ + 638.0, + 475.0 + ], + [ + 639.0, + 476.0 + ], + [ + 641.0, + 476.0 + ], + [ + 644.0, + 479.0 + ], + [ + 646.0, + 479.0 + ], + [ + 649.0, + 476.0 + ], + [ + 651.0, + 478.0 + ], + [ + 652.0, + 478.0 + ], + [ + 653.0, + 479.0 + ], + [ + 657.0, + 479.0 + ], + [ + 659.0, + 477.0 + ], + [ + 659.0, + 470.0 + ], + [ + 658.0, + 469.0 + ], + [ + 653.0, + 469.0 + ], + [ + 652.0, + 470.0 + ], + [ + 651.0, + 470.0 + ], + [ + 650.0, + 469.0 + ], + [ + 651.0, + 468.0 + ], + [ + 650.0, + 467.0 + ], + [ + 649.0, + 467.0 + ], + [ + 648.0, + 468.0 + ], + [ + 648.0, + 469.0 + ], + [ + 647.0, + 470.0 + ], + [ + 646.0, + 469.0 + ], + [ + 643.0, + 469.0 + ], + [ + 642.0, + 470.0 + ], + [ + 641.0, + 470.0 + ], + [ + 640.0, + 469.0 + ], + [ + 641.0, + 468.0 + ], + [ + 640.0, + 467.0 + ] + ], + [ + [ + 704.0, + 456.0 + ], + [ + 703.0, + 457.0 + ], + [ + 703.0, + 458.0 + ], + [ + 704.0, + 459.0 + ], + [ + 704.0, + 461.0 + ], + [ + 705.0, + 462.0 + ], + [ + 708.0, + 459.0 + ], + [ + 709.0, + 460.0 + ], + [ + 709.0, + 461.0 + ], + [ + 711.0, + 463.0 + ], + [ + 712.0, + 463.0 + ], + [ + 713.0, + 464.0 + ], + [ + 718.0, + 464.0 + ], + [ + 720.0, + 462.0 + ], + [ + 720.0, + 460.0 + ], + [ + 721.0, + 459.0 + ], + [ + 721.0, + 458.0 + ], + [ + 719.0, + 456.0 + ], + [ + 716.0, + 456.0 + ], + [ + 714.0, + 458.0 + ], + [ + 710.0, + 458.0 + ], + [ + 709.0, + 459.0 + ], + [ + 708.0, + 459.0 + ], + [ + 707.0, + 458.0 + ], + [ + 707.0, + 457.0 + ], + [ + 706.0, + 456.0 + ] + ], + [ + [ + 686.0, + 456.0 + ], + [ + 685.0, + 457.0 + ], + [ + 685.0, + 461.0 + ], + [ + 687.0, + 463.0 + ], + [ + 691.0, + 463.0 + ], + [ + 692.0, + 462.0 + ], + [ + 694.0, + 462.0 + ], + [ + 695.0, + 463.0 + ], + [ + 696.0, + 462.0 + ], + [ + 698.0, + 462.0 + ], + [ + 699.0, + 463.0 + ], + [ + 701.0, + 461.0 + ], + [ + 701.0, + 457.0 + ], + [ + 700.0, + 456.0 + ], + [ + 691.0, + 456.0 + ], + [ + 690.0, + 457.0 + ], + [ + 689.0, + 456.0 + ] + ], + [ + [ + 647.0, + 455.0 + ], + [ + 646.0, + 456.0 + ], + [ + 646.0, + 457.0 + ], + [ + 647.0, + 458.0 + ], + [ + 647.0, + 463.0 + ], + [ + 648.0, + 464.0 + ], + [ + 649.0, + 463.0 + ], + [ + 653.0, + 463.0 + ], + [ + 654.0, + 464.0 + ], + [ + 656.0, + 464.0 + ], + [ + 658.0, + 462.0 + ], + [ + 658.0, + 460.0 + ], + [ + 657.0, + 459.0 + ], + [ + 657.0, + 456.0 + ], + [ + 656.0, + 455.0 + ], + [ + 653.0, + 455.0 + ], + [ + 652.0, + 456.0 + ], + [ + 651.0, + 456.0 + ], + [ + 650.0, + 455.0 + ] + ], + [ + [ + 670.0, + 454.0 + ], + [ + 668.0, + 456.0 + ], + [ + 666.0, + 456.0 + ], + [ + 664.0, + 458.0 + ], + [ + 663.0, + 457.0 + ], + [ + 663.0, + 456.0 + ], + [ + 662.0, + 455.0 + ], + [ + 660.0, + 455.0 + ], + [ + 659.0, + 456.0 + ], + [ + 659.0, + 458.0 + ], + [ + 660.0, + 459.0 + ], + [ + 660.0, + 461.0 + ], + [ + 662.0, + 463.0 + ], + [ + 664.0, + 461.0 + ], + [ + 666.0, + 463.0 + ], + [ + 667.0, + 463.0 + ], + [ + 670.0, + 460.0 + ], + [ + 673.0, + 463.0 + ], + [ + 676.0, + 460.0 + ], + [ + 675.0, + 459.0 + ], + [ + 675.0, + 457.0 + ], + [ + 674.0, + 456.0 + ], + [ + 674.0, + 455.0 + ], + [ + 673.0, + 454.0 + ] + ], + [ + [ + 622.0, + 454.0 + ], + [ + 621.0, + 455.0 + ], + [ + 621.0, + 456.0 + ], + [ + 620.0, + 457.0 + ], + [ + 617.0, + 457.0 + ], + [ + 616.0, + 458.0 + ], + [ + 616.0, + 460.0 + ], + [ + 619.0, + 463.0 + ], + [ + 625.0, + 463.0 + ], + [ + 627.0, + 461.0 + ], + [ + 628.0, + 461.0 + ], + [ + 630.0, + 463.0 + ], + [ + 632.0, + 461.0 + ], + [ + 633.0, + 461.0 + ], + [ + 636.0, + 464.0 + ], + [ + 639.0, + 464.0 + ], + [ + 641.0, + 462.0 + ], + [ + 643.0, + 464.0 + ], + [ + 645.0, + 462.0 + ], + [ + 645.0, + 460.0 + ], + [ + 644.0, + 459.0 + ], + [ + 644.0, + 458.0 + ], + [ + 643.0, + 457.0 + ], + [ + 643.0, + 456.0 + ], + [ + 642.0, + 455.0 + ], + [ + 639.0, + 455.0 + ], + [ + 637.0, + 457.0 + ], + [ + 636.0, + 456.0 + ], + [ + 634.0, + 456.0 + ], + [ + 632.0, + 458.0 + ], + [ + 631.0, + 457.0 + ], + [ + 631.0, + 455.0 + ], + [ + 630.0, + 454.0 + ], + [ + 628.0, + 454.0 + ], + [ + 627.0, + 455.0 + ], + [ + 626.0, + 455.0 + ], + [ + 625.0, + 454.0 + ] + ], + [ + [ + 695.0, + 445.0 + ], + [ + 694.0, + 446.0 + ], + [ + 694.0, + 447.0 + ], + [ + 693.0, + 448.0 + ], + [ + 693.0, + 449.0 + ], + [ + 694.0, + 450.0 + ], + [ + 694.0, + 451.0 + ], + [ + 697.0, + 454.0 + ], + [ + 709.0, + 454.0 + ], + [ + 712.0, + 451.0 + ], + [ + 712.0, + 448.0 + ], + [ + 709.0, + 445.0 + ], + [ + 705.0, + 445.0 + ], + [ + 703.0, + 447.0 + ], + [ + 703.0, + 448.0 + ], + [ + 702.0, + 449.0 + ], + [ + 698.0, + 445.0 + ] + ], + [ + [ + 653.0, + 445.0 + ], + [ + 651.0, + 447.0 + ], + [ + 651.0, + 452.0 + ], + [ + 652.0, + 453.0 + ], + [ + 653.0, + 453.0 + ], + [ + 655.0, + 451.0 + ], + [ + 654.0, + 450.0 + ], + [ + 655.0, + 449.0 + ], + [ + 656.0, + 449.0 + ], + [ + 657.0, + 450.0 + ], + [ + 657.0, + 452.0 + ], + [ + 658.0, + 453.0 + ], + [ + 660.0, + 453.0 + ], + [ + 662.0, + 451.0 + ], + [ + 661.0, + 450.0 + ], + [ + 661.0, + 449.0 + ], + [ + 660.0, + 448.0 + ], + [ + 660.0, + 447.0 + ], + [ + 659.0, + 446.0 + ], + [ + 657.0, + 446.0 + ], + [ + 656.0, + 445.0 + ] + ], + [ + [ + 749.0, + 429.0 + ], + [ + 749.0, + 435.0 + ], + [ + 750.0, + 436.0 + ], + [ + 750.0, + 439.0 + ], + [ + 749.0, + 440.0 + ], + [ + 749.0, + 441.0 + ], + [ + 750.0, + 442.0 + ], + [ + 750.0, + 480.0 + ], + [ + 753.0, + 480.0 + ], + [ + 753.0, + 429.0 + ] + ], + [ + [ + 694.0, + 429.0 + ], + [ + 694.0, + 430.0 + ], + [ + 695.0, + 431.0 + ], + [ + 697.0, + 429.0 + ] + ], + [ + [ + 669.0, + 429.0 + ], + [ + 669.0, + 430.0 + ], + [ + 670.0, + 431.0 + ], + [ + 672.0, + 431.0 + ], + [ + 674.0, + 429.0 + ] + ], + [ + [ + 615.0, + 429.0 + ], + [ + 615.0, + 430.0 + ], + [ + 616.0, + 431.0 + ], + [ + 618.0, + 429.0 + ] + ], + [ + [ + 569.0, + 429.0 + ], + [ + 569.0, + 430.0 + ], + [ + 570.0, + 431.0 + ], + [ + 572.0, + 429.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 659.5, + 454.5 + ], + "size": [ + 51.0, + 185.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 665.49, + 464.05 + ], + "orientation_deg": -7.41, + "ink_pixels": 1335, + "detected": true + }, + "metrics": { + "box_coverage_pct": 100.0, + "containment_pct": 72.661, + "ink_fill_ratio": 0.138, + "polygon_overlap_proxy": 0.7266, + "distance_from_box_center_px": 9.06, + "margin_utilization": 1.1611, + "mean_dark_intensity": 170.92, + "gray_stddev": 39.93, + "signature_area_ratio": 0.0169 + }, + "provenance": { + "master_seed": 1005, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "name_sig1": { + "actual_box": [ + 610.0, + 468.0, + 697.0, + 479.0 + ], + "target_box": null + }, + "name_sig2": { + "actual_box": [ + 832.0, + 469.0, + 906.0, + 482.0 + ], + "target_box": null + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..1ad46b5a84dc0b487f274e1f9bb5e3b3a50b30a6 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_005_1c_scanned/case_005_1c_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e820887ad9f929ff1e6a352698667e8347688b2971dfed5815bd28331f5257ad +size 4085 diff --git a/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..986d337c77f3f7e856cd04f787ac596602a0c65c --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9e2ddd985a9bfc3538c5ca0877d005758ccf3eeb226af868230e9d2ad61649d +size 3361 diff --git a/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..fd6e480eda1b07a2ee7789cf06d968a4b8b3fac1 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:387cbd8beee945aca4d1f30b060fb5787e54bc31b7197face3d83e5477f9e82f +size 3709077 diff --git a/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..46830e9edc53b673452866c679b10a05bded4e58 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_ground_truth.json @@ -0,0 +1,1515 @@ +{ + "case_id": "case_006_1c_procedural", + "expected_verdict": "PASS", + "expected_reject_code": null, + "document_id": "case_000001", + "template": "proc_doc_07", + "template_kind": "procedural", + "document_type": "Account Opening", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Edward Williams", + "role": "Chief Executive Officer" + }, + "signature_source": { + "signature_id": "bhsig260_bengali_000270", + "path": "data/raw/bhsig260_bengali/23/B-S-23-G-07.tif", + "signer_group": "bhsig260_bengali_0023", + "source_type": "genuine", + "script": "bengali" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -3.594, + "scale_factor": 0.8974, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.7758, + "blur_sigma": 0.3475, + "feather_px": 0.796, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.6441, + "tier": "medium", + "misplacement": 0.8398, + "whitespace": 0.8072, + "faintness": 0.1767, + "smallness": 0.65, + "low_texture": 0.5592 + }, + "quality": { + "degraded": false, + "case_type": "1c", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "1c", + "verdict": "PASS", + "reject_code": null, + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_bengali_0023", + "expected_signer_script": "bengali", + "actual_signer_group": "bhsig260_bengali_0023", + "actual_source_type": "genuine", + "actual_signer_script": "bengali", + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "bhsig260_hindi_0069", + "script": "hindi", + "matches_expected": true, + "ref_path": "data\\raw\\bhsig260_hindi\\69\\H-S-69-G-08.tif" + } + }, + "augmentation": { + "rotate_deg": -3.594, + "scale_factor": 0.8974, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.0, + "blur_sigma": 0.3475, + "jpeg_quality": 64, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3534709244, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 3296978644, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1109328061, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2126468887, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 612971919, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3416060649, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3265410963, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 3842671789, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 590946984, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 329975603, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1006314186, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 1428346035, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3256931343, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 1712840847, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 668840550, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 625759411, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2704962618, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 4283800814, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1890711695, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 839117900, + "params": { + "quality": 64 + } + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 128.0, + 1905.0, + 274.0, + 1932.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 128.0, + 1905.0, + 274.0, + 1932.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 273, + 1914 + ], + [ + 257, + 1928 + ], + [ + 167, + 1931 + ], + [ + 133, + 1917 + ], + [ + 128, + 1908 + ], + [ + 224, + 1908 + ] + ], + "convex_hull_px": [ + [ + 273, + 1914 + ], + [ + 257, + 1928 + ], + [ + 167, + 1931 + ], + [ + 133, + 1917 + ], + [ + 128, + 1908 + ], + [ + 224, + 1908 + ] + ], + "display_contours": [ + [ + [ + 272.0, + 1912.0 + ], + [ + 269.0, + 1915.0 + ], + [ + 268.0, + 1915.0 + ], + [ + 267.0, + 1916.0 + ], + [ + 265.0, + 1916.0 + ], + [ + 264.0, + 1915.0 + ], + [ + 257.0, + 1915.0 + ], + [ + 254.0, + 1918.0 + ], + [ + 254.0, + 1920.0 + ], + [ + 253.0, + 1921.0 + ], + [ + 252.0, + 1921.0 + ], + [ + 251.0, + 1920.0 + ], + [ + 250.0, + 1920.0 + ], + [ + 249.0, + 1919.0 + ], + [ + 248.0, + 1919.0 + ], + [ + 247.0, + 1918.0 + ], + [ + 247.0, + 1914.0 + ], + [ + 246.0, + 1913.0 + ], + [ + 243.0, + 1913.0 + ], + [ + 242.0, + 1914.0 + ], + [ + 242.0, + 1915.0 + ], + [ + 243.0, + 1916.0 + ], + [ + 243.0, + 1917.0 + ], + [ + 244.0, + 1918.0 + ], + [ + 244.0, + 1919.0 + ], + [ + 245.0, + 1920.0 + ], + [ + 245.0, + 1921.0 + ], + [ + 246.0, + 1922.0 + ], + [ + 246.0, + 1923.0 + ], + [ + 245.0, + 1924.0 + ], + [ + 245.0, + 1926.0 + ], + [ + 246.0, + 1927.0 + ], + [ + 248.0, + 1927.0 + ], + [ + 251.0, + 1924.0 + ], + [ + 253.0, + 1926.0 + ], + [ + 254.0, + 1926.0 + ], + [ + 257.0, + 1929.0 + ], + [ + 259.0, + 1927.0 + ], + [ + 259.0, + 1926.0 + ], + [ + 258.0, + 1925.0 + ], + [ + 258.0, + 1924.0 + ], + [ + 257.0, + 1923.0 + ], + [ + 257.0, + 1920.0 + ], + [ + 258.0, + 1919.0 + ], + [ + 269.0, + 1919.0 + ], + [ + 274.0, + 1914.0 + ], + [ + 274.0, + 1913.0 + ], + [ + 273.0, + 1912.0 + ] + ], + [ + [ + 189.0, + 1912.0 + ], + [ + 188.0, + 1913.0 + ], + [ + 188.0, + 1914.0 + ], + [ + 189.0, + 1915.0 + ], + [ + 191.0, + 1913.0 + ], + [ + 190.0, + 1912.0 + ] + ], + [ + [ + 235.0, + 1909.0 + ], + [ + 234.0, + 1910.0 + ], + [ + 233.0, + 1910.0 + ], + [ + 232.0, + 1911.0 + ], + [ + 232.0, + 1912.0 + ], + [ + 230.0, + 1914.0 + ], + [ + 230.0, + 1916.0 + ], + [ + 232.0, + 1918.0 + ], + [ + 232.0, + 1923.0 + ], + [ + 234.0, + 1925.0 + ], + [ + 236.0, + 1925.0 + ], + [ + 238.0, + 1923.0 + ], + [ + 238.0, + 1922.0 + ], + [ + 237.0, + 1921.0 + ], + [ + 237.0, + 1920.0 + ], + [ + 236.0, + 1919.0 + ], + [ + 236.0, + 1917.0 + ], + [ + 235.0, + 1916.0 + ], + [ + 235.0, + 1914.0 + ], + [ + 238.0, + 1911.0 + ], + [ + 238.0, + 1910.0 + ], + [ + 237.0, + 1909.0 + ] + ], + [ + [ + 220.0, + 1907.0 + ], + [ + 218.0, + 1909.0 + ], + [ + 218.0, + 1910.0 + ], + [ + 217.0, + 1911.0 + ], + [ + 217.0, + 1912.0 + ], + [ + 218.0, + 1913.0 + ], + [ + 217.0, + 1914.0 + ], + [ + 215.0, + 1914.0 + ], + [ + 214.0, + 1913.0 + ], + [ + 214.0, + 1911.0 + ], + [ + 212.0, + 1909.0 + ], + [ + 211.0, + 1909.0 + ], + [ + 209.0, + 1911.0 + ], + [ + 209.0, + 1912.0 + ], + [ + 211.0, + 1914.0 + ], + [ + 211.0, + 1915.0 + ], + [ + 213.0, + 1917.0 + ], + [ + 213.0, + 1919.0 + ], + [ + 214.0, + 1920.0 + ], + [ + 214.0, + 1922.0 + ], + [ + 215.0, + 1923.0 + ], + [ + 216.0, + 1923.0 + ], + [ + 218.0, + 1921.0 + ], + [ + 218.0, + 1918.0 + ], + [ + 219.0, + 1917.0 + ], + [ + 220.0, + 1917.0 + ], + [ + 222.0, + 1919.0 + ], + [ + 222.0, + 1920.0 + ], + [ + 223.0, + 1921.0 + ], + [ + 223.0, + 1922.0 + ], + [ + 224.0, + 1923.0 + ], + [ + 224.0, + 1924.0 + ], + [ + 225.0, + 1925.0 + ], + [ + 227.0, + 1923.0 + ], + [ + 227.0, + 1921.0 + ], + [ + 225.0, + 1919.0 + ], + [ + 225.0, + 1918.0 + ], + [ + 224.0, + 1917.0 + ], + [ + 224.0, + 1916.0 + ], + [ + 223.0, + 1915.0 + ], + [ + 222.0, + 1915.0 + ], + [ + 220.0, + 1913.0 + ], + [ + 220.0, + 1912.0 + ], + [ + 222.0, + 1910.0 + ], + [ + 223.0, + 1910.0 + ], + [ + 224.0, + 1911.0 + ], + [ + 225.0, + 1911.0 + ], + [ + 227.0, + 1913.0 + ], + [ + 228.0, + 1913.0 + ], + [ + 230.0, + 1911.0 + ], + [ + 228.0, + 1909.0 + ], + [ + 227.0, + 1909.0 + ], + [ + 226.0, + 1908.0 + ], + [ + 225.0, + 1908.0 + ], + [ + 224.0, + 1907.0 + ] + ], + [ + [ + 156.0, + 1907.0 + ], + [ + 155.0, + 1908.0 + ], + [ + 155.0, + 1910.0 + ], + [ + 154.0, + 1911.0 + ], + [ + 154.0, + 1912.0 + ], + [ + 155.0, + 1913.0 + ], + [ + 155.0, + 1914.0 + ], + [ + 157.0, + 1916.0 + ], + [ + 160.0, + 1916.0 + ], + [ + 163.0, + 1919.0 + ], + [ + 162.0, + 1920.0 + ], + [ + 160.0, + 1920.0 + ], + [ + 157.0, + 1923.0 + ], + [ + 157.0, + 1928.0 + ], + [ + 158.0, + 1929.0 + ], + [ + 159.0, + 1928.0 + ], + [ + 162.0, + 1928.0 + ], + [ + 163.0, + 1929.0 + ], + [ + 164.0, + 1929.0 + ], + [ + 167.0, + 1932.0 + ], + [ + 169.0, + 1930.0 + ], + [ + 169.0, + 1928.0 + ], + [ + 170.0, + 1927.0 + ], + [ + 170.0, + 1925.0 + ], + [ + 171.0, + 1924.0 + ], + [ + 173.0, + 1924.0 + ], + [ + 175.0, + 1926.0 + ], + [ + 175.0, + 1927.0 + ], + [ + 176.0, + 1928.0 + ], + [ + 176.0, + 1929.0 + ], + [ + 177.0, + 1930.0 + ], + [ + 179.0, + 1928.0 + ], + [ + 179.0, + 1927.0 + ], + [ + 178.0, + 1926.0 + ], + [ + 178.0, + 1925.0 + ], + [ + 177.0, + 1924.0 + ], + [ + 177.0, + 1920.0 + ], + [ + 176.0, + 1919.0 + ], + [ + 176.0, + 1917.0 + ], + [ + 177.0, + 1916.0 + ], + [ + 180.0, + 1916.0 + ], + [ + 183.0, + 1919.0 + ], + [ + 183.0, + 1922.0 + ], + [ + 184.0, + 1923.0 + ], + [ + 184.0, + 1926.0 + ], + [ + 185.0, + 1927.0 + ], + [ + 187.0, + 1925.0 + ], + [ + 187.0, + 1921.0 + ], + [ + 186.0, + 1920.0 + ], + [ + 186.0, + 1917.0 + ], + [ + 185.0, + 1916.0 + ], + [ + 185.0, + 1915.0 + ], + [ + 184.0, + 1914.0 + ], + [ + 184.0, + 1913.0 + ], + [ + 183.0, + 1912.0 + ], + [ + 181.0, + 1912.0 + ], + [ + 180.0, + 1913.0 + ], + [ + 179.0, + 1912.0 + ], + [ + 166.0, + 1912.0 + ], + [ + 165.0, + 1913.0 + ], + [ + 165.0, + 1914.0 + ], + [ + 164.0, + 1915.0 + ], + [ + 162.0, + 1913.0 + ], + [ + 161.0, + 1913.0 + ], + [ + 160.0, + 1912.0 + ], + [ + 161.0, + 1911.0 + ], + [ + 162.0, + 1911.0 + ], + [ + 164.0, + 1909.0 + ], + [ + 163.0, + 1908.0 + ], + [ + 161.0, + 1908.0 + ], + [ + 160.0, + 1907.0 + ] + ], + [ + [ + 137.0, + 1904.0 + ], + [ + 134.0, + 1907.0 + ], + [ + 133.0, + 1907.0 + ], + [ + 132.0, + 1908.0 + ], + [ + 131.0, + 1908.0 + ], + [ + 130.0, + 1907.0 + ], + [ + 128.0, + 1907.0 + ], + [ + 127.0, + 1908.0 + ], + [ + 127.0, + 1909.0 + ], + [ + 129.0, + 1911.0 + ], + [ + 129.0, + 1914.0 + ], + [ + 133.0, + 1918.0 + ], + [ + 135.0, + 1918.0 + ], + [ + 137.0, + 1920.0 + ], + [ + 138.0, + 1920.0 + ], + [ + 139.0, + 1921.0 + ], + [ + 140.0, + 1921.0 + ], + [ + 141.0, + 1922.0 + ], + [ + 143.0, + 1920.0 + ], + [ + 143.0, + 1916.0 + ], + [ + 142.0, + 1915.0 + ], + [ + 142.0, + 1912.0 + ], + [ + 143.0, + 1911.0 + ], + [ + 144.0, + 1911.0 + ], + [ + 145.0, + 1912.0 + ], + [ + 147.0, + 1912.0 + ], + [ + 149.0, + 1914.0 + ], + [ + 149.0, + 1917.0 + ], + [ + 150.0, + 1918.0 + ], + [ + 150.0, + 1920.0 + ], + [ + 151.0, + 1921.0 + ], + [ + 153.0, + 1919.0 + ], + [ + 153.0, + 1917.0 + ], + [ + 152.0, + 1916.0 + ], + [ + 152.0, + 1912.0 + ], + [ + 151.0, + 1911.0 + ], + [ + 151.0, + 1909.0 + ], + [ + 149.0, + 1907.0 + ], + [ + 148.0, + 1907.0 + ], + [ + 146.0, + 1909.0 + ], + [ + 145.0, + 1908.0 + ], + [ + 144.0, + 1908.0 + ], + [ + 143.0, + 1907.0 + ], + [ + 142.0, + 1907.0 + ], + [ + 139.0, + 1904.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 200.19, + 1919.67 + ], + "size": [ + 25.0, + 145.12 + ], + "angle_deg": -87.98 + }, + "centroid_px": [ + 189.71, + 1916.87 + ], + "orientation_deg": 2.07, + "ink_pixels": 760, + "detected": true + }, + "metrics": { + "box_coverage_pct": 16.016, + "containment_pct": 100.0, + "ink_fill_ratio": 0.1928, + "polygon_overlap_proxy": 0.1602, + "distance_from_box_center_px": 10.75, + "margin_utilization": 0.6869, + "mean_dark_intensity": 190.36, + "gray_stddev": 26.45, + "signature_area_ratio": 0.00105 + }, + "provenance": { + "master_seed": 1006, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 412, + 527 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1006, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 885, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1447, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..86965f5ef0e64ac6cde61125adba4e6a5b0660ba --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_006_1c_procedural/case_006_1c_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38dd0a3e568b31daefcc3e69b1d57d05f453e5180c900f268ce994c4671be755 +size 4363 diff --git a/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..c434474fbb0ce531ea51d7aa0965074437757648 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6688b77b9d345e08854d3e7c9ea5e64771913e61717259c99f26fb2730127bea +size 819320 diff --git a/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..981cc33c4daffa018056ba037baa40d69b32b67c --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_ground_truth.json @@ -0,0 +1,1019 @@ +{ + "case_id": "case_007_1d_scanned", + "expected_verdict": "PASS", + "expected_reject_code": null, + "document_id": "case_000001", + "template": "bank_doc_09", + "template_kind": "scanned", + "document_type": "corporate_account_opening_mandate", + "page": 1, + "page_size": [ + 687, + 1024 + ], + "dpi": 300, + "signer": { + "name": "Hans Anderson", + "role": "First Authorised Signatory" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_002060", + "path": "data/raw/bhsig260_hindi/70/H-S-70-G-05.tif", + "signer_group": "bhsig260_hindi_0070", + "source_type": "genuine", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 1.523, + "scale_factor": 0.7987, + "anchor_point": "center", + "margin_target_box": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8698, + "blur_sigma": 0.4866, + "feather_px": 0.6788, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.7572, + "tier": "hard", + "misplacement": 0.8373, + "whitespace": 0.734, + "faintness": 0.7153, + "smallness": 0.9313, + "low_texture": 0.3465 + }, + "quality": { + "degraded": true, + "case_type": "1d", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "1d", + "verdict": "PASS", + "reject_code": null, + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_hindi_0070", + "expected_signer_script": "hindi", + "actual_signer_group": "bhsig260_hindi_0070", + "actual_source_type": "genuine", + "actual_signer_script": "hindi", + "companion": null + }, + "augmentation": { + "rotate_deg": 1.523, + "scale_factor": 0.7987, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "corner", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.2, + "blur_sigma": 0.4866, + "jpeg_quality": 70, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 1621643547, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 2565207083, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 595274431, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1243996318, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 702969812, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 2541843314, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4266195641, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 1140785494, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2518877555, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4063722016, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3182625397, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 3437399489, + "params": { + "resolved_type": "corner", + "corner": "top_right" + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1524274073, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 3207760399, + "params": { + "resolved_tone": "grey" + } + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 757093292, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 286013170, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3671938830, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 3045702630, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3900270995, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 120431610, + "params": { + "quality": 70 + } + } + } + }, + "geometry": { + "box_px": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "signature_bbox_px": [ + 157.0, + 804.0, + 249.0, + 825.0 + ], + "target_box": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "actual_box": [ + 157.0, + 804.0, + 249.0, + 825.0 + ], + "search_box_px": [ + 73.12500000000001, + 791.0, + 331.875, + 838.0 + ], + "signature_polygon_px": [ + [ + 248, + 821 + ], + [ + 161, + 824 + ], + [ + 157, + 818 + ], + [ + 159, + 815 + ], + [ + 191, + 804 + ], + [ + 225, + 804 + ] + ], + "convex_hull_px": [ + [ + 248, + 821 + ], + [ + 161, + 824 + ], + [ + 157, + 818 + ], + [ + 159, + 815 + ], + [ + 191, + 804 + ], + [ + 225, + 804 + ] + ], + "display_contours": [ + [ + [ + 176.0, + 814.0 + ], + [ + 172.0, + 818.0 + ], + [ + 172.0, + 820.0 + ], + [ + 173.0, + 821.0 + ], + [ + 174.0, + 821.0 + ], + [ + 175.0, + 822.0 + ], + [ + 176.0, + 822.0 + ], + [ + 178.0, + 824.0 + ], + [ + 180.0, + 822.0 + ], + [ + 180.0, + 821.0 + ], + [ + 179.0, + 820.0 + ], + [ + 179.0, + 816.0 + ], + [ + 180.0, + 815.0 + ], + [ + 179.0, + 814.0 + ] + ], + [ + [ + 159.0, + 814.0 + ], + [ + 158.0, + 815.0 + ], + [ + 158.0, + 816.0 + ], + [ + 156.0, + 818.0 + ], + [ + 156.0, + 820.0 + ], + [ + 161.0, + 825.0 + ], + [ + 164.0, + 825.0 + ], + [ + 165.0, + 824.0 + ], + [ + 166.0, + 824.0 + ], + [ + 167.0, + 825.0 + ], + [ + 168.0, + 824.0 + ], + [ + 169.0, + 824.0 + ], + [ + 171.0, + 822.0 + ], + [ + 170.0, + 821.0 + ], + [ + 170.0, + 819.0 + ], + [ + 169.0, + 818.0 + ], + [ + 169.0, + 815.0 + ], + [ + 168.0, + 814.0 + ], + [ + 167.0, + 814.0 + ], + [ + 164.0, + 817.0 + ], + [ + 163.0, + 816.0 + ], + [ + 163.0, + 815.0 + ], + [ + 162.0, + 814.0 + ] + ], + [ + [ + 219.0, + 803.0 + ], + [ + 218.0, + 804.0 + ], + [ + 218.0, + 805.0 + ], + [ + 217.0, + 806.0 + ], + [ + 217.0, + 810.0 + ], + [ + 218.0, + 811.0 + ], + [ + 218.0, + 812.0 + ], + [ + 219.0, + 813.0 + ], + [ + 220.0, + 813.0 + ], + [ + 221.0, + 814.0 + ], + [ + 220.0, + 815.0 + ], + [ + 220.0, + 816.0 + ], + [ + 221.0, + 817.0 + ], + [ + 221.0, + 818.0 + ], + [ + 222.0, + 819.0 + ], + [ + 222.0, + 820.0 + ], + [ + 224.0, + 822.0 + ], + [ + 226.0, + 820.0 + ], + [ + 225.0, + 819.0 + ], + [ + 225.0, + 817.0 + ], + [ + 226.0, + 816.0 + ], + [ + 228.0, + 818.0 + ], + [ + 228.0, + 820.0 + ], + [ + 230.0, + 822.0 + ], + [ + 232.0, + 820.0 + ], + [ + 235.0, + 820.0 + ], + [ + 237.0, + 818.0 + ], + [ + 237.0, + 817.0 + ], + [ + 238.0, + 816.0 + ], + [ + 239.0, + 817.0 + ], + [ + 239.0, + 820.0 + ], + [ + 242.0, + 823.0 + ], + [ + 247.0, + 823.0 + ], + [ + 249.0, + 821.0 + ], + [ + 249.0, + 820.0 + ], + [ + 247.0, + 818.0 + ], + [ + 246.0, + 818.0 + ], + [ + 242.0, + 814.0 + ], + [ + 239.0, + 814.0 + ], + [ + 238.0, + 815.0 + ], + [ + 237.0, + 815.0 + ], + [ + 236.0, + 814.0 + ], + [ + 233.0, + 814.0 + ], + [ + 232.0, + 813.0 + ], + [ + 234.0, + 811.0 + ], + [ + 233.0, + 810.0 + ], + [ + 232.0, + 810.0 + ], + [ + 231.0, + 809.0 + ], + [ + 230.0, + 809.0 + ], + [ + 229.0, + 808.0 + ], + [ + 229.0, + 806.0 + ], + [ + 228.0, + 805.0 + ], + [ + 227.0, + 805.0 + ], + [ + 225.0, + 803.0 + ] + ], + [ + [ + 191.0, + 803.0 + ], + [ + 190.0, + 804.0 + ], + [ + 190.0, + 805.0 + ], + [ + 189.0, + 806.0 + ], + [ + 189.0, + 811.0 + ], + [ + 190.0, + 812.0 + ], + [ + 189.0, + 813.0 + ], + [ + 188.0, + 813.0 + ], + [ + 187.0, + 814.0 + ], + [ + 186.0, + 814.0 + ], + [ + 185.0, + 813.0 + ], + [ + 183.0, + 813.0 + ], + [ + 182.0, + 814.0 + ], + [ + 182.0, + 819.0 + ], + [ + 185.0, + 822.0 + ], + [ + 186.0, + 822.0 + ], + [ + 187.0, + 823.0 + ], + [ + 190.0, + 820.0 + ], + [ + 191.0, + 821.0 + ], + [ + 191.0, + 822.0 + ], + [ + 192.0, + 823.0 + ], + [ + 194.0, + 821.0 + ], + [ + 194.0, + 818.0 + ], + [ + 195.0, + 817.0 + ], + [ + 196.0, + 818.0 + ], + [ + 196.0, + 819.0 + ], + [ + 197.0, + 820.0 + ], + [ + 197.0, + 824.0 + ], + [ + 198.0, + 825.0 + ], + [ + 200.0, + 823.0 + ], + [ + 201.0, + 823.0 + ], + [ + 203.0, + 825.0 + ], + [ + 206.0, + 825.0 + ], + [ + 208.0, + 823.0 + ], + [ + 207.0, + 822.0 + ], + [ + 205.0, + 822.0 + ], + [ + 204.0, + 821.0 + ], + [ + 204.0, + 820.0 + ], + [ + 205.0, + 819.0 + ], + [ + 206.0, + 820.0 + ], + [ + 207.0, + 820.0 + ], + [ + 209.0, + 818.0 + ], + [ + 209.0, + 814.0 + ], + [ + 208.0, + 813.0 + ], + [ + 205.0, + 813.0 + ], + [ + 203.0, + 815.0 + ], + [ + 202.0, + 815.0 + ], + [ + 201.0, + 816.0 + ], + [ + 200.0, + 815.0 + ], + [ + 200.0, + 814.0 + ], + [ + 199.0, + 813.0 + ], + [ + 198.0, + 813.0 + ], + [ + 197.0, + 812.0 + ], + [ + 199.0, + 810.0 + ], + [ + 199.0, + 808.0 + ], + [ + 198.0, + 807.0 + ], + [ + 198.0, + 806.0 + ], + [ + 196.0, + 804.0 + ], + [ + 195.0, + 804.0 + ], + [ + 194.0, + 803.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 202.5, + 814.0 + ], + "size": [ + 20.0, + 91.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 200.95, + 816.12 + ], + "orientation_deg": -2.71, + "ink_pixels": 514, + "detected": true + }, + "metrics": { + "box_coverage_pct": 16.267, + "containment_pct": 100.0, + "ink_fill_ratio": 0.266, + "polygon_overlap_proxy": 0.1627, + "distance_from_box_center_px": 2.25, + "margin_utilization": 0.4101, + "mean_dark_intensity": 175.83, + "gray_stddev": 39.21, + "signature_area_ratio": 0.00275 + }, + "provenance": { + "master_seed": 1007, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "company_name": { + "actual_box": [ + 80.0, + 169.0, + 271.0, + 183.0 + ], + "target_box": null + } + } + }, + "additional_signatures": [ + { + "box_id": "sig2", + "role": "Second Authorised Signatory", + "signature_id": "signet_001445", + "signer_group": "signet_0040", + "source_type": "genuine", + "script": "latin", + "ref_path": "data/raw/signet/train/040/05_040.png" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..33f1eb8b9fdff6cf8f9d7ec1adb64443a3cfaa74 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd258880dcb7d2d2cfebe6272a76752de760d2c6824fd984d2939112d1aa5cba +size 4190 diff --git a/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_sig2_signature.png b/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_sig2_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..59b6d1fb677df384c73582a18266d901c7f51988 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_007_1d_scanned/case_007_1d_scanned_sig2_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edc7388bb0bb5f2fa23a5d5bacb9216f669433e12bdad361a5403696678562c0 +size 38814 diff --git a/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..43a5cd1b13336d807600f0e0687fc3638b7e199d --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbfb9a70ceb00c37b13acee1a46818e8525297a068bf9b1e64927a7bab255d9a +size 3085511 diff --git a/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..302af82bc8321d8b08d6beb2c3d1979dbdcdb810 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_ground_truth.json @@ -0,0 +1,1169 @@ +{ + "case_id": "case_008_1d_procedural", + "expected_verdict": "PASS", + "expected_reject_code": null, + "document_id": "case_000001", + "template": "proc_doc_15", + "template_kind": "procedural", + "document_type": "Bank Statement", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Scott Wright", + "role": "Beneficiary Sign-off" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_001598", + "path": "data/raw/bhsig260_hindi/44/H-S-44-G-07.tif", + "signer_group": "bhsig260_hindi_0044", + "source_type": "genuine", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 0.486, + "scale_factor": 0.9442, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.7727, + "blur_sigma": 0.4172, + "feather_px": 0.8553, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.4664, + "tier": "medium", + "misplacement": 0.6785, + "whitespace": 0.83, + "faintness": 0.0, + "smallness": 0.1667, + "low_texture": 0.3795 + }, + "quality": { + "degraded": true, + "case_type": "1d", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "1d", + "verdict": "PASS", + "reject_code": null, + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_hindi_0044", + "expected_signer_script": "hindi", + "actual_signer_group": "bhsig260_hindi_0044", + "actual_source_type": "genuine", + "actual_signer_script": "hindi", + "companion": null + }, + "augmentation": { + "rotate_deg": 0.486, + "scale_factor": 0.9442, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "edge", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.2, + "blur_sigma": 0.4172, + "jpeg_quality": 63, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3625307114, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 3156097789, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 4252380242, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2345878302, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3031223703, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.3, + "seed": 55298579, + "params": { + "sigma": 0.53 + } + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2875180214, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 3642576527, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2328441277, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1843334360, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3326046098, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 2671371101, + "params": { + "resolved_type": "edge", + "side": "bottom" + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3545395541, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 3421481614, + "params": { + "resolved_tone": "sepia" + } + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3426926668, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 641628938, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2016857592, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2676977970, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3637398637, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 934920463, + "params": { + "quality": 63 + } + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 122.0, + 1900.0, + 278.0, + 1960.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 122.0, + 1900.0, + 278.0, + 1960.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 277, + 1913 + ], + [ + 252, + 1932 + ], + [ + 154, + 1959 + ], + [ + 124, + 1931 + ], + [ + 122, + 1917 + ], + [ + 256, + 1900 + ] + ], + "convex_hull_px": [ + [ + 277, + 1913 + ], + [ + 252, + 1932 + ], + [ + 154, + 1959 + ], + [ + 124, + 1931 + ], + [ + 122, + 1917 + ], + [ + 256, + 1900 + ] + ], + "display_contours": [ + [ + [ + 154.0, + 1957.0 + ], + [ + 153.0, + 1958.0 + ], + [ + 153.0, + 1959.0 + ], + [ + 154.0, + 1960.0 + ], + [ + 156.0, + 1958.0 + ], + [ + 155.0, + 1957.0 + ] + ], + [ + [ + 180.0, + 1913.0 + ], + [ + 179.0, + 1914.0 + ], + [ + 126.0, + 1914.0 + ], + [ + 125.0, + 1915.0 + ], + [ + 122.0, + 1915.0 + ], + [ + 121.0, + 1916.0 + ], + [ + 121.0, + 1917.0 + ], + [ + 122.0, + 1918.0 + ], + [ + 122.0, + 1928.0 + ], + [ + 123.0, + 1929.0 + ], + [ + 123.0, + 1931.0 + ], + [ + 124.0, + 1932.0 + ], + [ + 125.0, + 1932.0 + ], + [ + 126.0, + 1933.0 + ], + [ + 128.0, + 1933.0 + ], + [ + 129.0, + 1934.0 + ], + [ + 131.0, + 1934.0 + ], + [ + 133.0, + 1936.0 + ], + [ + 133.0, + 1939.0 + ], + [ + 134.0, + 1940.0 + ], + [ + 134.0, + 1941.0 + ], + [ + 135.0, + 1942.0 + ], + [ + 137.0, + 1942.0 + ], + [ + 140.0, + 1939.0 + ], + [ + 139.0, + 1938.0 + ], + [ + 139.0, + 1937.0 + ], + [ + 138.0, + 1936.0 + ], + [ + 138.0, + 1929.0 + ], + [ + 139.0, + 1928.0 + ], + [ + 140.0, + 1929.0 + ], + [ + 140.0, + 1933.0 + ], + [ + 141.0, + 1934.0 + ], + [ + 141.0, + 1935.0 + ], + [ + 142.0, + 1936.0 + ], + [ + 143.0, + 1936.0 + ], + [ + 144.0, + 1937.0 + ], + [ + 154.0, + 1937.0 + ], + [ + 157.0, + 1934.0 + ], + [ + 156.0, + 1933.0 + ], + [ + 156.0, + 1922.0 + ], + [ + 154.0, + 1920.0 + ], + [ + 155.0, + 1919.0 + ], + [ + 173.0, + 1919.0 + ], + [ + 174.0, + 1920.0 + ], + [ + 174.0, + 1924.0 + ], + [ + 173.0, + 1925.0 + ], + [ + 172.0, + 1925.0 + ], + [ + 171.0, + 1924.0 + ], + [ + 165.0, + 1924.0 + ], + [ + 164.0, + 1925.0 + ], + [ + 163.0, + 1925.0 + ], + [ + 162.0, + 1926.0 + ], + [ + 162.0, + 1927.0 + ], + [ + 161.0, + 1928.0 + ], + [ + 161.0, + 1931.0 + ], + [ + 164.0, + 1934.0 + ], + [ + 166.0, + 1934.0 + ], + [ + 169.0, + 1931.0 + ], + [ + 172.0, + 1931.0 + ], + [ + 173.0, + 1932.0 + ], + [ + 173.0, + 1933.0 + ], + [ + 176.0, + 1936.0 + ], + [ + 179.0, + 1933.0 + ], + [ + 179.0, + 1920.0 + ], + [ + 180.0, + 1919.0 + ], + [ + 184.0, + 1919.0 + ], + [ + 187.0, + 1916.0 + ], + [ + 187.0, + 1915.0 + ], + [ + 185.0, + 1913.0 + ] + ], + [ + [ + 256.0, + 1899.0 + ], + [ + 255.0, + 1900.0 + ], + [ + 252.0, + 1900.0 + ], + [ + 251.0, + 1901.0 + ], + [ + 251.0, + 1908.0 + ], + [ + 252.0, + 1909.0 + ], + [ + 252.0, + 1910.0 + ], + [ + 251.0, + 1911.0 + ], + [ + 244.0, + 1911.0 + ], + [ + 243.0, + 1910.0 + ], + [ + 242.0, + 1910.0 + ], + [ + 241.0, + 1911.0 + ], + [ + 239.0, + 1911.0 + ], + [ + 238.0, + 1910.0 + ], + [ + 212.0, + 1910.0 + ], + [ + 211.0, + 1911.0 + ], + [ + 211.0, + 1913.0 + ], + [ + 213.0, + 1915.0 + ], + [ + 218.0, + 1915.0 + ], + [ + 219.0, + 1916.0 + ], + [ + 219.0, + 1919.0 + ], + [ + 220.0, + 1920.0 + ], + [ + 219.0, + 1921.0 + ], + [ + 218.0, + 1921.0 + ], + [ + 217.0, + 1922.0 + ], + [ + 215.0, + 1922.0 + ], + [ + 213.0, + 1924.0 + ], + [ + 213.0, + 1927.0 + ], + [ + 217.0, + 1931.0 + ], + [ + 218.0, + 1930.0 + ], + [ + 221.0, + 1930.0 + ], + [ + 224.0, + 1933.0 + ], + [ + 225.0, + 1933.0 + ], + [ + 227.0, + 1931.0 + ], + [ + 227.0, + 1925.0 + ], + [ + 230.0, + 1922.0 + ], + [ + 232.0, + 1922.0 + ], + [ + 233.0, + 1923.0 + ], + [ + 232.0, + 1924.0 + ], + [ + 232.0, + 1932.0 + ], + [ + 234.0, + 1934.0 + ], + [ + 236.0, + 1934.0 + ], + [ + 239.0, + 1931.0 + ], + [ + 244.0, + 1931.0 + ], + [ + 247.0, + 1928.0 + ], + [ + 248.0, + 1929.0 + ], + [ + 248.0, + 1930.0 + ], + [ + 249.0, + 1931.0 + ], + [ + 249.0, + 1932.0 + ], + [ + 250.0, + 1933.0 + ], + [ + 252.0, + 1933.0 + ], + [ + 254.0, + 1931.0 + ], + [ + 257.0, + 1931.0 + ], + [ + 260.0, + 1928.0 + ], + [ + 260.0, + 1924.0 + ], + [ + 261.0, + 1923.0 + ], + [ + 260.0, + 1922.0 + ], + [ + 260.0, + 1921.0 + ], + [ + 261.0, + 1920.0 + ], + [ + 260.0, + 1919.0 + ], + [ + 260.0, + 1918.0 + ], + [ + 262.0, + 1916.0 + ], + [ + 266.0, + 1916.0 + ], + [ + 267.0, + 1917.0 + ], + [ + 274.0, + 1917.0 + ], + [ + 278.0, + 1913.0 + ], + [ + 276.0, + 1911.0 + ], + [ + 272.0, + 1911.0 + ], + [ + 271.0, + 1912.0 + ], + [ + 262.0, + 1912.0 + ], + [ + 261.0, + 1911.0 + ], + [ + 259.0, + 1911.0 + ], + [ + 257.0, + 1909.0 + ], + [ + 257.0, + 1905.0 + ], + [ + 258.0, + 1904.0 + ], + [ + 259.0, + 1904.0 + ], + [ + 260.0, + 1905.0 + ], + [ + 263.0, + 1905.0 + ], + [ + 264.0, + 1906.0 + ], + [ + 265.0, + 1906.0 + ], + [ + 267.0, + 1904.0 + ], + [ + 267.0, + 1903.0 + ], + [ + 266.0, + 1902.0 + ], + [ + 265.0, + 1902.0 + ], + [ + 263.0, + 1900.0 + ], + [ + 260.0, + 1900.0 + ], + [ + 259.0, + 1899.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 201.09, + 1929.95 + ], + "size": [ + 154.7, + 46.92 + ], + "angle_deg": -6.58 + }, + "centroid_px": [ + 197.53, + 1921.49 + ], + "orientation_deg": -4.46, + "ink_pixels": 1591, + "detected": true + }, + "metrics": { + "box_coverage_pct": 32.15, + "containment_pct": 100.0, + "ink_fill_ratio": 0.17, + "polygon_overlap_proxy": 0.3215, + "distance_from_box_center_px": 2.88, + "margin_utilization": 0.8097, + "mean_dark_intensity": 155.03, + "gray_stddev": 37.23, + "signature_area_ratio": 0.0025 + }, + "provenance": { + "master_seed": 1008, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 251, + 527 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 997, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 884, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..cf6e0f64a8fe055fe650ad28b270108c7fdf3e55 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_008_1d_procedural/case_008_1d_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c7de32fdccb0a7d6aeac38ab50c78ec697808756573252b17e5946022a4c4c1 +size 2977 diff --git a/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..d8d8b3ac6af4fa08fe925a3d2d174d3476848129 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18156ef173c768c5f9621f3c22aab9cc7ed3187895c7b101b58d3ca4cd48a517 +size 1827952 diff --git a/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..6a2ba9706181b4c0b2164535ba06c9c4a2c9ca68 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_ground_truth.json @@ -0,0 +1,546 @@ +{ + "case_id": "case_009_2a_scanned", + "expected_verdict": "REJECT", + "expected_reject_code": "NO_SIGNATURE", + "document_id": "case_000001", + "template": "bank_doc_07", + "template_kind": "scanned", + "document_type": "bank_mandate_attestation", + "page": 1, + "page_size": [ + 1024, + 1536 + ], + "dpi": 300, + "signer": { + "name": "Scott Martinez", + "role": "Bank Manager" + }, + "signature_source": { + "signature_id": null, + "path": null, + "signer_group": null, + "source_type": "none", + "script": null + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 0.0, + "scale_factor": 0.0, + "anchor_point": "center", + "margin_target_box": [ + 345.0, + 973.0, + 700.0, + 990.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8557, + "blur_sigma": 0.4281, + "feather_px": 1.1958, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.7592, + "tier": "hard", + "misplacement": 0.9264, + "whitespace": 0.174, + "faintness": 0.8886, + "smallness": 0.9862, + "low_texture": 0.7455 + }, + "quality": { + "degraded": false, + "case_type": "2a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "2a", + "verdict": "REJECT", + "reject_code": "NO_SIGNATURE", + "target_box_id": "sig2", + "expected_signer_group": "signet_0050", + "expected_signer_script": "latin", + "actual_signer_group": null, + "actual_source_type": "none", + "actual_signer_script": null, + "companion": null + }, + "augmentation": { + "rotate_deg": 0.0, + "scale_factor": 0.0, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "fold", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.2, + "blur_sigma": 0.4281, + "jpeg_quality": 81, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 772399034, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 2398900836, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 552144100, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 213703920, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 288132056, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3735243037, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 524013168, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 362492883, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2022314253, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2518387391, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2663539261, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 2921061118, + "params": { + "resolved_type": "fold", + "folds": [ + { + "vertical": true, + "position_frac": 0.374 + } + ] + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1495068860, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 3385561330, + "params": { + "resolved_tone": "grey" + } + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 95476556, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2700891157, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2279194337, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 1263113784, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 792158599, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 164682076, + "params": { + "quality": 81 + } + } + } + }, + "geometry": { + "box_px": [ + 345.0, + 973.0, + 700.0, + 990.0 + ], + "signature_bbox_px": [ + 367.0, + 967.0, + 398.0, + 995.0 + ], + "target_box": [ + 345.0, + 973.0, + 700.0, + 990.0 + ], + "actual_box": [ + 367.0, + 967.0, + 398.0, + 995.0 + ], + "search_box_px": [ + 318.375, + 967.0, + 726.625, + 996.0 + ], + "signature_polygon_px": [ + [ + 397, + 967 + ], + [ + 395, + 993 + ], + [ + 370, + 993 + ], + [ + 367, + 967 + ] + ], + "convex_hull_px": [ + [ + 397, + 967 + ], + [ + 395, + 993 + ], + [ + 370, + 993 + ], + [ + 367, + 967 + ] + ], + "display_contours": [ + [ + [ + 366.0, + 967.0 + ], + [ + 366.0, + 968.0 + ], + [ + 367.0, + 969.0 + ], + [ + 368.0, + 969.0 + ], + [ + 369.0, + 970.0 + ], + [ + 366.0, + 973.0 + ], + [ + 366.0, + 974.0 + ], + [ + 367.0, + 975.0 + ], + [ + 369.0, + 973.0 + ], + [ + 370.0, + 974.0 + ], + [ + 370.0, + 979.0 + ], + [ + 372.0, + 981.0 + ], + [ + 369.0, + 984.0 + ], + [ + 369.0, + 988.0 + ], + [ + 370.0, + 989.0 + ], + [ + 370.0, + 990.0 + ], + [ + 369.0, + 991.0 + ], + [ + 369.0, + 993.0 + ], + [ + 370.0, + 994.0 + ], + [ + 371.0, + 994.0 + ], + [ + 372.0, + 995.0 + ], + [ + 393.0, + 995.0 + ], + [ + 394.0, + 994.0 + ], + [ + 395.0, + 994.0 + ], + [ + 397.0, + 992.0 + ], + [ + 397.0, + 989.0 + ], + [ + 395.0, + 987.0 + ], + [ + 396.0, + 986.0 + ], + [ + 395.0, + 985.0 + ], + [ + 395.0, + 984.0 + ], + [ + 396.0, + 983.0 + ], + [ + 396.0, + 981.0 + ], + [ + 397.0, + 980.0 + ], + [ + 396.0, + 979.0 + ], + [ + 396.0, + 972.0 + ], + [ + 398.0, + 970.0 + ], + [ + 398.0, + 967.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 382.0, + 980.5 + ], + "size": [ + 27.0, + 30.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 382.68, + 980.14 + ], + "orientation_deg": 79.59, + "ink_pixels": 717, + "detected": true + }, + "metrics": { + "box_coverage_pct": 7.665, + "containment_pct": 64.699, + "ink_fill_ratio": 0.826, + "polygon_overlap_proxy": 0.0736, + "distance_from_box_center_px": 139.83, + "margin_utilization": 0.8788, + "mean_dark_intensity": 196.63, + "gray_stddev": 15.27, + "signature_area_ratio": 0.00055 + }, + "provenance": { + "master_seed": 1009, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "name": { + "actual_box": [ + 314.0, + 498.0, + 449.0, + 513.0 + ], + "target_box": null + } + } + }, + "additional_signatures": [ + { + "box_id": "sig1", + "role": "Account Holder", + "signature_id": "bhsig260_hindi_001578", + "signer_group": "bhsig260_hindi_0043", + "source_type": "genuine", + "script": "hindi", + "ref_path": "data/raw/bhsig260_hindi/43/H-S-43-G-03.tif" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_sig1_signature.png b/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_sig1_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..e4201a73471aefd5fdca90409793a437d305925a --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_009_2a_scanned/case_009_2a_scanned_sig1_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcd4a9239fb831285deff4e18e2ac7c7b5c502ea13a2b1eae133c08470599bb9 +size 3950 diff --git a/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..ff1ea43bdaca0a3a825d72aa7495b7b09f4dcf6f --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45be883bbfd9c113ac03bb01e78c0d057419dcde4dfb197e870fdd2766072f89 +size 2634321 diff --git a/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..93fac91dc0d6e7fa10ed54d46a1c0df6cf623ee0 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_ground_truth.json @@ -0,0 +1,475 @@ +{ + "case_id": "case_010_2a_procedural", + "expected_verdict": "REJECT", + "expected_reject_code": "NO_SIGNATURE", + "document_id": "case_000001", + "template": "proc_doc_06", + "template_kind": "procedural", + "document_type": "Loan Agreement", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "David Adams", + "role": "Authorized Representative" + }, + "signature_source": { + "signature_id": null, + "path": null, + "signer_group": null, + "source_type": "none", + "script": null + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 0.0, + "scale_factor": 0.0, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8567, + "blur_sigma": 0.407, + "feather_px": 0.5976, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 1.0, + "tier": "hard", + "misplacement": 1.0, + "whitespace": 1.0, + "faintness": 1.0, + "smallness": 1.0, + "low_texture": 1.0 + }, + "quality": { + "degraded": false, + "case_type": "2a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "2a", + "verdict": "REJECT", + "reject_code": "NO_SIGNATURE", + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_bengali_0049", + "expected_signer_script": "bengali", + "actual_signer_group": null, + "actual_source_type": "none", + "actual_signer_script": null, + "companion": null + }, + "augmentation": { + "rotate_deg": 0.0, + "scale_factor": 0.0, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "edge", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.567, + "discoloration_strength": 0.2, + "blur_sigma": 0.407, + "jpeg_quality": 50, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 2943698331, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 1325884790, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2537017116, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1309398587, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1004287941, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 1851754379, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 509965736, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 266396714, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 4100394556, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2573263336, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3185576312, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 1781667332, + "params": { + "resolved_type": "edge", + "side": "bottom" + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2457348772, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 1251272342, + "params": { + "resolved_tone": "sepia" + } + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 573096861, + "params": { + "tilt_deg": 0.567 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2899288806, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 611687351, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 4111396484, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3642908029, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 2648429611, + "params": { + "quality": 50 + } + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [], + "convex_hull_px": [], + "display_contours": [], + "min_rotated_rect": { + "center": [ + 0.0, + 0.0 + ], + "size": [ + 0.0, + 0.0 + ], + "angle_deg": 0.0 + }, + "centroid_px": [ + NaN, + NaN + ], + "orientation_deg": 0.0, + "ink_pixels": 0, + "detected": false + }, + "metrics": { + "box_coverage_pct": 0.0, + "containment_pct": 0.0, + "ink_fill_ratio": 0.0, + "polygon_overlap_proxy": 0.0, + "distance_from_box_center_px": NaN, + "margin_utilization": 0.0, + "mean_dark_intensity": 255.0, + "gray_stddev": 0.0, + "signature_area_ratio": 0.0 + }, + "provenance": { + "master_seed": 1010, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 450, + 527 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1006, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 887, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + }, + "additional_signatures": [ + { + "box_id": "sig2", + "role": "Lead Arranger Officer", + "signature_id": "bhsig260_hindi_000092", + "signer_group": "bhsig260_hindi_0103", + "source_type": "genuine", + "script": "hindi", + "ref_path": "data/raw/bhsig260_hindi/103/H-S-103-G-05.tif" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_sig2_signature.png b/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_sig2_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..ae7fbe5db3f751eacc4b06416736e3a30ec3905c --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_010_2a_procedural/case_010_2a_procedural_sig2_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cd20afda0309d6bbab27c1bdb6345f9f41c8b020e0c280c463bae71df90e584 +size 3897 diff --git a/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..26da63f69b07cfd6639505ea3d2303bedb71ab60 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ba26df43bec629c62d7ff440b4d03076eb507d7b6b015f484c2dc3513e26ed2 +size 1258388 diff --git a/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..bbaa8789bb56963fc53ab20f8f8aac417ab2e31b --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_ground_truth.json @@ -0,0 +1,1659 @@ +{ + "case_id": "case_011_2b_scanned", + "expected_verdict": "REJECT", + "expected_reject_code": "NO_SIGNATURE", + "document_id": "case_000001", + "template": "bank_doc_08", + "template_kind": "scanned", + "document_type": "bank_account_verification_mandate", + "page": 1, + "page_size": [ + 1024, + 1536 + ], + "dpi": 300, + "signer": { + "name": "Emily Davis", + "role": "Bank Manager" + }, + "signature_source": { + "signature_id": null, + "path": null, + "signer_group": null, + "source_type": "none", + "script": null + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -3.864, + "scale_factor": 0.8128, + "anchor_point": "center", + "margin_target_box": [ + 685.0, + 568.0, + 935.0, + 590.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.9313, + "blur_sigma": 0.2071, + "feather_px": 0.8193, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.4785, + "tier": "medium", + "misplacement": 0.344, + "whitespace": 0.7722, + "faintness": 0.3571, + "smallness": 0.8738, + "low_texture": 0.0113 + }, + "quality": { + "degraded": false, + "case_type": "2b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "2b", + "verdict": "REJECT", + "reject_code": "NO_SIGNATURE", + "target_box_id": "sig2", + "expected_signer_group": "bhsig260_hindi_0063", + "expected_signer_script": "hindi", + "actual_signer_group": null, + "actual_source_type": "printed", + "actual_signer_script": null, + "companion": null + }, + "augmentation": { + "rotate_deg": -3.864, + "scale_factor": 0.8128, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 1.689, + "discoloration_strength": 0.0, + "blur_sigma": 0.2071, + "jpeg_quality": 65, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3682166318, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 4201854127, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1020817454, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3019839479, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3453067388, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3403485809, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3295389722, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 3640964460, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2263271172, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2561897892, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4186920466, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 3054110714, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3962512936, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 3528124909, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 868591794, + "params": { + "tilt_deg": 1.689 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 93289964, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2278520696, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2771676126, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3120003780, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 1309057746, + "params": { + "quality": 65 + } + } + } + }, + "geometry": { + "box_px": [ + 685.0, + 568.0, + 935.0, + 590.0 + ], + "signature_bbox_px": [ + 666.0, + 564.0, + 914.0, + 596.0 + ], + "target_box": [ + 685.0, + 568.0, + 935.0, + 590.0 + ], + "actual_box": [ + 666.0, + 564.0, + 914.0, + 596.0 + ], + "search_box_px": [ + 666.25, + 562.0, + 953.75, + 596.0 + ], + "signature_polygon_px": [ + [ + 913, + 567 + ], + [ + 883, + 595 + ], + [ + 804, + 595 + ], + [ + 666, + 580 + ], + [ + 666, + 572 + ], + [ + 714, + 564 + ] + ], + "convex_hull_px": [ + [ + 913, + 567 + ], + [ + 883, + 595 + ], + [ + 804, + 595 + ], + [ + 666, + 580 + ], + [ + 666, + 572 + ], + [ + 714, + 564 + ] + ], + "display_contours": [ + [ + [ + 859.0, + 588.0 + ], + [ + 858.0, + 589.0 + ], + [ + 858.0, + 590.0 + ], + [ + 859.0, + 591.0 + ], + [ + 863.0, + 591.0 + ], + [ + 864.0, + 592.0 + ], + [ + 867.0, + 592.0 + ], + [ + 868.0, + 593.0 + ], + [ + 870.0, + 593.0 + ], + [ + 871.0, + 594.0 + ], + [ + 874.0, + 594.0 + ], + [ + 875.0, + 595.0 + ], + [ + 884.0, + 595.0 + ], + [ + 883.0, + 594.0 + ], + [ + 882.0, + 594.0 + ], + [ + 881.0, + 593.0 + ], + [ + 880.0, + 593.0 + ], + [ + 879.0, + 592.0 + ], + [ + 877.0, + 592.0 + ], + [ + 876.0, + 591.0 + ], + [ + 875.0, + 591.0 + ], + [ + 874.0, + 590.0 + ], + [ + 872.0, + 590.0 + ], + [ + 871.0, + 589.0 + ], + [ + 869.0, + 589.0 + ], + [ + 868.0, + 588.0 + ] + ], + [ + [ + 824.0, + 587.0 + ], + [ + 823.0, + 588.0 + ], + [ + 820.0, + 588.0 + ], + [ + 819.0, + 589.0 + ], + [ + 816.0, + 589.0 + ], + [ + 815.0, + 590.0 + ], + [ + 814.0, + 590.0 + ], + [ + 813.0, + 591.0 + ], + [ + 811.0, + 591.0 + ], + [ + 810.0, + 592.0 + ], + [ + 808.0, + 592.0 + ], + [ + 807.0, + 593.0 + ], + [ + 806.0, + 593.0 + ], + [ + 805.0, + 594.0 + ], + [ + 804.0, + 594.0 + ], + [ + 803.0, + 595.0 + ], + [ + 812.0, + 595.0 + ], + [ + 813.0, + 594.0 + ], + [ + 814.0, + 594.0 + ], + [ + 815.0, + 593.0 + ], + [ + 818.0, + 593.0 + ], + [ + 819.0, + 592.0 + ], + [ + 823.0, + 592.0 + ], + [ + 824.0, + 591.0 + ], + [ + 827.0, + 591.0 + ], + [ + 829.0, + 589.0 + ], + [ + 828.0, + 588.0 + ], + [ + 826.0, + 588.0 + ], + [ + 825.0, + 587.0 + ] + ], + [ + [ + 684.0, + 565.0 + ], + [ + 683.0, + 566.0 + ], + [ + 683.0, + 567.0 + ], + [ + 684.0, + 568.0 + ], + [ + 684.0, + 571.0 + ], + [ + 683.0, + 572.0 + ], + [ + 681.0, + 570.0 + ], + [ + 679.0, + 570.0 + ], + [ + 678.0, + 571.0 + ], + [ + 674.0, + 571.0 + ], + [ + 673.0, + 572.0 + ], + [ + 672.0, + 572.0 + ], + [ + 671.0, + 571.0 + ], + [ + 666.0, + 571.0 + ], + [ + 666.0, + 581.0 + ], + [ + 668.0, + 579.0 + ], + [ + 670.0, + 581.0 + ], + [ + 672.0, + 581.0 + ], + [ + 673.0, + 580.0 + ], + [ + 674.0, + 581.0 + ], + [ + 676.0, + 581.0 + ], + [ + 678.0, + 579.0 + ], + [ + 678.0, + 576.0 + ], + [ + 679.0, + 575.0 + ], + [ + 680.0, + 576.0 + ], + [ + 680.0, + 581.0 + ], + [ + 682.0, + 583.0 + ], + [ + 683.0, + 583.0 + ], + [ + 684.0, + 582.0 + ], + [ + 687.0, + 582.0 + ], + [ + 689.0, + 580.0 + ], + [ + 691.0, + 582.0 + ], + [ + 693.0, + 582.0 + ], + [ + 695.0, + 580.0 + ], + [ + 691.0, + 576.0 + ], + [ + 691.0, + 573.0 + ], + [ + 693.0, + 571.0 + ], + [ + 692.0, + 570.0 + ], + [ + 690.0, + 570.0 + ], + [ + 689.0, + 571.0 + ], + [ + 688.0, + 570.0 + ], + [ + 688.0, + 566.0 + ], + [ + 687.0, + 565.0 + ] + ], + [ + [ + 788.0, + 564.0 + ], + [ + 787.0, + 565.0 + ], + [ + 787.0, + 568.0 + ], + [ + 788.0, + 569.0 + ], + [ + 793.0, + 569.0 + ], + [ + 795.0, + 567.0 + ], + [ + 792.0, + 564.0 + ], + [ + 791.0, + 564.0 + ], + [ + 790.0, + 565.0 + ], + [ + 789.0, + 564.0 + ] + ], + [ + [ + 714.0, + 563.0 + ], + [ + 713.0, + 564.0 + ], + [ + 713.0, + 565.0 + ], + [ + 712.0, + 566.0 + ], + [ + 712.0, + 567.0 + ], + [ + 711.0, + 568.0 + ], + [ + 711.0, + 569.0 + ], + [ + 710.0, + 570.0 + ], + [ + 710.0, + 571.0 + ], + [ + 709.0, + 572.0 + ], + [ + 708.0, + 571.0 + ], + [ + 708.0, + 570.0 + ], + [ + 707.0, + 569.0 + ], + [ + 707.0, + 568.0 + ], + [ + 705.0, + 566.0 + ], + [ + 705.0, + 565.0 + ], + [ + 704.0, + 564.0 + ], + [ + 701.0, + 564.0 + ], + [ + 700.0, + 565.0 + ], + [ + 700.0, + 568.0 + ], + [ + 701.0, + 569.0 + ], + [ + 701.0, + 580.0 + ], + [ + 702.0, + 581.0 + ], + [ + 703.0, + 581.0 + ], + [ + 706.0, + 578.0 + ], + [ + 708.0, + 580.0 + ], + [ + 709.0, + 580.0 + ], + [ + 711.0, + 578.0 + ], + [ + 714.0, + 581.0 + ], + [ + 715.0, + 580.0 + ], + [ + 730.0, + 580.0 + ], + [ + 733.0, + 577.0 + ], + [ + 733.0, + 576.0 + ], + [ + 734.0, + 575.0 + ], + [ + 735.0, + 576.0 + ], + [ + 740.0, + 576.0 + ], + [ + 742.0, + 574.0 + ], + [ + 744.0, + 576.0 + ], + [ + 746.0, + 576.0 + ], + [ + 747.0, + 575.0 + ], + [ + 748.0, + 575.0 + ], + [ + 749.0, + 576.0 + ], + [ + 749.0, + 577.0 + ], + [ + 747.0, + 579.0 + ], + [ + 747.0, + 581.0 + ], + [ + 749.0, + 583.0 + ], + [ + 754.0, + 583.0 + ], + [ + 755.0, + 582.0 + ], + [ + 756.0, + 582.0 + ], + [ + 758.0, + 580.0 + ], + [ + 758.0, + 579.0 + ], + [ + 760.0, + 577.0 + ], + [ + 761.0, + 578.0 + ], + [ + 772.0, + 578.0 + ], + [ + 775.0, + 575.0 + ], + [ + 777.0, + 575.0 + ], + [ + 778.0, + 576.0 + ], + [ + 778.0, + 577.0 + ], + [ + 779.0, + 578.0 + ], + [ + 780.0, + 578.0 + ], + [ + 782.0, + 576.0 + ], + [ + 783.0, + 577.0 + ], + [ + 783.0, + 578.0 + ], + [ + 784.0, + 579.0 + ], + [ + 793.0, + 579.0 + ], + [ + 794.0, + 580.0 + ], + [ + 794.0, + 581.0 + ], + [ + 795.0, + 582.0 + ], + [ + 797.0, + 582.0 + ], + [ + 800.0, + 579.0 + ], + [ + 801.0, + 579.0 + ], + [ + 802.0, + 578.0 + ], + [ + 806.0, + 578.0 + ], + [ + 807.0, + 579.0 + ], + [ + 809.0, + 579.0 + ], + [ + 810.0, + 580.0 + ], + [ + 812.0, + 580.0 + ], + [ + 813.0, + 579.0 + ], + [ + 815.0, + 579.0 + ], + [ + 816.0, + 578.0 + ], + [ + 818.0, + 578.0 + ], + [ + 820.0, + 580.0 + ], + [ + 825.0, + 580.0 + ], + [ + 827.0, + 578.0 + ], + [ + 828.0, + 578.0 + ], + [ + 830.0, + 580.0 + ], + [ + 831.0, + 580.0 + ], + [ + 833.0, + 578.0 + ], + [ + 834.0, + 578.0 + ], + [ + 836.0, + 580.0 + ], + [ + 840.0, + 580.0 + ], + [ + 841.0, + 581.0 + ], + [ + 843.0, + 581.0 + ], + [ + 844.0, + 580.0 + ], + [ + 845.0, + 580.0 + ], + [ + 847.0, + 578.0 + ], + [ + 847.0, + 576.0 + ], + [ + 848.0, + 575.0 + ], + [ + 852.0, + 575.0 + ], + [ + 853.0, + 574.0 + ], + [ + 867.0, + 574.0 + ], + [ + 868.0, + 573.0 + ], + [ + 877.0, + 573.0 + ], + [ + 878.0, + 572.0 + ], + [ + 892.0, + 572.0 + ], + [ + 893.0, + 571.0 + ], + [ + 907.0, + 571.0 + ], + [ + 908.0, + 570.0 + ], + [ + 911.0, + 570.0 + ], + [ + 914.0, + 567.0 + ], + [ + 913.0, + 566.0 + ], + [ + 904.0, + 566.0 + ], + [ + 903.0, + 567.0 + ], + [ + 893.0, + 567.0 + ], + [ + 892.0, + 568.0 + ], + [ + 882.0, + 568.0 + ], + [ + 881.0, + 569.0 + ], + [ + 869.0, + 569.0 + ], + [ + 868.0, + 570.0 + ], + [ + 858.0, + 570.0 + ], + [ + 857.0, + 571.0 + ], + [ + 846.0, + 571.0 + ], + [ + 844.0, + 569.0 + ], + [ + 840.0, + 569.0 + ], + [ + 839.0, + 568.0 + ], + [ + 839.0, + 567.0 + ], + [ + 838.0, + 566.0 + ], + [ + 836.0, + 566.0 + ], + [ + 835.0, + 567.0 + ], + [ + 835.0, + 568.0 + ], + [ + 833.0, + 570.0 + ], + [ + 832.0, + 570.0 + ], + [ + 831.0, + 571.0 + ], + [ + 829.0, + 569.0 + ], + [ + 824.0, + 569.0 + ], + [ + 823.0, + 568.0 + ], + [ + 821.0, + 568.0 + ], + [ + 820.0, + 569.0 + ], + [ + 819.0, + 569.0 + ], + [ + 816.0, + 566.0 + ], + [ + 815.0, + 566.0 + ], + [ + 814.0, + 565.0 + ], + [ + 807.0, + 565.0 + ], + [ + 806.0, + 566.0 + ], + [ + 806.0, + 573.0 + ], + [ + 804.0, + 575.0 + ], + [ + 803.0, + 575.0 + ], + [ + 802.0, + 574.0 + ], + [ + 802.0, + 572.0 + ], + [ + 801.0, + 571.0 + ], + [ + 803.0, + 569.0 + ], + [ + 802.0, + 568.0 + ], + [ + 800.0, + 568.0 + ], + [ + 799.0, + 569.0 + ], + [ + 799.0, + 570.0 + ], + [ + 798.0, + 571.0 + ], + [ + 784.0, + 571.0 + ], + [ + 783.0, + 572.0 + ], + [ + 782.0, + 571.0 + ], + [ + 774.0, + 571.0 + ], + [ + 772.0, + 569.0 + ], + [ + 772.0, + 567.0 + ], + [ + 773.0, + 566.0 + ], + [ + 774.0, + 566.0 + ], + [ + 775.0, + 567.0 + ], + [ + 777.0, + 565.0 + ], + [ + 776.0, + 564.0 + ], + [ + 775.0, + 564.0 + ], + [ + 774.0, + 565.0 + ], + [ + 773.0, + 564.0 + ], + [ + 771.0, + 564.0 + ], + [ + 770.0, + 563.0 + ], + [ + 765.0, + 563.0 + ], + [ + 763.0, + 565.0 + ], + [ + 763.0, + 566.0 + ], + [ + 762.0, + 567.0 + ], + [ + 758.0, + 567.0 + ], + [ + 757.0, + 566.0 + ], + [ + 754.0, + 566.0 + ], + [ + 753.0, + 565.0 + ], + [ + 752.0, + 565.0 + ], + [ + 751.0, + 566.0 + ], + [ + 749.0, + 566.0 + ], + [ + 748.0, + 567.0 + ], + [ + 748.0, + 568.0 + ], + [ + 747.0, + 569.0 + ], + [ + 744.0, + 566.0 + ], + [ + 741.0, + 566.0 + ], + [ + 740.0, + 567.0 + ], + [ + 739.0, + 567.0 + ], + [ + 738.0, + 568.0 + ], + [ + 737.0, + 568.0 + ], + [ + 736.0, + 567.0 + ], + [ + 728.0, + 567.0 + ], + [ + 726.0, + 569.0 + ], + [ + 725.0, + 568.0 + ], + [ + 720.0, + 568.0 + ], + [ + 719.0, + 569.0 + ], + [ + 718.0, + 568.0 + ], + [ + 718.0, + 564.0 + ], + [ + 717.0, + 563.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 789.29, + 579.55 + ], + "size": [ + 30.28, + 246.84 + ], + "angle_deg": -88.8 + }, + "centroid_px": [ + 778.44, + 574.39 + ], + "orientation_deg": 0.58, + "ink_pixels": 1808, + "detected": true + }, + "metrics": { + "box_coverage_pct": 80.753, + "containment_pct": 77.763, + "ink_fill_ratio": 0.2278, + "polygon_overlap_proxy": 0.656, + "distance_from_box_center_px": 31.9, + "margin_utilization": 1.1546, + "mean_dark_intensity": 132.85, + "gray_stddev": 59.32, + "signature_area_ratio": 0.00505 + }, + "provenance": { + "master_seed": 1011, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "name_sig2": { + "actual_box": [ + 752.0, + 445.0, + 862.0, + 464.0 + ], + "target_box": null + }, + "name_sig1": { + "actual_box": [ + 312.0, + 370.0, + 492.0, + 385.0 + ], + "target_box": null + } + } + }, + "additional_signatures": [ + { + "box_id": "sig1", + "role": "Account Holder", + "signature_id": "bhsig260_bengali_001198", + "signer_group": "bhsig260_bengali_0076", + "source_type": "genuine", + "script": "bengali", + "ref_path": "data/raw/bhsig260_bengali/76/B-S-76-G-07.tif" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_sig1_signature.png b/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_sig1_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..057d0e67b4156afa242397d0efb3fa4251bfa9ba --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_011_2b_scanned/case_011_2b_scanned_sig1_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee30d989e48f9bede51ef2f2a3ef87a43b9378d7e730240d3aad81e666322f86 +size 3919 diff --git a/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..bf6f664c5fee935d8afc698936e46286072c25b2 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44df6cc4893409dd51345adc9824a7d353e0994ef72e2cf978161e37b2577d09 +size 7237461 diff --git a/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..9c46c4b3b5d2b0bc4ef3bf882069fc487172214d --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_ground_truth.json @@ -0,0 +1,1325 @@ +{ + "case_id": "case_012_2b_procedural", + "expected_verdict": "REJECT", + "expected_reject_code": "NO_SIGNATURE", + "document_id": "case_000001", + "template": "proc_doc_04", + "template_kind": "procedural", + "document_type": "Loan Agreement", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Mei Young", + "role": "Corporate Guarantor" + }, + "signature_source": { + "signature_id": null, + "path": null, + "signer_group": null, + "source_type": "none", + "script": null + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -1.925, + "scale_factor": 0.8753, + "anchor_point": "center", + "margin_target_box": [ + 960.0, + 1880.0, + 1160.0, + 1960.0 + ], + "misplaced": true, + "misplacement_dx": -20.18, + "misplacement_dy": -30.73 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.9116, + "blur_sigma": 0.5101, + "feather_px": 0.8737, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.3214, + "tier": "easy", + "misplacement": 0.3288, + "whitespace": 0.8848, + "faintness": 0.0, + "smallness": 0.0, + "low_texture": 0.2932 + }, + "quality": { + "degraded": false, + "case_type": "2b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "2b", + "verdict": "REJECT", + "reject_code": "NO_SIGNATURE", + "target_box_id": "sig2", + "expected_signer_group": "signet_0049", + "expected_signer_script": "latin", + "actual_signer_group": null, + "actual_source_type": "printed", + "actual_signer_script": null, + "companion": null + }, + "augmentation": { + "rotate_deg": -1.925, + "scale_factor": 0.8753, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 2.99, + "discoloration_strength": 0.0, + "blur_sigma": 0.5101, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3957476644, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 3879667115, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2191633018, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2575074114, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2696093263, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 2800030967, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3358635290, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 916902544, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3314961386, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3094653390, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1363481643, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 2508336731, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1632708329, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 2794027202, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 2070134908, + "params": { + "tilt_deg": 2.99 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 242628051, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1558821413, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 1757709111, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1936877396, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 3662394351, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 960.0, + 1880.0, + 1160.0, + 1960.0 + ], + "signature_bbox_px": [ + 1004.0, + 1874.0, + 1175.0, + 1966.0 + ], + "target_box": [ + 960.0, + 1880.0, + 1160.0, + 1960.0 + ], + "actual_box": [ + 1004.0, + 1874.0, + 1175.0, + 1966.0 + ], + "search_box_px": [ + 945.0, + 1874.0, + 1175.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 1173, + 1963 + ], + [ + 1108, + 1965 + ], + [ + 1005, + 1955 + ], + [ + 1013, + 1874 + ], + [ + 1149, + 1874 + ], + [ + 1174, + 1944 + ] + ], + "convex_hull_px": [ + [ + 1173, + 1963 + ], + [ + 1108, + 1965 + ], + [ + 1005, + 1955 + ], + [ + 1013, + 1874 + ], + [ + 1149, + 1874 + ], + [ + 1174, + 1944 + ] + ], + "display_contours": [ + [ + [ + 1108.0, + 1964.0 + ], + [ + 1107.0, + 1965.0 + ], + [ + 1112.0, + 1965.0 + ], + [ + 1111.0, + 1964.0 + ] + ], + [ + [ + 1137.0, + 1963.0 + ], + [ + 1136.0, + 1964.0 + ], + [ + 1136.0, + 1965.0 + ], + [ + 1146.0, + 1965.0 + ], + [ + 1147.0, + 1964.0 + ], + [ + 1146.0, + 1963.0 + ] + ], + [ + [ + 1125.0, + 1963.0 + ], + [ + 1124.0, + 1964.0 + ], + [ + 1123.0, + 1964.0 + ], + [ + 1122.0, + 1965.0 + ], + [ + 1121.0, + 1965.0 + ], + [ + 1120.0, + 1964.0 + ], + [ + 1116.0, + 1964.0 + ], + [ + 1115.0, + 1965.0 + ], + [ + 1134.0, + 1965.0 + ], + [ + 1133.0, + 1964.0 + ], + [ + 1132.0, + 1964.0 + ], + [ + 1131.0, + 1963.0 + ] + ], + [ + [ + 1165.0, + 1961.0 + ], + [ + 1161.0, + 1965.0 + ], + [ + 1172.0, + 1965.0 + ], + [ + 1174.0, + 1963.0 + ], + [ + 1172.0, + 1961.0 + ] + ], + [ + [ + 1161.0, + 1943.0 + ], + [ + 1160.0, + 1944.0 + ], + [ + 1142.0, + 1944.0 + ], + [ + 1141.0, + 1945.0 + ], + [ + 1123.0, + 1945.0 + ], + [ + 1122.0, + 1946.0 + ], + [ + 1104.0, + 1946.0 + ], + [ + 1103.0, + 1947.0 + ], + [ + 1084.0, + 1947.0 + ], + [ + 1083.0, + 1948.0 + ], + [ + 1065.0, + 1948.0 + ], + [ + 1064.0, + 1949.0 + ], + [ + 1047.0, + 1949.0 + ], + [ + 1046.0, + 1950.0 + ], + [ + 1027.0, + 1950.0 + ], + [ + 1026.0, + 1951.0 + ], + [ + 1008.0, + 1951.0 + ], + [ + 1007.0, + 1952.0 + ], + [ + 1004.0, + 1952.0 + ], + [ + 1003.0, + 1953.0 + ], + [ + 1003.0, + 1954.0 + ], + [ + 1005.0, + 1956.0 + ], + [ + 1006.0, + 1955.0 + ], + [ + 1024.0, + 1955.0 + ], + [ + 1025.0, + 1954.0 + ], + [ + 1044.0, + 1954.0 + ], + [ + 1045.0, + 1953.0 + ], + [ + 1063.0, + 1953.0 + ], + [ + 1064.0, + 1952.0 + ], + [ + 1082.0, + 1952.0 + ], + [ + 1083.0, + 1951.0 + ], + [ + 1102.0, + 1951.0 + ], + [ + 1103.0, + 1950.0 + ], + [ + 1120.0, + 1950.0 + ], + [ + 1121.0, + 1949.0 + ], + [ + 1139.0, + 1949.0 + ], + [ + 1140.0, + 1948.0 + ], + [ + 1158.0, + 1948.0 + ], + [ + 1159.0, + 1947.0 + ], + [ + 1174.0, + 1947.0 + ], + [ + 1174.0, + 1943.0 + ] + ], + [ + [ + 1128.0, + 1874.0 + ], + [ + 1128.0, + 1885.0 + ], + [ + 1130.0, + 1887.0 + ], + [ + 1132.0, + 1887.0 + ], + [ + 1133.0, + 1886.0 + ], + [ + 1134.0, + 1887.0 + ], + [ + 1134.0, + 1889.0 + ], + [ + 1138.0, + 1893.0 + ], + [ + 1140.0, + 1893.0 + ], + [ + 1141.0, + 1894.0 + ], + [ + 1142.0, + 1893.0 + ], + [ + 1145.0, + 1893.0 + ], + [ + 1150.0, + 1888.0 + ], + [ + 1150.0, + 1885.0 + ], + [ + 1151.0, + 1884.0 + ], + [ + 1150.0, + 1883.0 + ], + [ + 1150.0, + 1874.0 + ], + [ + 1144.0, + 1874.0 + ], + [ + 1144.0, + 1880.0 + ], + [ + 1143.0, + 1881.0 + ], + [ + 1141.0, + 1881.0 + ], + [ + 1140.0, + 1880.0 + ], + [ + 1140.0, + 1878.0 + ], + [ + 1139.0, + 1877.0 + ], + [ + 1139.0, + 1876.0 + ], + [ + 1140.0, + 1875.0 + ], + [ + 1140.0, + 1874.0 + ] + ], + [ + [ + 1084.0, + 1874.0 + ], + [ + 1084.0, + 1882.0 + ], + [ + 1085.0, + 1883.0 + ], + [ + 1085.0, + 1884.0 + ], + [ + 1088.0, + 1887.0 + ], + [ + 1089.0, + 1887.0 + ], + [ + 1090.0, + 1888.0 + ], + [ + 1094.0, + 1888.0 + ], + [ + 1095.0, + 1887.0 + ], + [ + 1096.0, + 1887.0 + ], + [ + 1101.0, + 1882.0 + ], + [ + 1102.0, + 1883.0 + ], + [ + 1102.0, + 1885.0 + ], + [ + 1103.0, + 1886.0 + ], + [ + 1104.0, + 1886.0 + ], + [ + 1105.0, + 1887.0 + ], + [ + 1106.0, + 1887.0 + ], + [ + 1107.0, + 1888.0 + ], + [ + 1108.0, + 1887.0 + ], + [ + 1114.0, + 1887.0 + ], + [ + 1117.0, + 1884.0 + ], + [ + 1120.0, + 1887.0 + ], + [ + 1121.0, + 1887.0 + ], + [ + 1124.0, + 1884.0 + ], + [ + 1124.0, + 1874.0 + ], + [ + 1111.0, + 1874.0 + ], + [ + 1111.0, + 1880.0 + ], + [ + 1109.0, + 1882.0 + ], + [ + 1108.0, + 1882.0 + ], + [ + 1107.0, + 1881.0 + ], + [ + 1107.0, + 1874.0 + ], + [ + 1095.0, + 1874.0 + ], + [ + 1095.0, + 1880.0 + ], + [ + 1093.0, + 1882.0 + ], + [ + 1091.0, + 1882.0 + ], + [ + 1090.0, + 1881.0 + ], + [ + 1090.0, + 1875.0 + ], + [ + 1091.0, + 1874.0 + ] + ], + [ + [ + 1072.0, + 1874.0 + ], + [ + 1072.0, + 1876.0 + ], + [ + 1073.0, + 1877.0 + ], + [ + 1073.0, + 1878.0 + ], + [ + 1074.0, + 1879.0 + ], + [ + 1074.0, + 1886.0 + ], + [ + 1076.0, + 1888.0 + ], + [ + 1077.0, + 1888.0 + ], + [ + 1080.0, + 1885.0 + ], + [ + 1080.0, + 1877.0 + ], + [ + 1081.0, + 1876.0 + ], + [ + 1081.0, + 1875.0 + ], + [ + 1082.0, + 1874.0 + ] + ], + [ + [ + 1012.0, + 1874.0 + ], + [ + 1012.0, + 1875.0 + ], + [ + 1011.0, + 1876.0 + ], + [ + 1011.0, + 1879.0 + ], + [ + 1012.0, + 1880.0 + ], + [ + 1012.0, + 1884.0 + ], + [ + 1011.0, + 1885.0 + ], + [ + 1011.0, + 1886.0 + ], + [ + 1012.0, + 1887.0 + ], + [ + 1012.0, + 1888.0 + ], + [ + 1013.0, + 1889.0 + ], + [ + 1015.0, + 1889.0 + ], + [ + 1017.0, + 1887.0 + ], + [ + 1017.0, + 1886.0 + ], + [ + 1019.0, + 1884.0 + ], + [ + 1020.0, + 1885.0 + ], + [ + 1020.0, + 1886.0 + ], + [ + 1021.0, + 1887.0 + ], + [ + 1021.0, + 1888.0 + ], + [ + 1022.0, + 1889.0 + ], + [ + 1024.0, + 1889.0 + ], + [ + 1027.0, + 1886.0 + ], + [ + 1027.0, + 1884.0 + ], + [ + 1028.0, + 1883.0 + ], + [ + 1029.0, + 1884.0 + ], + [ + 1029.0, + 1886.0 + ], + [ + 1032.0, + 1889.0 + ], + [ + 1035.0, + 1886.0 + ], + [ + 1035.0, + 1885.0 + ], + [ + 1036.0, + 1884.0 + ], + [ + 1040.0, + 1888.0 + ], + [ + 1041.0, + 1888.0 + ], + [ + 1042.0, + 1889.0 + ], + [ + 1045.0, + 1889.0 + ], + [ + 1046.0, + 1888.0 + ], + [ + 1048.0, + 1888.0 + ], + [ + 1051.0, + 1885.0 + ], + [ + 1051.0, + 1884.0 + ], + [ + 1052.0, + 1883.0 + ], + [ + 1053.0, + 1884.0 + ], + [ + 1053.0, + 1886.0 + ], + [ + 1055.0, + 1888.0 + ], + [ + 1056.0, + 1888.0 + ], + [ + 1059.0, + 1885.0 + ], + [ + 1059.0, + 1874.0 + ], + [ + 1025.0, + 1874.0 + ], + [ + 1025.0, + 1875.0 + ], + [ + 1024.0, + 1876.0 + ], + [ + 1023.0, + 1876.0 + ], + [ + 1022.0, + 1875.0 + ], + [ + 1022.0, + 1874.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 1089.0, + 1919.5 + ], + "size": [ + 91.0, + 170.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 1086.49, + 1903.13 + ], + "orientation_deg": 12.71, + "ink_pixels": 1813, + "detected": true + }, + "metrics": { + "box_coverage_pct": 74.273, + "containment_pct": 87.454, + "ink_fill_ratio": 0.1152, + "polygon_overlap_proxy": 0.6712, + "distance_from_box_center_px": 31.41, + "margin_utilization": 1.1414, + "mean_dark_intensity": 146.21, + "gray_stddev": 42.41, + "signature_area_ratio": 0.00421 + }, + "provenance": { + "master_seed": 1012, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 502, + 527 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1006, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 887, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + }, + "additional_signatures": [ + { + "box_id": "sig1", + "role": "Primary Debtor", + "signature_id": "bhsig260_hindi_002345", + "signer_group": "bhsig260_hindi_0087", + "source_type": "genuine", + "script": "hindi", + "ref_path": "data/raw/bhsig260_hindi/87/H-S-087-G-02.tif" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_sig1_signature.png b/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_sig1_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..8a68764a52c5ed46e2824e51cf6b2cff26b8c1d5 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_012_2b_procedural/case_012_2b_procedural_sig1_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38e5de9a82b101bbe3b170a053b81316204974089838f2e3bc25cacb6d8d7945 +size 4505 diff --git a/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..820a86920963276b9e9e94aa192d7ab2330614af --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d218e9c0cfec06c3267cd59b2fa823d5b05819b6344feed76d1ad9bebcb0940 +size 893520 diff --git a/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..ef7af21e9806fa4262c03c320e9736d6d6a07f92 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_ground_truth.json @@ -0,0 +1,1063 @@ +{ + "case_id": "case_013_3a_scanned", + "expected_verdict": "REJECT", + "expected_reject_code": "IDENTITY_MISMATCH", + "document_id": "case_000001", + "template": "bank_doc_05", + "template_kind": "scanned", + "document_type": "corporate_bank_loan_agreement", + "page": 1, + "page_size": [ + 687, + 1024 + ], + "dpi": 300, + "signer": { + "name": "Margaret Mitchell", + "role": "Borrower" + }, + "signature_source": { + "signature_id": "signet_001632", + "path": "data/raw/signet/train/048/04_048.png", + "signer_group": "signet_0048", + "source_type": "genuine", + "script": "latin" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -1.301, + "scale_factor": 0.8728, + "anchor_point": "center", + "margin_target_box": [ + 65.0, + 780.0, + 320.0, + 815.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8542, + "blur_sigma": 0.1599, + "feather_px": 0.963, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.7691, + "tier": "hard", + "misplacement": 0.8147, + "whitespace": 0.8188, + "faintness": 0.6177, + "smallness": 0.9105, + "low_texture": 0.601 + }, + "quality": { + "degraded": false, + "case_type": "3a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "3a", + "verdict": "REJECT", + "reject_code": "IDENTITY_MISMATCH", + "target_box_id": "sig1", + "expected_signer_group": "signet_0022", + "expected_signer_script": "latin", + "actual_signer_group": "signet_0048", + "actual_source_type": "genuine", + "actual_signer_script": "latin", + "companion": null + }, + "augmentation": { + "rotate_deg": -1.301, + "scale_factor": 0.8728, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.2, + "blur_sigma": 0.1599, + "jpeg_quality": 51, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 1514643463, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 1296516100, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1533301602, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2597628768, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3310183649, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 4225898699, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2441184368, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 3379393539, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2984981933, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2922535126, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3737057403, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 420381183, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1769266441, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 2443224448, + "params": { + "resolved_tone": "grey" + } + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 2244050388, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 984449179, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2708610289, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2113116958, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1818474351, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 3145733453, + "params": { + "quality": 51 + } + } + } + }, + "geometry": { + "box_px": [ + 65.0, + 780.0, + 320.0, + 815.0 + ], + "signature_bbox_px": [ + 145.0, + 785.0, + 242.0, + 811.0 + ], + "target_box": [ + 65.0, + 780.0, + 320.0, + 815.0 + ], + "actual_box": [ + 145.0, + 785.0, + 242.0, + 811.0 + ], + "search_box_px": [ + 45.875000000000014, + 774.0, + 339.125, + 821.0 + ], + "signature_polygon_px": [ + [ + 241, + 801 + ], + [ + 215, + 810 + ], + [ + 155, + 806 + ], + [ + 145, + 792 + ], + [ + 157, + 786 + ], + [ + 168, + 785 + ], + [ + 209, + 789 + ], + [ + 237, + 796 + ] + ], + "convex_hull_px": [ + [ + 241, + 801 + ], + [ + 215, + 810 + ], + [ + 155, + 806 + ], + [ + 145, + 792 + ], + [ + 157, + 786 + ], + [ + 168, + 785 + ], + [ + 209, + 789 + ], + [ + 237, + 796 + ] + ], + "display_contours": [ + [ + [ + 199.0, + 805.0 + ], + [ + 198.0, + 806.0 + ], + [ + 198.0, + 807.0 + ], + [ + 199.0, + 808.0 + ], + [ + 202.0, + 808.0 + ], + [ + 204.0, + 806.0 + ], + [ + 203.0, + 805.0 + ] + ], + [ + [ + 191.0, + 805.0 + ], + [ + 190.0, + 806.0 + ], + [ + 190.0, + 807.0 + ], + [ + 191.0, + 808.0 + ], + [ + 192.0, + 808.0 + ], + [ + 194.0, + 806.0 + ], + [ + 193.0, + 805.0 + ] + ], + [ + [ + 197.0, + 800.0 + ], + [ + 196.0, + 801.0 + ], + [ + 196.0, + 802.0 + ], + [ + 197.0, + 803.0 + ], + [ + 198.0, + 803.0 + ], + [ + 200.0, + 801.0 + ], + [ + 199.0, + 800.0 + ] + ], + [ + [ + 208.0, + 788.0 + ], + [ + 207.0, + 789.0 + ], + [ + 206.0, + 789.0 + ], + [ + 205.0, + 790.0 + ], + [ + 205.0, + 792.0 + ], + [ + 207.0, + 794.0 + ], + [ + 208.0, + 793.0 + ], + [ + 210.0, + 793.0 + ], + [ + 212.0, + 795.0 + ], + [ + 212.0, + 796.0 + ], + [ + 213.0, + 797.0 + ], + [ + 213.0, + 798.0 + ], + [ + 215.0, + 800.0 + ], + [ + 215.0, + 802.0 + ], + [ + 213.0, + 804.0 + ], + [ + 213.0, + 805.0 + ], + [ + 212.0, + 806.0 + ], + [ + 212.0, + 809.0 + ], + [ + 214.0, + 811.0 + ], + [ + 215.0, + 811.0 + ], + [ + 220.0, + 806.0 + ], + [ + 220.0, + 804.0 + ], + [ + 221.0, + 803.0 + ], + [ + 222.0, + 804.0 + ], + [ + 223.0, + 803.0 + ], + [ + 228.0, + 803.0 + ], + [ + 229.0, + 802.0 + ], + [ + 230.0, + 803.0 + ], + [ + 230.0, + 804.0 + ], + [ + 231.0, + 805.0 + ], + [ + 232.0, + 805.0 + ], + [ + 234.0, + 803.0 + ], + [ + 240.0, + 803.0 + ], + [ + 242.0, + 801.0 + ], + [ + 241.0, + 800.0 + ], + [ + 239.0, + 800.0 + ], + [ + 237.0, + 798.0 + ], + [ + 238.0, + 797.0 + ], + [ + 238.0, + 796.0 + ], + [ + 237.0, + 795.0 + ], + [ + 234.0, + 795.0 + ], + [ + 233.0, + 796.0 + ], + [ + 232.0, + 796.0 + ], + [ + 231.0, + 795.0 + ], + [ + 229.0, + 795.0 + ], + [ + 228.0, + 796.0 + ], + [ + 225.0, + 796.0 + ], + [ + 224.0, + 797.0 + ], + [ + 224.0, + 798.0 + ], + [ + 223.0, + 799.0 + ], + [ + 222.0, + 799.0 + ], + [ + 221.0, + 798.0 + ], + [ + 222.0, + 797.0 + ], + [ + 221.0, + 796.0 + ], + [ + 219.0, + 796.0 + ], + [ + 218.0, + 797.0 + ], + [ + 217.0, + 796.0 + ], + [ + 217.0, + 795.0 + ], + [ + 211.0, + 789.0 + ], + [ + 210.0, + 789.0 + ], + [ + 209.0, + 788.0 + ] + ], + [ + [ + 162.0, + 784.0 + ], + [ + 161.0, + 785.0 + ], + [ + 157.0, + 785.0 + ], + [ + 156.0, + 786.0 + ], + [ + 155.0, + 786.0 + ], + [ + 154.0, + 787.0 + ], + [ + 152.0, + 787.0 + ], + [ + 151.0, + 788.0 + ], + [ + 149.0, + 788.0 + ], + [ + 147.0, + 790.0 + ], + [ + 146.0, + 790.0 + ], + [ + 144.0, + 792.0 + ], + [ + 144.0, + 795.0 + ], + [ + 145.0, + 796.0 + ], + [ + 146.0, + 796.0 + ], + [ + 149.0, + 793.0 + ], + [ + 150.0, + 793.0 + ], + [ + 151.0, + 792.0 + ], + [ + 152.0, + 793.0 + ], + [ + 152.0, + 801.0 + ], + [ + 153.0, + 802.0 + ], + [ + 153.0, + 805.0 + ], + [ + 155.0, + 807.0 + ], + [ + 158.0, + 804.0 + ], + [ + 157.0, + 803.0 + ], + [ + 157.0, + 801.0 + ], + [ + 158.0, + 800.0 + ], + [ + 159.0, + 800.0 + ], + [ + 160.0, + 801.0 + ], + [ + 160.0, + 802.0 + ], + [ + 161.0, + 803.0 + ], + [ + 171.0, + 803.0 + ], + [ + 173.0, + 801.0 + ], + [ + 175.0, + 803.0 + ], + [ + 176.0, + 802.0 + ], + [ + 177.0, + 803.0 + ], + [ + 181.0, + 803.0 + ], + [ + 183.0, + 801.0 + ], + [ + 181.0, + 799.0 + ], + [ + 179.0, + 799.0 + ], + [ + 178.0, + 800.0 + ], + [ + 175.0, + 800.0 + ], + [ + 174.0, + 801.0 + ], + [ + 173.0, + 800.0 + ], + [ + 173.0, + 797.0 + ], + [ + 174.0, + 796.0 + ], + [ + 175.0, + 796.0 + ], + [ + 177.0, + 794.0 + ], + [ + 176.0, + 793.0 + ], + [ + 174.0, + 793.0 + ], + [ + 173.0, + 794.0 + ], + [ + 171.0, + 792.0 + ], + [ + 168.0, + 792.0 + ], + [ + 166.0, + 794.0 + ], + [ + 166.0, + 798.0 + ], + [ + 165.0, + 799.0 + ], + [ + 164.0, + 798.0 + ], + [ + 161.0, + 798.0 + ], + [ + 160.0, + 797.0 + ], + [ + 159.0, + 797.0 + ], + [ + 158.0, + 796.0 + ], + [ + 157.0, + 796.0 + ], + [ + 156.0, + 795.0 + ], + [ + 156.0, + 791.0 + ], + [ + 157.0, + 790.0 + ], + [ + 158.0, + 790.0 + ], + [ + 159.0, + 789.0 + ], + [ + 162.0, + 789.0 + ], + [ + 163.0, + 788.0 + ], + [ + 165.0, + 788.0 + ], + [ + 166.0, + 787.0 + ], + [ + 167.0, + 787.0 + ], + [ + 169.0, + 785.0 + ], + [ + 168.0, + 784.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 192.92, + 797.63 + ], + "size": [ + 21.83, + 96.39 + ], + "angle_deg": -86.12 + }, + "centroid_px": [ + 188.81, + 797.6 + ], + "orientation_deg": 4.7, + "ink_pixels": 457, + "detected": true + }, + "metrics": { + "box_coverage_pct": 18.527, + "containment_pct": 100.0, + "ink_fill_ratio": 0.1812, + "polygon_overlap_proxy": 0.1853, + "distance_from_box_center_px": 3.69, + "margin_utilization": 0.3892, + "mean_dark_intensity": 164.12, + "gray_stddev": 23.94, + "signature_area_ratio": 0.00358 + }, + "provenance": { + "master_seed": 1013, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "name_sig1": { + "actual_box": [ + 77.0, + 855.0, + 184.0, + 869.0 + ], + "target_box": null + }, + "name_sig2": { + "actual_box": [ + 370.0, + 855.0, + 443.0, + 869.0 + ], + "target_box": null + } + } + }, + "additional_signatures": [ + { + "box_id": "sig2", + "role": "Authorized Signatory", + "signature_id": "bhsig260_hindi_002045", + "signer_group": "bhsig260_hindi_0007", + "source_type": "genuine", + "script": "hindi", + "ref_path": "data/raw/bhsig260_hindi/7/H-S-7-G-06.tif" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..fd7e2a217aefaab0fe2ebc9fa7f4f855b657f194 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b64a89a51d41d43513d33895b0b5e2f1f0c432dfb24d746da3648ab99666e32 +size 52753 diff --git a/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_sig2_signature.png b/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_sig2_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f6a3ecf6d55c95f55c9359b7df7ffe5b1710d5 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_013_3a_scanned/case_013_3a_scanned_sig2_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4395dd88dfa1c9158ea0a0acd31878bf1734d7d650a5d71e8096cf2233a3e301 +size 3942 diff --git a/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..5119c281e4da98ae724276f000fc90d2924a6b7a --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a850b7bf00383c7d6d75a070b68268ff00679adfa710a498fe0a2b5639d725c4 +size 6696149 diff --git a/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..edeea38701b1ef3c5329873206854ba21cca7457 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_ground_truth.json @@ -0,0 +1,2071 @@ +{ + "case_id": "case_014_3a_procedural", + "expected_verdict": "REJECT", + "expected_reject_code": "IDENTITY_MISMATCH", + "document_id": "case_000001", + "template": "proc_doc_12", + "template_kind": "procedural", + "document_type": "Promissory Note", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Samantha Adams", + "role": "Authorized Chief Treasurer" + }, + "signature_source": { + "signature_id": "bhsig260_bengali_000792", + "path": "data/raw/bhsig260_bengali/53/B-S-53-G-01.tif", + "signer_group": "bhsig260_bengali_0053", + "source_type": "genuine", + "script": "bengali" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 0.419, + "scale_factor": 0.895, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.9119, + "blur_sigma": 0.2749, + "feather_px": 1.1485, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.305, + "tier": "easy", + "misplacement": 0.3054, + "whitespace": 0.8746, + "faintness": 0.0, + "smallness": 0.0, + "low_texture": 0.2317 + }, + "quality": { + "degraded": false, + "case_type": "3a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "3a", + "verdict": "REJECT", + "reject_code": "IDENTITY_MISMATCH", + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_bengali_0039", + "expected_signer_script": "bengali", + "actual_signer_group": "bhsig260_bengali_0053", + "actual_source_type": "genuine", + "actual_signer_script": "bengali", + "companion": null + }, + "augmentation": { + "rotate_deg": 0.419, + "scale_factor": 0.895, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "corner", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": -1.262, + "discoloration_strength": 0.0, + "blur_sigma": 0.2749, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3338734686, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 2370457536, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3442635778, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3219091613, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3173657491, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 2724510685, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 181826557, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 3546560644, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2988857311, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3635277144, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3769447274, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 1392755400, + "params": { + "resolved_type": "corner", + "corner": "bottom_right" + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3071595745, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 1993282348, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 2795384734, + "params": { + "tilt_deg": -1.262 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2882816559, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3820780084, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2844628807, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 4261603484, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 1164292709, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 85.0, + 1890.0, + 315.0, + 1966.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 85.0, + 1890.0, + 315.0, + 1966.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 314, + 1948 + ], + [ + 288, + 1965 + ], + [ + 85, + 1965 + ], + [ + 85, + 1943 + ], + [ + 109, + 1898 + ], + [ + 255, + 1890 + ] + ], + "convex_hull_px": [ + [ + 314, + 1948 + ], + [ + 288, + 1965 + ], + [ + 85, + 1965 + ], + [ + 85, + 1943 + ], + [ + 109, + 1898 + ], + [ + 255, + 1890 + ] + ], + "display_contours": [ + [ + [ + 286.0, + 1964.0 + ], + [ + 285.0, + 1965.0 + ], + [ + 289.0, + 1965.0 + ], + [ + 288.0, + 1964.0 + ] + ], + [ + [ + 278.0, + 1964.0 + ], + [ + 277.0, + 1965.0 + ], + [ + 282.0, + 1965.0 + ], + [ + 281.0, + 1964.0 + ] + ], + [ + [ + 259.0, + 1964.0 + ], + [ + 258.0, + 1965.0 + ], + [ + 263.0, + 1965.0 + ], + [ + 262.0, + 1964.0 + ] + ], + [ + [ + 251.0, + 1964.0 + ], + [ + 250.0, + 1965.0 + ], + [ + 255.0, + 1965.0 + ], + [ + 254.0, + 1964.0 + ] + ], + [ + [ + 241.0, + 1963.0 + ], + [ + 239.0, + 1965.0 + ], + [ + 246.0, + 1965.0 + ], + [ + 245.0, + 1964.0 + ], + [ + 244.0, + 1964.0 + ], + [ + 243.0, + 1963.0 + ] + ], + [ + [ + 220.0, + 1963.0 + ], + [ + 219.0, + 1964.0 + ], + [ + 219.0, + 1965.0 + ], + [ + 229.0, + 1965.0 + ], + [ + 230.0, + 1964.0 + ], + [ + 229.0, + 1963.0 + ] + ], + [ + [ + 206.0, + 1963.0 + ], + [ + 204.0, + 1965.0 + ], + [ + 216.0, + 1965.0 + ], + [ + 214.0, + 1963.0 + ] + ], + [ + [ + 187.0, + 1962.0 + ], + [ + 186.0, + 1963.0 + ], + [ + 186.0, + 1965.0 + ], + [ + 196.0, + 1965.0 + ], + [ + 197.0, + 1964.0 + ], + [ + 195.0, + 1962.0 + ] + ], + [ + [ + 173.0, + 1962.0 + ], + [ + 172.0, + 1963.0 + ], + [ + 172.0, + 1965.0 + ], + [ + 183.0, + 1965.0 + ], + [ + 183.0, + 1964.0 + ], + [ + 181.0, + 1962.0 + ] + ], + [ + [ + 166.0, + 1962.0 + ], + [ + 165.0, + 1963.0 + ], + [ + 165.0, + 1965.0 + ], + [ + 170.0, + 1965.0 + ], + [ + 170.0, + 1963.0 + ], + [ + 169.0, + 1962.0 + ] + ], + [ + [ + 136.0, + 1961.0 + ], + [ + 134.0, + 1963.0 + ], + [ + 134.0, + 1965.0 + ], + [ + 142.0, + 1965.0 + ], + [ + 143.0, + 1964.0 + ], + [ + 144.0, + 1965.0 + ], + [ + 155.0, + 1965.0 + ], + [ + 156.0, + 1964.0 + ], + [ + 157.0, + 1965.0 + ], + [ + 162.0, + 1965.0 + ], + [ + 162.0, + 1963.0 + ], + [ + 161.0, + 1962.0 + ], + [ + 158.0, + 1962.0 + ], + [ + 157.0, + 1963.0 + ], + [ + 156.0, + 1962.0 + ], + [ + 153.0, + 1962.0 + ], + [ + 152.0, + 1961.0 + ], + [ + 144.0, + 1961.0 + ], + [ + 142.0, + 1963.0 + ], + [ + 140.0, + 1961.0 + ] + ], + [ + [ + 118.0, + 1961.0 + ], + [ + 117.0, + 1962.0 + ], + [ + 117.0, + 1965.0 + ], + [ + 131.0, + 1965.0 + ], + [ + 131.0, + 1962.0 + ], + [ + 130.0, + 1961.0 + ], + [ + 126.0, + 1961.0 + ], + [ + 125.0, + 1962.0 + ], + [ + 125.0, + 1963.0 + ], + [ + 124.0, + 1964.0 + ], + [ + 123.0, + 1963.0 + ], + [ + 123.0, + 1962.0 + ], + [ + 122.0, + 1961.0 + ] + ], + [ + [ + 96.0, + 1960.0 + ], + [ + 95.0, + 1961.0 + ], + [ + 95.0, + 1965.0 + ], + [ + 107.0, + 1965.0 + ], + [ + 108.0, + 1964.0 + ], + [ + 111.0, + 1964.0 + ], + [ + 112.0, + 1965.0 + ], + [ + 113.0, + 1965.0 + ], + [ + 115.0, + 1963.0 + ], + [ + 115.0, + 1962.0 + ], + [ + 114.0, + 1961.0 + ], + [ + 104.0, + 1961.0 + ], + [ + 102.0, + 1963.0 + ], + [ + 102.0, + 1964.0 + ], + [ + 101.0, + 1965.0 + ], + [ + 100.0, + 1964.0 + ], + [ + 100.0, + 1961.0 + ], + [ + 99.0, + 1960.0 + ] + ], + [ + [ + 85.0, + 1960.0 + ], + [ + 85.0, + 1965.0 + ], + [ + 87.0, + 1965.0 + ], + [ + 88.0, + 1964.0 + ], + [ + 90.0, + 1964.0 + ], + [ + 91.0, + 1965.0 + ], + [ + 93.0, + 1963.0 + ], + [ + 93.0, + 1961.0 + ], + [ + 92.0, + 1960.0 + ] + ], + [ + [ + 85.0, + 1942.0 + ], + [ + 85.0, + 1946.0 + ], + [ + 118.0, + 1946.0 + ], + [ + 119.0, + 1947.0 + ], + [ + 165.0, + 1947.0 + ], + [ + 166.0, + 1948.0 + ], + [ + 207.0, + 1948.0 + ], + [ + 208.0, + 1949.0 + ], + [ + 254.0, + 1949.0 + ], + [ + 255.0, + 1950.0 + ], + [ + 300.0, + 1950.0 + ], + [ + 301.0, + 1951.0 + ], + [ + 314.0, + 1951.0 + ], + [ + 314.0, + 1947.0 + ], + [ + 294.0, + 1947.0 + ], + [ + 293.0, + 1946.0 + ], + [ + 249.0, + 1946.0 + ], + [ + 248.0, + 1945.0 + ], + [ + 203.0, + 1945.0 + ], + [ + 202.0, + 1944.0 + ], + [ + 158.0, + 1944.0 + ], + [ + 157.0, + 1943.0 + ], + [ + 112.0, + 1943.0 + ], + [ + 111.0, + 1942.0 + ] + ], + [ + [ + 227.0, + 1902.0 + ], + [ + 226.0, + 1903.0 + ], + [ + 226.0, + 1905.0 + ], + [ + 227.0, + 1906.0 + ], + [ + 228.0, + 1906.0 + ], + [ + 231.0, + 1903.0 + ], + [ + 230.0, + 1902.0 + ] + ], + [ + [ + 206.0, + 1901.0 + ], + [ + 205.0, + 1902.0 + ], + [ + 205.0, + 1905.0 + ], + [ + 206.0, + 1906.0 + ], + [ + 207.0, + 1906.0 + ], + [ + 210.0, + 1903.0 + ], + [ + 211.0, + 1904.0 + ], + [ + 211.0, + 1905.0 + ], + [ + 212.0, + 1906.0 + ], + [ + 212.0, + 1907.0 + ], + [ + 213.0, + 1908.0 + ], + [ + 214.0, + 1908.0 + ], + [ + 217.0, + 1905.0 + ], + [ + 219.0, + 1907.0 + ], + [ + 223.0, + 1903.0 + ], + [ + 222.0, + 1902.0 + ], + [ + 218.0, + 1902.0 + ], + [ + 217.0, + 1903.0 + ], + [ + 216.0, + 1902.0 + ], + [ + 212.0, + 1902.0 + ], + [ + 211.0, + 1903.0 + ], + [ + 210.0, + 1903.0 + ], + [ + 209.0, + 1902.0 + ], + [ + 208.0, + 1902.0 + ], + [ + 207.0, + 1901.0 + ] + ], + [ + [ + 192.0, + 1897.0 + ], + [ + 191.0, + 1898.0 + ], + [ + 191.0, + 1901.0 + ], + [ + 192.0, + 1902.0 + ], + [ + 192.0, + 1904.0 + ], + [ + 191.0, + 1905.0 + ], + [ + 191.0, + 1908.0 + ], + [ + 192.0, + 1909.0 + ], + [ + 192.0, + 1911.0 + ], + [ + 191.0, + 1912.0 + ], + [ + 190.0, + 1912.0 + ], + [ + 189.0, + 1913.0 + ], + [ + 189.0, + 1914.0 + ], + [ + 188.0, + 1915.0 + ], + [ + 188.0, + 1917.0 + ], + [ + 190.0, + 1919.0 + ], + [ + 196.0, + 1913.0 + ], + [ + 196.0, + 1912.0 + ], + [ + 198.0, + 1910.0 + ], + [ + 197.0, + 1909.0 + ], + [ + 197.0, + 1908.0 + ], + [ + 196.0, + 1907.0 + ], + [ + 196.0, + 1906.0 + ], + [ + 197.0, + 1905.0 + ], + [ + 198.0, + 1905.0 + ], + [ + 200.0, + 1903.0 + ], + [ + 201.0, + 1903.0 + ], + [ + 205.0, + 1899.0 + ], + [ + 204.0, + 1898.0 + ], + [ + 194.0, + 1898.0 + ], + [ + 193.0, + 1897.0 + ] + ], + [ + [ + 211.0, + 1893.0 + ], + [ + 210.0, + 1894.0 + ], + [ + 210.0, + 1895.0 + ], + [ + 211.0, + 1896.0 + ], + [ + 215.0, + 1896.0 + ], + [ + 217.0, + 1894.0 + ], + [ + 216.0, + 1893.0 + ] + ], + [ + [ + 223.0, + 1892.0 + ], + [ + 222.0, + 1893.0 + ], + [ + 221.0, + 1893.0 + ], + [ + 220.0, + 1894.0 + ], + [ + 220.0, + 1895.0 + ], + [ + 221.0, + 1896.0 + ], + [ + 226.0, + 1896.0 + ], + [ + 228.0, + 1894.0 + ], + [ + 229.0, + 1895.0 + ], + [ + 229.0, + 1896.0 + ], + [ + 227.0, + 1898.0 + ], + [ + 227.0, + 1899.0 + ], + [ + 228.0, + 1900.0 + ], + [ + 231.0, + 1900.0 + ], + [ + 233.0, + 1898.0 + ], + [ + 233.0, + 1897.0 + ], + [ + 236.0, + 1894.0 + ], + [ + 235.0, + 1893.0 + ], + [ + 233.0, + 1893.0 + ], + [ + 232.0, + 1892.0 + ], + [ + 231.0, + 1892.0 + ], + [ + 230.0, + 1893.0 + ], + [ + 230.0, + 1894.0 + ], + [ + 229.0, + 1895.0 + ], + [ + 226.0, + 1892.0 + ] + ], + [ + [ + 138.0, + 1892.0 + ], + [ + 137.0, + 1893.0 + ], + [ + 130.0, + 1893.0 + ], + [ + 129.0, + 1894.0 + ], + [ + 128.0, + 1894.0 + ], + [ + 127.0, + 1895.0 + ], + [ + 112.0, + 1895.0 + ], + [ + 111.0, + 1896.0 + ], + [ + 110.0, + 1896.0 + ], + [ + 108.0, + 1898.0 + ], + [ + 108.0, + 1902.0 + ], + [ + 110.0, + 1904.0 + ], + [ + 112.0, + 1904.0 + ], + [ + 113.0, + 1905.0 + ], + [ + 114.0, + 1905.0 + ], + [ + 115.0, + 1906.0 + ], + [ + 119.0, + 1902.0 + ], + [ + 120.0, + 1903.0 + ], + [ + 120.0, + 1904.0 + ], + [ + 121.0, + 1905.0 + ], + [ + 122.0, + 1905.0 + ], + [ + 126.0, + 1901.0 + ], + [ + 128.0, + 1903.0 + ], + [ + 128.0, + 1904.0 + ], + [ + 129.0, + 1905.0 + ], + [ + 130.0, + 1905.0 + ], + [ + 131.0, + 1906.0 + ], + [ + 132.0, + 1906.0 + ], + [ + 133.0, + 1905.0 + ], + [ + 134.0, + 1905.0 + ], + [ + 138.0, + 1901.0 + ], + [ + 139.0, + 1902.0 + ], + [ + 139.0, + 1903.0 + ], + [ + 136.0, + 1906.0 + ], + [ + 136.0, + 1907.0 + ], + [ + 132.0, + 1911.0 + ], + [ + 132.0, + 1914.0 + ], + [ + 134.0, + 1916.0 + ], + [ + 136.0, + 1916.0 + ], + [ + 137.0, + 1917.0 + ], + [ + 137.0, + 1919.0 + ], + [ + 139.0, + 1921.0 + ], + [ + 140.0, + 1921.0 + ], + [ + 141.0, + 1920.0 + ], + [ + 142.0, + 1920.0 + ], + [ + 143.0, + 1919.0 + ], + [ + 144.0, + 1919.0 + ], + [ + 145.0, + 1918.0 + ], + [ + 146.0, + 1918.0 + ], + [ + 148.0, + 1916.0 + ], + [ + 149.0, + 1916.0 + ], + [ + 150.0, + 1915.0 + ], + [ + 151.0, + 1915.0 + ], + [ + 152.0, + 1916.0 + ], + [ + 152.0, + 1918.0 + ], + [ + 153.0, + 1919.0 + ], + [ + 153.0, + 1921.0 + ], + [ + 156.0, + 1924.0 + ], + [ + 159.0, + 1924.0 + ], + [ + 160.0, + 1923.0 + ], + [ + 162.0, + 1923.0 + ], + [ + 164.0, + 1921.0 + ], + [ + 165.0, + 1921.0 + ], + [ + 166.0, + 1920.0 + ], + [ + 167.0, + 1920.0 + ], + [ + 169.0, + 1918.0 + ], + [ + 170.0, + 1918.0 + ], + [ + 176.0, + 1912.0 + ], + [ + 176.0, + 1911.0 + ], + [ + 175.0, + 1910.0 + ], + [ + 173.0, + 1910.0 + ], + [ + 171.0, + 1912.0 + ], + [ + 170.0, + 1912.0 + ], + [ + 168.0, + 1914.0 + ], + [ + 167.0, + 1914.0 + ], + [ + 166.0, + 1915.0 + ], + [ + 165.0, + 1915.0 + ], + [ + 162.0, + 1918.0 + ], + [ + 161.0, + 1918.0 + ], + [ + 160.0, + 1917.0 + ], + [ + 162.0, + 1915.0 + ], + [ + 162.0, + 1913.0 + ], + [ + 163.0, + 1912.0 + ], + [ + 163.0, + 1907.0 + ], + [ + 165.0, + 1905.0 + ], + [ + 166.0, + 1905.0 + ], + [ + 168.0, + 1903.0 + ], + [ + 168.0, + 1902.0 + ], + [ + 169.0, + 1901.0 + ], + [ + 170.0, + 1902.0 + ], + [ + 170.0, + 1907.0 + ], + [ + 171.0, + 1908.0 + ], + [ + 172.0, + 1908.0 + ], + [ + 175.0, + 1905.0 + ], + [ + 175.0, + 1902.0 + ], + [ + 176.0, + 1901.0 + ], + [ + 176.0, + 1900.0 + ], + [ + 178.0, + 1898.0 + ], + [ + 185.0, + 1898.0 + ], + [ + 188.0, + 1895.0 + ], + [ + 186.0, + 1893.0 + ], + [ + 178.0, + 1893.0 + ], + [ + 177.0, + 1894.0 + ], + [ + 174.0, + 1894.0 + ], + [ + 172.0, + 1896.0 + ], + [ + 168.0, + 1896.0 + ], + [ + 167.0, + 1897.0 + ], + [ + 164.0, + 1897.0 + ], + [ + 163.0, + 1898.0 + ], + [ + 162.0, + 1898.0 + ], + [ + 161.0, + 1897.0 + ], + [ + 161.0, + 1896.0 + ], + [ + 159.0, + 1894.0 + ], + [ + 153.0, + 1894.0 + ], + [ + 152.0, + 1895.0 + ], + [ + 150.0, + 1895.0 + ], + [ + 148.0, + 1897.0 + ], + [ + 148.0, + 1900.0 + ], + [ + 152.0, + 1904.0 + ], + [ + 151.0, + 1905.0 + ], + [ + 149.0, + 1905.0 + ], + [ + 148.0, + 1906.0 + ], + [ + 146.0, + 1906.0 + ], + [ + 144.0, + 1908.0 + ], + [ + 143.0, + 1908.0 + ], + [ + 140.0, + 1911.0 + ], + [ + 139.0, + 1911.0 + ], + [ + 138.0, + 1910.0 + ], + [ + 138.0, + 1909.0 + ], + [ + 143.0, + 1904.0 + ], + [ + 143.0, + 1903.0 + ], + [ + 144.0, + 1902.0 + ], + [ + 144.0, + 1901.0 + ], + [ + 145.0, + 1900.0 + ], + [ + 145.0, + 1898.0 + ], + [ + 146.0, + 1897.0 + ], + [ + 146.0, + 1895.0 + ], + [ + 144.0, + 1893.0 + ], + [ + 143.0, + 1893.0 + ], + [ + 142.0, + 1892.0 + ] + ], + [ + [ + 243.0, + 1889.0 + ], + [ + 242.0, + 1890.0 + ], + [ + 242.0, + 1891.0 + ], + [ + 243.0, + 1892.0 + ], + [ + 245.0, + 1892.0 + ], + [ + 246.0, + 1891.0 + ], + [ + 247.0, + 1892.0 + ], + [ + 254.0, + 1892.0 + ], + [ + 256.0, + 1890.0 + ], + [ + 255.0, + 1889.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 199.5, + 1927.5 + ], + "size": [ + 75.0, + 229.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 171.65, + 1925.99 + ], + "orientation_deg": 6.27, + "ink_pixels": 2192, + "detected": true + }, + "metrics": { + "box_coverage_pct": 76.666, + "containment_pct": 88.077, + "ink_fill_ratio": 0.1254, + "polygon_overlap_proxy": 0.6946, + "distance_from_box_center_px": 28.97, + "margin_utilization": 1.1466, + "mean_dark_intensity": 152.27, + "gray_stddev": 46.1, + "signature_area_ratio": 0.00467 + }, + "provenance": { + "master_seed": 1014, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 423, + 527 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1006, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 880, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..c9f1330abe5014b8a039ec7e03d810d4671cdc61 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_014_3a_procedural/case_014_3a_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7d8a467a7bce5b08f1424e6b42d9e488902e9689dee79d564ff6b2f80ad31c3 +size 4408 diff --git a/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..61cf1a86627d26331e91dea0ccefacf84f88ee57 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68f7713b7006a3b4c951cf12702c3831135a5fc7ae524079a487cde8d87dab0e +size 1256353 diff --git a/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..c36aa1617841dfc14a695c7c7bd7752fbf58c3ef --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_ground_truth.json @@ -0,0 +1,1052 @@ +{ + "case_id": "case_015_3b_scanned", + "expected_verdict": "REJECT", + "expected_reject_code": "IDENTITY_MISMATCH", + "document_id": "case_000001", + "template": "bank_doc_06", + "template_kind": "scanned", + "document_type": "corporate_bank_loan_agreement", + "page": 1, + "page_size": [ + 1024, + 559 + ], + "dpi": 300, + "signer": { + "name": "Linda Nguyen", + "role": "Borrower (Authorized Signatory)" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_000196", + "path": "data/raw/bhsig260_hindi/11/H-S-011-F-05.tif", + "signer_group": "bhsig260_hindi_0011", + "source_type": "forged", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 1.571, + "scale_factor": 0.8671, + "anchor_point": "center", + "margin_target_box": [ + 90.0, + 421.0, + 345.0, + 433.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.7523, + "blur_sigma": 0.1604, + "feather_px": 1.0153, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.666, + "tier": "hard", + "misplacement": 0.5507, + "whitespace": 0.814, + "faintness": 0.7481, + "smallness": 0.7873, + "low_texture": 0.4273 + }, + "quality": { + "degraded": false, + "case_type": "3b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "3b", + "verdict": "REJECT", + "reject_code": "IDENTITY_MISMATCH", + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_hindi_0011", + "expected_signer_script": "hindi", + "actual_signer_group": "bhsig260_hindi_0011", + "actual_source_type": "forged", + "actual_signer_script": "hindi", + "companion": null + }, + "augmentation": { + "rotate_deg": 1.571, + "scale_factor": 0.8671, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "corner", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": -1.921, + "discoloration_strength": 0.0, + "blur_sigma": 0.1604, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 1117724120, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 3422076270, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3922872201, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 239454033, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1068486915, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3520776287, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3671221098, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 2957489714, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2558091711, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1333155895, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 935497882, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 3320686448, + "params": { + "resolved_type": "corner", + "corner": "top_right" + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 456005929, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 873634902, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 3364307481, + "params": { + "tilt_deg": -1.921 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3973367670, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4229042773, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 1771546797, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3676470711, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 348116856, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 90.0, + 421.0, + 345.0, + 433.0 + ], + "signature_bbox_px": [ + 71.0, + 415.0, + 303.0, + 436.0 + ], + "target_box": [ + 90.0, + 421.0, + 345.0, + 433.0 + ], + "actual_box": [ + 71.0, + 415.0, + 303.0, + 436.0 + ], + "search_box_px": [ + 70.87500000000001, + 415.0, + 364.125, + 439.0 + ], + "signature_polygon_px": [ + [ + 302, + 415 + ], + [ + 241, + 431 + ], + [ + 71, + 434 + ], + [ + 71, + 425 + ], + [ + 171, + 415 + ] + ], + "convex_hull_px": [ + [ + 302, + 415 + ], + [ + 241, + 431 + ], + [ + 71, + 434 + ], + [ + 71, + 425 + ], + [ + 171, + 415 + ] + ], + "display_contours": [ + [ + [ + 121.0, + 433.0 + ], + [ + 120.0, + 434.0 + ], + [ + 120.0, + 435.0 + ], + [ + 121.0, + 436.0 + ], + [ + 123.0, + 434.0 + ], + [ + 122.0, + 433.0 + ] + ], + [ + [ + 85.0, + 432.0 + ], + [ + 84.0, + 433.0 + ], + [ + 84.0, + 434.0 + ], + [ + 85.0, + 435.0 + ], + [ + 88.0, + 435.0 + ], + [ + 90.0, + 433.0 + ], + [ + 89.0, + 432.0 + ] + ], + [ + [ + 71.0, + 424.0 + ], + [ + 71.0, + 430.0 + ], + [ + 72.0, + 431.0 + ], + [ + 71.0, + 432.0 + ], + [ + 71.0, + 435.0 + ], + [ + 73.0, + 435.0 + ], + [ + 76.0, + 432.0 + ], + [ + 76.0, + 430.0 + ], + [ + 77.0, + 429.0 + ], + [ + 78.0, + 430.0 + ], + [ + 78.0, + 432.0 + ], + [ + 79.0, + 433.0 + ], + [ + 81.0, + 431.0 + ], + [ + 81.0, + 430.0 + ], + [ + 79.0, + 428.0 + ], + [ + 81.0, + 426.0 + ], + [ + 81.0, + 425.0 + ], + [ + 80.0, + 424.0 + ], + [ + 79.0, + 424.0 + ], + [ + 78.0, + 425.0 + ], + [ + 77.0, + 424.0 + ], + [ + 76.0, + 424.0 + ], + [ + 74.0, + 426.0 + ], + [ + 72.0, + 424.0 + ] + ], + [ + [ + 299.0, + 415.0 + ], + [ + 299.0, + 417.0 + ], + [ + 300.0, + 418.0 + ], + [ + 303.0, + 415.0 + ] + ], + [ + [ + 291.0, + 415.0 + ], + [ + 291.0, + 417.0 + ], + [ + 292.0, + 418.0 + ], + [ + 294.0, + 418.0 + ], + [ + 297.0, + 415.0 + ] + ], + [ + [ + 211.0, + 415.0 + ], + [ + 211.0, + 416.0 + ], + [ + 213.0, + 418.0 + ], + [ + 213.0, + 419.0 + ], + [ + 212.0, + 420.0 + ], + [ + 212.0, + 422.0 + ], + [ + 213.0, + 423.0 + ], + [ + 215.0, + 423.0 + ], + [ + 216.0, + 424.0 + ], + [ + 216.0, + 427.0 + ], + [ + 217.0, + 428.0 + ], + [ + 220.0, + 428.0 + ], + [ + 221.0, + 429.0 + ], + [ + 221.0, + 431.0 + ], + [ + 222.0, + 432.0 + ], + [ + 225.0, + 429.0 + ], + [ + 225.0, + 428.0 + ], + [ + 227.0, + 426.0 + ], + [ + 228.0, + 426.0 + ], + [ + 230.0, + 428.0 + ], + [ + 232.0, + 426.0 + ], + [ + 233.0, + 426.0 + ], + [ + 234.0, + 427.0 + ], + [ + 234.0, + 428.0 + ], + [ + 237.0, + 431.0 + ], + [ + 238.0, + 431.0 + ], + [ + 239.0, + 432.0 + ], + [ + 241.0, + 432.0 + ], + [ + 245.0, + 428.0 + ], + [ + 251.0, + 428.0 + ], + [ + 253.0, + 430.0 + ], + [ + 255.0, + 428.0 + ], + [ + 255.0, + 425.0 + ], + [ + 256.0, + 424.0 + ], + [ + 257.0, + 425.0 + ], + [ + 257.0, + 427.0 + ], + [ + 258.0, + 428.0 + ], + [ + 260.0, + 426.0 + ], + [ + 260.0, + 420.0 + ], + [ + 259.0, + 419.0 + ], + [ + 259.0, + 418.0 + ], + [ + 258.0, + 417.0 + ], + [ + 254.0, + 417.0 + ], + [ + 253.0, + 416.0 + ], + [ + 254.0, + 415.0 + ], + [ + 248.0, + 415.0 + ], + [ + 248.0, + 416.0 + ], + [ + 247.0, + 417.0 + ], + [ + 244.0, + 417.0 + ], + [ + 243.0, + 418.0 + ], + [ + 240.0, + 418.0 + ], + [ + 238.0, + 416.0 + ], + [ + 238.0, + 415.0 + ], + [ + 226.0, + 415.0 + ], + [ + 226.0, + 417.0 + ], + [ + 224.0, + 419.0 + ], + [ + 223.0, + 419.0 + ], + [ + 221.0, + 417.0 + ], + [ + 221.0, + 415.0 + ] + ], + [ + [ + 170.0, + 415.0 + ], + [ + 169.0, + 416.0 + ], + [ + 167.0, + 416.0 + ], + [ + 166.0, + 417.0 + ], + [ + 166.0, + 423.0 + ], + [ + 169.0, + 426.0 + ], + [ + 169.0, + 427.0 + ], + [ + 168.0, + 428.0 + ], + [ + 168.0, + 429.0 + ], + [ + 171.0, + 432.0 + ], + [ + 172.0, + 431.0 + ], + [ + 173.0, + 431.0 + ], + [ + 174.0, + 432.0 + ], + [ + 175.0, + 432.0 + ], + [ + 176.0, + 433.0 + ], + [ + 178.0, + 431.0 + ], + [ + 178.0, + 430.0 + ], + [ + 179.0, + 429.0 + ], + [ + 180.0, + 430.0 + ], + [ + 182.0, + 428.0 + ], + [ + 182.0, + 425.0 + ], + [ + 183.0, + 424.0 + ], + [ + 184.0, + 424.0 + ], + [ + 185.0, + 425.0 + ], + [ + 185.0, + 427.0 + ], + [ + 186.0, + 428.0 + ], + [ + 192.0, + 428.0 + ], + [ + 193.0, + 429.0 + ], + [ + 195.0, + 427.0 + ], + [ + 196.0, + 427.0 + ], + [ + 197.0, + 428.0 + ], + [ + 199.0, + 428.0 + ], + [ + 200.0, + 429.0 + ], + [ + 201.0, + 429.0 + ], + [ + 202.0, + 430.0 + ], + [ + 204.0, + 428.0 + ], + [ + 205.0, + 428.0 + ], + [ + 207.0, + 426.0 + ], + [ + 207.0, + 424.0 + ], + [ + 205.0, + 422.0 + ], + [ + 204.0, + 422.0 + ], + [ + 201.0, + 419.0 + ], + [ + 190.0, + 419.0 + ], + [ + 189.0, + 420.0 + ], + [ + 188.0, + 420.0 + ], + [ + 187.0, + 419.0 + ], + [ + 183.0, + 419.0 + ], + [ + 182.0, + 420.0 + ], + [ + 179.0, + 420.0 + ], + [ + 178.0, + 419.0 + ], + [ + 178.0, + 418.0 + ], + [ + 177.0, + 417.0 + ], + [ + 177.0, + 415.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 186.47, + 423.65 + ], + "size": [ + 231.5, + 18.32 + ], + "angle_deg": -1.91 + }, + "centroid_px": [ + 204.92, + 423.02 + ], + "orientation_deg": -2.33, + "ink_pixels": 906, + "detected": true + }, + "metrics": { + "box_coverage_pct": 61.021, + "containment_pct": 63.019, + "ink_fill_ratio": 0.186, + "polygon_overlap_proxy": 0.4493, + "distance_from_box_center_px": 13.2, + "margin_utilization": 1.1516, + "mean_dark_intensity": 179.77, + "gray_stddev": 34.36, + "signature_area_ratio": 0.00851 + }, + "provenance": { + "master_seed": 1015, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "name_sig1": { + "actual_box": [ + 151.0, + 452.0, + 237.0, + 465.0 + ], + "target_box": null + }, + "name_sig2": { + "actual_box": [ + 626.0, + 451.0, + 696.0, + 461.0 + ], + "target_box": null + } + } + }, + "additional_signatures": [ + { + "box_id": "sig2", + "role": "Bank Representative", + "signature_id": "signet_001447", + "signer_group": "signet_0040", + "source_type": "genuine", + "script": "latin", + "ref_path": "data/raw/signet/train/040/07_040.png" + } + ] +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..75461ba5124ab67249dbf53eba601cd45aa4bd29 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfa5b8f65d8f8a5afa045d4667705efe8bb9ecd7ccb5da72e14103a5a5303601 +size 5432 diff --git a/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_sig2_signature.png b/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_sig2_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..9a90d32d0282c508d4f7098b0f9571259b76510d --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_015_3b_scanned/case_015_3b_scanned_sig2_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75ad5563ab1305415b98a65ad8896df62685af75d45d6cd2ca5f687a5e4e778e +size 41068 diff --git a/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..b2430bff1407d183e18489e879dfb5d352da3f1b --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6eeb08a7e96d217fd9538cb3cf8d62275019f78b4ffd61d1371ab7b49c5a6a4 +size 2096049 diff --git a/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..941916fc02a9a0c45bcb4f2e67aac2685c4e3fd3 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_ground_truth.json @@ -0,0 +1,1383 @@ +{ + "case_id": "case_016_3b_procedural", + "expected_verdict": "REJECT", + "expected_reject_code": "IDENTITY_MISMATCH", + "document_id": "case_000001", + "template": "proc_doc_12", + "template_kind": "procedural", + "document_type": "Promissory Note", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Fatima Lee", + "role": "Authorized Chief Treasurer" + }, + "signature_source": { + "signature_id": "bhsig260_bengali_000756", + "path": "data/raw/bhsig260_bengali/51/B-S-51-F-05.tif", + "signer_group": "bhsig260_bengali_0051", + "source_type": "forged", + "script": "bengali" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 2.393, + "scale_factor": 0.7882, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8167, + "blur_sigma": 0.335, + "feather_px": 0.892, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.5829, + "tier": "medium", + "misplacement": 0.8292, + "whitespace": 0.7772, + "faintness": 0.0, + "smallness": 0.6233, + "low_texture": 0.4378 + }, + "quality": { + "degraded": false, + "case_type": "3b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "3b", + "verdict": "REJECT", + "reject_code": "IDENTITY_MISMATCH", + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_bengali_0051", + "expected_signer_script": "bengali", + "actual_signer_group": "bhsig260_bengali_0051", + "actual_source_type": "forged", + "actual_signer_script": "bengali", + "companion": null + }, + "augmentation": { + "rotate_deg": 2.393, + "scale_factor": 0.7882, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "corner", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.0, + "blur_sigma": 0.335, + "jpeg_quality": 45, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 608571981, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 611938361, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3472243104, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3295715632, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 748533042, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 4054951789, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1975566294, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 1021626175, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2897466613, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3034949170, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1923459700, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 2157649264, + "params": { + "resolved_type": "corner", + "corner": "bottom_right" + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3342061203, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 1302719452, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3749776691, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1560030073, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 455675811, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 1305440087, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2694616439, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 3218988300, + "params": { + "quality": 45 + } + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 137.0, + 1905.0, + 265.0, + 1938.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 137.0, + 1905.0, + 265.0, + 1938.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 264, + 1915 + ], + [ + 254, + 1929 + ], + [ + 230, + 1936 + ], + [ + 199, + 1937 + ], + [ + 148, + 1928 + ], + [ + 137, + 1921 + ], + [ + 169, + 1907 + ], + [ + 187, + 1905 + ] + ], + "convex_hull_px": [ + [ + 264, + 1915 + ], + [ + 254, + 1929 + ], + [ + 230, + 1936 + ], + [ + 199, + 1937 + ], + [ + 148, + 1928 + ], + [ + 137, + 1921 + ], + [ + 169, + 1907 + ], + [ + 187, + 1905 + ] + ], + "display_contours": [ + [ + [ + 258.0, + 1912.0 + ], + [ + 257.0, + 1913.0 + ], + [ + 256.0, + 1913.0 + ], + [ + 253.0, + 1916.0 + ], + [ + 253.0, + 1917.0 + ], + [ + 252.0, + 1918.0 + ], + [ + 252.0, + 1919.0 + ], + [ + 253.0, + 1920.0 + ], + [ + 252.0, + 1921.0 + ], + [ + 249.0, + 1921.0 + ], + [ + 248.0, + 1920.0 + ], + [ + 250.0, + 1918.0 + ], + [ + 249.0, + 1917.0 + ], + [ + 243.0, + 1917.0 + ], + [ + 242.0, + 1918.0 + ], + [ + 242.0, + 1919.0 + ], + [ + 243.0, + 1920.0 + ], + [ + 242.0, + 1921.0 + ], + [ + 240.0, + 1921.0 + ], + [ + 239.0, + 1922.0 + ], + [ + 238.0, + 1922.0 + ], + [ + 237.0, + 1921.0 + ], + [ + 235.0, + 1921.0 + ], + [ + 233.0, + 1923.0 + ], + [ + 232.0, + 1922.0 + ], + [ + 232.0, + 1920.0 + ], + [ + 234.0, + 1918.0 + ], + [ + 233.0, + 1917.0 + ], + [ + 232.0, + 1917.0 + ], + [ + 231.0, + 1916.0 + ], + [ + 228.0, + 1916.0 + ], + [ + 227.0, + 1917.0 + ], + [ + 226.0, + 1917.0 + ], + [ + 225.0, + 1918.0 + ], + [ + 225.0, + 1919.0 + ], + [ + 224.0, + 1920.0 + ], + [ + 223.0, + 1920.0 + ], + [ + 221.0, + 1922.0 + ], + [ + 221.0, + 1931.0 + ], + [ + 225.0, + 1935.0 + ], + [ + 226.0, + 1935.0 + ], + [ + 228.0, + 1937.0 + ], + [ + 230.0, + 1937.0 + ], + [ + 232.0, + 1935.0 + ], + [ + 230.0, + 1933.0 + ], + [ + 228.0, + 1933.0 + ], + [ + 224.0, + 1929.0 + ], + [ + 224.0, + 1924.0 + ], + [ + 225.0, + 1923.0 + ], + [ + 226.0, + 1923.0 + ], + [ + 228.0, + 1925.0 + ], + [ + 228.0, + 1928.0 + ], + [ + 229.0, + 1929.0 + ], + [ + 230.0, + 1929.0 + ], + [ + 231.0, + 1928.0 + ], + [ + 232.0, + 1928.0 + ], + [ + 233.0, + 1927.0 + ], + [ + 234.0, + 1927.0 + ], + [ + 236.0, + 1929.0 + ], + [ + 237.0, + 1929.0 + ], + [ + 238.0, + 1928.0 + ], + [ + 240.0, + 1928.0 + ], + [ + 241.0, + 1929.0 + ], + [ + 242.0, + 1929.0 + ], + [ + 245.0, + 1926.0 + ], + [ + 245.0, + 1925.0 + ], + [ + 244.0, + 1924.0 + ], + [ + 244.0, + 1922.0 + ], + [ + 243.0, + 1921.0 + ], + [ + 244.0, + 1920.0 + ], + [ + 246.0, + 1920.0 + ], + [ + 247.0, + 1921.0 + ], + [ + 246.0, + 1922.0 + ], + [ + 246.0, + 1923.0 + ], + [ + 247.0, + 1924.0 + ], + [ + 247.0, + 1927.0 + ], + [ + 248.0, + 1928.0 + ], + [ + 251.0, + 1928.0 + ], + [ + 253.0, + 1930.0 + ], + [ + 254.0, + 1930.0 + ], + [ + 256.0, + 1928.0 + ], + [ + 255.0, + 1927.0 + ], + [ + 255.0, + 1922.0 + ], + [ + 254.0, + 1921.0 + ], + [ + 254.0, + 1920.0 + ], + [ + 257.0, + 1917.0 + ], + [ + 262.0, + 1917.0 + ], + [ + 263.0, + 1918.0 + ], + [ + 265.0, + 1916.0 + ], + [ + 265.0, + 1915.0 + ], + [ + 262.0, + 1912.0 + ], + [ + 261.0, + 1912.0 + ], + [ + 260.0, + 1913.0 + ], + [ + 259.0, + 1912.0 + ] + ], + [ + [ + 187.0, + 1904.0 + ], + [ + 184.0, + 1907.0 + ], + [ + 184.0, + 1908.0 + ], + [ + 183.0, + 1909.0 + ], + [ + 183.0, + 1912.0 + ], + [ + 182.0, + 1913.0 + ], + [ + 181.0, + 1913.0 + ], + [ + 180.0, + 1912.0 + ], + [ + 180.0, + 1911.0 + ], + [ + 179.0, + 1910.0 + ], + [ + 179.0, + 1909.0 + ], + [ + 178.0, + 1908.0 + ], + [ + 177.0, + 1908.0 + ], + [ + 175.0, + 1906.0 + ], + [ + 169.0, + 1906.0 + ], + [ + 168.0, + 1907.0 + ], + [ + 167.0, + 1907.0 + ], + [ + 164.0, + 1910.0 + ], + [ + 163.0, + 1910.0 + ], + [ + 162.0, + 1911.0 + ], + [ + 162.0, + 1912.0 + ], + [ + 161.0, + 1913.0 + ], + [ + 161.0, + 1914.0 + ], + [ + 160.0, + 1915.0 + ], + [ + 160.0, + 1916.0 + ], + [ + 159.0, + 1917.0 + ], + [ + 155.0, + 1917.0 + ], + [ + 154.0, + 1918.0 + ], + [ + 154.0, + 1919.0 + ], + [ + 153.0, + 1920.0 + ], + [ + 151.0, + 1918.0 + ], + [ + 150.0, + 1918.0 + ], + [ + 149.0, + 1917.0 + ], + [ + 145.0, + 1917.0 + ], + [ + 144.0, + 1916.0 + ], + [ + 142.0, + 1916.0 + ], + [ + 140.0, + 1918.0 + ], + [ + 137.0, + 1918.0 + ], + [ + 136.0, + 1919.0 + ], + [ + 136.0, + 1921.0 + ], + [ + 137.0, + 1922.0 + ], + [ + 141.0, + 1922.0 + ], + [ + 143.0, + 1924.0 + ], + [ + 143.0, + 1925.0 + ], + [ + 146.0, + 1928.0 + ], + [ + 147.0, + 1928.0 + ], + [ + 148.0, + 1929.0 + ], + [ + 149.0, + 1929.0 + ], + [ + 150.0, + 1928.0 + ], + [ + 151.0, + 1928.0 + ], + [ + 152.0, + 1927.0 + ], + [ + 154.0, + 1927.0 + ], + [ + 156.0, + 1929.0 + ], + [ + 157.0, + 1928.0 + ], + [ + 159.0, + 1928.0 + ], + [ + 160.0, + 1929.0 + ], + [ + 162.0, + 1929.0 + ], + [ + 164.0, + 1927.0 + ], + [ + 164.0, + 1925.0 + ], + [ + 165.0, + 1924.0 + ], + [ + 167.0, + 1926.0 + ], + [ + 167.0, + 1927.0 + ], + [ + 168.0, + 1928.0 + ], + [ + 169.0, + 1928.0 + ], + [ + 170.0, + 1929.0 + ], + [ + 173.0, + 1929.0 + ], + [ + 174.0, + 1928.0 + ], + [ + 175.0, + 1928.0 + ], + [ + 180.0, + 1923.0 + ], + [ + 182.0, + 1923.0 + ], + [ + 183.0, + 1924.0 + ], + [ + 183.0, + 1928.0 + ], + [ + 185.0, + 1930.0 + ], + [ + 187.0, + 1928.0 + ], + [ + 187.0, + 1927.0 + ], + [ + 188.0, + 1926.0 + ], + [ + 189.0, + 1927.0 + ], + [ + 189.0, + 1928.0 + ], + [ + 191.0, + 1930.0 + ], + [ + 193.0, + 1930.0 + ], + [ + 194.0, + 1931.0 + ], + [ + 194.0, + 1933.0 + ], + [ + 195.0, + 1934.0 + ], + [ + 195.0, + 1935.0 + ], + [ + 196.0, + 1936.0 + ], + [ + 197.0, + 1936.0 + ], + [ + 199.0, + 1938.0 + ], + [ + 201.0, + 1938.0 + ], + [ + 203.0, + 1936.0 + ], + [ + 203.0, + 1935.0 + ], + [ + 202.0, + 1934.0 + ], + [ + 201.0, + 1934.0 + ], + [ + 200.0, + 1933.0 + ], + [ + 199.0, + 1933.0 + ], + [ + 198.0, + 1932.0 + ], + [ + 198.0, + 1928.0 + ], + [ + 199.0, + 1927.0 + ], + [ + 200.0, + 1927.0 + ], + [ + 202.0, + 1929.0 + ], + [ + 202.0, + 1930.0 + ], + [ + 204.0, + 1932.0 + ], + [ + 206.0, + 1930.0 + ], + [ + 206.0, + 1928.0 + ], + [ + 205.0, + 1927.0 + ], + [ + 205.0, + 1926.0 + ], + [ + 208.0, + 1923.0 + ], + [ + 207.0, + 1922.0 + ], + [ + 207.0, + 1921.0 + ], + [ + 206.0, + 1920.0 + ], + [ + 204.0, + 1920.0 + ], + [ + 203.0, + 1919.0 + ], + [ + 203.0, + 1918.0 + ], + [ + 201.0, + 1916.0 + ], + [ + 201.0, + 1914.0 + ], + [ + 200.0, + 1913.0 + ], + [ + 200.0, + 1912.0 + ], + [ + 199.0, + 1911.0 + ], + [ + 199.0, + 1910.0 + ], + [ + 193.0, + 1904.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 200.04, + 1921.47 + ], + "size": [ + 31.1, + 125.81 + ], + "angle_deg": -83.39 + }, + "centroid_px": [ + 193.96, + 1921.45 + ], + "orientation_deg": 1.5, + "ink_pixels": 941, + "detected": true + }, + "metrics": { + "box_coverage_pct": 17.078, + "containment_pct": 100.0, + "ink_fill_ratio": 0.2228, + "polygon_overlap_proxy": 0.1708, + "distance_from_box_center_px": 6.22, + "margin_utilization": 0.6148, + "mean_dark_intensity": 182.49, + "gray_stddev": 33.73, + "signature_area_ratio": 0.00113 + }, + "provenance": { + "master_seed": 1016, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 433, + 522 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1007, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 880, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1447, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..17281020e755b744994980e0bf15d8ad40c3b6d3 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_016_3b_procedural/case_016_3b_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27cfc5feea57014eec603f532a1e7bc30af22d342e09fbd3b1d1996d00448ab2 +size 4566 diff --git a/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..ee9ff49c3a1468868cfe5b9c8c77beadcd28bade --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a87bd17e4b3bae51790edaf7bcf95e0aae89072a2222170a9fd12789126d6d16 +size 3825 diff --git a/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..b82be3d287c0610412e8c3a302983420dcce9e7b --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:078609fbb43064b4e48972a006c3d5559cfe21aaa0cdf5f05cc05e338f0cdc14 +size 607017 diff --git a/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..ceaaf366decb993a9dbfc60ac07e9582aa6503d9 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_ground_truth.json @@ -0,0 +1,974 @@ +{ + "case_id": "case_017_4a_scanned", + "expected_verdict": "REJECT", + "expected_reject_code": "ROLE_MISMATCH", + "document_id": "case_000001", + "template": "bank_doc_09", + "template_kind": "scanned", + "document_type": "corporate_account_opening_mandate", + "page": 1, + "page_size": [ + 687, + 1024 + ], + "dpi": 300, + "signer": { + "name": "Sandra Harris", + "role": "First Authorised Signatory" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_001401", + "path": "data/raw/bhsig260_hindi/33/H-S-33-G-02.tif", + "signer_group": "bhsig260_hindi_0033", + "source_type": "genuine", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -2.12, + "scale_factor": 0.8071, + "anchor_point": "center", + "margin_target_box": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8595, + "blur_sigma": 0.3549, + "feather_px": 0.6821, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.6322, + "tier": "medium", + "misplacement": 0.412, + "whitespace": 0.9394, + "faintness": 0.7393, + "smallness": 0.5975, + "low_texture": 0.6263 + }, + "quality": { + "degraded": false, + "case_type": "4a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "4a", + "verdict": "REJECT", + "reject_code": "ROLE_MISMATCH", + "target_box_id": "sig1", + "expected_signer_group": "signet_0032", + "expected_signer_script": "latin", + "actual_signer_group": "bhsig260_hindi_0033", + "actual_source_type": "genuine", + "actual_signer_script": "hindi", + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "bhsig260_hindi_0033", + "script": "hindi", + "matches_expected": true, + "ref_path": "data\\raw\\bhsig260_hindi\\33\\H-S-33-G-06.tif" + } + }, + "augmentation": { + "rotate_deg": -2.12, + "scale_factor": 0.8071, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "fold", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 3.047, + "discoloration_strength": 0.0, + "blur_sigma": 0.3549, + "jpeg_quality": 38, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3987669741, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 456511477, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2301870569, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2433648228, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1697549452, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.3, + "seed": 483534346, + "params": { + "sigma": 0.53 + } + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1745012797, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 2577024308, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2272506136, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2987429393, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2273898093, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 2321037157, + "params": { + "resolved_type": "fold", + "folds": [ + { + "vertical": true, + "position_frac": 0.713 + } + ] + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3341470969, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 714562186, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 647922620, + "params": { + "tilt_deg": 3.047 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2756425385, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2338095019, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2112160586, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 868029453, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 3339367192, + "params": { + "quality": 38 + } + } + } + }, + "geometry": { + "box_px": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "signature_bbox_px": [ + 90.0, + 791.0, + 331.0, + 838.0 + ], + "target_box": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "actual_box": [ + 90.0, + 791.0, + 331.0, + 838.0 + ], + "search_box_px": [ + 73.12500000000001, + 791.0, + 331.875, + 838.0 + ], + "signature_polygon_px": [ + [ + 330, + 834 + ], + [ + 93, + 837 + ], + [ + 90, + 791 + ], + [ + 141, + 791 + ] + ], + "convex_hull_px": [ + [ + 330, + 834 + ], + [ + 93, + 837 + ], + [ + 90, + 791 + ], + [ + 141, + 791 + ] + ], + "display_contours": [ + [ + [ + 324.0, + 833.0 + ], + [ + 323.0, + 834.0 + ], + [ + 306.0, + 834.0 + ], + [ + 305.0, + 835.0 + ], + [ + 285.0, + 835.0 + ], + [ + 284.0, + 836.0 + ], + [ + 266.0, + 836.0 + ], + [ + 265.0, + 837.0 + ], + [ + 326.0, + 837.0 + ], + [ + 327.0, + 836.0 + ], + [ + 329.0, + 836.0 + ], + [ + 331.0, + 834.0 + ], + [ + 330.0, + 833.0 + ] + ], + [ + [ + 190.0, + 820.0 + ], + [ + 189.0, + 821.0 + ], + [ + 189.0, + 822.0 + ], + [ + 188.0, + 823.0 + ], + [ + 187.0, + 823.0 + ], + [ + 186.0, + 824.0 + ], + [ + 186.0, + 827.0 + ], + [ + 188.0, + 829.0 + ], + [ + 190.0, + 829.0 + ], + [ + 193.0, + 832.0 + ], + [ + 194.0, + 832.0 + ], + [ + 197.0, + 829.0 + ], + [ + 198.0, + 829.0 + ], + [ + 201.0, + 832.0 + ], + [ + 202.0, + 832.0 + ], + [ + 203.0, + 833.0 + ], + [ + 205.0, + 831.0 + ], + [ + 205.0, + 830.0 + ], + [ + 207.0, + 828.0 + ], + [ + 208.0, + 828.0 + ], + [ + 210.0, + 830.0 + ], + [ + 211.0, + 830.0 + ], + [ + 213.0, + 828.0 + ], + [ + 213.0, + 827.0 + ], + [ + 215.0, + 825.0 + ], + [ + 215.0, + 821.0 + ], + [ + 214.0, + 820.0 + ], + [ + 211.0, + 820.0 + ], + [ + 210.0, + 821.0 + ], + [ + 210.0, + 822.0 + ], + [ + 208.0, + 824.0 + ], + [ + 207.0, + 823.0 + ], + [ + 207.0, + 821.0 + ], + [ + 206.0, + 820.0 + ], + [ + 204.0, + 820.0 + ], + [ + 203.0, + 821.0 + ], + [ + 202.0, + 821.0 + ], + [ + 201.0, + 820.0 + ], + [ + 199.0, + 820.0 + ], + [ + 198.0, + 821.0 + ], + [ + 198.0, + 822.0 + ], + [ + 197.0, + 823.0 + ], + [ + 196.0, + 822.0 + ], + [ + 196.0, + 821.0 + ], + [ + 195.0, + 820.0 + ] + ], + [ + [ + 201.0, + 812.0 + ], + [ + 198.0, + 815.0 + ], + [ + 198.0, + 817.0 + ], + [ + 199.0, + 818.0 + ], + [ + 204.0, + 818.0 + ], + [ + 206.0, + 816.0 + ], + [ + 206.0, + 815.0 + ], + [ + 203.0, + 812.0 + ] + ], + [ + [ + 238.0, + 811.0 + ], + [ + 237.0, + 812.0 + ], + [ + 237.0, + 813.0 + ], + [ + 236.0, + 814.0 + ], + [ + 236.0, + 819.0 + ], + [ + 237.0, + 820.0 + ], + [ + 236.0, + 821.0 + ], + [ + 236.0, + 824.0 + ], + [ + 237.0, + 825.0 + ], + [ + 237.0, + 827.0 + ], + [ + 239.0, + 829.0 + ], + [ + 240.0, + 828.0 + ], + [ + 241.0, + 828.0 + ], + [ + 242.0, + 827.0 + ], + [ + 246.0, + 827.0 + ], + [ + 248.0, + 825.0 + ], + [ + 249.0, + 826.0 + ], + [ + 249.0, + 829.0 + ], + [ + 252.0, + 832.0 + ], + [ + 254.0, + 830.0 + ], + [ + 254.0, + 827.0 + ], + [ + 256.0, + 825.0 + ], + [ + 256.0, + 821.0 + ], + [ + 255.0, + 820.0 + ], + [ + 255.0, + 819.0 + ], + [ + 256.0, + 818.0 + ], + [ + 255.0, + 817.0 + ], + [ + 252.0, + 817.0 + ], + [ + 251.0, + 816.0 + ], + [ + 253.0, + 814.0 + ], + [ + 251.0, + 812.0 + ], + [ + 249.0, + 812.0 + ], + [ + 248.0, + 813.0 + ], + [ + 248.0, + 814.0 + ], + [ + 250.0, + 816.0 + ], + [ + 249.0, + 817.0 + ], + [ + 246.0, + 817.0 + ], + [ + 245.0, + 816.0 + ], + [ + 245.0, + 815.0 + ], + [ + 243.0, + 813.0 + ], + [ + 243.0, + 812.0 + ], + [ + 242.0, + 811.0 + ] + ], + [ + [ + 188.0, + 811.0 + ], + [ + 187.0, + 812.0 + ], + [ + 187.0, + 814.0 + ], + [ + 188.0, + 815.0 + ], + [ + 188.0, + 817.0 + ], + [ + 189.0, + 818.0 + ], + [ + 192.0, + 818.0 + ], + [ + 194.0, + 816.0 + ], + [ + 194.0, + 815.0 + ], + [ + 190.0, + 811.0 + ] + ], + [ + [ + 136.0, + 791.0 + ], + [ + 136.0, + 792.0 + ], + [ + 137.0, + 793.0 + ], + [ + 140.0, + 793.0 + ], + [ + 142.0, + 791.0 + ] + ], + [ + [ + 99.0, + 791.0 + ], + [ + 99.0, + 792.0 + ], + [ + 100.0, + 793.0 + ], + [ + 102.0, + 791.0 + ] + ], + [ + [ + 89.0, + 791.0 + ], + [ + 89.0, + 792.0 + ], + [ + 90.0, + 793.0 + ], + [ + 90.0, + 816.0 + ], + [ + 91.0, + 817.0 + ], + [ + 91.0, + 832.0 + ], + [ + 92.0, + 833.0 + ], + [ + 92.0, + 837.0 + ], + [ + 95.0, + 837.0 + ], + [ + 95.0, + 829.0 + ], + [ + 94.0, + 828.0 + ], + [ + 94.0, + 809.0 + ], + [ + 93.0, + 808.0 + ], + [ + 93.0, + 791.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 210.0, + 814.0 + ], + "size": [ + 46.0, + 240.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 211.86, + 822.83 + ], + "orientation_deg": 5.47, + "ink_pixels": 686, + "detected": true + }, + "metrics": { + "box_coverage_pct": 67.837, + "containment_pct": 81.535, + "ink_fill_ratio": 0.0606, + "polygon_overlap_proxy": 0.588, + "distance_from_box_center_px": 12.53, + "margin_utilization": 1.1387, + "mean_dark_intensity": 178.71, + "gray_stddev": 22.42, + "signature_area_ratio": 0.0161 + }, + "provenance": { + "master_seed": 1017, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "company_name": { + "actual_box": [ + 80.0, + 169.0, + 287.0, + 180.0 + ], + "target_box": null + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ae4f3962dc1b4589efc6594583fe691bd7ecb0 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_017_4a_scanned/case_017_4a_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:532b26793641f6e1e8e1086dbaf35f689b2003a75bdbe8fc7065af50ddb8ec4b +size 3851 diff --git a/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..1855eab2021522f90d0633ea67d0a660c73587cb --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:503a3f9611576871944e01b009cc7cc1ac13754ebd9567af55bca5929b44504e +size 4748 diff --git a/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..1c35ca84f51d397927db5e58d533ea1e98f73f8c --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:287f81af0f86558461d2b57936740a1ec84f0185e63e2d991dedf2bde7a0310b +size 7592638 diff --git a/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..bc6bf9b7441cc12a402ea3790416022c0a4247ff --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_ground_truth.json @@ -0,0 +1,1487 @@ +{ + "case_id": "case_018_4a_procedural", + "expected_verdict": "REJECT", + "expected_reject_code": "ROLE_MISMATCH", + "document_id": "case_000001", + "template": "proc_doc_11", + "template_kind": "procedural", + "document_type": "Promissory Note", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Emily Jones", + "role": "Board Witness Exec" + }, + "signature_source": { + "signature_id": "bhsig260_bengali_000185", + "path": "data/raw/bhsig260_bengali/19/B-S-19-G-02.tif", + "signer_group": "bhsig260_bengali_0019", + "source_type": "genuine", + "script": "bengali" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -2.332, + "scale_factor": 0.9366, + "anchor_point": "center", + "margin_target_box": [ + 960.0, + 1880.0, + 1160.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8318, + "blur_sigma": 0.43, + "feather_px": 0.7326, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.6246, + "tier": "medium", + "misplacement": 0.8397, + "whitespace": 0.7735, + "faintness": 0.1267, + "smallness": 0.6967, + "low_texture": 0.4618 + }, + "quality": { + "degraded": false, + "case_type": "4a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "4a", + "verdict": "REJECT", + "reject_code": "ROLE_MISMATCH", + "target_box_id": "sig2", + "expected_signer_group": "bhsig260_bengali_0057", + "expected_signer_script": "bengali", + "actual_signer_group": "bhsig260_bengali_0019", + "actual_source_type": "genuine", + "actual_signer_script": "bengali", + "companion": { + "box_id": "sig1", + "filled": true, + "signer_group": "bhsig260_bengali_0019", + "script": "bengali", + "matches_expected": true, + "ref_path": "data\\raw\\bhsig260_bengali\\19\\B-S-19-G-04.tif" + } + }, + "augmentation": { + "rotate_deg": -2.332, + "scale_factor": 0.9366, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.2, + "blur_sigma": 0.43, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3331752145, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 3565707797, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 964274616, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2394788162, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2333114159, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.3, + "seed": 2117022030, + "params": { + "sigma": 0.53 + } + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 693107333, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 2771221446, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2545399955, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 854972915, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1216967927, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 3396944184, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2288818888, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 3222305397, + "params": { + "resolved_tone": "grey" + } + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3162740712, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2594185492, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2212779440, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 52148160, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1625790516, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 2009166647, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 960.0, + 1880.0, + 1160.0, + 1960.0 + ], + "signature_bbox_px": [ + 986.0, + 1911.0, + 1134.0, + 1934.0 + ], + "target_box": [ + 960.0, + 1880.0, + 1160.0, + 1960.0 + ], + "actual_box": [ + 986.0, + 1911.0, + 1134.0, + 1934.0 + ], + "search_box_px": [ + 945.0, + 1874.0, + 1175.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 1133, + 1919 + ], + [ + 1119, + 1933 + ], + [ + 1048, + 1931 + ], + [ + 987, + 1921 + ], + [ + 986, + 1911 + ], + [ + 1119, + 1911 + ] + ], + "convex_hull_px": [ + [ + 1133, + 1919 + ], + [ + 1119, + 1933 + ], + [ + 1048, + 1931 + ], + [ + 987, + 1921 + ], + [ + 986, + 1911 + ], + [ + 1119, + 1911 + ] + ], + "display_contours": [ + [ + [ + 1092.0, + 1925.0 + ], + [ + 1091.0, + 1926.0 + ], + [ + 1091.0, + 1928.0 + ], + [ + 1092.0, + 1929.0 + ], + [ + 1093.0, + 1929.0 + ], + [ + 1096.0, + 1926.0 + ], + [ + 1097.0, + 1927.0 + ], + [ + 1097.0, + 1928.0 + ], + [ + 1098.0, + 1929.0 + ], + [ + 1099.0, + 1928.0 + ], + [ + 1102.0, + 1928.0 + ], + [ + 1104.0, + 1926.0 + ], + [ + 1105.0, + 1927.0 + ], + [ + 1105.0, + 1928.0 + ], + [ + 1106.0, + 1929.0 + ], + [ + 1107.0, + 1929.0 + ], + [ + 1110.0, + 1926.0 + ], + [ + 1111.0, + 1927.0 + ], + [ + 1111.0, + 1930.0 + ], + [ + 1112.0, + 1931.0 + ], + [ + 1113.0, + 1931.0 + ], + [ + 1115.0, + 1929.0 + ], + [ + 1115.0, + 1926.0 + ], + [ + 1114.0, + 1925.0 + ], + [ + 1113.0, + 1925.0 + ], + [ + 1111.0, + 1927.0 + ], + [ + 1109.0, + 1925.0 + ], + [ + 1106.0, + 1925.0 + ], + [ + 1105.0, + 1926.0 + ], + [ + 1104.0, + 1926.0 + ], + [ + 1103.0, + 1925.0 + ], + [ + 1098.0, + 1925.0 + ], + [ + 1097.0, + 1926.0 + ], + [ + 1096.0, + 1926.0 + ], + [ + 1095.0, + 1925.0 + ] + ], + [ + [ + 1132.0, + 1918.0 + ], + [ + 1131.0, + 1919.0 + ], + [ + 1131.0, + 1920.0 + ], + [ + 1132.0, + 1921.0 + ], + [ + 1134.0, + 1919.0 + ], + [ + 1133.0, + 1918.0 + ] + ], + [ + [ + 1037.0, + 1913.0 + ], + [ + 1035.0, + 1915.0 + ], + [ + 1035.0, + 1916.0 + ], + [ + 1036.0, + 1917.0 + ], + [ + 1037.0, + 1917.0 + ], + [ + 1038.0, + 1918.0 + ], + [ + 1039.0, + 1918.0 + ], + [ + 1040.0, + 1919.0 + ], + [ + 1040.0, + 1920.0 + ], + [ + 1039.0, + 1921.0 + ], + [ + 1039.0, + 1922.0 + ], + [ + 1041.0, + 1924.0 + ], + [ + 1041.0, + 1925.0 + ], + [ + 1040.0, + 1926.0 + ], + [ + 1039.0, + 1926.0 + ], + [ + 1038.0, + 1927.0 + ], + [ + 1038.0, + 1929.0 + ], + [ + 1040.0, + 1931.0 + ], + [ + 1043.0, + 1931.0 + ], + [ + 1044.0, + 1930.0 + ], + [ + 1046.0, + 1930.0 + ], + [ + 1048.0, + 1932.0 + ], + [ + 1050.0, + 1930.0 + ], + [ + 1050.0, + 1926.0 + ], + [ + 1051.0, + 1925.0 + ], + [ + 1053.0, + 1925.0 + ], + [ + 1054.0, + 1924.0 + ], + [ + 1055.0, + 1924.0 + ], + [ + 1057.0, + 1926.0 + ], + [ + 1058.0, + 1926.0 + ], + [ + 1061.0, + 1923.0 + ], + [ + 1062.0, + 1924.0 + ], + [ + 1062.0, + 1929.0 + ], + [ + 1063.0, + 1930.0 + ], + [ + 1064.0, + 1930.0 + ], + [ + 1066.0, + 1928.0 + ], + [ + 1066.0, + 1922.0 + ], + [ + 1065.0, + 1921.0 + ], + [ + 1066.0, + 1920.0 + ], + [ + 1069.0, + 1920.0 + ], + [ + 1070.0, + 1921.0 + ], + [ + 1068.0, + 1923.0 + ], + [ + 1068.0, + 1924.0 + ], + [ + 1069.0, + 1925.0 + ], + [ + 1070.0, + 1925.0 + ], + [ + 1071.0, + 1926.0 + ], + [ + 1069.0, + 1928.0 + ], + [ + 1069.0, + 1929.0 + ], + [ + 1071.0, + 1931.0 + ], + [ + 1072.0, + 1931.0 + ], + [ + 1074.0, + 1929.0 + ], + [ + 1076.0, + 1929.0 + ], + [ + 1078.0, + 1927.0 + ], + [ + 1078.0, + 1920.0 + ], + [ + 1077.0, + 1919.0 + ], + [ + 1077.0, + 1918.0 + ], + [ + 1076.0, + 1917.0 + ], + [ + 1073.0, + 1917.0 + ], + [ + 1072.0, + 1916.0 + ], + [ + 1067.0, + 1916.0 + ], + [ + 1066.0, + 1915.0 + ], + [ + 1062.0, + 1915.0 + ], + [ + 1060.0, + 1917.0 + ], + [ + 1059.0, + 1916.0 + ], + [ + 1057.0, + 1916.0 + ], + [ + 1056.0, + 1917.0 + ], + [ + 1056.0, + 1918.0 + ], + [ + 1055.0, + 1919.0 + ], + [ + 1052.0, + 1919.0 + ], + [ + 1051.0, + 1920.0 + ], + [ + 1050.0, + 1919.0 + ], + [ + 1050.0, + 1918.0 + ], + [ + 1051.0, + 1917.0 + ], + [ + 1052.0, + 1917.0 + ], + [ + 1054.0, + 1915.0 + ], + [ + 1053.0, + 1914.0 + ], + [ + 1047.0, + 1914.0 + ], + [ + 1046.0, + 1915.0 + ], + [ + 1045.0, + 1915.0 + ], + [ + 1043.0, + 1913.0 + ] + ], + [ + [ + 1114.0, + 1910.0 + ], + [ + 1113.0, + 1911.0 + ], + [ + 1113.0, + 1912.0 + ], + [ + 1112.0, + 1913.0 + ], + [ + 1112.0, + 1914.0 + ], + [ + 1113.0, + 1915.0 + ], + [ + 1113.0, + 1916.0 + ], + [ + 1112.0, + 1917.0 + ], + [ + 1106.0, + 1917.0 + ], + [ + 1105.0, + 1916.0 + ], + [ + 1102.0, + 1916.0 + ], + [ + 1101.0, + 1917.0 + ], + [ + 1100.0, + 1916.0 + ], + [ + 1097.0, + 1916.0 + ], + [ + 1096.0, + 1917.0 + ], + [ + 1096.0, + 1919.0 + ], + [ + 1097.0, + 1920.0 + ], + [ + 1097.0, + 1922.0 + ], + [ + 1098.0, + 1923.0 + ], + [ + 1100.0, + 1923.0 + ], + [ + 1101.0, + 1922.0 + ], + [ + 1102.0, + 1923.0 + ], + [ + 1104.0, + 1923.0 + ], + [ + 1105.0, + 1922.0 + ], + [ + 1106.0, + 1923.0 + ], + [ + 1109.0, + 1923.0 + ], + [ + 1111.0, + 1921.0 + ], + [ + 1111.0, + 1920.0 + ], + [ + 1113.0, + 1918.0 + ], + [ + 1114.0, + 1918.0 + ], + [ + 1117.0, + 1921.0 + ], + [ + 1118.0, + 1921.0 + ], + [ + 1119.0, + 1922.0 + ], + [ + 1119.0, + 1928.0 + ], + [ + 1118.0, + 1929.0 + ], + [ + 1118.0, + 1933.0 + ], + [ + 1119.0, + 1934.0 + ], + [ + 1122.0, + 1931.0 + ], + [ + 1122.0, + 1926.0 + ], + [ + 1123.0, + 1925.0 + ], + [ + 1123.0, + 1922.0 + ], + [ + 1122.0, + 1921.0 + ], + [ + 1122.0, + 1920.0 + ], + [ + 1123.0, + 1919.0 + ], + [ + 1123.0, + 1915.0 + ], + [ + 1122.0, + 1914.0 + ], + [ + 1122.0, + 1913.0 + ], + [ + 1119.0, + 1910.0 + ] + ], + [ + [ + 986.0, + 1910.0 + ], + [ + 985.0, + 1911.0 + ], + [ + 985.0, + 1912.0 + ], + [ + 986.0, + 1913.0 + ], + [ + 988.0, + 1913.0 + ], + [ + 989.0, + 1914.0 + ], + [ + 988.0, + 1915.0 + ], + [ + 987.0, + 1915.0 + ], + [ + 986.0, + 1916.0 + ], + [ + 986.0, + 1921.0 + ], + [ + 987.0, + 1922.0 + ], + [ + 991.0, + 1922.0 + ], + [ + 992.0, + 1921.0 + ], + [ + 993.0, + 1921.0 + ], + [ + 994.0, + 1920.0 + ], + [ + 995.0, + 1920.0 + ], + [ + 996.0, + 1921.0 + ], + [ + 999.0, + 1918.0 + ], + [ + 999.0, + 1917.0 + ], + [ + 1000.0, + 1916.0 + ], + [ + 1001.0, + 1917.0 + ], + [ + 1001.0, + 1920.0 + ], + [ + 997.0, + 1924.0 + ], + [ + 997.0, + 1925.0 + ], + [ + 998.0, + 1926.0 + ], + [ + 999.0, + 1926.0 + ], + [ + 1000.0, + 1925.0 + ], + [ + 1001.0, + 1925.0 + ], + [ + 1004.0, + 1922.0 + ], + [ + 1004.0, + 1920.0 + ], + [ + 1005.0, + 1919.0 + ], + [ + 1006.0, + 1920.0 + ], + [ + 1006.0, + 1921.0 + ], + [ + 1007.0, + 1922.0 + ], + [ + 1010.0, + 1919.0 + ], + [ + 1012.0, + 1921.0 + ], + [ + 1012.0, + 1922.0 + ], + [ + 1013.0, + 1923.0 + ], + [ + 1016.0, + 1923.0 + ], + [ + 1018.0, + 1921.0 + ], + [ + 1020.0, + 1923.0 + ], + [ + 1021.0, + 1923.0 + ], + [ + 1023.0, + 1921.0 + ], + [ + 1023.0, + 1915.0 + ], + [ + 1024.0, + 1914.0 + ], + [ + 1022.0, + 1912.0 + ], + [ + 1020.0, + 1912.0 + ], + [ + 1019.0, + 1913.0 + ], + [ + 1019.0, + 1914.0 + ], + [ + 1018.0, + 1915.0 + ], + [ + 1017.0, + 1914.0 + ], + [ + 1013.0, + 1914.0 + ], + [ + 1012.0, + 1915.0 + ], + [ + 1011.0, + 1915.0 + ], + [ + 1010.0, + 1914.0 + ], + [ + 1007.0, + 1914.0 + ], + [ + 1006.0, + 1915.0 + ], + [ + 1005.0, + 1915.0 + ], + [ + 1004.0, + 1914.0 + ], + [ + 1002.0, + 1914.0 + ], + [ + 1001.0, + 1915.0 + ], + [ + 1000.0, + 1915.0 + ], + [ + 999.0, + 1914.0 + ], + [ + 997.0, + 1914.0 + ], + [ + 996.0, + 1915.0 + ], + [ + 995.0, + 1915.0 + ], + [ + 994.0, + 1914.0 + ], + [ + 989.0, + 1914.0 + ], + [ + 988.0, + 1913.0 + ], + [ + 990.0, + 1911.0 + ], + [ + 989.0, + 1910.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 1059.5, + 1922.0 + ], + "size": [ + 22.0, + 147.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 1057.24, + 1920.5 + ], + "orientation_deg": 1.44, + "ink_pixels": 771, + "detected": true + }, + "metrics": { + "box_coverage_pct": 16.031, + "containment_pct": 100.0, + "ink_fill_ratio": 0.2265, + "polygon_overlap_proxy": 0.1603, + "distance_from_box_center_px": 2.81, + "margin_utilization": 0.6976, + "mean_dark_intensity": 188.56, + "gray_stddev": 32.29, + "signature_area_ratio": 0.00091 + }, + "provenance": { + "master_seed": 1018, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 511, + 527 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1009, + 523 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 880, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..030bf87203f796bef16f6611aea972215a063231 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_018_4a_procedural/case_018_4a_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35e7d942e913504358fe643fdd8605b51b753f9a8da131c26cd0a055bc9dc419 +size 4529 diff --git a/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..91c8e15d725af43697943fbf49ffb54a5a4aec82 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3277c2eb87bd8afb6e33b7d073aa1e835a97986e7f38a7598fd33c4e0637e5e +size 64260 diff --git a/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..550467281ecf6d4f22420d2365724396450a0714 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:def1942093a5c642f37abac3bd742af5e8aad4aac89df8d81b48fd9fbb10fba7 +size 3128080 diff --git a/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..c3bc517577b287f1246a535309382adf67c81e67 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_ground_truth.json @@ -0,0 +1,560 @@ +{ + "case_id": "case_019_4b_scanned", + "expected_verdict": "REJECT", + "expected_reject_code": "ROLE_MISMATCH", + "document_id": "case_000001", + "template": "bank_doc_08", + "template_kind": "scanned", + "document_type": "bank_account_verification_mandate", + "page": 1, + "page_size": [ + 1024, + 1536 + ], + "dpi": 300, + "signer": { + "name": "Stephanie Martinez", + "role": "Account Holder" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_000410", + "path": "data/raw/bhsig260_hindi/121/H-S-121-G-03.tif", + "signer_group": "bhsig260_hindi_0121", + "source_type": "genuine", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -2.465, + "scale_factor": 0.8345, + "anchor_point": "center", + "margin_target_box": [ + 345.0, + 672.0, + 495.0, + 696.0 + ], + "misplaced": true, + "misplacement_dx": 41.0, + "misplacement_dy": -29.82 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8447, + "blur_sigma": 0.3153, + "feather_px": 0.6429, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.6823, + "tier": "hard", + "misplacement": 0.9895, + "whitespace": 0.5625, + "faintness": 0.3696, + "smallness": 0.9972, + "low_texture": 0.0 + }, + "quality": { + "degraded": false, + "case_type": "4b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "4b", + "verdict": "REJECT", + "reject_code": "ROLE_MISMATCH", + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_hindi_0074", + "expected_signer_script": "hindi", + "actual_signer_group": "bhsig260_hindi_0121", + "actual_source_type": "genuine", + "actual_signer_script": "hindi", + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "signet_0035", + "script": "latin", + "matches_expected": true, + "ref_path": "data\\raw\\signet\\train\\035\\10_035.png" + } + }, + "augmentation": { + "rotate_deg": -2.465, + "scale_factor": 0.8345, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "edge", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": -2.757, + "discoloration_strength": 0.2, + "blur_sigma": 0.3153, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3033529655, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 2485896919, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3230126532, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2130671747, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1312243522, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3120871685, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3421983718, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 4037745996, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 638942329, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4038682938, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1455896954, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 1433255821, + "params": { + "resolved_type": "edge", + "side": "bottom" + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 4282749636, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 1035642923, + "params": { + "resolved_tone": "yellow" + } + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 2256739292, + "params": { + "tilt_deg": -2.757 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1300576399, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3120846308, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 951527071, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2547043388, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 261164721, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 345.0, + 672.0, + 495.0, + 696.0 + ], + "signature_bbox_px": [ + 334.0, + 673.0, + 350.0, + 684.0 + ], + "target_box": [ + 345.0, + 672.0, + 495.0, + 696.0 + ], + "actual_box": [ + 334.0, + 673.0, + 350.0, + 684.0 + ], + "search_box_px": [ + 333.75, + 666.0, + 506.25, + 702.0 + ], + "signature_polygon_px": [ + [ + 349, + 682 + ], + [ + 348, + 683 + ], + [ + 334, + 683 + ], + [ + 334, + 673 + ], + [ + 348, + 673 + ], + [ + 349, + 674 + ] + ], + "convex_hull_px": [ + [ + 349, + 682 + ], + [ + 348, + 683 + ], + [ + 334, + 683 + ], + [ + 334, + 673 + ], + [ + 348, + 673 + ], + [ + 349, + 674 + ] + ], + "display_contours": [ + [ + [ + 347.0, + 680.0 + ], + [ + 346.0, + 681.0 + ], + [ + 346.0, + 683.0 + ], + [ + 347.0, + 684.0 + ], + [ + 348.0, + 684.0 + ], + [ + 350.0, + 682.0 + ], + [ + 350.0, + 681.0 + ], + [ + 349.0, + 680.0 + ] + ], + [ + [ + 334.0, + 672.0 + ], + [ + 334.0, + 678.0 + ], + [ + 335.0, + 679.0 + ], + [ + 334.0, + 680.0 + ], + [ + 334.0, + 684.0 + ], + [ + 337.0, + 681.0 + ], + [ + 338.0, + 682.0 + ], + [ + 338.0, + 683.0 + ], + [ + 339.0, + 684.0 + ], + [ + 341.0, + 684.0 + ], + [ + 343.0, + 682.0 + ], + [ + 342.0, + 681.0 + ], + [ + 342.0, + 676.0 + ], + [ + 343.0, + 675.0 + ], + [ + 344.0, + 675.0 + ], + [ + 345.0, + 674.0 + ], + [ + 346.0, + 675.0 + ], + [ + 346.0, + 676.0 + ], + [ + 347.0, + 677.0 + ], + [ + 350.0, + 674.0 + ], + [ + 348.0, + 672.0 + ], + [ + 347.0, + 672.0 + ], + [ + 346.0, + 673.0 + ], + [ + 345.0, + 672.0 + ], + [ + 339.0, + 672.0 + ], + [ + 338.0, + 673.0 + ], + [ + 337.0, + 673.0 + ], + [ + 336.0, + 672.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 341.5, + 678.0 + ], + "size": [ + 10.0, + 15.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 340.51, + 677.71 + ], + "orientation_deg": -4.52, + "ink_pixels": 77, + "detected": true + }, + "metrics": { + "box_coverage_pct": 1.083, + "containment_pct": 26.174, + "ink_fill_ratio": 0.4375, + "polygon_overlap_proxy": 0.0105, + "distance_from_box_center_px": 79.74, + "margin_utilization": 1.1415, + "mean_dark_intensity": 134.35, + "gray_stddev": 75.94, + "signature_area_ratio": 0.00011 + }, + "provenance": { + "master_seed": 1019, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "name_sig1": { + "actual_box": [ + 312.0, + 370.0, + 493.0, + 389.0 + ], + "target_box": null + }, + "name_sig2": { + "actual_box": [ + 752.0, + 445.0, + 856.0, + 460.0 + ], + "target_box": null + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..82717719b46fb4be003ba3f4fba3ba9baa28bf37 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_019_4b_scanned/case_019_4b_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:297123f18564544f60f09408f4484b8010d949ebd74594af7fd263608a11b1c0 +size 3298 diff --git a/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..5bb2d41f688ff9007e4ed937fb8b7b47e56adecd --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b31413bf10731c17d1bf993e098fdc61d2da30cd074c50491b1164e60366e45 +size 4661 diff --git a/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..df1b3e967329a99440112d76e8c174e8eaa74f1c --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17dfbbbfc08706c84a9ed5c43adebb821f2782fb763fdb4deffa278eef7788d6 +size 6854799 diff --git a/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..4c8f854f5fbf4b1a520f12a88271e7a9f05663d6 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_ground_truth.json @@ -0,0 +1,2861 @@ +{ + "case_id": "case_020_4b_procedural", + "expected_verdict": "REJECT", + "expected_reject_code": "ROLE_MISMATCH", + "document_id": "case_000001", + "template": "proc_doc_09", + "template_kind": "procedural", + "document_type": "Account Opening", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Amanda Suzuki", + "role": "Applicant Agent" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_001694", + "path": "data/raw/bhsig260_hindi/5/H-S-5-G-07.tif", + "signer_group": "bhsig260_hindi_0005", + "source_type": "genuine", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 3.467, + "scale_factor": 0.8963, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8672, + "blur_sigma": 0.3764, + "feather_px": 1.5564, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.2546, + "tier": "easy", + "misplacement": 0.2412, + "whitespace": 0.8144, + "faintness": 0.0, + "smallness": 0.0, + "low_texture": 0.073 + }, + "quality": { + "degraded": false, + "case_type": "4b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "4b", + "verdict": "REJECT", + "reject_code": "ROLE_MISMATCH", + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_bengali_0009", + "expected_signer_script": "bengali", + "actual_signer_group": "bhsig260_hindi_0005", + "actual_source_type": "genuine", + "actual_signer_script": "hindi", + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "bhsig260_hindi_0110", + "script": "hindi", + "matches_expected": true, + "ref_path": "data\\raw\\bhsig260_hindi\\110\\H-S-110-G-02.tif" + } + }, + "augmentation": { + "rotate_deg": 3.467, + "scale_factor": 0.8963, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": -3.101, + "discoloration_strength": 0.2, + "blur_sigma": 0.3764, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3073087132, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 579614353, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3988691688, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3345413353, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 929039084, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.3, + "seed": 4210420324, + "params": { + "sigma": 0.53 + } + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3948835247, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 2294222104, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 4102669179, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1212854358, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4028058406, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 3488756555, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3721970247, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 1645103962, + "params": { + "resolved_tone": "sepia" + } + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 1956038887, + "params": { + "tilt_deg": -3.101 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2207733652, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3039251307, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 1214139715, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3656161651, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 2925053170, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 85.0, + 1874.0, + 315.0, + 1964.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 85.0, + 1874.0, + 315.0, + 1964.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 314, + 1963 + ], + [ + 85, + 1950 + ], + [ + 85, + 1886 + ], + [ + 119, + 1874 + ], + [ + 225, + 1874 + ], + [ + 314, + 1932 + ] + ], + "convex_hull_px": [ + [ + 314, + 1963 + ], + [ + 85, + 1950 + ], + [ + 85, + 1886 + ], + [ + 119, + 1874 + ], + [ + 225, + 1874 + ], + [ + 314, + 1932 + ] + ], + "display_contours": [ + [ + [ + 313.0, + 1950.0 + ], + [ + 312.0, + 1951.0 + ], + [ + 312.0, + 1956.0 + ], + [ + 311.0, + 1957.0 + ], + [ + 311.0, + 1962.0 + ], + [ + 312.0, + 1963.0 + ], + [ + 313.0, + 1963.0 + ], + [ + 314.0, + 1964.0 + ], + [ + 314.0, + 1950.0 + ] + ], + [ + [ + 197.0, + 1946.0 + ], + [ + 196.0, + 1947.0 + ], + [ + 196.0, + 1949.0 + ], + [ + 197.0, + 1950.0 + ], + [ + 199.0, + 1950.0 + ], + [ + 202.0, + 1947.0 + ], + [ + 201.0, + 1946.0 + ] + ], + [ + [ + 213.0, + 1943.0 + ], + [ + 212.0, + 1944.0 + ], + [ + 212.0, + 1945.0 + ], + [ + 211.0, + 1946.0 + ], + [ + 211.0, + 1947.0 + ], + [ + 210.0, + 1948.0 + ], + [ + 210.0, + 1950.0 + ], + [ + 209.0, + 1951.0 + ], + [ + 209.0, + 1952.0 + ], + [ + 208.0, + 1953.0 + ], + [ + 208.0, + 1954.0 + ], + [ + 207.0, + 1955.0 + ], + [ + 207.0, + 1957.0 + ], + [ + 208.0, + 1958.0 + ], + [ + 210.0, + 1958.0 + ], + [ + 213.0, + 1955.0 + ], + [ + 216.0, + 1955.0 + ], + [ + 217.0, + 1956.0 + ], + [ + 217.0, + 1957.0 + ], + [ + 218.0, + 1958.0 + ], + [ + 220.0, + 1958.0 + ], + [ + 222.0, + 1956.0 + ], + [ + 223.0, + 1957.0 + ], + [ + 223.0, + 1958.0 + ], + [ + 224.0, + 1959.0 + ], + [ + 226.0, + 1959.0 + ], + [ + 228.0, + 1957.0 + ], + [ + 228.0, + 1956.0 + ], + [ + 229.0, + 1955.0 + ], + [ + 231.0, + 1955.0 + ], + [ + 235.0, + 1951.0 + ], + [ + 236.0, + 1952.0 + ], + [ + 236.0, + 1956.0 + ], + [ + 235.0, + 1957.0 + ], + [ + 235.0, + 1958.0 + ], + [ + 236.0, + 1959.0 + ], + [ + 239.0, + 1959.0 + ], + [ + 242.0, + 1956.0 + ], + [ + 244.0, + 1956.0 + ], + [ + 248.0, + 1952.0 + ], + [ + 249.0, + 1953.0 + ], + [ + 249.0, + 1956.0 + ], + [ + 248.0, + 1957.0 + ], + [ + 248.0, + 1959.0 + ], + [ + 249.0, + 1960.0 + ], + [ + 258.0, + 1960.0 + ], + [ + 259.0, + 1959.0 + ], + [ + 261.0, + 1961.0 + ], + [ + 263.0, + 1961.0 + ], + [ + 265.0, + 1959.0 + ], + [ + 265.0, + 1957.0 + ], + [ + 266.0, + 1956.0 + ], + [ + 267.0, + 1957.0 + ], + [ + 267.0, + 1958.0 + ], + [ + 269.0, + 1960.0 + ], + [ + 270.0, + 1960.0 + ], + [ + 271.0, + 1961.0 + ], + [ + 277.0, + 1961.0 + ], + [ + 278.0, + 1960.0 + ], + [ + 280.0, + 1962.0 + ], + [ + 281.0, + 1962.0 + ], + [ + 284.0, + 1959.0 + ], + [ + 287.0, + 1959.0 + ], + [ + 288.0, + 1960.0 + ], + [ + 288.0, + 1961.0 + ], + [ + 289.0, + 1962.0 + ], + [ + 291.0, + 1962.0 + ], + [ + 292.0, + 1961.0 + ], + [ + 293.0, + 1961.0 + ], + [ + 295.0, + 1963.0 + ], + [ + 296.0, + 1963.0 + ], + [ + 299.0, + 1960.0 + ], + [ + 300.0, + 1960.0 + ], + [ + 301.0, + 1961.0 + ], + [ + 301.0, + 1962.0 + ], + [ + 302.0, + 1963.0 + ], + [ + 305.0, + 1963.0 + ], + [ + 307.0, + 1961.0 + ], + [ + 307.0, + 1952.0 + ], + [ + 308.0, + 1951.0 + ], + [ + 307.0, + 1950.0 + ], + [ + 303.0, + 1950.0 + ], + [ + 302.0, + 1951.0 + ], + [ + 299.0, + 1948.0 + ], + [ + 295.0, + 1948.0 + ], + [ + 294.0, + 1949.0 + ], + [ + 294.0, + 1957.0 + ], + [ + 293.0, + 1958.0 + ], + [ + 292.0, + 1957.0 + ], + [ + 292.0, + 1956.0 + ], + [ + 291.0, + 1955.0 + ], + [ + 291.0, + 1953.0 + ], + [ + 290.0, + 1952.0 + ], + [ + 290.0, + 1949.0 + ], + [ + 288.0, + 1947.0 + ], + [ + 284.0, + 1947.0 + ], + [ + 283.0, + 1948.0 + ], + [ + 283.0, + 1949.0 + ], + [ + 282.0, + 1950.0 + ], + [ + 282.0, + 1951.0 + ], + [ + 281.0, + 1952.0 + ], + [ + 281.0, + 1954.0 + ], + [ + 280.0, + 1955.0 + ], + [ + 280.0, + 1956.0 + ], + [ + 279.0, + 1957.0 + ], + [ + 275.0, + 1957.0 + ], + [ + 274.0, + 1958.0 + ], + [ + 271.0, + 1955.0 + ], + [ + 271.0, + 1952.0 + ], + [ + 273.0, + 1950.0 + ], + [ + 275.0, + 1950.0 + ], + [ + 276.0, + 1951.0 + ], + [ + 277.0, + 1951.0 + ], + [ + 279.0, + 1949.0 + ], + [ + 279.0, + 1948.0 + ], + [ + 278.0, + 1947.0 + ], + [ + 270.0, + 1947.0 + ], + [ + 269.0, + 1948.0 + ], + [ + 268.0, + 1948.0 + ], + [ + 267.0, + 1949.0 + ], + [ + 264.0, + 1946.0 + ], + [ + 261.0, + 1946.0 + ], + [ + 260.0, + 1947.0 + ], + [ + 260.0, + 1956.0 + ], + [ + 259.0, + 1957.0 + ], + [ + 255.0, + 1957.0 + ], + [ + 254.0, + 1956.0 + ], + [ + 254.0, + 1949.0 + ], + [ + 255.0, + 1948.0 + ], + [ + 255.0, + 1947.0 + ], + [ + 254.0, + 1946.0 + ], + [ + 252.0, + 1946.0 + ], + [ + 251.0, + 1945.0 + ], + [ + 250.0, + 1945.0 + ], + [ + 249.0, + 1946.0 + ], + [ + 249.0, + 1947.0 + ], + [ + 248.0, + 1948.0 + ], + [ + 245.0, + 1945.0 + ], + [ + 237.0, + 1945.0 + ], + [ + 235.0, + 1947.0 + ], + [ + 233.0, + 1945.0 + ], + [ + 232.0, + 1945.0 + ], + [ + 231.0, + 1944.0 + ], + [ + 224.0, + 1944.0 + ], + [ + 223.0, + 1945.0 + ], + [ + 223.0, + 1954.0 + ], + [ + 222.0, + 1955.0 + ], + [ + 221.0, + 1954.0 + ], + [ + 221.0, + 1952.0 + ], + [ + 220.0, + 1951.0 + ], + [ + 220.0, + 1947.0 + ], + [ + 219.0, + 1946.0 + ], + [ + 219.0, + 1945.0 + ], + [ + 218.0, + 1944.0 + ], + [ + 215.0, + 1944.0 + ], + [ + 214.0, + 1943.0 + ] + ], + [ + [ + 85.0, + 1937.0 + ], + [ + 85.0, + 1946.0 + ], + [ + 86.0, + 1947.0 + ], + [ + 85.0, + 1948.0 + ], + [ + 85.0, + 1951.0 + ], + [ + 88.0, + 1951.0 + ], + [ + 92.0, + 1947.0 + ], + [ + 93.0, + 1948.0 + ], + [ + 93.0, + 1950.0 + ], + [ + 95.0, + 1952.0 + ], + [ + 96.0, + 1952.0 + ], + [ + 98.0, + 1950.0 + ], + [ + 98.0, + 1947.0 + ], + [ + 99.0, + 1946.0 + ], + [ + 100.0, + 1947.0 + ], + [ + 100.0, + 1948.0 + ], + [ + 104.0, + 1952.0 + ], + [ + 111.0, + 1952.0 + ], + [ + 114.0, + 1949.0 + ], + [ + 115.0, + 1950.0 + ], + [ + 115.0, + 1952.0 + ], + [ + 116.0, + 1953.0 + ], + [ + 118.0, + 1953.0 + ], + [ + 120.0, + 1951.0 + ], + [ + 120.0, + 1950.0 + ], + [ + 121.0, + 1949.0 + ], + [ + 123.0, + 1951.0 + ], + [ + 123.0, + 1952.0 + ], + [ + 124.0, + 1953.0 + ], + [ + 127.0, + 1953.0 + ], + [ + 128.0, + 1952.0 + ], + [ + 130.0, + 1954.0 + ], + [ + 132.0, + 1954.0 + ], + [ + 135.0, + 1951.0 + ], + [ + 138.0, + 1951.0 + ], + [ + 139.0, + 1952.0 + ], + [ + 139.0, + 1953.0 + ], + [ + 140.0, + 1954.0 + ], + [ + 142.0, + 1954.0 + ], + [ + 144.0, + 1952.0 + ], + [ + 143.0, + 1951.0 + ], + [ + 143.0, + 1949.0 + ], + [ + 142.0, + 1948.0 + ], + [ + 142.0, + 1945.0 + ], + [ + 141.0, + 1944.0 + ], + [ + 141.0, + 1943.0 + ], + [ + 142.0, + 1942.0 + ], + [ + 143.0, + 1943.0 + ], + [ + 145.0, + 1943.0 + ], + [ + 146.0, + 1944.0 + ], + [ + 146.0, + 1953.0 + ], + [ + 148.0, + 1955.0 + ], + [ + 151.0, + 1952.0 + ], + [ + 151.0, + 1945.0 + ], + [ + 152.0, + 1944.0 + ], + [ + 153.0, + 1944.0 + ], + [ + 154.0, + 1943.0 + ], + [ + 155.0, + 1944.0 + ], + [ + 155.0, + 1952.0 + ], + [ + 158.0, + 1955.0 + ], + [ + 164.0, + 1955.0 + ], + [ + 167.0, + 1952.0 + ], + [ + 167.0, + 1951.0 + ], + [ + 168.0, + 1950.0 + ], + [ + 168.0, + 1946.0 + ], + [ + 169.0, + 1945.0 + ], + [ + 170.0, + 1946.0 + ], + [ + 170.0, + 1955.0 + ], + [ + 171.0, + 1956.0 + ], + [ + 173.0, + 1956.0 + ], + [ + 175.0, + 1954.0 + ], + [ + 175.0, + 1953.0 + ], + [ + 176.0, + 1952.0 + ], + [ + 177.0, + 1953.0 + ], + [ + 177.0, + 1954.0 + ], + [ + 179.0, + 1956.0 + ], + [ + 181.0, + 1956.0 + ], + [ + 183.0, + 1954.0 + ], + [ + 184.0, + 1955.0 + ], + [ + 184.0, + 1956.0 + ], + [ + 185.0, + 1957.0 + ], + [ + 193.0, + 1957.0 + ], + [ + 195.0, + 1955.0 + ], + [ + 197.0, + 1957.0 + ], + [ + 199.0, + 1957.0 + ], + [ + 201.0, + 1955.0 + ], + [ + 201.0, + 1954.0 + ], + [ + 200.0, + 1953.0 + ], + [ + 197.0, + 1953.0 + ], + [ + 195.0, + 1955.0 + ], + [ + 193.0, + 1953.0 + ], + [ + 190.0, + 1953.0 + ], + [ + 189.0, + 1952.0 + ], + [ + 190.0, + 1951.0 + ], + [ + 192.0, + 1951.0 + ], + [ + 194.0, + 1949.0 + ], + [ + 194.0, + 1948.0 + ], + [ + 193.0, + 1947.0 + ], + [ + 193.0, + 1946.0 + ], + [ + 195.0, + 1944.0 + ], + [ + 195.0, + 1943.0 + ], + [ + 194.0, + 1942.0 + ], + [ + 185.0, + 1942.0 + ], + [ + 184.0, + 1943.0 + ], + [ + 184.0, + 1953.0 + ], + [ + 183.0, + 1954.0 + ], + [ + 182.0, + 1953.0 + ], + [ + 182.0, + 1952.0 + ], + [ + 181.0, + 1951.0 + ], + [ + 181.0, + 1948.0 + ], + [ + 182.0, + 1947.0 + ], + [ + 182.0, + 1944.0 + ], + [ + 180.0, + 1942.0 + ], + [ + 178.0, + 1942.0 + ], + [ + 177.0, + 1941.0 + ], + [ + 171.0, + 1941.0 + ], + [ + 170.0, + 1942.0 + ], + [ + 169.0, + 1942.0 + ], + [ + 168.0, + 1941.0 + ], + [ + 164.0, + 1941.0 + ], + [ + 163.0, + 1942.0 + ], + [ + 163.0, + 1950.0 + ], + [ + 162.0, + 1951.0 + ], + [ + 161.0, + 1951.0 + ], + [ + 160.0, + 1950.0 + ], + [ + 160.0, + 1946.0 + ], + [ + 161.0, + 1945.0 + ], + [ + 161.0, + 1942.0 + ], + [ + 159.0, + 1940.0 + ], + [ + 143.0, + 1940.0 + ], + [ + 141.0, + 1942.0 + ], + [ + 138.0, + 1939.0 + ], + [ + 134.0, + 1939.0 + ], + [ + 133.0, + 1940.0 + ], + [ + 133.0, + 1942.0 + ], + [ + 132.0, + 1943.0 + ], + [ + 132.0, + 1944.0 + ], + [ + 131.0, + 1945.0 + ], + [ + 131.0, + 1947.0 + ], + [ + 130.0, + 1948.0 + ], + [ + 129.0, + 1947.0 + ], + [ + 129.0, + 1942.0 + ], + [ + 130.0, + 1941.0 + ], + [ + 128.0, + 1939.0 + ], + [ + 125.0, + 1939.0 + ], + [ + 124.0, + 1940.0 + ], + [ + 124.0, + 1941.0 + ], + [ + 123.0, + 1942.0 + ], + [ + 122.0, + 1941.0 + ], + [ + 122.0, + 1940.0 + ], + [ + 120.0, + 1938.0 + ], + [ + 117.0, + 1938.0 + ], + [ + 115.0, + 1940.0 + ], + [ + 114.0, + 1940.0 + ], + [ + 112.0, + 1938.0 + ], + [ + 103.0, + 1938.0 + ], + [ + 100.0, + 1941.0 + ], + [ + 99.0, + 1940.0 + ], + [ + 99.0, + 1938.0 + ], + [ + 98.0, + 1937.0 + ], + [ + 95.0, + 1937.0 + ], + [ + 94.0, + 1938.0 + ], + [ + 94.0, + 1940.0 + ], + [ + 93.0, + 1941.0 + ], + [ + 93.0, + 1943.0 + ], + [ + 92.0, + 1944.0 + ], + [ + 91.0, + 1943.0 + ], + [ + 90.0, + 1943.0 + ], + [ + 89.0, + 1942.0 + ], + [ + 92.0, + 1939.0 + ], + [ + 92.0, + 1938.0 + ], + [ + 91.0, + 1937.0 + ] + ], + [ + [ + 85.0, + 1918.0 + ], + [ + 85.0, + 1922.0 + ], + [ + 90.0, + 1922.0 + ], + [ + 91.0, + 1923.0 + ], + [ + 108.0, + 1923.0 + ], + [ + 109.0, + 1924.0 + ], + [ + 127.0, + 1924.0 + ], + [ + 128.0, + 1925.0 + ], + [ + 146.0, + 1925.0 + ], + [ + 147.0, + 1926.0 + ], + [ + 163.0, + 1926.0 + ], + [ + 164.0, + 1927.0 + ], + [ + 182.0, + 1927.0 + ], + [ + 183.0, + 1928.0 + ], + [ + 202.0, + 1928.0 + ], + [ + 203.0, + 1929.0 + ], + [ + 219.0, + 1929.0 + ], + [ + 220.0, + 1930.0 + ], + [ + 238.0, + 1930.0 + ], + [ + 239.0, + 1931.0 + ], + [ + 257.0, + 1931.0 + ], + [ + 258.0, + 1932.0 + ], + [ + 275.0, + 1932.0 + ], + [ + 276.0, + 1933.0 + ], + [ + 293.0, + 1933.0 + ], + [ + 294.0, + 1934.0 + ], + [ + 312.0, + 1934.0 + ], + [ + 313.0, + 1935.0 + ], + [ + 314.0, + 1935.0 + ], + [ + 314.0, + 1931.0 + ], + [ + 310.0, + 1931.0 + ], + [ + 309.0, + 1930.0 + ], + [ + 291.0, + 1930.0 + ], + [ + 290.0, + 1929.0 + ], + [ + 272.0, + 1929.0 + ], + [ + 271.0, + 1928.0 + ], + [ + 254.0, + 1928.0 + ], + [ + 253.0, + 1927.0 + ], + [ + 237.0, + 1927.0 + ], + [ + 236.0, + 1926.0 + ], + [ + 218.0, + 1926.0 + ], + [ + 217.0, + 1925.0 + ], + [ + 200.0, + 1925.0 + ], + [ + 199.0, + 1924.0 + ], + [ + 181.0, + 1924.0 + ], + [ + 180.0, + 1923.0 + ], + [ + 162.0, + 1923.0 + ], + [ + 161.0, + 1922.0 + ], + [ + 145.0, + 1922.0 + ], + [ + 144.0, + 1921.0 + ], + [ + 125.0, + 1921.0 + ], + [ + 124.0, + 1920.0 + ], + [ + 107.0, + 1920.0 + ], + [ + 106.0, + 1919.0 + ], + [ + 88.0, + 1919.0 + ], + [ + 87.0, + 1918.0 + ] + ], + [ + [ + 220.0, + 1874.0 + ], + [ + 218.0, + 1876.0 + ], + [ + 218.0, + 1877.0 + ], + [ + 217.0, + 1878.0 + ], + [ + 217.0, + 1882.0 + ], + [ + 216.0, + 1883.0 + ], + [ + 206.0, + 1883.0 + ], + [ + 205.0, + 1884.0 + ], + [ + 204.0, + 1883.0 + ], + [ + 197.0, + 1883.0 + ], + [ + 196.0, + 1884.0 + ], + [ + 194.0, + 1884.0 + ], + [ + 193.0, + 1885.0 + ], + [ + 192.0, + 1885.0 + ], + [ + 191.0, + 1884.0 + ], + [ + 188.0, + 1884.0 + ], + [ + 187.0, + 1885.0 + ], + [ + 186.0, + 1885.0 + ], + [ + 184.0, + 1887.0 + ], + [ + 184.0, + 1889.0 + ], + [ + 183.0, + 1890.0 + ], + [ + 183.0, + 1891.0 + ], + [ + 182.0, + 1892.0 + ], + [ + 181.0, + 1891.0 + ], + [ + 180.0, + 1891.0 + ], + [ + 178.0, + 1893.0 + ], + [ + 178.0, + 1894.0 + ], + [ + 177.0, + 1895.0 + ], + [ + 177.0, + 1896.0 + ], + [ + 181.0, + 1900.0 + ], + [ + 186.0, + 1900.0 + ], + [ + 187.0, + 1901.0 + ], + [ + 189.0, + 1901.0 + ], + [ + 191.0, + 1899.0 + ], + [ + 191.0, + 1898.0 + ], + [ + 194.0, + 1895.0 + ], + [ + 198.0, + 1899.0 + ], + [ + 201.0, + 1899.0 + ], + [ + 202.0, + 1900.0 + ], + [ + 207.0, + 1900.0 + ], + [ + 208.0, + 1899.0 + ], + [ + 209.0, + 1899.0 + ], + [ + 210.0, + 1898.0 + ], + [ + 212.0, + 1898.0 + ], + [ + 215.0, + 1901.0 + ], + [ + 217.0, + 1899.0 + ], + [ + 219.0, + 1901.0 + ], + [ + 220.0, + 1901.0 + ], + [ + 221.0, + 1902.0 + ], + [ + 222.0, + 1902.0 + ], + [ + 224.0, + 1900.0 + ], + [ + 224.0, + 1890.0 + ], + [ + 225.0, + 1889.0 + ], + [ + 226.0, + 1889.0 + ], + [ + 229.0, + 1886.0 + ], + [ + 229.0, + 1885.0 + ], + [ + 226.0, + 1882.0 + ], + [ + 225.0, + 1882.0 + ], + [ + 224.0, + 1881.0 + ], + [ + 224.0, + 1880.0 + ], + [ + 227.0, + 1877.0 + ], + [ + 227.0, + 1876.0 + ], + [ + 226.0, + 1875.0 + ], + [ + 226.0, + 1874.0 + ] + ], + [ + [ + 118.0, + 1874.0 + ], + [ + 118.0, + 1875.0 + ], + [ + 119.0, + 1876.0 + ], + [ + 122.0, + 1876.0 + ], + [ + 124.0, + 1878.0 + ], + [ + 124.0, + 1880.0 + ], + [ + 122.0, + 1882.0 + ], + [ + 117.0, + 1882.0 + ], + [ + 116.0, + 1883.0 + ], + [ + 114.0, + 1883.0 + ], + [ + 113.0, + 1882.0 + ], + [ + 112.0, + 1882.0 + ], + [ + 111.0, + 1881.0 + ], + [ + 108.0, + 1881.0 + ], + [ + 106.0, + 1883.0 + ], + [ + 106.0, + 1885.0 + ], + [ + 107.0, + 1886.0 + ], + [ + 106.0, + 1887.0 + ], + [ + 102.0, + 1887.0 + ], + [ + 101.0, + 1888.0 + ], + [ + 100.0, + 1887.0 + ], + [ + 95.0, + 1887.0 + ], + [ + 93.0, + 1889.0 + ], + [ + 93.0, + 1890.0 + ], + [ + 95.0, + 1892.0 + ], + [ + 93.0, + 1894.0 + ], + [ + 93.0, + 1895.0 + ], + [ + 92.0, + 1896.0 + ], + [ + 90.0, + 1896.0 + ], + [ + 89.0, + 1895.0 + ], + [ + 90.0, + 1894.0 + ], + [ + 88.0, + 1892.0 + ], + [ + 88.0, + 1888.0 + ], + [ + 89.0, + 1887.0 + ], + [ + 87.0, + 1885.0 + ], + [ + 85.0, + 1885.0 + ], + [ + 85.0, + 1896.0 + ], + [ + 86.0, + 1896.0 + ], + [ + 87.0, + 1897.0 + ], + [ + 87.0, + 1898.0 + ], + [ + 88.0, + 1899.0 + ], + [ + 96.0, + 1899.0 + ], + [ + 101.0, + 1894.0 + ], + [ + 101.0, + 1893.0 + ], + [ + 100.0, + 1892.0 + ], + [ + 101.0, + 1891.0 + ], + [ + 105.0, + 1891.0 + ], + [ + 106.0, + 1892.0 + ], + [ + 105.0, + 1893.0 + ], + [ + 105.0, + 1894.0 + ], + [ + 107.0, + 1896.0 + ], + [ + 110.0, + 1896.0 + ], + [ + 111.0, + 1895.0 + ], + [ + 112.0, + 1895.0 + ], + [ + 113.0, + 1896.0 + ], + [ + 114.0, + 1895.0 + ], + [ + 116.0, + 1895.0 + ], + [ + 119.0, + 1898.0 + ], + [ + 120.0, + 1898.0 + ], + [ + 121.0, + 1899.0 + ], + [ + 124.0, + 1896.0 + ], + [ + 125.0, + 1896.0 + ], + [ + 127.0, + 1898.0 + ], + [ + 130.0, + 1895.0 + ], + [ + 130.0, + 1888.0 + ], + [ + 131.0, + 1887.0 + ], + [ + 132.0, + 1888.0 + ], + [ + 132.0, + 1889.0 + ], + [ + 134.0, + 1891.0 + ], + [ + 134.0, + 1892.0 + ], + [ + 133.0, + 1893.0 + ], + [ + 133.0, + 1894.0 + ], + [ + 134.0, + 1895.0 + ], + [ + 134.0, + 1896.0 + ], + [ + 135.0, + 1897.0 + ], + [ + 139.0, + 1897.0 + ], + [ + 141.0, + 1899.0 + ], + [ + 142.0, + 1899.0 + ], + [ + 144.0, + 1897.0 + ], + [ + 144.0, + 1894.0 + ], + [ + 146.0, + 1892.0 + ], + [ + 149.0, + 1895.0 + ], + [ + 151.0, + 1895.0 + ], + [ + 153.0, + 1893.0 + ], + [ + 153.0, + 1892.0 + ], + [ + 152.0, + 1891.0 + ], + [ + 152.0, + 1890.0 + ], + [ + 153.0, + 1889.0 + ], + [ + 153.0, + 1887.0 + ], + [ + 154.0, + 1886.0 + ], + [ + 158.0, + 1886.0 + ], + [ + 161.0, + 1883.0 + ], + [ + 161.0, + 1882.0 + ], + [ + 160.0, + 1881.0 + ], + [ + 156.0, + 1881.0 + ], + [ + 154.0, + 1879.0 + ], + [ + 154.0, + 1874.0 + ], + [ + 148.0, + 1874.0 + ], + [ + 148.0, + 1876.0 + ], + [ + 149.0, + 1877.0 + ], + [ + 149.0, + 1878.0 + ], + [ + 146.0, + 1881.0 + ], + [ + 143.0, + 1881.0 + ], + [ + 142.0, + 1880.0 + ], + [ + 142.0, + 1879.0 + ], + [ + 141.0, + 1878.0 + ], + [ + 141.0, + 1876.0 + ], + [ + 142.0, + 1875.0 + ], + [ + 142.0, + 1874.0 + ], + [ + 138.0, + 1874.0 + ], + [ + 137.0, + 1875.0 + ], + [ + 137.0, + 1880.0 + ], + [ + 136.0, + 1881.0 + ], + [ + 130.0, + 1881.0 + ], + [ + 129.0, + 1880.0 + ], + [ + 129.0, + 1878.0 + ], + [ + 128.0, + 1877.0 + ], + [ + 128.0, + 1874.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 200.03, + 1914.74 + ], + "size": [ + 84.07, + 232.85 + ], + "angle_deg": -86.88 + }, + "centroid_px": [ + 185.07, + 1922.97 + ], + "orientation_deg": 10.86, + "ink_pixels": 3841, + "detected": true + }, + "metrics": { + "box_coverage_pct": 86.573, + "containment_pct": 85.995, + "ink_fill_ratio": 0.1856, + "polygon_overlap_proxy": 0.7588, + "distance_from_box_center_px": 15.22, + "margin_utilization": 1.15, + "mean_dark_intensity": 144.72, + "gray_stddev": 55.62, + "signature_area_ratio": 0.00553 + }, + "provenance": { + "master_seed": 1020, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 119, + 505, + 288, + 522 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 993, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 885, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1458, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..4b557e79845b25f4146317fe97c25fd8078bfb11 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_020_4b_procedural/case_020_4b_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b32dea6abca7bb5fac1e116c2643151be0c3927035a659a8cae945c06f1835b3 +size 3517 diff --git a/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..c7257a2a8e6073ed9daa2026df27b8d5f1398aff --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5eaba1463fb8d9d1a146c07e592170fdac5bad80604f0c43002e518498a095e9 +size 335202 diff --git a/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..4bbcd4be795f4507ccd66328bf7afa0ca0ab9665 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56851358e2972af0ae27266f10d3767b6c847e334b3bd373551c2498b7dbb2aa +size 1005105 diff --git a/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..95d498fed8169f06e1dfe14670c2880d6ffe890e --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_021_5a_scanned/case_021_5a_scanned_ground_truth.json @@ -0,0 +1,358 @@ +{ + "case_id": "case_021_5a_scanned", + "expected_verdict": "REJECT", + "expected_reject_code": "REQUIRED_SIGNER_ABSENT", + "document_id": "case_000001", + "template": "bank_doc_05", + "template_kind": "scanned", + "document_type": "corporate_bank_loan_agreement", + "page": 1, + "page_size": [ + 687, + 1024 + ], + "dpi": 300, + "signer": { + "name": "Carlos Wilson", + "role": "Borrower" + }, + "signature_source": { + "signature_id": null, + "path": null, + "signer_group": null, + "source_type": "none", + "script": null + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 0.0, + "scale_factor": 0.0, + "anchor_point": "center", + "margin_target_box": [ + 65.0, + 780.0, + 320.0, + 815.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8483, + "blur_sigma": 0.5409, + "feather_px": 0.5383, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 1.0, + "tier": "hard", + "misplacement": 1.0, + "whitespace": 1.0, + "faintness": 1.0, + "smallness": 1.0, + "low_texture": 1.0 + }, + "quality": { + "degraded": false, + "case_type": "5a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "5a", + "verdict": "REJECT", + "reject_code": "REQUIRED_SIGNER_ABSENT", + "target_box_id": "sig1", + "expected_signer_group": "signet_0069", + "expected_signer_script": "latin", + "actual_signer_group": null, + "actual_source_type": "none", + "actual_signer_script": null, + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "signet_0069", + "script": "latin", + "matches_expected": true, + "ref_path": "data\\raw\\signet\\test\\069\\07_069.png" + } + }, + "augmentation": { + "rotate_deg": 0.0, + "scale_factor": 0.0, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.2, + "blur_sigma": 0.5409, + "jpeg_quality": 52, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3154621354, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 1227406993, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 4222247252, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1812055203, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1188979729, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3465631779, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 527157017, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 2599710068, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3204243783, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2240038342, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4224049261, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 4058667882, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 4232469767, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 2266527075, + "params": { + "resolved_tone": "yellow" + } + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 2763987165, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1778256012, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3591495110, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 3905130985, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1133375116, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 259189142, + "params": { + "quality": 52 + } + } + } + }, + "geometry": { + "box_px": [ + 65.0, + 780.0, + 320.0, + 815.0 + ], + "signature_bbox_px": [ + 65.0, + 780.0, + 320.0, + 815.0 + ], + "target_box": [ + 65.0, + 780.0, + 320.0, + 815.0 + ], + "actual_box": [ + 65.0, + 780.0, + 320.0, + 815.0 + ], + "search_box_px": [ + 45.875000000000014, + 774.0, + 339.125, + 821.0 + ], + "signature_polygon_px": [], + "convex_hull_px": [], + "display_contours": [], + "min_rotated_rect": { + "center": [ + 0.0, + 0.0 + ], + "size": [ + 0.0, + 0.0 + ], + "angle_deg": 0.0 + }, + "centroid_px": [ + NaN, + NaN + ], + "orientation_deg": 0.0, + "ink_pixels": 0, + "detected": false + }, + "metrics": { + "box_coverage_pct": 0.0, + "containment_pct": 0.0, + "ink_fill_ratio": 0.0, + "polygon_overlap_proxy": 0.0, + "distance_from_box_center_px": NaN, + "margin_utilization": 0.0, + "mean_dark_intensity": 255.0, + "gray_stddev": 0.0, + "signature_area_ratio": 0.0 + }, + "provenance": { + "master_seed": 1021, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "name_sig1": { + "actual_box": [ + 77.0, + 856.0, + 163.0, + 866.0 + ], + "target_box": null + }, + "name_sig2": { + "actual_box": [ + 370.0, + 856.0, + 456.0, + 866.0 + ], + "target_box": null + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..6e5e920ed0513fbdcc9a66f09e736f8f8f4bff07 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acd2da92db718807755c488d6094fdaee5f5448bfe5c00b977aa2afba5a6a09c +size 4284 diff --git a/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..099cf8b0ff53dcffe263b75077dfaac05a5a5c3a --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72905e3d16d829893a65362677ea03248a048b2ff8548fc33a4ea6f5e2e17c30 +size 7313549 diff --git a/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..4a0cfe696051c96cae853ef7cfe5c0e16e37d819 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_022_5a_procedural/case_022_5a_procedural_ground_truth.json @@ -0,0 +1,464 @@ +{ + "case_id": "case_022_5a_procedural", + "expected_verdict": "REJECT", + "expected_reject_code": "REQUIRED_SIGNER_ABSENT", + "document_id": "case_000001", + "template": "proc_doc_09", + "template_kind": "procedural", + "document_type": "Account Opening", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Jennifer White", + "role": "Applicant Agent" + }, + "signature_source": { + "signature_id": null, + "path": null, + "signer_group": null, + "source_type": "none", + "script": null + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 0.0, + "scale_factor": 0.0, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.7862, + "blur_sigma": 0.4948, + "feather_px": 1.3073, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 1.0, + "tier": "hard", + "misplacement": 1.0, + "whitespace": 1.0, + "faintness": 1.0, + "smallness": 1.0, + "low_texture": 1.0 + }, + "quality": { + "degraded": false, + "case_type": "5a", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "5a", + "verdict": "REJECT", + "reject_code": "REQUIRED_SIGNER_ABSENT", + "target_box_id": "sig1", + "expected_signer_group": "signer_0020", + "expected_signer_script": "latin", + "actual_signer_group": null, + "actual_source_type": "none", + "actual_signer_script": null, + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "bhsig260_hindi_0012", + "script": "hindi", + "matches_expected": true, + "ref_path": "data\\raw\\bhsig260_hindi\\12\\H-S-12-G-04.tif" + } + }, + "augmentation": { + "rotate_deg": 0.0, + "scale_factor": 0.0, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 3.05, + "discoloration_strength": 0.0, + "blur_sigma": 0.4948, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 3941632230, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 828253081, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 718272143, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 288360725, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1417307043, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 311268604, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3697157822, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 3961303917, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2072743454, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2565592229, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 126458338, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 3397508503, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1044906521, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 435285377, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.3, + "seed": 3948930973, + "params": { + "tilt_deg": 3.05 + } + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 1962143373, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 493191990, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 4150390862, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 2593424635, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 2006137942, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [], + "convex_hull_px": [], + "display_contours": [], + "min_rotated_rect": { + "center": [ + 0.0, + 0.0 + ], + "size": [ + 0.0, + 0.0 + ], + "angle_deg": 0.0 + }, + "centroid_px": [ + NaN, + NaN + ], + "orientation_deg": 0.0, + "ink_pixels": 0, + "detected": false + }, + "metrics": { + "box_coverage_pct": 0.0, + "containment_pct": 0.0, + "ink_fill_ratio": 0.0, + "polygon_overlap_proxy": 0.0, + "distance_from_box_center_px": NaN, + "margin_utilization": 0.0, + "mean_dark_intensity": 255.0, + "gray_stddev": 0.0, + "signature_area_ratio": 0.0 + }, + "provenance": { + "master_seed": 1022, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 273, + 522 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 993, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 885, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..5dfe2280278ea5c2148618938895f051a1cd093e --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:831f2a46ca558f11c8a410c50b19c9abc3ce773d5cdfe74a3cab95afbfba76b7 +size 4325 diff --git a/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_document.png b/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_document.png new file mode 100644 index 0000000000000000000000000000000000000000..41f54e33f34066c1e2cafb5f5d3a9394e4f9cc3c --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6bea23cff3f2a7e1425d8ded8d0012f10a2dd573a45dbbd1936ea5cb2485015 +size 615551 diff --git a/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..ff82583c9ee92c06606aa56fd12d71524d6d272a --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_ground_truth.json @@ -0,0 +1,710 @@ +{ + "case_id": "case_023_5b_scanned", + "expected_verdict": "REJECT", + "expected_reject_code": "REQUIRED_SIGNER_ABSENT", + "document_id": "case_000001", + "template": "bank_doc_09", + "template_kind": "scanned", + "document_type": "corporate_account_opening_mandate", + "page": 1, + "page_size": [ + 687, + 1024 + ], + "dpi": 300, + "signer": { + "name": "Stephen King", + "role": "First Authorised Signatory" + }, + "signature_source": { + "signature_id": "bhsig260_hindi_001454", + "path": "data/raw/bhsig260_hindi/36/H-S-36-G-07.tif", + "signer_group": "bhsig260_hindi_0036", + "source_type": "genuine", + "script": "hindi" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": 3.974, + "scale_factor": 0.8915, + "anchor_point": "center", + "margin_target_box": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.8, + "blur_sigma": 0.2316, + "feather_px": 1.3439, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.7202, + "tier": "hard", + "misplacement": 0.7512, + "whitespace": 0.7095, + "faintness": 0.7458, + "smallness": 0.8698, + "low_texture": 0.3573 + }, + "quality": { + "degraded": false, + "case_type": "5b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "5b", + "verdict": "REJECT", + "reject_code": "REQUIRED_SIGNER_ABSENT", + "target_box_id": "sig1", + "expected_signer_group": "bhsig260_bengali_0028", + "expected_signer_script": "bengali", + "actual_signer_group": "bhsig260_hindi_0036", + "actual_source_type": "genuine", + "actual_signer_script": "hindi", + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "bhsig260_bengali_0078", + "script": "bengali", + "matches_expected": true, + "ref_path": "data\\raw\\bhsig260_bengali\\78\\B-S-78-G-02.tif" + } + }, + "augmentation": { + "rotate_deg": 3.974, + "scale_factor": 0.8915, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": "fold", + "shadow_strength": 0.35, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.0, + "blur_sigma": 0.2316, + "jpeg_quality": 42, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 1595767300, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 740898397, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 390199544, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 141661778, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 603724917, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3166027497, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3899822808, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 2241133199, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 461242675, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 4137734976, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2005572479, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": true, + "strength": 0.35, + "probability": 0.4, + "seed": 1490743238, + "params": { + "resolved_type": "fold", + "folds": [ + { + "vertical": true, + "position_frac": 0.255 + } + ] + } + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3492776275, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 3268516195, + "params": {} + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 2109813381, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3237846106, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 3904206396, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 1760848030, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3073001352, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": true, + "strength": 0.3, + "probability": 0.5, + "seed": 1468619422, + "params": { + "quality": 42 + } + } + } + }, + "geometry": { + "box_px": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "signature_bbox_px": [ + 164.0, + 791.0, + 242.0, + 838.0 + ], + "target_box": [ + 90.0, + 797.0, + 315.0, + 832.0 + ], + "actual_box": [ + 164.0, + 791.0, + 242.0, + 838.0 + ], + "search_box_px": [ + 73.12500000000001, + 791.0, + 331.875, + 838.0 + ], + "signature_polygon_px": [ + [ + 241, + 818 + ], + [ + 182, + 837 + ], + [ + 169, + 836 + ], + [ + 164, + 819 + ], + [ + 168, + 793 + ], + [ + 182, + 791 + ], + [ + 239, + 811 + ] + ], + "convex_hull_px": [ + [ + 241, + 818 + ], + [ + 182, + 837 + ], + [ + 169, + 836 + ], + [ + 164, + 819 + ], + [ + 168, + 793 + ], + [ + 182, + 791 + ], + [ + 239, + 811 + ] + ], + "display_contours": [ + [ + [ + 235.0, + 810.0 + ], + [ + 234.0, + 811.0 + ], + [ + 221.0, + 811.0 + ], + [ + 218.0, + 814.0 + ], + [ + 218.0, + 819.0 + ], + [ + 221.0, + 822.0 + ], + [ + 223.0, + 822.0 + ], + [ + 224.0, + 821.0 + ], + [ + 239.0, + 821.0 + ], + [ + 242.0, + 818.0 + ], + [ + 242.0, + 813.0 + ], + [ + 239.0, + 810.0 + ] + ], + [ + [ + 169.0, + 791.0 + ], + [ + 167.0, + 793.0 + ], + [ + 167.0, + 794.0 + ], + [ + 168.0, + 795.0 + ], + [ + 168.0, + 798.0 + ], + [ + 169.0, + 799.0 + ], + [ + 169.0, + 800.0 + ], + [ + 170.0, + 801.0 + ], + [ + 168.0, + 803.0 + ], + [ + 168.0, + 806.0 + ], + [ + 169.0, + 807.0 + ], + [ + 169.0, + 808.0 + ], + [ + 165.0, + 812.0 + ], + [ + 165.0, + 815.0 + ], + [ + 164.0, + 816.0 + ], + [ + 164.0, + 817.0 + ], + [ + 163.0, + 818.0 + ], + [ + 163.0, + 819.0 + ], + [ + 164.0, + 820.0 + ], + [ + 164.0, + 821.0 + ], + [ + 167.0, + 824.0 + ], + [ + 167.0, + 826.0 + ], + [ + 168.0, + 827.0 + ], + [ + 168.0, + 828.0 + ], + [ + 167.0, + 829.0 + ], + [ + 167.0, + 831.0 + ], + [ + 168.0, + 832.0 + ], + [ + 169.0, + 832.0 + ], + [ + 170.0, + 833.0 + ], + [ + 168.0, + 835.0 + ], + [ + 168.0, + 836.0 + ], + [ + 169.0, + 837.0 + ], + [ + 183.0, + 837.0 + ], + [ + 184.0, + 836.0 + ], + [ + 184.0, + 835.0 + ], + [ + 183.0, + 834.0 + ], + [ + 182.0, + 834.0 + ], + [ + 181.0, + 833.0 + ], + [ + 183.0, + 831.0 + ], + [ + 183.0, + 830.0 + ], + [ + 189.0, + 824.0 + ], + [ + 189.0, + 822.0 + ], + [ + 190.0, + 821.0 + ], + [ + 192.0, + 821.0 + ], + [ + 194.0, + 819.0 + ], + [ + 194.0, + 818.0 + ], + [ + 195.0, + 817.0 + ], + [ + 194.0, + 816.0 + ], + [ + 194.0, + 812.0 + ], + [ + 193.0, + 811.0 + ], + [ + 193.0, + 810.0 + ], + [ + 192.0, + 809.0 + ], + [ + 192.0, + 808.0 + ], + [ + 190.0, + 806.0 + ], + [ + 188.0, + 806.0 + ], + [ + 187.0, + 805.0 + ], + [ + 186.0, + 805.0 + ], + [ + 185.0, + 804.0 + ], + [ + 184.0, + 804.0 + ], + [ + 183.0, + 803.0 + ], + [ + 183.0, + 791.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 202.5, + 814.0 + ], + "size": [ + 46.0, + 77.0 + ], + "angle_deg": -90.0 + }, + "centroid_px": [ + 187.84, + 814.31 + ], + "orientation_deg": 2.75, + "ink_pixels": 1065, + "detected": true + }, + "metrics": { + "box_coverage_pct": 25.578, + "containment_pct": 90.065, + "ink_fill_ratio": 0.2905, + "polygon_overlap_proxy": 0.2488, + "distance_from_box_center_px": 14.66, + "margin_utilization": 0.3962, + "mean_dark_intensity": 179.5, + "gray_stddev": 38.56, + "signature_area_ratio": 0.00521 + }, + "provenance": { + "master_seed": 1023, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "company_name": { + "actual_box": [ + 80.0, + 169.0, + 290.0, + 183.0 + ], + "target_box": null + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..5b653b08ed79b6eeef315f1a7d81041cdba189e1 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_023_5b_scanned/case_023_5b_scanned_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d59b2c306372d5d0ccb180dbf3abc0abb875ea266e14159da9b4a97c1d27805 +size 3360 diff --git a/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_companion_signature.png b/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_companion_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..3f7da1a523223e3044ec6de22280aeba19c519e0 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_companion_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f49454ba91eb7e608a9c1be3bc93a1dd68363863b377094e2a695589aa8fb8c6 +size 92646 diff --git a/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_document.png b/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_document.png new file mode 100644 index 0000000000000000000000000000000000000000..fadf75cfeeab2ecf3a689458a66ebd0e333602ac --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_document.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c539c7b2fb1d5a6f0849ee062af8c96402c4cf43a3ec8ce70ddca238146a45e +size 7611604 diff --git a/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_ground_truth.json b/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_ground_truth.json new file mode 100644 index 0000000000000000000000000000000000000000..5eacb6d8c39fad4424f0609d26e82cfab0351a15 --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_ground_truth.json @@ -0,0 +1,1011 @@ +{ + "case_id": "case_024_5b_procedural", + "expected_verdict": "REJECT", + "expected_reject_code": "REQUIRED_SIGNER_ABSENT", + "document_id": "case_000001", + "template": "proc_doc_11", + "template_kind": "procedural", + "document_type": "Promissory Note", + "page": 1, + "page_size": [ + 1700, + 2200 + ], + "dpi": 200, + "signer": { + "name": "Noah Hill", + "role": "Issuing Debtor" + }, + "signature_source": { + "signature_id": "sig_002041", + "path": "data/raw/cedar/genuine/original_38_10.png", + "signer_group": "signer_0038", + "source_type": "genuine", + "script": "latin" + }, + "placement": { + "x_offset": 0.0, + "y_offset": 0.0, + "rotation_deg": -2.097, + "scale_factor": 0.9055, + "anchor_point": "center", + "margin_target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "misplaced": false, + "misplacement_dx": 0.0, + "misplacement_dy": 0.0 + }, + "rendering": { + "blend_mode": "multiply", + "ink_opacity": 0.9418, + "blur_sigma": 0.2641, + "feather_px": 0.7122, + "scanner_noise": 0.02 + }, + "difficulty": { + "score": 0.629, + "tier": "medium", + "misplacement": 0.8876, + "whitespace": 0.8474, + "faintness": 0.0, + "smallness": 0.6967, + "low_texture": 0.4433 + }, + "quality": { + "degraded": false, + "case_type": "5b", + "notes": null, + "scanner_quality": 0.85, + "paper_quality": 0.85, + "render_quality": 0.85 + }, + "verification": { + "case_type": "5b", + "verdict": "REJECT", + "reject_code": "REQUIRED_SIGNER_ABSENT", + "target_box_id": "sig1", + "expected_signer_group": "signet_0056", + "expected_signer_script": "latin", + "actual_signer_group": "signer_0038", + "actual_source_type": "genuine", + "actual_signer_script": "latin", + "companion": { + "box_id": "sig2", + "filled": true, + "signer_group": "signet_0029", + "script": "latin", + "matches_expected": true, + "ref_path": "data\\raw\\signet\\train\\029\\07_029.png" + } + }, + "augmentation": { + "rotate_deg": -2.097, + "scale_factor": 0.9055, + "stroke": 0.0, + "noise_amount": 0.06, + "stain_type": null, + "stain_strength": 0.0, + "shadow_type": null, + "shadow_strength": 0.0, + "coffee_stain_strength": 0.0, + "crumple_strength": 0.0, + "tilt_deg": 0.0, + "discoloration_strength": 0.2, + "blur_sigma": 0.2641, + "jpeg_quality": null, + "scanner_noise": 0.02, + "effects": { + "rotation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.0, + "seed": 2985003969, + "params": {} + }, + "stroke_dilation": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.25, + "seed": 1676512876, + "params": {} + }, + "ink_fading": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3308359329, + "params": {} + }, + "ink_bleed": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 3116377197, + "params": {} + }, + "motion_blur": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 4175112612, + "params": {} + }, + "gaussian_blur": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 3484534778, + "params": {} + }, + "perspective": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1264414436, + "params": {} + }, + "contrast": { + "enabled": true, + "applied": true, + "strength": 0.15, + "probability": 0.4, + "seed": 194325385, + "params": {} + }, + "gamma": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 3492920789, + "params": {} + }, + "coffee_stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 607294602, + "params": {} + }, + "stain": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 2338613612, + "params": {} + }, + "shadow": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.4, + "seed": 4250729904, + "params": {} + }, + "crumple": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 1546322223, + "params": {} + }, + "discoloration": { + "enabled": true, + "applied": true, + "strength": 0.2, + "probability": 0.5, + "seed": 1053951037, + "params": { + "resolved_tone": "grey" + } + }, + "page_tilt": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.3, + "seed": 258813810, + "params": {} + }, + "dust": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.2, + "seed": 2313537884, + "params": {} + }, + "scanner_streaks": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.1, + "seed": 1762409348, + "params": {} + }, + "scanner_noise": { + "enabled": true, + "applied": true, + "strength": 0.06, + "probability": 1.0, + "seed": 2044202543, + "params": {} + }, + "salt_pepper": { + "enabled": false, + "applied": false, + "strength": 0.0, + "probability": 0.15, + "seed": 808960898, + "params": {} + }, + "jpeg_compression": { + "enabled": true, + "applied": false, + "strength": 0.0, + "probability": 0.5, + "seed": 526470841, + "params": {} + } + } + }, + "geometry": { + "box_px": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "signature_bbox_px": [ + 157.0, + 1909.0, + 254.0, + 1944.0 + ], + "target_box": [ + 100.0, + 1880.0, + 300.0, + 1960.0 + ], + "actual_box": [ + 157.0, + 1909.0, + 254.0, + 1944.0 + ], + "search_box_px": [ + 85.00000000000001, + 1874.0, + 315.0, + 1966.0 + ], + "signature_polygon_px": [ + [ + 253, + 1920 + ], + [ + 157, + 1943 + ], + [ + 158, + 1914 + ], + [ + 169, + 1909 + ], + [ + 234, + 1914 + ] + ], + "convex_hull_px": [ + [ + 253, + 1920 + ], + [ + 157, + 1943 + ], + [ + 158, + 1914 + ], + [ + 169, + 1909 + ], + [ + 234, + 1914 + ] + ], + "display_contours": [ + [ + [ + 250.0, + 1919.0 + ], + [ + 249.0, + 1920.0 + ], + [ + 249.0, + 1921.0 + ], + [ + 250.0, + 1922.0 + ], + [ + 252.0, + 1922.0 + ], + [ + 254.0, + 1920.0 + ], + [ + 253.0, + 1919.0 + ] + ], + [ + [ + 231.0, + 1913.0 + ], + [ + 230.0, + 1914.0 + ], + [ + 230.0, + 1915.0 + ], + [ + 229.0, + 1916.0 + ], + [ + 229.0, + 1917.0 + ], + [ + 228.0, + 1918.0 + ], + [ + 228.0, + 1921.0 + ], + [ + 229.0, + 1922.0 + ], + [ + 229.0, + 1924.0 + ], + [ + 230.0, + 1925.0 + ], + [ + 233.0, + 1922.0 + ], + [ + 237.0, + 1922.0 + ], + [ + 239.0, + 1920.0 + ], + [ + 238.0, + 1919.0 + ], + [ + 237.0, + 1919.0 + ], + [ + 236.0, + 1918.0 + ], + [ + 237.0, + 1917.0 + ], + [ + 237.0, + 1915.0 + ], + [ + 236.0, + 1914.0 + ], + [ + 235.0, + 1914.0 + ], + [ + 234.0, + 1913.0 + ] + ], + [ + [ + 219.0, + 1912.0 + ], + [ + 218.0, + 1913.0 + ], + [ + 218.0, + 1917.0 + ], + [ + 217.0, + 1918.0 + ], + [ + 217.0, + 1920.0 + ], + [ + 216.0, + 1921.0 + ], + [ + 216.0, + 1925.0 + ], + [ + 217.0, + 1926.0 + ], + [ + 219.0, + 1924.0 + ], + [ + 219.0, + 1923.0 + ], + [ + 220.0, + 1922.0 + ], + [ + 220.0, + 1921.0 + ], + [ + 222.0, + 1919.0 + ], + [ + 223.0, + 1920.0 + ], + [ + 226.0, + 1917.0 + ], + [ + 226.0, + 1915.0 + ], + [ + 223.0, + 1912.0 + ] + ], + [ + [ + 169.0, + 1908.0 + ], + [ + 168.0, + 1909.0 + ], + [ + 167.0, + 1909.0 + ], + [ + 166.0, + 1910.0 + ], + [ + 164.0, + 1910.0 + ], + [ + 163.0, + 1911.0 + ], + [ + 162.0, + 1911.0 + ], + [ + 161.0, + 1912.0 + ], + [ + 159.0, + 1912.0 + ], + [ + 157.0, + 1914.0 + ], + [ + 157.0, + 1918.0 + ], + [ + 159.0, + 1920.0 + ], + [ + 160.0, + 1920.0 + ], + [ + 163.0, + 1923.0 + ], + [ + 163.0, + 1926.0 + ], + [ + 162.0, + 1927.0 + ], + [ + 162.0, + 1928.0 + ], + [ + 160.0, + 1930.0 + ], + [ + 160.0, + 1931.0 + ], + [ + 159.0, + 1932.0 + ], + [ + 159.0, + 1933.0 + ], + [ + 158.0, + 1934.0 + ], + [ + 158.0, + 1935.0 + ], + [ + 157.0, + 1936.0 + ], + [ + 157.0, + 1937.0 + ], + [ + 156.0, + 1938.0 + ], + [ + 156.0, + 1943.0 + ], + [ + 157.0, + 1944.0 + ], + [ + 159.0, + 1944.0 + ], + [ + 161.0, + 1942.0 + ], + [ + 161.0, + 1937.0 + ], + [ + 162.0, + 1936.0 + ], + [ + 162.0, + 1935.0 + ], + [ + 164.0, + 1933.0 + ], + [ + 164.0, + 1931.0 + ], + [ + 165.0, + 1930.0 + ], + [ + 165.0, + 1929.0 + ], + [ + 168.0, + 1926.0 + ], + [ + 169.0, + 1926.0 + ], + [ + 171.0, + 1928.0 + ], + [ + 174.0, + 1925.0 + ], + [ + 181.0, + 1925.0 + ], + [ + 182.0, + 1926.0 + ], + [ + 183.0, + 1925.0 + ], + [ + 187.0, + 1925.0 + ], + [ + 188.0, + 1924.0 + ], + [ + 192.0, + 1924.0 + ], + [ + 194.0, + 1922.0 + ], + [ + 197.0, + 1922.0 + ], + [ + 198.0, + 1923.0 + ], + [ + 203.0, + 1923.0 + ], + [ + 206.0, + 1920.0 + ], + [ + 205.0, + 1919.0 + ], + [ + 204.0, + 1919.0 + ], + [ + 203.0, + 1918.0 + ], + [ + 202.0, + 1918.0 + ], + [ + 201.0, + 1919.0 + ], + [ + 199.0, + 1919.0 + ], + [ + 198.0, + 1918.0 + ], + [ + 197.0, + 1918.0 + ], + [ + 196.0, + 1917.0 + ], + [ + 196.0, + 1916.0 + ], + [ + 195.0, + 1915.0 + ], + [ + 193.0, + 1915.0 + ], + [ + 191.0, + 1913.0 + ], + [ + 189.0, + 1913.0 + ], + [ + 188.0, + 1914.0 + ], + [ + 188.0, + 1916.0 + ], + [ + 184.0, + 1920.0 + ], + [ + 176.0, + 1920.0 + ], + [ + 175.0, + 1919.0 + ], + [ + 174.0, + 1919.0 + ], + [ + 173.0, + 1918.0 + ], + [ + 173.0, + 1916.0 + ], + [ + 174.0, + 1915.0 + ], + [ + 174.0, + 1913.0 + ], + [ + 175.0, + 1912.0 + ], + [ + 175.0, + 1909.0 + ], + [ + 174.0, + 1908.0 + ] + ] + ], + "min_rotated_rect": { + "center": [ + 201.6, + 1917.11 + ], + "size": [ + 98.72, + 30.78 + ], + "angle_deg": -13.31 + }, + "centroid_px": [ + 187.63, + 1920.72 + ], + "orientation_deg": -5.06, + "ink_pixels": 518, + "detected": true + }, + "metrics": { + "box_coverage_pct": 11.241, + "containment_pct": 100.0, + "ink_fill_ratio": 0.1526, + "polygon_overlap_proxy": 0.1124, + "distance_from_box_center_px": 12.4, + "margin_utilization": 0.5364, + "mean_dark_intensity": 158.11, + "gray_stddev": 33.4, + "signature_area_ratio": 0.00091 + }, + "provenance": { + "master_seed": 1024, + "sample_index": 1 + }, + "cosmetics": { + "field_boxes": { + "full_legal_name": { + "actual_box": [ + 120, + 505, + 475, + 522 + ], + "target_box": [ + 100, + 380, + 850, + 520 + ] + }, + "registration_no": { + "actual_box": [ + 870, + 505, + 1006, + 522 + ], + "target_box": [ + 850, + 380, + 1600, + 520 + ] + }, + "country_of_incorporation": { + "actual_box": [ + 120, + 635, + 229, + 657 + ], + "target_box": [ + 100, + 520, + 850, + 660 + ] + }, + "tax_jurisdiction": { + "actual_box": [ + 870, + 635, + 905, + 652 + ], + "target_box": [ + 850, + 520, + 1600, + 660 + ] + }, + "registered_address": { + "actual_box": [ + 120, + 885, + 403, + 907 + ], + "target_box": [ + 100, + 800, + 1600, + 916 + ] + }, + "checkbox_row0": { + "actual_box": [ + 120, + 1096, + 144, + 1120 + ], + "target_box": [ + 120, + 1096, + 144, + 1120 + ] + }, + "swift_row0": { + "actual_box": [ + 700, + 1105, + 880, + 1122 + ], + "target_box": [ + 680, + 1090, + 1040, + 1130 + ] + }, + "currency_row0": { + "actual_box": [ + 1100, + 1105, + 1152, + 1122 + ], + "target_box": [ + 1080, + 1090, + 1280, + 1130 + ] + }, + "amount_row0": { + "actual_box": [ + 1340, + 1105, + 1460, + 1125 + ], + "target_box": [ + 1320, + 1090, + 1600, + 1130 + ] + } + } + } +} \ No newline at end of file diff --git a/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_reference_signature.png b/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_reference_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..9464c22e7f79cfa57c250b870faf9cfb7531990d --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/case_024_5b_procedural/case_024_5b_procedural_reference_signature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc07f61baa38e29271def9db4381f921a1624e797da4ed42ce2255247fcb7f2d +size 119505 diff --git a/unified_pipeline/data/showcase_dataset/reference_signers_index.json b/unified_pipeline/data/showcase_dataset/reference_signers_index.json new file mode 100644 index 0000000000000000000000000000000000000000..ba91b5d6fe62f0dc8a8922b865b72f5f56a30a1b --- /dev/null +++ b/unified_pipeline/data/showcase_dataset/reference_signers_index.json @@ -0,0 +1,699 @@ +[ + { + "signer_group": "bhsig260_hindi_0087", + "display_name": "Ashley Moore", + "script": "hindi", + "roles": [ + "Primary Debtor" + ], + "appears_in": [ + { + "case_id": "case_012_2b_procedural", + "role": "Primary Debtor", + "as": "additional", + "template": "proc_doc_04" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/87/H-S-087-G-02.tif" + ] + }, + { + "signer_group": "signet_0035", + "display_name": "Brian Clark", + "script": "latin", + "roles": [ + "Bank Manager" + ], + "appears_in": [ + { + "case_id": "case_019_4b_scanned", + "role": "Bank Manager", + "as": "companion", + "template": "bank_doc_08" + } + ], + "reference_images": [ + "data/raw/signet/train/035/10_035.png" + ] + }, + { + "signer_group": "signet_0049", + "display_name": "Brian Ivanov", + "script": "latin", + "roles": [ + "First Authorised Signatory" + ], + "appears_in": [ + { + "case_id": "case_003_1b_scanned", + "role": "First Authorised Signatory", + "as": "additional", + "template": "bank_doc_09" + } + ], + "reference_images": [ + "data/raw/signet/train/049/11_049.png" + ] + }, + { + "signer_group": "signet_0069", + "display_name": "Carlos Wilson", + "script": "latin", + "roles": [ + "Authorized Signatory" + ], + "appears_in": [ + { + "case_id": "case_021_5a_scanned", + "role": "Authorized Signatory", + "as": "companion", + "template": "bank_doc_05" + } + ], + "reference_images": [ + "data/raw/signet/test/069/07_069.png" + ] + }, + { + "signer_group": "bhsig260_hindi_0103", + "display_name": "Deborah Wright", + "script": "hindi", + "roles": [ + "Lead Arranger Officer" + ], + "appears_in": [ + { + "case_id": "case_010_2a_procedural", + "role": "Lead Arranger Officer", + "as": "additional", + "template": "proc_doc_06" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/103/H-S-103-G-05.tif" + ] + }, + { + "signer_group": "signer_0020", + "display_name": "Donald Ali", + "script": "latin", + "roles": [ + "Primary Debtor" + ], + "appears_in": [ + { + "case_id": "case_004_1b_procedural", + "role": "Primary Debtor", + "as": "primary", + "template": "proc_doc_04" + } + ], + "reference_images": [ + "data/raw/cedar/genuine/original_20_10.png" + ] + }, + { + "signer_group": "bhsig260_bengali_0076", + "display_name": "Edward Hernandez", + "script": "bengali", + "roles": [ + "Account Holder" + ], + "appears_in": [ + { + "case_id": "case_011_2b_scanned", + "role": "Account Holder", + "as": "additional", + "template": "bank_doc_08" + } + ], + "reference_images": [ + "data/raw/bhsig260_bengali/76/B-S-76-G-07.tif" + ] + }, + { + "signer_group": "bhsig260_bengali_0023", + "display_name": "Edward Williams", + "script": "bengali", + "roles": [ + "Chief Executive Officer" + ], + "appears_in": [ + { + "case_id": "case_006_1c_procedural", + "role": "Chief Executive Officer", + "as": "primary", + "template": "proc_doc_07" + } + ], + "reference_images": [ + "data/raw/bhsig260_bengali/23/B-S-23-G-07.tif" + ] + }, + { + "signer_group": "bhsig260_bengali_0019", + "display_name": "Emily Jones", + "script": "bengali", + "roles": [ + "Board Witness Exec", + "Issuing Debtor" + ], + "appears_in": [ + { + "case_id": "case_018_4a_procedural", + "role": "Board Witness Exec", + "as": "primary", + "template": "proc_doc_11" + }, + { + "case_id": "case_018_4a_procedural", + "role": "Issuing Debtor", + "as": "companion", + "template": "proc_doc_11" + } + ], + "reference_images": [ + "data/raw/bhsig260_bengali/19/B-S-19-G-02.tif", + "data/raw/bhsig260_bengali/19/B-S-19-G-04.tif" + ] + }, + { + "signer_group": "bhsig260_bengali_0051", + "display_name": "Fatima Lee", + "script": "bengali", + "roles": [ + "Authorized Chief Treasurer" + ], + "appears_in": [ + { + "case_id": "case_016_3b_procedural", + "role": "Authorized Chief Treasurer", + "as": "primary", + "template": "proc_doc_12" + } + ], + "reference_images": [ + "data/raw/bhsig260_bengali/51/B-S-51-F-05.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0070", + "display_name": "Hans Anderson", + "script": "hindi", + "roles": [ + "First Authorised Signatory" + ], + "appears_in": [ + { + "case_id": "case_007_1d_scanned", + "role": "First Authorised Signatory", + "as": "primary", + "template": "bank_doc_09" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/70/H-S-70-G-05.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0043", + "display_name": "Hiroshi Muller", + "script": "hindi", + "roles": [ + "Account Holder" + ], + "appears_in": [ + { + "case_id": "case_009_2a_scanned", + "role": "Account Holder", + "as": "additional", + "template": "bank_doc_07" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/43/H-S-43-G-03.tif" + ] + }, + { + "signer_group": "signet_0006", + "display_name": "Ingrid Rossi", + "script": "latin", + "roles": [ + "Bank Representative" + ], + "appears_in": [ + { + "case_id": "case_005_1c_scanned", + "role": "Bank Representative", + "as": "companion", + "template": "bank_doc_04" + } + ], + "reference_images": [ + "data/raw/signet/train/006/006_04.PNG" + ] + }, + { + "signer_group": "bhsig260_hindi_0128", + "display_name": "Jason Brown", + "script": "hindi", + "roles": [ + "Corporate Guarantor" + ], + "appears_in": [ + { + "case_id": "case_004_1b_procedural", + "role": "Corporate Guarantor", + "as": "additional", + "template": "proc_doc_04" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/128/H-S-128-G-06.tif" + ] + }, + { + "signer_group": "signet_0029", + "display_name": "Jason Ramirez", + "script": "latin", + "roles": [ + "Board Witness Exec" + ], + "appears_in": [ + { + "case_id": "case_024_5b_procedural", + "role": "Board Witness Exec", + "as": "companion", + "template": "proc_doc_11" + } + ], + "reference_images": [ + "data/raw/signet/train/029/07_029.png" + ] + }, + { + "signer_group": "bhsig260_bengali_0078", + "display_name": "Jessica Campbell", + "script": "bengali", + "roles": [ + "Second Authorised Signatory" + ], + "appears_in": [ + { + "case_id": "case_023_5b_scanned", + "role": "Second Authorised Signatory", + "as": "companion", + "template": "bank_doc_09" + } + ], + "reference_images": [ + "data/raw/bhsig260_bengali/78/B-S-78-G-02.tif" + ] + }, + { + "signer_group": "signet_0040", + "display_name": "Jessica Chen", + "script": "latin", + "roles": [ + "Second Authorised Signatory", + "Bank Representative" + ], + "appears_in": [ + { + "case_id": "case_007_1d_scanned", + "role": "Second Authorised Signatory", + "as": "additional", + "template": "bank_doc_09" + }, + { + "case_id": "case_015_3b_scanned", + "role": "Bank Representative", + "as": "additional", + "template": "bank_doc_06" + } + ], + "reference_images": [ + "data/raw/signet/train/040/05_040.png", + "data/raw/signet/train/040/07_040.png" + ] + }, + { + "signer_group": "signet_0009", + "display_name": "John Thompson", + "script": "latin", + "roles": [ + "Bank Manager" + ], + "appears_in": [ + { + "case_id": "case_001_1a_scanned", + "role": "Bank Manager", + "as": "primary", + "template": "bank_doc_08" + } + ], + "reference_images": [ + "data/raw/signet/train/009/009_24.PNG" + ] + }, + { + "signer_group": "bhsig260_hindi_0005", + "display_name": "Kimberly Perez", + "script": "hindi", + "roles": [ + "Account Holder", + "Applicant Agent" + ], + "appears_in": [ + { + "case_id": "case_001_1a_scanned", + "role": "Account Holder", + "as": "additional", + "template": "bank_doc_08" + }, + { + "case_id": "case_020_4b_procedural", + "role": "Applicant Agent", + "as": "primary", + "template": "proc_doc_09" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/5/H-S-5-G-01.tif", + "data/raw/bhsig260_hindi/5/H-S-5-G-07.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0069", + "display_name": "Laura Young", + "script": "hindi", + "roles": [ + "Company Secretary" + ], + "appears_in": [ + { + "case_id": "case_006_1c_procedural", + "role": "Company Secretary", + "as": "companion", + "template": "proc_doc_07" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/69/H-S-69-G-08.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0110", + "display_name": "Liam Thomas", + "script": "hindi", + "roles": [ + "Internal Compliance Controller" + ], + "appears_in": [ + { + "case_id": "case_020_4b_procedural", + "role": "Internal Compliance Controller", + "as": "companion", + "template": "proc_doc_09" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/110/H-S-110-G-02.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0011", + "display_name": "Linda Nguyen", + "script": "hindi", + "roles": [ + "Borrower (Authorized Signatory)" + ], + "appears_in": [ + { + "case_id": "case_015_3b_scanned", + "role": "Borrower (Authorized Signatory)", + "as": "primary", + "template": "bank_doc_06" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/11/H-S-011-F-05.tif" + ] + }, + { + "signer_group": "signet_0048", + "display_name": "Margaret Mitchell", + "script": "latin", + "roles": [ + "Borrower" + ], + "appears_in": [ + { + "case_id": "case_013_3a_scanned", + "role": "Borrower", + "as": "primary", + "template": "bank_doc_05" + } + ], + "reference_images": [ + "data/raw/signet/train/048/04_048.png" + ] + }, + { + "signer_group": "bhsig260_bengali_0032", + "display_name": "Melissa Young", + "script": "bengali", + "roles": [ + "Corporate Guarantor" + ], + "appears_in": [ + { + "case_id": "case_002_1a_procedural", + "role": "Corporate Guarantor", + "as": "additional", + "template": "proc_doc_04" + } + ], + "reference_images": [ + "data/raw/bhsig260_bengali/32/B-S-32-G-03.tif" + ] + }, + { + "signer_group": "bhsig260_bengali_0072", + "display_name": "Nancy Ivanov", + "script": "bengali", + "roles": [ + "Second Authorised Signatory" + ], + "appears_in": [ + { + "case_id": "case_003_1b_scanned", + "role": "Second Authorised Signatory", + "as": "primary", + "template": "bank_doc_09" + } + ], + "reference_images": [ + "data/raw/bhsig260_bengali/72/B-S-72-G-07.tif" + ] + }, + { + "signer_group": "signer_0038", + "display_name": "Noah Hill", + "script": "latin", + "roles": [ + "Issuing Debtor" + ], + "appears_in": [ + { + "case_id": "case_024_5b_procedural", + "role": "Issuing Debtor", + "as": "primary", + "template": "proc_doc_11" + } + ], + "reference_images": [ + "data/raw/cedar/genuine/original_38_10.png" + ] + }, + { + "signer_group": "bhsig260_hindi_0067", + "display_name": "Patricia Baker", + "script": "hindi", + "roles": [ + "Borrower (Authorized Signatory)" + ], + "appears_in": [ + { + "case_id": "case_005_1c_scanned", + "role": "Borrower (Authorized Signatory)", + "as": "primary", + "template": "bank_doc_04" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/67/H-S-67-G-08.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0012", + "display_name": "Priya Tanaka", + "script": "hindi", + "roles": [ + "Internal Compliance Controller" + ], + "appears_in": [ + { + "case_id": "case_022_5a_procedural", + "role": "Internal Compliance Controller", + "as": "companion", + "template": "proc_doc_09" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/12/H-S-12-G-04.tif" + ] + }, + { + "signer_group": "bhsig260_bengali_0053", + "display_name": "Samantha Adams", + "script": "bengali", + "roles": [ + "Authorized Chief Treasurer" + ], + "appears_in": [ + { + "case_id": "case_014_3a_procedural", + "role": "Authorized Chief Treasurer", + "as": "primary", + "template": "proc_doc_12" + } + ], + "reference_images": [ + "data/raw/bhsig260_bengali/53/B-S-53-G-01.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0017", + "display_name": "Sandra Harris", + "script": "hindi", + "roles": [ + "Primary Debtor" + ], + "appears_in": [ + { + "case_id": "case_002_1a_procedural", + "role": "Primary Debtor", + "as": "primary", + "template": "proc_doc_04" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/17/H-S-017-G-01.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0033", + "display_name": "Sandra Harris", + "script": "hindi", + "roles": [ + "First Authorised Signatory", + "Second Authorised Signatory" + ], + "appears_in": [ + { + "case_id": "case_017_4a_scanned", + "role": "First Authorised Signatory", + "as": "primary", + "template": "bank_doc_09" + }, + { + "case_id": "case_017_4a_scanned", + "role": "Second Authorised Signatory", + "as": "companion", + "template": "bank_doc_09" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/33/H-S-33-G-02.tif", + "data/raw/bhsig260_hindi/33/H-S-33-G-06.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0007", + "display_name": "Scott Taylor", + "script": "hindi", + "roles": [ + "Authorized Signatory" + ], + "appears_in": [ + { + "case_id": "case_013_3a_scanned", + "role": "Authorized Signatory", + "as": "additional", + "template": "bank_doc_05" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/7/H-S-7-G-06.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0044", + "display_name": "Scott Wright", + "script": "hindi", + "roles": [ + "Beneficiary Sign-off" + ], + "appears_in": [ + { + "case_id": "case_008_1d_procedural", + "role": "Beneficiary Sign-off", + "as": "primary", + "template": "proc_doc_15" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/44/H-S-44-G-07.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0121", + "display_name": "Stephanie Martinez", + "script": "hindi", + "roles": [ + "Account Holder" + ], + "appears_in": [ + { + "case_id": "case_019_4b_scanned", + "role": "Account Holder", + "as": "primary", + "template": "bank_doc_08" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/121/H-S-121-G-03.tif" + ] + }, + { + "signer_group": "bhsig260_hindi_0036", + "display_name": "Stephen King", + "script": "hindi", + "roles": [ + "First Authorised Signatory" + ], + "appears_in": [ + { + "case_id": "case_023_5b_scanned", + "role": "First Authorised Signatory", + "as": "primary", + "template": "bank_doc_09" + } + ], + "reference_images": [ + "data/raw/bhsig260_hindi/36/H-S-36-G-07.tif" + ] + } +] \ No newline at end of file diff --git a/unified_pipeline/docs/unified_pipeline_Metrics_Thresholds_Formulas.pptx b/unified_pipeline/docs/unified_pipeline_Metrics_Thresholds_Formulas.pptx new file mode 100644 index 0000000000000000000000000000000000000000..11c6ac3acb52fc69e1435150a0e8c979403fd61b --- /dev/null +++ b/unified_pipeline/docs/unified_pipeline_Metrics_Thresholds_Formulas.pptx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:605d2537b6d3128026d1451b9cf748754dc779f79a9fc21cdad7a6d917b66fe8 +size 3319145 diff --git a/unified_pipeline/main.py b/unified_pipeline/main.py new file mode 100644 index 0000000000000000000000000000000000000000..e410a25caade27c98cce886c1c4a7aed946d56cf --- /dev/null +++ b/unified_pipeline/main.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +"""CLI entry point for the synthetic signed bank document generation pipeline. + +Examples: + Generate 10 samples with all defaults:: + + python main.py + + Generate 10,000 samples using a custom config:: + + python main.py --config configs/generate.yaml --count 10000 + + Override arbitrary nested config values from the command line:: + + python main.py --set placement.rotation_deg.max=6 --set augmentation.coffee_stain.enabled=true + + Generate a single, fully reproducible sample from one template with a debug figure:: + + python main.py --count 1 --seed 42 --template bank_doc_03 --save-visualization + + Request a specific difficulty score (not a tier) with a before/after preview:: + + python main.py --count 5 --target-difficulty 0.55 --save-before-after +""" + +from __future__ import annotations + +import argparse +import json +import logging +import sys +from pathlib import Path + +from src.config.loader import load_config, save_resolved_config +from src.generator import DatasetGenerator +from src.utils.logging_utils import configure_logging, get_logger + +logger = get_logger(__name__) + + +def build_arg_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Generate synthetic signed bank documents with rich ML-ready metadata.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument("--config", type=str, default=None, help="Path to a user YAML config, merged over configs/default.yaml.") + parser.add_argument("--count", type=int, default=None, help="Number of samples to generate (overrides config).") + parser.add_argument("--seed", type=int, default=None, help="Master random seed (overrides config).") + parser.add_argument("--workers", type=int, default=None, help="Number of worker processes (overrides config).") + parser.add_argument("--output", type=str, default=None, help="Output directory (overrides config).") + parser.add_argument("--template", type=str, default=None, help="Restrict generation to a single template id, e.g. bank_doc_03.") + parser.add_argument("--start-index", type=int, default=None, help="First sample index (overrides config).") + parser.add_argument("--overwrite", action="store_true", default=None, help="Regenerate samples even if their output files already exist.") + parser.add_argument("--save-visualization", action="store_true", default=None, help="Also save a debug visualization PNG per sample.") + parser.add_argument( + "--save-before-after", action="store_true", default=None, + help="Also save a before/after degradation comparison PNG per sample (clean render vs. final look).", + ) + parser.add_argument( + "--target-difficulty", type=float, default=None, metavar="SCORE", + help="Request a specific difficulty score (e.g. 0.55), not a tier -- retries (see --difficulty-max-attempts) " + "until within --difficulty-tolerance, keeping the closest attempt if none matches.", + ) + parser.add_argument("--difficulty-tolerance", type=float, default=None, metavar="DELTA", help="Acceptable |score - target| for --target-difficulty (default 0.05).") + parser.add_argument("--difficulty-max-attempts", type=int, default=None, metavar="N", help="Retry attempts for --target-difficulty (default 5).") + parser.add_argument( + "--set", dest="overrides", action="append", default=[], + metavar="key.path=value", help="Arbitrary dot-path config override; may be repeated.", + ) + parser.add_argument("--log-level", type=str, default="INFO", choices=["DEBUG", "INFO", "WARNING", "ERROR"]) + return parser + + +def _collect_overrides(args: argparse.Namespace) -> list[str]: + overrides = list(args.overrides) + if args.count is not None: + overrides.append(f"generation.count={args.count}") + if args.workers is not None: + overrides.append(f"generation.workers={args.workers}") + if args.output is not None: + overrides.append(f"paths.output_dir={args.output}") + if args.start_index is not None: + overrides.append(f"generation.start_index={args.start_index}") + if args.overwrite: + overrides.append("generation.overwrite=true") + if args.save_visualization: + overrides.append("generation.save_visualization=true") + if args.save_before_after: + overrides.append("generation.save_before_after_preview=true") + if args.target_difficulty is not None: + overrides.append(f"generation.target_difficulty={args.target_difficulty}") + if args.difficulty_tolerance is not None: + overrides.append(f"generation.difficulty_tolerance={args.difficulty_tolerance}") + if args.difficulty_max_attempts is not None: + overrides.append(f"generation.difficulty_max_attempts={args.difficulty_max_attempts}") + if args.seed is not None: + overrides.append(f"seed={args.seed}") + if args.template is not None: + overrides.append(f"document.template={args.template}") + return overrides + + +def main(argv: "list[str] | None" = None) -> int: + args = build_arg_parser().parse_args(argv) + configure_logging(level=getattr(logging, args.log_level)) + + overrides = _collect_overrides(args) + config = load_config(config_path=args.config, overrides=overrides) + + logger.info("Starting generation: seed=%d, count=%d, workers=%d, output=%s", + config.seed, config.generation.count, config.generation.workers, config.paths.output_dir) + + output_dir = Path(config.paths.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + save_resolved_config(config, output_dir / "resolved_config.yaml") + + generator = DatasetGenerator(config) + summary = generator.generate() + + summary_path = output_dir / "generation_summary.json" + summary_path.write_text(json.dumps(summary.as_dict(), indent=2), encoding="utf-8") + logger.info("Wrote generation summary to %s", summary_path) + logger.info( + "Done: %d/%d succeeded, %d failed, %d skipped (already existed). Difficulty tiers: %s", + summary.succeeded, summary.total, summary.failed, summary.skipped_existing, summary.tier_counts, + ) + if summary.validation_failed: + logger.warning("%d sample(s) failed validation.", summary.validation_failed) + + return 1 if summary.failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/unified_pipeline/pytest.ini b/unified_pipeline/pytest.ini new file mode 100644 index 0000000000000000000000000000000000000000..6c91dc3607a67e829fbac511bbe04d00db52e5f7 --- /dev/null +++ b/unified_pipeline/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +pythonpath = . +testpaths = tests +python_files = test_*.py diff --git a/unified_pipeline/requirements.txt b/unified_pipeline/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..5058f3a903b3f9505f9a79a8c3e155ae7ae693ec --- /dev/null +++ b/unified_pipeline/requirements.txt @@ -0,0 +1,16 @@ +numpy>=1.26 +opencv-python>=4.9 +scipy>=1.12 +Pillow>=10.2 +scikit-image>=0.22 +Shapely>=2.0 +PyYAML>=6.0 +pydantic>=2.6 +albumentations>=1.4 +omegaconf>=2.3 +matplotlib>=3.8 +tqdm>=4.66 +pytest>=8.0 +rich>=13.7 +questionary>=2.0 +streamlit>=1.35 diff --git a/unified_pipeline/scripts/build_degradation_slides.py b/unified_pipeline/scripts/build_degradation_slides.py new file mode 100644 index 0000000000000000000000000000000000000000..cb05808eadac4ac573d11660fe3727de2c5e2037 --- /dev/null +++ b/unified_pipeline/scripts/build_degradation_slides.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python +"""Append a before/after slide per augmentation effect, plus normal- and +extreme-case multi-degradation slides, to Metrics_Thresholds_Formulas.pptx. + +Reuses the assets written by generate_before_after_assets.py and +generate_case_assets.py (run those first). New slides replicate the deck's +existing visual language exactly (navy header bar, orange accent line, teal +section label, Calibri/Consolas body text, bottom-right page number) so they +read as part of the same deck rather than a bolt-on. + +The new slides are inserted as a new section 8 ("Degradation gallery") and +section 9 ("Multi-degradation cases"), placed right before the closing "Key +takeaways" slide, and the Agenda slide (slide 2) is extended with the two new +entries. A one-time backup of the original file is kept alongside it. + +Usage: + python scripts/build_degradation_slides.py +""" + +from __future__ import annotations + +import copy +import json +import shutil +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from pptx import Presentation +from pptx.dml.color import RGBColor +from pptx.enum.shapes import MSO_SHAPE +from pptx.enum.text import MSO_ANCHOR, PP_ALIGN +from pptx.util import Emu, Pt +from PIL import Image + +from scripts.generate_before_after_assets import ( + ALL_EFFECTS, DISPLAY_NAME, ZOOM_EFFECTS, +) +from scripts.generate_multiple_degradation_extreme_gallery import _fmt_effect + +ROOT = Path(__file__).resolve().parents[1] +PPTX_PATH = ROOT / "Metrics_Thresholds_Formulas.pptx" +BACKUP_PATH = ROOT / "Metrics_Thresholds_Formulas.backup.pptx" +DEG_ROOT = ROOT / "degradation" + +# ---------------------------------------------------------------- palette -- +NAVY, TEAL, ORANGE = "0F2A43", "1B7A7A", "E07A2C" +LIGHT_TEAL, GRAY, LIGHT_BG, TEXT_DARK, WHITE = "9BC5C5", "6B767C", "F4F7F8", "222A30", "FFFFFF" + + +def rgb(hexstr: str) -> RGBColor: + return RGBColor.from_string(hexstr) + + +# ------------------------------------------------------------- geometry --- +SLIDE_W, SLIDE_H = Emu(12191695), Emu(6858000) +HEADER_H, ACCENT_H = Emu(1051560), Emu(38100) +TITLE_X = Emu(502920) +CONTENT_X, CONTENT_W = Emu(640080), Emu(10881360) +FOOTER_X, FOOTER_Y = Emu(11475720), Emu(6510528) +FOOTER_W, FOOTER_H = Emu(640080), Emu(274320) + +LEFT_X, RIGHT_X = Emu(640080), Emu(6263640) +COL_W = Emu(5257800) +LABEL_Y, LABEL_H = Emu(1554480), Emu(365760) +IMG_Y = Emu(LABEL_Y + LABEL_H + Emu(91440)) + +CAPTION_Y = Emu(1150000) +CAPTION_H = Emu(320040) + +# ------------------------------------------------------ formula captions -- +FORMULAS = { + "rotation": "extra_deg = U(-1,1) × strength × 8.0", + "stroke_dilation": "ksize = round(1 + strength×5) [odd]", + "ink_fading": "mult = clip(1 − strength×(1−field)×0.9, .05, 1)", + "ink_bleed": "ksize = round(1+strength×4); halo floor = .35+.35×strength", + "motion_blur": "length = round(3 + strength×16)", + "gaussian_blur": "sigma = 0.2 + strength×2.2", + "perspective": "scale = 0.01 + strength×0.09", + "contrast": "contrast_limit = ±(strength×0.45)", + "gamma": "delta = round(strength×45); gamma_limit = (100−delta, 100+delta), lo≥40", + "coffee_stain": "n_blobs = 1+randint(0,1+round(strength×3)); opacity = .10+.30×strength", + "stain": "radius = (.06+.16×strength)×min(w,h); opacity scales with strength", + "shadow": "darkness = 0.15+0.55×strength; band/radius/fold-width scale with strength", + "crumple": "amplitude = strength×min(w,h)×0.015", + "discoloration": "tint strength scales with strength per tone", + "page_tilt": "angle = U(-1,1) × (1.5 + strength×6.5)", + "dust": "n_specks = (h×w)×0.00003×(1+strength×6)", + "scanner_streaks": "n_streaks = 1+int(strength×6)", + "scanner_noise": "GaussNoise std = 0.01 + strength×0.12", + "salt_pepper": "amount = 0.002 + strength×0.09", + "jpeg_compression": "quality = randint(min_quality, max_quality) — NOT driven by strength", +} + + +def short_list(names: list[str], limit: int = 8) -> str: + if len(names) <= limit: + return ", ".join(names) if names else "none" + return ", ".join(names[:limit]) + f", +{len(names) - limit} more" + + +# ----------------------------------------------------------- primitives --- +def add_rect(slide, x, y, w, h, color: str): + shp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, w, h) + shp.fill.solid() + shp.fill.fore_color.rgb = rgb(color) + shp.line.fill.background() + return shp + + +def add_text( + slide, x, y, w, h, text, size, color, *, bold=False, font="Calibri", + align=PP_ALIGN.LEFT, anchor=None, word_wrap=True, +): + tb = slide.shapes.add_textbox(x, y, w, h) + tf = tb.text_frame + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = 0 + tf.word_wrap = word_wrap + if anchor is not None: + tf.vertical_anchor = anchor + lines = text.split("\n") + for i, line in enumerate(lines): + p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() + p.alignment = align + run = p.add_run() + run.text = line + run.font.size = Pt(size) + run.font.bold = bold + run.font.name = font + run.font.color.rgb = rgb(color) + return tb + + +def add_bg(slide): + add_rect(slide, Emu(0), Emu(0), SLIDE_W, SLIDE_H, WHITE) + + +def add_header(slide, section_label: str, title: str, page_num: int): + add_rect(slide, Emu(0), Emu(0), SLIDE_W, HEADER_H, NAVY) + add_rect(slide, Emu(0), HEADER_H, SLIDE_W, ACCENT_H, ORANGE) + add_text(slide, TITLE_X, Emu(128016), Emu(9144000), Emu(274320), section_label, 12, LIGHT_TEAL, bold=True) + add_text(slide, TITLE_X, Emu(384048), Emu(10515600), Emu(594360), title, 28, WHITE, bold=True) + add_text( + slide, FOOTER_X, FOOTER_Y, FOOTER_W, FOOTER_H, str(page_num), 11, GRAY, + align=PP_ALIGN.RIGHT, + ) + + +def fit_picture(slide, img_path: Path, box_x, box_y, box_w, box_h): + iw, ih = Image.open(img_path).size + scale = min(box_w / iw, box_h / ih) + disp_w, disp_h = int(iw * scale), int(ih * scale) + left = int(box_x + (box_w - disp_w) / 2) + top = int(box_y + (box_h - disp_h) / 2) + slide.shapes.add_picture(str(img_path), left, top, width=disp_w, height=disp_h) + + +def add_panel_label(slide, x, color, text): + add_rect(slide, x, LABEL_Y, COL_W, LABEL_H, color) + add_text( + slide, x, LABEL_Y, COL_W, LABEL_H, text, 14, WHITE, bold=True, + align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE, word_wrap=False, + ) + + +# -------------------------------------------------------------- slides ---- +def build_before_after_slide(prs, layout, effect: str, page_num: int, manifest: dict): + slide = prs.slides.add_slide(layout) + add_bg(slide) + add_header(slide, "8. DEGRADATION GALLERY", DISPLAY_NAME[effect], page_num) + + rec = manifest[effect] + resolved = _fmt_effect(effect, {"strength": rec["strength"], "params": rec["params"] or {}}) + caption = ( + f"{FORMULAS[effect]} | resolved: {resolved} | " + f"difficulty after = {rec['difficulty_score']:.3f} ({rec['difficulty_tier']})" + ) + add_text(slide, CONTENT_X, CAPTION_Y, CONTENT_W, CAPTION_H, caption, 12, GRAY, font="Consolas") + + add_panel_label(slide, LEFT_X, TEAL, "BEFORE - clean render") + add_panel_label(slide, RIGHT_X, ORANGE, f"AFTER - {DISPLAY_NAME[effect]} (strength={rec['strength']:.2f})") + + img_bottom = Emu(6100000) + img_h = Emu(img_bottom - IMG_Y) + before_name = "before_zoom.png" if effect in ZOOM_EFFECTS else "before.png" + after_name = "after_zoom.png" if effect in ZOOM_EFFECTS else "after.png" + fit_picture(slide, DEG_ROOT / effect / before_name, LEFT_X, IMG_Y, COL_W, img_h) + fit_picture(slide, DEG_ROOT / effect / after_name, RIGHT_X, IMG_Y, COL_W, img_h) + return slide + + +def build_case_slide(prs, layout, kind: str, page_num: int): + slide = prs.slides.add_slide(layout) + add_bg(slide) + title = "Normal-severity combinations" if kind == "normal" else "Extreme-severity combinations" + add_header(slide, "9. MULTI-DEGRADATION CASES", title, page_num) + + if kind == "normal": + folder = ROOT / "multiple_degradation" / "case_assets" + subtitle = ( + "configs/generate.yaml -- every effect independently rolls its own realistic " + "probability per sample (mixed, moderate combinations)" + ) + color = TEAL + else: + folder = ROOT / "multiple_degradation_extreme" / "case_assets" + subtitle = ( + "worst case -- every effect probability=0.85, strength 0.7-1.0, plus heavy " + "misplacement / faint+blurred ink (see scripts/generate_multiple_degradation_extreme_gallery.py)" + ) + color = ORANGE + + manifest = json.loads((folder / "case_manifest.json").read_text()) + add_text(slide, CONTENT_X, CAPTION_Y, CONTENT_W, CAPTION_H, subtitle, 12, GRAY, font="Consolas") + + img_bottom = Emu(5850000) + img_h = Emu(img_bottom - IMG_Y) + cap_y = Emu(img_bottom + Emu(45720)) + cap_h = Emu(600000) + + for i, x in enumerate((LEFT_X, RIGHT_X)): + key = f"case_{i + 1}" + rec = manifest[key] + add_panel_label(slide, x, color, f"Sample {i + 1} - {rec['n_applied']}/{rec['n_total']} effects applied") + fit_picture(slide, folder / f"{key}.png", x, IMG_Y, COL_W, img_h) + cap = ( + f"difficulty = {rec['difficulty_score']:.3f} ({rec['difficulty_tier']}) | " + f"placement: rot={rec['placement']['rotation_deg']:.1f} deg " + f"scale={rec['placement']['scale_factor']:.2f} misplaced={rec['placement']['misplaced']}\n" + f"applied: {short_list(rec['applied_effects'])}" + ) + add_text(slide, x, cap_y, COL_W, cap_h, cap, 10, TEXT_DARK, font="Consolas") + return slide + + +# ---------------------------------------------------------- agenda edit --- +def extend_agenda(prs): + slide = prs.slides[1] + target = None + for shape in slide.shapes: + if shape.name == "TextBox 7": + target = shape + break + if target is None: + print("WARNING: could not find agenda TextBox 7 -- skipping agenda update") + return + + tf = target.text_frame + n_original = len(tf.paragraphs) + last_p = tf.paragraphs[-1]._p + new_texts = ( + "8. Degradation gallery — before & after, all 20 effects", + "9. Multi-degradation cases — normal vs. extreme severity", + ) + for text in new_texts: + new_p = copy.deepcopy(last_p) + run_elm = new_p.findall(".//{http://schemas.openxmlformats.org/drawingml/2006/main}t") + run_elm[0].text = text + last_p.addnext(new_p) + last_p = new_p + + # Shape height doesn't clip a top-anchored textbox -- text simply overflows past + # it -- so what actually matters is total rendered text height staying inside the + # slide. 9 lines at the original 22pt ran ~24pt past the bottom edge, so shrink + # every run down to 20pt (measured to comfortably clear the slide bottom). + for para in tf.paragraphs: + for run in para.runs: + run.font.size = Pt(20) + + +# ---------------------------------------------------------------- main ---- +def main(): + if not BACKUP_PATH.exists(): + shutil.copy2(PPTX_PATH, BACKUP_PATH) + print(f"Backed up original deck -> {BACKUP_PATH}") + + manifest = json.loads((DEG_ROOT / "before_after_manifest.json").read_text()) + + prs = Presentation(str(PPTX_PATH)) + layout = prs.slides[2].slide_layout # "Blank" layout used by all content slides + + sld_id_lst = prs.slides._sldIdLst + closing_sld_id = sld_id_lst[-1] # "Key takeaways" slide, must stay last + + extend_agenda(prs) + + page_num = 19 + for effect in ALL_EFFECTS: + build_before_after_slide(prs, layout, effect, page_num, manifest) + page_num += 1 + + build_case_slide(prs, layout, "normal", page_num) + page_num += 1 + build_case_slide(prs, layout, "extreme", page_num) + page_num += 1 + + sld_id_lst.remove(closing_sld_id) + sld_id_lst.append(closing_sld_id) + + prs.save(str(PPTX_PATH)) + print(f"Saved {PPTX_PATH} -- {len(prs.slides.__iter__.__self__._sldIdLst)} slides total") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/scripts/degrade_cli.py b/unified_pipeline/scripts/degrade_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..5acf63135c4c228ae739837c1dfc934f95f6c3c7 --- /dev/null +++ b/unified_pipeline/scripts/degrade_cli.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python +"""Interactive terminal wizard for generating synthetic signed documents with +hand-picked degradation effects. + +A friendlier front end over main.py's --set override system: arrow-key menus +and checkboxes instead of memorizing config dot-paths. Same pipeline underneath +(GeneratorContext), so output is identical to what main.py would produce with +the equivalent --set flags. + +Usage: + python scripts/degrade_cli.py +""" + +from __future__ import annotations + +import json +import os +import sys +from datetime import datetime +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +import questionary +from questionary import Choice, Separator +from rich.console import Console +from rich.panel import Panel +from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn +from rich.table import Table + +from src.config.loader import load_config +from src.generator import GeneratorContext +from src.templates.template_loader import TemplateLoader +from src.utils.image_io import save_image_rgb +from src.visualization.visualizer import save_visualization +from scripts.generate_before_after_assets import ALL_EFFECTS, DISPLAY_NAME, DOCUMENT_EFFECTS, SIGNATURE_EFFECTS +from scripts.generate_multiple_degradation_extreme_gallery import build_overrides as extreme_overrides + +ROOT = Path(__file__).resolve().parents[1] +console = Console() + +# ---- palette, matched to Metrics_Thresholds_Formulas.pptx ----------------- +NAVY, TEAL, ORANGE, GRAY = "#0F2A43", "#1B7A7A", "#E07A2C", "#6B767C" + +QSTYLE = questionary.Style([ + ("qmark", f"fg:{ORANGE} bold"), + ("question", "bold"), + ("answer", f"fg:{TEAL} bold"), + ("pointer", f"fg:{ORANGE} bold"), + ("highlighted", f"fg:{TEAL} bold"), + ("selected", f"fg:{TEAL}"), + ("instruction", f"fg:{GRAY}"), + ("separator", f"fg:{GRAY}"), +]) + + +def banner() -> None: + console.print(Panel.fit( + "[bold white]Synthetic Signed Document Generator[/bold white]\n" + f"[{TEAL}]pick degradations -> generate -> inspect[/{TEAL}]", + border_style=TEAL, padding=(1, 4), + )) + + +def discover_templates() -> list[tuple[str, str]]: + cfg = load_config() + loader = TemplateLoader(cfg.paths.image_dir, cfg.paths.annotations_dir) + loader.discover() + return sorted((tid, t.annotation.document_type) for tid, t in loader._templates.items()) + + +def valid_float01(v: str) -> "bool | str": + try: + f = float(v) + except ValueError: + return "Enter a number between 0.0 and 1.0" + return True if 0.0 <= f <= 1.0 else "Must be between 0.0 and 1.0" + + +def valid_posint(v: str) -> "bool | str": + return True if v.isdigit() and int(v) > 0 else "Enter a positive whole number" + + +# ------------------------------------------------------------- questions --- +def ask_mode() -> str: + return questionary.select( + "How should degradations be applied?", + choices=[ + Choice(title="Pick effects - isolated (ONLY the chosen effects fire, nothing else)", value="isolate"), + Choice(title="Pick effects - mixed (chosen effects forced on, others may also roll)", value="mixed"), + Choice(title="Preset - normal severity (realistic random mix, configs/generate.yaml)", value="normal"), + Choice(title="Preset - extreme severity (worst case, everything cranked)", value="extreme"), + ], + style=QSTYLE, + ).ask() + + +def ask_template() -> "str | None": + templates = discover_templates() + choices = [Choice(title="Random each sample", value=None)] + choices += [Choice(title=f"{tid} ({doc_type})", value=tid) for tid, doc_type in templates] + return questionary.select("Which document template?", choices=choices, style=QSTYLE).ask() + + +def ask_effects() -> list[str]: + choices = [Separator("-- signature-level --")] + choices += [Choice(title=DISPLAY_NAME[e], value=e) for e in SIGNATURE_EFFECTS] + choices += [Separator("-- document-level --")] + choices += [Choice(title=DISPLAY_NAME[e], value=e) for e in DOCUMENT_EFFECTS] + selected = questionary.checkbox( + "Pick degradations (space to toggle, enter to confirm)", choices=choices, style=QSTYLE, + ).ask() + if selected is None: + raise KeyboardInterrupt + if not selected: + console.print(f"[{ORANGE}]Pick at least one effect.[/{ORANGE}]") + return ask_effects() + return selected + + +def ask_strengths(effects: list[str]) -> dict[str, float]: + global_raw = questionary.text( + "Strength for all selected effects (0.0-1.0)", default="0.75", + validate=valid_float01, style=QSTYLE, + ).ask() + global_strength = float(global_raw) + strengths = {e: global_strength for e in effects} + if questionary.confirm("Customize strength per effect?", default=False, style=QSTYLE).ask(): + for e in effects: + raw = questionary.text( + f" {DISPLAY_NAME[e]} strength", default=str(global_strength), + validate=valid_float01, style=QSTYLE, + ).ask() + strengths[e] = float(raw) + return strengths + + +def ask_count() -> int: + raw = questionary.text("How many samples?", default="3", validate=valid_posint, style=QSTYLE).ask() + return int(raw) + + +def ask_save_viz() -> bool: + return questionary.confirm( + "Also save the debug-panel visualization (crop / mask / metrics / difficulty)?", + default=True, style=QSTYLE, + ).ask() + + +def ask_output_dir() -> Path: + default = f"output/interactive_{datetime.now():%Y%m%d_%H%M%S}" + raw = questionary.text("Output folder", default=default, style=QSTYLE).ask() + return ROOT / raw + + +# ------------------------------------------------------------ overrides ---- +def build_pick_overrides(mode: str, template: "str | None", effects: list[str], strengths: dict[str, float]) -> list[str]: + overrides = ["generation.run_validation=false"] + if template: + overrides.append(f"document.template={template}") + + if mode == "isolate": + for name in ALL_EFFECTS: + on = name in effects + overrides.append(f"augmentation.{name}.enabled={'true' if on else 'false'}") + overrides.append(f"augmentation.{name}.probability={'1.0' if on else '0.0'}") + for name in effects: + overrides.append(f"augmentation.{name}.strength={strengths[name]}") + else: # mixed + for name in effects: + overrides.append(f"augmentation.{name}.enabled=true") + overrides.append(f"augmentation.{name}.probability=1.0") + overrides.append(f"augmentation.{name}.strength={strengths[name]}") + return overrides + + +# ---------------------------------------------------------------- report --- +def show_plan(mode: str, template, effects, strengths, count: int, out_dir: Path) -> None: + table = Table(title="Plan", title_style=f"bold {NAVY}", show_header=False, border_style=GRAY) + table.add_row("Mode", mode) + table.add_row("Template", template or "random per sample") + if effects: + table.add_row("Effects", ", ".join(f"{DISPLAY_NAME[e]} ({strengths[e]:.2f})" for e in effects)) + table.add_row("Samples", str(count)) + table.add_row("Output", str(out_dir)) + console.print(table) + + +def show_results(results: list[tuple[str, float, str, list[str]]], out_dir: Path) -> None: + table = Table(title="Results", border_style=GRAY, header_style=f"bold white on {NAVY}") + table.add_column("Sample") + table.add_column("Difficulty") + table.add_column("Tier") + table.add_column("Effects applied") + tier_color = {"easy": "green", "medium": ORANGE, "hard": "red"} + for sid, score, tier, applied in results: + table.add_row( + sid, f"{score:.3f}", f"[{tier_color.get(tier, 'white')}]{tier}[/]", + ", ".join(applied) if applied else "[dim]none[/dim]", + ) + console.print(table) + console.print(f"\n[{TEAL}]Wrote {len(results)} sample(s) -> {out_dir}[/{TEAL}]") + + +# ------------------------------------------------------------------ main --- +def main() -> int: + banner() + try: + mode = ask_mode() + if mode is None: + raise KeyboardInterrupt + + template, effects, strengths = None, [], {} + if mode in ("isolate", "mixed"): + template = ask_template() + effects = ask_effects() + strengths = ask_strengths(effects) + + count = ask_count() + save_viz = ask_save_viz() + out_dir = ask_output_dir() + + show_plan(mode, template, effects, strengths, count, out_dir) + if not questionary.confirm("Generate now?", default=True, style=QSTYLE).ask(): + console.print("Cancelled.") + return 0 + + if mode == "normal": + config = load_config( + config_path=ROOT / "configs" / "generate.yaml", + overrides=["document.template=bank_doc_05", "generation.run_validation=false"], + ) + elif mode == "extreme": + config = load_config(overrides=extreme_overrides()) + else: + config = load_config(overrides=build_pick_overrides(mode, template, effects, strengths)) + + out_dir.mkdir(parents=True, exist_ok=True) + ctx = GeneratorContext(config) + prefix, digits = config.generation.filename_prefix, config.generation.filename_digits + + results = [] + with Progress( + SpinnerColumn(), TextColumn("[progress.description]{task.description}"), + BarColumn(complete_style=TEAL), TextColumn("{task.completed}/{task.total}"), + TimeElapsedColumn(), console=console, + ) as progress: + task = progress.add_task("Generating", total=count) + for i in range(1, count + 1): + metadata, final_image, _ = ctx.run_sample(i) + sample_id = f"{prefix}_{i:0{digits}d}" + + save_image_rgb(out_dir / f"{sample_id}.png", final_image) + ctx.metadata_exporter.export(metadata, out_dir / f"{sample_id}.json") + if save_viz: + save_visualization(out_dir / f"{sample_id}_viz.png", final_image, metadata) + + applied = [n for n, r in metadata["augmentation"]["effects"].items() if r["applied"]] + results.append((sample_id, metadata["difficulty"]["score"], metadata["difficulty"]["tier"], applied)) + progress.advance(task) + + console.print() + show_results(results, out_dir) + + if questionary.confirm("Open the output folder now?", default=True, style=QSTYLE).ask(): + if os.name == "nt": + os.startfile(out_dir) # noqa: S606 + else: + console.print(str(out_dir)) + + return 0 + + except KeyboardInterrupt: + console.print("\n[dim]Cancelled.[/dim]") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/unified_pipeline/scripts/detect_cosmetic_field_coordinates.py b/unified_pipeline/scripts/detect_cosmetic_field_coordinates.py new file mode 100644 index 0000000000000000000000000000000000000000..fa4bd1a1f5bcf3ed900055ae1b23ec2365dbb604 --- /dev/null +++ b/unified_pipeline/scripts/detect_cosmetic_field_coordinates.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python +"""One-time measurement pass: detect each scanned template's cosmetic-field +anchor line and cache it into ``annotations/.json``, alongside +the existing hand-measured ``signature_boxes``. + +HOW EACH COORDINATE IS ACTUALLY MEASURED (in order): + +1. A generous SEARCH REGION is hand-picked per field, by visually + inspecting the rendered template image once (a rectangle expressed as + ``(x0, y0, x1, y1)`` fractions of the page's width/height -- see + ``FIELD_REGION_RECIPES`` below). This step is the only "by eye" part, + and it only needs to be generous/approximately right, not precise -- + the next step finds the exact line within it. +2. Within that region, the SAME classical-CV horizontal-rule-line detector + already used to auto-detect signature boxes when no manual annotation + exists (``src/templates/box_detector.py`` -- binarize, morphological + opening with a wide horizontal kernel to isolate long thin dark runs, + merge nearby fragments) finds the actual blank underline or table + row-divider PIXEL-PERFECTLY. This is deterministic given the same + image, so it only needs to run once per template, ever. +3. The result -- (field name, "above_line" or "below_line" mode, and the + detected line's exact x/y/width) -- is written into + ``annotations/.json``'s ``pages[0].cosmetic_fields``, + the same file (and same coordinate space) ``signature_boxes`` already + lives in. + +This script is run manually, NOT as part of normal generation -- once a +template's cosmetic_fields are cached in its annotation file, +:func:`src.cosmetics.scanned_cosmetic_fields.fill_scanned_cosmetic_fields` +just reads them directly, no image analysis at generation time. + +Usage: + python scripts/detect_cosmetic_field_coordinates.py [--force] +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +import numpy as np +from PIL import Image + +from src.templates.box_detector import _binarize, _detect_horizontal_lines, _merge_nearby_lines +from src.templates.template_loader import TemplateLoader +from src.utils.image_io import load_image_rgb + +ROOT = Path(__file__).resolve().parents[1] + +# Search regions -- the ONLY hand-eyeballed input. See the module docstring. +# Kept here (not imported from scanned_cosmetic_fields.py) so this script is +# the single place that owns "how to (re)measure a template from scratch"; +# scanned_cosmetic_fields.py only ever reads the already-measured result. +FIELD_REGION_RECIPES: "dict[str, list[dict]]" = { + # bank_doc_03/04/05/06/08 all have TWO signature boxes, and (confirmed + # by visually cropping each template around every candidate line) each + # box has its OWN "Print Name:"-style line -- not one shared field. + # ``box_id`` ties a field to the signature box whose signer's name it + # should get filled with, so ``fill_scanned_cosmetic_fields()`` never + # has to guess which of two blank lines belongs to which party. + # + # bank_doc_03's sig1 region originally also contained a WIDER "By:" + # rule line above the actual "Print Name:" line -- the old single-field + # recipe's generous region let ``_find_line_in_region``'s "pick the + # widest line" heuristic grab "By:" instead, a real (if invisible) + # placement bug fixed here by narrowing the region to the exact + # "Print Name:" line's y-band instead of the whole By/Print Name/Title + # block. + "bank_doc_03": [ + {"field": "name_sig1", "mode": "above_line", "region_frac": (0.547, 0.814, 0.742, 0.850), "box_id": "sig1"}, + {"field": "name_sig2", "mode": "above_line", "region_frac": (0.771, 0.814, 0.967, 0.850), "box_id": "sig2"}, + ], + "bank_doc_04": [ + {"field": "name_sig1", "mode": "above_line", "region_frac": (0.547, 0.855, 0.742, 0.873), "box_id": "sig1"}, + {"field": "name_sig2", "mode": "above_line", "region_frac": (0.752, 0.844, 0.977, 0.884), "box_id": "sig2"}, + ], + "bank_doc_05": [ + {"field": "name_sig1", "mode": "above_line", "region_frac": (0.03, 0.75, 0.48, 0.88), "box_id": "sig1"}, + {"field": "name_sig2", "mode": "above_line", "region_frac": (0.502, 0.835, 0.946, 0.864), "box_id": "sig2"}, + ], + "bank_doc_06": [ + {"field": "name_sig1", "mode": "above_line", "region_frac": (0.098, 0.823, 0.478, 0.841), "box_id": "sig1"}, + {"field": "name_sig2", "mode": "above_line", "region_frac": (0.576, 0.814, 0.889, 0.855), "box_id": "sig2"}, + ], + "bank_doc_07": [{"field": "name", "mode": "above_line", "region_frac": (0.28, 0.28, 0.72, 0.35)}], + "bank_doc_08": [ + # "Name of Account Holder:" (sig1) and "Bank Manager's Name:" (sig2) + # -- the latter was never measured before, so it was always left + # blank no matter what filled the Account Holder's line. + {"field": "name_sig1", "mode": "above_line", "region_frac": (0.14, 0.225, 0.48, 0.27), "box_id": "sig1"}, + {"field": "name_sig2", "mode": "above_line", "region_frac": (0.684, 0.296, 0.986, 0.311), "box_id": "sig2"}, + ], + "bank_doc_09": [{"field": "company_name", "mode": "below_line", "region_frac": (0.05, 0.153, 0.55, 0.168)}], +} + + +def _find_line_in_region(gray: "np.ndarray", region_px: "tuple[int, int, int, int]") -> "tuple[int, int, int] | None": + x0, y0, x1, y1 = region_px + crop = gray[y0:y1, x0:x1] + if crop.size == 0: + return None + binary = _binarize(crop) + lines = _merge_nearby_lines(_detect_horizontal_lines(binary, min_width_frac=0.15)) + if not lines: + return None + best = max(lines, key=lambda l: l.w) + return (x0 + best.x, y0 + best.y, best.w) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--force", action="store_true", help="Re-measure and overwrite even if cosmetic_fields already exist.") + args = parser.parse_args() + + loader = TemplateLoader(ROOT / "templates" / "scanned", ROOT / "annotations") + templates = loader.discover() + + for template_id, recipes in FIELD_REGION_RECIPES.items(): + template = templates[template_id] + annotation_path = ROOT / "annotations" / f"{template_id}.json" + data = json.loads(annotation_path.read_text(encoding="utf-8")) + page = data["pages"][0] + + if page.get("cosmetic_fields") and not args.force: + print(f"{template_id}: already has cosmetic_fields, skipping (use --force to re-measure)") + continue + + img = load_image_rgb(template.image_path) + gray = np.array(Image.fromarray(img).convert("L")) + h, w = gray.shape[:2] + + measured = [] + for spec in recipes: + fx0, fy0, fx1, fy1 = spec["region_frac"] + region_px = (int(fx0 * w), int(fy0 * h), int(fx1 * w), int(fy1 * h)) + found = _find_line_in_region(gray, region_px) + if found is None: + print(f"{template_id}: field '{spec['field']}' -- NO LINE FOUND in region {spec['region_frac']}, skipped") + continue + line_x, line_y, line_w = found + entry = {"field": spec["field"], "mode": spec["mode"], "line_x": line_x, "line_y": line_y, "line_w": line_w} + if spec.get("box_id"): + entry["box_id"] = spec["box_id"] + measured.append(entry) + print(f"{template_id}: field '{spec['field']}' -> line=({line_x},{line_y},w={line_w}) box_id={spec.get('box_id')}") + + page["cosmetic_fields"] = measured + annotation_path.write_text(json.dumps(data, indent=2), encoding="utf-8") + + print("\nDone. Re-run with --force to re-measure from scratch (e.g. after a template image changes).") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/scripts/generate_before_after_assets.py b/unified_pipeline/scripts/generate_before_after_assets.py new file mode 100644 index 0000000000000000000000000000000000000000..fc5c3baae1786742b72d73ff9d7c90c922d46e40 --- /dev/null +++ b/unified_pipeline/scripts/generate_before_after_assets.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +"""Generate paired before/after images for every augmentation effect, for slides. + +For each of the 25 configurable effects, renders two samples that are +identical in every respect (same template, signature, placement, box) except +that one has every augmentation disabled ("before") and the other has just +that one effect forced on at a fixed strength ("after"). Because placement is +frozen and both runs share the same (seed, sample_index), the underlying +document/signature/box are pixel-identical up to the effect itself. + +Signature-level effects are saved as a tight crop around the signature box +(where the effect is visible at slide scale); document-level effects are +saved as the full page. Also writes a JSON manifest with the resolved +strength/params/formula description used per effect, for slide captions. + +Usage: + python scripts/generate_before_after_assets.py +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from PIL import Image + +from src.config.loader import load_config +from src.generator import GeneratorContext +from src.visualization.visualizer import _crop_region + +SIGNATURE_EFFECTS = [ + "rotation", "stroke_dilation", + "ink_fading", "ink_bleed", "motion_blur", "gaussian_blur", +] +DOCUMENT_EFFECTS = [ + "perspective", "contrast", "gamma", "coffee_stain", "stain", + "shadow", "crumple", "discoloration", "page_tilt", "dust", "scanner_streaks", + "scanner_noise", "salt_pepper", "jpeg_compression", +] +ALL_EFFECTS = SIGNATURE_EFFECTS + DOCUMENT_EFFECTS + +OUT_ROOT = Path(__file__).resolve().parents[1] / "degradation" +TEMPLATE_ID = "bank_doc_05" +FORCED_STRENGTH = 1.0 # max strength: matches the "range at strength=1.0" figures already on the formula slides +SAMPLE_INDEX = 2 # picked for a bolder/higher-ink-fill signature than index 1, more visible at slide scale +CROP_PAD = 45 +CROP_UPSCALE = 2 + +# Purely tonal/textural document effects are nearly invisible at full-page slide +# scale, so also cut a fixed, upscaled body-text zoom for these (kept identical +# between before/after so the comparison is apples-to-apples). Structural effects +# (crumple, page_tilt, perspective, shadow, scanner_streaks) already read clearly +# on the full page and don't need one. +ZOOM_EFFECTS = { + "contrast", "gamma", "coffee_stain", "stain", + "discoloration", "dust", "scanner_noise", "salt_pepper", "jpeg_compression", +} +ZOOM_BOX = (40, 110, 460, 340) # (x1, y1, x2, y2) in full-page pixel space + +DISPLAY_NAME = { + "rotation": "Rotation jitter", + "stroke_dilation": "Stroke dilation", + "ink_fading": "Ink fading", + "ink_bleed": "Ink bleed", + "motion_blur": "Motion blur", + "gaussian_blur": "Gaussian blur", + "perspective": "Perspective warp", + "contrast": "Contrast shift", + "gamma": "Gamma shift", + "coffee_stain": "Coffee stain", + "stain": "Water / paper stain", + "shadow": "Shadow", + "crumple": "Crumple / wrinkle", + "discoloration": "Discoloration", + "page_tilt": "Page tilt", + "dust": "Dust specks", + "scanner_streaks": "Scanner streaks", + "scanner_noise": "Scanner noise", + "salt_pepper": "Salt & pepper noise", + "jpeg_compression": "JPEG compression", +} + + +def base_overrides() -> list[str]: + return [ + f"document.template={TEMPLATE_ID}", + "placement.rotation_deg.min=0", "placement.rotation_deg.max=0", + "placement.scale_factor.min=0.85", "placement.scale_factor.max=0.85", + "placement.x_offset=0", "placement.y_offset=0", + "placement.misplacement_probability=0.0", + "generation.run_validation=false", + ] + + +def all_disabled_overrides() -> list[str]: + overrides = base_overrides() + for name in ALL_EFFECTS: + overrides.append(f"augmentation.{name}.enabled=false") + overrides.append(f"augmentation.{name}.probability=0.0") + return overrides + + +def effect_overrides(target_effect: str) -> list[str]: + overrides = base_overrides() + for name in ALL_EFFECTS: + is_target = name == target_effect + overrides.append(f"augmentation.{name}.enabled={'true' if is_target else 'false'}") + overrides.append(f"augmentation.{name}.probability={'1.0' if is_target else '0.0'}") + overrides.append(f"augmentation.{target_effect}.strength={FORCED_STRENGTH}") + return overrides + + +def main() -> None: + OUT_ROOT.mkdir(parents=True, exist_ok=True) + manifest: dict[str, dict] = {} + + baseline_config = load_config(overrides=all_disabled_overrides()) + baseline_ctx = GeneratorContext(baseline_config) + baseline_meta, baseline_img, _ = baseline_ctx.run_sample(SAMPLE_INDEX) + box = baseline_meta["geometry"]["target_box"] + + for effect in ALL_EFFECTS: + out_dir = OUT_ROOT / effect + out_dir.mkdir(parents=True, exist_ok=True) + + config = load_config(overrides=effect_overrides(effect)) + ctx = GeneratorContext(config) + meta, after_img, _ = ctx.run_sample(SAMPLE_INDEX) + + before_img, after_img_final = baseline_img, after_img + if effect in SIGNATURE_EFFECTS: + before_out = _crop_region(before_img, box, pad=CROP_PAD) + after_out = _crop_region(after_img_final, box, pad=CROP_PAD) + before_pil = Image.fromarray(before_out) + after_pil = Image.fromarray(after_out) + before_pil = before_pil.resize( + (before_pil.width * CROP_UPSCALE, before_pil.height * CROP_UPSCALE), Image.LANCZOS, + ) + after_pil = after_pil.resize( + (after_pil.width * CROP_UPSCALE, after_pil.height * CROP_UPSCALE), Image.LANCZOS, + ) + else: + before_pil, after_pil = Image.fromarray(before_img), Image.fromarray(after_img_final) + + before_pil.save(out_dir / "before.png") + after_pil.save(out_dir / "after.png") + + if effect in ZOOM_EFFECTS: + x1, y1, x2, y2 = ZOOM_BOX + zb = Image.fromarray(before_img[y1:y2, x1:x2]) + za = Image.fromarray(after_img_final[y1:y2, x1:x2]) + zb = zb.resize((zb.width * CROP_UPSCALE, zb.height * CROP_UPSCALE), Image.LANCZOS) + za = za.resize((za.width * CROP_UPSCALE, za.height * CROP_UPSCALE), Image.LANCZOS) + zb.save(out_dir / "before_zoom.png") + za.save(out_dir / "after_zoom.png") + + rec = meta["augmentation"]["effects"].get(effect, {}) + manifest[effect] = { + "display_name": DISPLAY_NAME[effect], + "category": "signature" if effect in SIGNATURE_EFFECTS else "document", + "strength": rec.get("strength"), + "params": rec.get("params"), + "difficulty_score": meta["difficulty"]["score"], + "difficulty_tier": meta["difficulty"]["tier"], + } + print(f"[{effect}] before/after -> {out_dir} (strength={rec.get('strength')}, params={rec.get('params')})") + + manifest_path = OUT_ROOT / "before_after_manifest.json" + manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") + print(f"\nWrote manifest -> {manifest_path}") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/scripts/generate_case_assets.py b/unified_pipeline/scripts/generate_case_assets.py new file mode 100644 index 0000000000000000000000000000000000000000..6e76cae1e0e5b611cfaaf21cfcb43ff5434e92fd --- /dev/null +++ b/unified_pipeline/scripts/generate_case_assets.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +"""Generate plain full-page images (no dashboard overlay) for normal vs. extreme +multi-degradation cases, for slides. + +Reuses the same two configurations as scripts/generate_multiple_degradation_gallery.py +("normal": configs/generate.yaml's realistic per-effect probabilities, unmodified) +and scripts/generate_multiple_degradation_extreme_gallery.py ("extreme": every +effect pushed to high probability/strength plus worst-case placement/rendering), +but writes the bare rendered page instead of the multi-panel debug figure, since +slides just need the document image plus a short caption. + +Usage: + python scripts/generate_case_assets.py +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from PIL import Image + +from src.config.loader import load_config +from src.generator import GeneratorContext +from scripts.generate_multiple_degradation_extreme_gallery import build_overrides as extreme_overrides + +ROOT = Path(__file__).resolve().parents[1] +TEMPLATE_ID = "bank_doc_05" +N_SAMPLES = 2 + + +def _run_case(out_dir: Path, config_path, overrides: list[str]) -> None: + out_dir.mkdir(parents=True, exist_ok=True) + config = load_config(config_path=config_path, overrides=overrides) + ctx = GeneratorContext(config) + + manifest = {} + for i in range(1, N_SAMPLES + 1): + meta, final_image, _ = ctx.run_sample(i) + Image.fromarray(final_image).save(out_dir / f"case_{i}.png") + + effects = meta["augmentation"]["effects"] + applied = [n for n, r in effects.items() if r["applied"]] + manifest[f"case_{i}"] = { + "applied_effects": applied, + "n_applied": len(applied), + "n_total": len(effects), + "difficulty_score": meta["difficulty"]["score"], + "difficulty_tier": meta["difficulty"]["tier"], + "placement": { + "rotation_deg": meta["placement"]["rotation_deg"], + "scale_factor": meta["placement"]["scale_factor"], + "misplaced": meta["placement"]["misplaced"], + }, + } + print(f"[{out_dir.parent.name}/case_{i}] {len(applied)}/{len(effects)} effects, " + f"difficulty={meta['difficulty']['score']:.3f} ({meta['difficulty']['tier']})") + + (out_dir / "case_manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8") + + +def main() -> None: + # Normal: configs/generate.yaml's realistic "varied batch" probabilities, unmodified. + _run_case( + ROOT / "multiple_degradation" / "case_assets", + ROOT / "configs" / "generate.yaml", + [f"document.template={TEMPLATE_ID}", "generation.run_validation=false"], + ) + # Extreme: every effect pushed high, worst-case placement/rendering. + _run_case( + ROOT / "multiple_degradation_extreme" / "case_assets", + None, + extreme_overrides(), + ) + print("\nDone.") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/scripts/generate_degradation_gallery.py b/unified_pipeline/scripts/generate_degradation_gallery.py new file mode 100644 index 0000000000000000000000000000000000000000..b7164ab1d93fb3520f642e6d5cc44b49eea53ad9 --- /dev/null +++ b/unified_pipeline/scripts/generate_degradation_gallery.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +"""Generate a labeled sample gallery of every augmentation effect in isolation. + +For each of the 25 configurable augmentation families, forces that one effect +on (probability=1.0) and disables every other effect, so the visual result is +attributable to that effect alone. Placement is frozen (no rotation/scale/ +misplacement jitter, fixed template) so samples across effects are directly +comparable. Writes 5 sample images per effect to degradation//, each +a multi-panel figure (full page, signature crop, ink mask, coverage metrics, +difficulty breakdown) via the existing visualizer. + +Usage: + python scripts/generate_degradation_gallery.py +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from src.config.loader import load_config +from src.generator import GeneratorContext +from src.visualization.visualizer import save_visualization + +SIGNATURE_EFFECTS = [ + "rotation", "stroke_dilation", + "ink_fading", "ink_bleed", "motion_blur", "gaussian_blur", +] +DOCUMENT_EFFECTS = [ + "perspective", "contrast", "gamma", "coffee_stain", "stain", + "shadow", "crumple", "discoloration", "page_tilt", "dust", "scanner_streaks", + "scanner_noise", "salt_pepper", "jpeg_compression", +] +ALL_EFFECTS = SIGNATURE_EFFECTS + DOCUMENT_EFFECTS + +OUT_ROOT = Path(__file__).resolve().parents[1] / "degradation" +SAMPLES_PER_EFFECT = 5 +TEMPLATE_ID = "bank_doc_05" # decent-sized signature box (255x35px) +FORCED_STRENGTH = 0.75 + + +def build_overrides(target_effect: str) -> list[str]: + overrides = [ + f"document.template={TEMPLATE_ID}", + "placement.rotation_deg.min=0", "placement.rotation_deg.max=0", + "placement.scale_factor.min=0.85", "placement.scale_factor.max=0.85", + "placement.x_offset=0", "placement.y_offset=0", + "placement.misplacement_probability=0.0", + "generation.run_validation=false", + ] + for name in ALL_EFFECTS: + is_target = name == target_effect + overrides.append(f"augmentation.{name}.enabled={'true' if is_target else 'false'}") + overrides.append(f"augmentation.{name}.probability={'1.0' if is_target else '0.0'}") + overrides.append(f"augmentation.{target_effect}.strength={FORCED_STRENGTH}") + return overrides + + +def main() -> None: + OUT_ROOT.mkdir(parents=True, exist_ok=True) + for effect in ALL_EFFECTS: + out_dir = OUT_ROOT / effect + out_dir.mkdir(parents=True, exist_ok=True) + + config = load_config(overrides=build_overrides(effect)) + ctx = GeneratorContext(config) + + for i in range(1, SAMPLES_PER_EFFECT + 1): + metadata, final_image, _ = ctx.run_sample(i) + save_visualization(out_dir / f"sample_{i}.png", final_image, metadata) + + print(f"[{effect}] wrote {SAMPLES_PER_EFFECT} samples -> {out_dir}") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/scripts/generate_multiple_degradation_extreme_gallery.py b/unified_pipeline/scripts/generate_multiple_degradation_extreme_gallery.py new file mode 100644 index 0000000000000000000000000000000000000000..f57d641f951606d7fdf1f315e3b27d3de8c22f04 --- /dev/null +++ b/unified_pipeline/scripts/generate_multiple_degradation_extreme_gallery.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +"""Generate WORST-CASE samples: many degradations stacked at high strength on one template. + +Like scripts/generate_multiple_degradation_gallery.py, but every augmentation +family's probability and strength range are pushed high (instead of using +configs/generate.yaml's mild "realistic batch" defaults), and placement/ +rendering are also pushed toward their hardest settings (heavy misplacement, +small/faint signature). Most effects fire on most samples, so each sample +ends up with a large, clearly-visible stack of simultaneous degradations. + +The caption band at the top of each image lists not just which effects +fired but their resolved *extent*: strength, plus any effect-specific +parameter (blob count, shadow type, JPEG quality, wrinkle count/amplitude, +etc.) alongside the resolved placement/rendering severity for that sample. + +Usage: + python scripts/generate_multiple_degradation_extreme_gallery.py +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from PIL import Image, ImageDraw, ImageFont + +from src.config.loader import load_config +from src.generator import GeneratorContext +from src.visualization.visualizer import save_visualization +from scripts.generate_degradation_gallery import ALL_EFFECTS + +ROOT = Path(__file__).resolve().parents[1] +OUT_DIR = ROOT / "multiple_degradation_extreme" +N_SAMPLES = 10 +TEMPLATE_ID = "bank_doc_05" + +EFFECT_PROBABILITY = 0.85 # near-certain per effect, but still leaves sample-to-sample variety +STRENGTH_MIN, STRENGTH_MAX = 0.7, 1.0 + + +def _load_font(size: int): + for candidate in (r"C:\Windows\Fonts\consola.ttf", r"C:\Windows\Fonts\arial.ttf"): + if Path(candidate).exists(): + return ImageFont.truetype(candidate, size) + return ImageFont.load_default() + + +def build_overrides() -> list[str]: + overrides = [ + f"document.template={TEMPLATE_ID}", + "generation.run_validation=false", + # Worst-case placement: large rotation, small scale, frequent/strong misplacement. + "placement.rotation_deg.min=-15", "placement.rotation_deg.max=15", + "placement.scale_factor.min=0.3", "placement.scale_factor.max=0.6", + "placement.margin=0.03", + "placement.misplacement_probability=0.5", + "placement.misplacement_strength.min=0.5", "placement.misplacement_strength.max=1.0", + "placement.allow_partial_outside=true", + # Worst-case rendering: faint, blurred, heavily feathered ink. + "rendering.ink_opacity.min=0.3", "rendering.ink_opacity.max=0.55", + "rendering.blur_sigma.min=0.6", "rendering.blur_sigma.max=1.4", + "rendering.feather_px.min=1.5", "rendering.feather_px.max=3.0", + "rendering.scanner_noise=0.05", + ] + for name in ALL_EFFECTS: + overrides.append(f"augmentation.{name}.enabled=true") + overrides.append(f"augmentation.{name}.probability={EFFECT_PROBABILITY}") + if name == "jpeg_compression": + overrides += [ + "augmentation.jpeg_compression.strength=0.9", + "augmentation.jpeg_compression.min_quality=10", + "augmentation.jpeg_compression.max_quality=30", + ] + else: + overrides.append(f"augmentation.{name}.strength.min={STRENGTH_MIN}") + overrides.append(f"augmentation.{name}.strength.max={STRENGTH_MAX}") + return overrides + + +def _fmt_effect(name: str, rec: dict) -> str: + params = rec.get("params") or {} + if name == "jpeg_compression" and "quality" in params: + return f"{name}=q{params['quality']}" + tag = f"{name}={rec['strength']:.2f}" + if "resolved_type" in params: + tag += f"({params['resolved_type']})" + elif "resolved_tone" in params: + tag += f"({params['resolved_tone']})" + elif name == "crumple" and "wrinkle_lines" in params: + tag += f"({params['wrinkle_lines']}L,{params['displacement_amplitude_px']:.1f}px)" + elif name == "coffee_stain" and "blob_count" in params: + tag += f"({params['blob_count']}blobs)" + elif name == "page_tilt" and "tilt_deg" in params: + tag += f"({params['tilt_deg']:.1f}deg)" + elif name == "dust" and "speck_count" in params: + tag += f"({params['speck_count']}px)" + elif name == "scanner_streaks" and "streak_count" in params: + tag += f"({params['streak_count']}streaks)" + return tag + + +def _annotate(path: Path, metadata: dict) -> list[str]: + effects = metadata["augmentation"]["effects"] + applied_names = [n for n, r in effects.items() if r["applied"]] + applied_tags = [_fmt_effect(n, effects[n]) for n in applied_names] + + placement, rendering, difficulty = metadata["placement"], metadata["rendering"], metadata["difficulty"] + header = ( + f"{metadata['document_id']} | difficulty={difficulty['score']:.3f} ({difficulty['tier']}) | " + f"placement: rot={placement['rotation_deg']:.1f}deg scale={placement['scale_factor']:.2f} " + f"misplaced={placement['misplaced']} | " + f"render: ink_opacity={rendering['ink_opacity']:.2f} blur_sigma={rendering['blur_sigma']:.2f}" + ) + body = f"Applied ({len(applied_tags)}/{len(effects)}): " + (", ".join(applied_tags) if applied_tags else "none") + + img = Image.open(path).convert("RGB") + font = _load_font(15) + draw_probe = ImageDraw.Draw(img) + max_w = img.width - 28 + + # Pixel-accurate wrap of the (potentially long) applied-effects line. + words = body.replace(", ", " ,SPLIT_ME ").split(" ,SPLIT_ME ") + lines, current = [], "" + for tok in words: + candidate = (current + ", " + tok) if current else tok + if draw_probe.textlength(candidate, font=font) <= max_w: + current = candidate + else: + if current: + lines.append(current) + current = tok + if current: + lines.append(current) + + all_lines = [header] + lines + line_h = 22 + band_h = 14 + line_h * len(all_lines) + + canvas = Image.new("RGB", (img.width, img.height + band_h), "white") + canvas.paste(img, (0, band_h)) + draw = ImageDraw.Draw(canvas) + draw.rectangle([0, 0, canvas.width, band_h], fill=(60, 18, 18)) + y = 8 + for i, line in enumerate(all_lines): + draw.text((14, y), line, fill="white" if i == 0 else (255, 214, 194), font=font) + y += line_h + canvas.save(path) + return applied_names + + +def main() -> None: + OUT_DIR.mkdir(parents=True, exist_ok=True) + config = load_config(overrides=build_overrides()) + ctx = GeneratorContext(config) + + for i in range(1, N_SAMPLES + 1): + metadata, final_image, _ = ctx.run_sample(i) + out_path = OUT_DIR / f"sample_{i}.png" + save_visualization(out_path, final_image, metadata) + applied = _annotate(out_path, metadata) + print(f"[sample_{i}] {len(applied)}/{len(metadata['augmentation']['effects'])} effects -> {', '.join(applied)}") + + print(f"\nWrote {N_SAMPLES} extreme samples -> {OUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/scripts/generate_multiple_degradation_gallery.py b/unified_pipeline/scripts/generate_multiple_degradation_gallery.py new file mode 100644 index 0000000000000000000000000000000000000000..8a7086a415668d6b06fc882076dcd68e21dbebe0 --- /dev/null +++ b/unified_pipeline/scripts/generate_multiple_degradation_gallery.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +"""Generate samples with multiple random degradations stacked on one template. + +Unlike scripts/generate_degradation_gallery.py (which isolates exactly one +effect per sample for comparison), this uses the realistic "varied batch" +probabilities from configs/generate.yaml unmodified -- every enabled effect +independently rolls its own probability per sample, so different samples end +up with different *combinations* of simultaneously-applied degradations, all +on the same template/box for a fair before/after comparison. + +Each output image is the standard metrics visualization (full page, crop, +ink mask, coverage metrics, difficulty breakdown) plus a caption band listing +exactly which effects fired for that sample. + +Usage: + python scripts/generate_multiple_degradation_gallery.py +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from PIL import Image, ImageDraw, ImageFont + +from src.config.loader import load_config +from src.generator import GeneratorContext +from src.visualization.visualizer import save_visualization + +ROOT = Path(__file__).resolve().parents[1] +OUT_DIR = ROOT / "multiple_degradation" +N_SAMPLES = 10 +TEMPLATE_ID = "bank_doc_05" # same template used in the single-effect gallery + + +def _load_font(size: int) -> "ImageFont.FreeTypeFont | ImageFont.ImageFont": + for candidate in (r"C:\Windows\Fonts\consola.ttf", r"C:\Windows\Fonts\arial.ttf"): + if Path(candidate).exists(): + return ImageFont.truetype(candidate, size) + return ImageFont.load_default() + + +def _annotate_applied_effects(path: Path, metadata: dict) -> list[str]: + effects = metadata["augmentation"]["effects"] + applied = [name for name, rec in effects.items() if rec["applied"]] + difficulty = metadata["difficulty"] + + caption = ( + f"{metadata['document_id']} | applied degradations " + f"({len(applied)}/{len(effects)}): {', '.join(applied) if applied else 'none'}" + f" | difficulty = {difficulty['score']:.3f} ({difficulty['tier']})" + ) + + img = Image.open(path).convert("RGB") + band_h = 40 + canvas = Image.new("RGB", (img.width, img.height + band_h), "white") + canvas.paste(img, (0, band_h)) + draw = ImageDraw.Draw(canvas) + font = _load_font(16) + draw.rectangle([0, 0, canvas.width, band_h], fill=(15, 42, 67)) + draw.text((14, 11), caption, fill="white", font=font) + canvas.save(path) + return applied + + +def main() -> None: + OUT_DIR.mkdir(parents=True, exist_ok=True) + + overrides = [ + f"document.template={TEMPLATE_ID}", + "generation.run_validation=false", + ] + config = load_config(config_path=ROOT / "configs" / "generate.yaml", overrides=overrides) + ctx = GeneratorContext(config) + + for i in range(1, N_SAMPLES + 1): + metadata, final_image, _ = ctx.run_sample(i) + out_path = OUT_DIR / f"sample_{i}.png" + save_visualization(out_path, final_image, metadata) + applied = _annotate_applied_effects(out_path, metadata) + print(f"[sample_{i}] {len(applied)} effects applied -> {', '.join(applied) or 'none'}") + + print(f"\nWrote {N_SAMPLES} samples -> {OUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/scripts/generate_showcase_dataset.py b/unified_pipeline/scripts/generate_showcase_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..93ebfb42c72447dc139bcd62531c67b42784d2af --- /dev/null +++ b/unified_pipeline/scripts/generate_showcase_dataset.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +"""Generate the flagship showcase dataset: all 12 verification case types, +each rendered once on a SCANNED template and once on a PROCEDURAL template +-- 24 cases total, demonstrating every case type and both template families +side by side in one clearly-organized, self-contained folder. + +Folder/file naming is deliberately verbose and self-describing (no need to +open a JSON to know what a case is): + + case_001_1a_scanned/ + case_001_1a_scanned_document.png + case_001_1a_scanned_reference_signature.png + case_001_1a_scanned_ground_truth.json + case_002_1a_procedural/ + ... + before_after/ + case_001_1a_scanned_before_after.png + case_002_1a_procedural_before_after.png + ... # every case's metrics preview, grouped in one folder -- + # easier to flip through in a row than opening 24 case folders + +Usage: + python scripts/generate_showcase_dataset.py [--output data/showcase_dataset] [--seed-base 1000] +""" + +from __future__ import annotations + +import argparse +import csv +import json +import shutil +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from src.config.loader import load_config +from src.generator import GeneratorContext, _export_reference_signature, compute_extra_signature_overlays +from src.utils.image_io import save_image_rgb +from src.verification.case_types import CASE_TABLE +from src.visualization.before_after_preview import save_before_after_preview + +ROOT = Path(__file__).resolve().parents[1] + +# Which base config each provider uses -- "procedural" pulls in the +# recalibrated difficulty constants + cosmetic fields (configs/procedural_templates.yaml), +# "scanned" uses the deck-literal defaults (configs/default.yaml). See +# unified_pipeline/README.md's "Difficulty calibration profiles" section +# for why these two profiles use different reference constants. +PROVIDER_CONFIG = { + "scanned": None, # None = configs/default.yaml only + "procedural": str(ROOT / "configs" / "procedural_templates.yaml"), +} + + +def _record_signer( + index: "dict[str, dict]", signer_group: "str | None", display_name: "str | None", script: "str | None", + role: "str | None", case_id: str, template_id: str, kind: str, ref_path: "str | None", +) -> None: + """Adds one (case, box) appearance to the cross-case reference-signer + index -- answers "which name/role is this signature actually of, and + where else does that same person's ink appear in this dataset" without + having to cross-reference every case folder's ground_truth.json by hand. + No-op for boxes with no real signer (blank/printed-text cases).""" + if not signer_group: + return + entry = index.setdefault(signer_group, { + "signer_group": signer_group, "display_name": display_name, "script": script, + "roles": [], "appears_in": [], "reference_images": [], + }) + if role and role not in entry["roles"]: + entry["roles"].append(role) + entry["appears_in"].append({"case_id": case_id, "role": role, "as": kind, "template": template_id}) + if ref_path: + ref_path = ref_path.replace("\\", "/") # some source paths come through OS-native, some already posix + if ref_path not in entry["reference_images"]: + entry["reference_images"].append(ref_path) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--output", default=str(ROOT / "data" / "showcase_dataset")) + parser.add_argument("--seed-base", type=int, default=1000) + args = parser.parse_args() + + out_dir = Path(args.output) + if out_dir.exists(): + shutil.rmtree(out_dir) + out_dir.mkdir(parents=True) + + rows = [] + signer_index: "dict[str, dict]" = {} + i = 0 + for code, spec in CASE_TABLE.items(): + for provider in ("scanned", "procedural"): + i += 1 + case_id = f"case_{i:03d}_{code}_{provider}" + seed = args.seed_base + i + config = load_config( + config_path=PROVIDER_CONFIG[provider], + overrides=[ + f"seed={seed}", + f"quality.case_type={code}", + f"paths.template_provider={provider}", + "document.enable_cosmetic_fields=true", + ], + ) + ctx = GeneratorContext(config) + metadata, final_image, validation_report = ctx.run_sample(1) + metadata["case_id"] = case_id # overwrite the generic sample_000001-style id with the showcase name + + case_dir = out_dir / case_id + case_dir.mkdir() + save_image_rgb(case_dir / f"{case_id}_document.png", final_image) + + v = metadata["verification"] + sig_source = metadata.get("signature_source") or {} + if sig_source.get("path"): + _export_reference_signature(sig_source["path"], case_dir / f"{case_id}_reference_signature.png") + _record_signer( + signer_index, v["actual_signer_group"], metadata["signer"]["name"], v["actual_signer_script"], + metadata["signer"]["role"], case_id, metadata["template"], "primary", sig_source["path"], + ) + companion = (metadata.get("verification") or {}).get("companion") or {} + if companion.get("ref_path"): + _export_reference_signature(companion["ref_path"], case_dir / f"{case_id}_companion_signature.png") + companion_box = next((b for b in ctx.template_provider.get(metadata["template"]).annotation.pages[0].signature_boxes if b.id == companion["box_id"]), None) + _record_signer( + signer_index, companion["signer_group"], ctx.identity_registry.display_name(companion["signer_group"]), + companion["script"], companion_box.role if companion_box else None, case_id, metadata["template"], "companion", companion["ref_path"], + ) + for extra in metadata.get("additional_signatures") or []: + _export_reference_signature(extra["ref_path"], case_dir / f"{case_id}_{extra['box_id']}_signature.png") + _record_signer( + signer_index, extra["signer_group"], ctx.identity_registry.display_name(extra["signer_group"]), + extra["script"], extra["role"], case_id, metadata["template"], "additional", extra["ref_path"], + ) + + ctx.metadata_exporter.export(metadata, case_dir / f"{case_id}_ground_truth.json") + + if ctx.last_clean_image is not None: + # Grouped in one shared folder rather than scattered one-per-case + # -- easier to flip through every case's metrics preview in a + # row without opening each case folder individually. + extra_boxes = compute_extra_signature_overlays(ctx, metadata, final_image) + save_before_after_preview( + out_dir / "before_after" / f"{case_id}_before_after.png", ctx.last_clean_image, final_image, + metadata, config.difficulty, extra_boxes, + ) + + passed = validation_report is not None and validation_report.passed + rows.append({ + "case_id": case_id, "case_type": code, "description": spec.description, + "template_provider": provider, "template_id": metadata["template"], + "verdict": v["verdict"], "reject_code": v["reject_code"] or "", + "signer_name": metadata["signer"]["name"], "signer_script": v["expected_signer_script"], + "difficulty_tier": metadata["difficulty"]["tier"], "difficulty_score": metadata["difficulty"]["score"], + "validation_passed": passed, + }) + print(f"[{case_id}] template={metadata['template']:<12} verdict={v['verdict']:<7} " + f"{v['reject_code'] or '-':<24} difficulty={metadata['difficulty']['tier']} " + f"({'OK' if passed else 'VALIDATION FAILED'})") + + index_path = out_dir / "SHOWCASE_INDEX.csv" + with index_path.open("w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=list(rows[0].keys())) + writer.writeheader() + writer.writerows(rows) + + # Cross-case identity index: every name/role/entity that appears + # anywhere in this dataset, with which case(s) it appears in and + # which actual reference signature image(s) back it -- so "who is + # 'John Thompson' and where does his ink come from" is one lookup + # instead of grepping every case folder's ground_truth.json by hand. + signers_path = out_dir / "reference_signers_index.json" + signers_path.write_text( + json.dumps(sorted(signer_index.values(), key=lambda e: e["display_name"] or ""), indent=2), encoding="utf-8", + ) + + n_failed = sum(1 for r in rows if not r["validation_passed"]) + print(f"\nWrote {len(rows)} cases ({len(rows) // 2} case types x 2 template providers) to {out_dir}") + print(f"Index: {index_path}") + print(f"Reference-signer index: {signers_path} ({len(signer_index)} distinct signer(s))") + if n_failed: + print(f"WARNING: {n_failed} case(s) failed validation -- check the log above.") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/scripts/generate_verification_suite.py b/unified_pipeline/scripts/generate_verification_suite.py new file mode 100644 index 0000000000000000000000000000000000000000..848e09cea391b09076931e442e16c69bbd5cb6e0 --- /dev/null +++ b/unified_pipeline/scripts/generate_verification_suite.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +"""Generate a labeled sample batch covering all 12 verification test-case types. + +For each ``case_type`` in ``src.verification.case_types.CASE_TABLE`` (1a +through 5b), generates a couple of samples via ``GeneratorContext`` directly +(bypassing ``DatasetGenerator`` since each case needs its own config +override), writes the rendered PNGs + metadata JSONs to +``output/verification_suite/``, and prints a verdict/reject_code summary +table. + +Usage: + python scripts/generate_verification_suite.py [--per-case N] +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from src.config.loader import load_config +from src.generator import GeneratorContext +from src.metadata.metadata_exporter import MetadataExporter +from src.utils.image_io import save_image_rgb +from src.verification.case_types import CASE_TABLE + +ROOT = Path(__file__).resolve().parents[1] +OUT_DIR = ROOT / "output" / "verification_suite" + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--per-case", type=int, default=2, help="Samples to generate per case type.") + parser.add_argument("--seed", type=int, default=4242, help="Master seed.") + args = parser.parse_args() + + OUT_DIR.mkdir(parents=True, exist_ok=True) + exporter = MetadataExporter() + summary_rows = [] + + sample_index = 1 + for code in CASE_TABLE: + config = load_config( + config_path=str(ROOT / "configs" / "examples" / "verification_cases.yaml"), + overrides=[f"quality.case_type={code}", f"seed={args.seed}"], + ) + ctx = GeneratorContext(config) + + for _ in range(args.per_case): + sample_id = f"case_{sample_index:03d}" + metadata, final_image, _ = ctx.run_sample(sample_index) + metadata["document_id"] = sample_id + + save_image_rgb(OUT_DIR / f"{sample_id}.png", final_image) + exporter.export(metadata, OUT_DIR / f"{sample_id}.json") + + v = metadata["verification"] + summary_rows.append({ + "sample_id": sample_id, "case_type": code, "template": metadata["template"], + "box_id": v["target_box_id"], "verdict": v["verdict"], "reject_code": v["reject_code"], + }) + print(f"[{sample_id}] case_type={code:<3} template={metadata['template']:<12} " + f"box={v['target_box_id']:<5} -> {v['verdict']:<7} {v['reject_code'] or '-'}") + sample_index += 1 + + (OUT_DIR / "summary.json").write_text(json.dumps(summary_rows, indent=2), encoding="utf-8") + print(f"\nWrote {len(summary_rows)} samples to {OUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/unified_pipeline/src/__init__.py b/unified_pipeline/src/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8558c6a061cdaa2f95c0a1b21a0424909a17e049 --- /dev/null +++ b/unified_pipeline/src/__init__.py @@ -0,0 +1,3 @@ +"""Synthetic signed bank document generation pipeline.""" + +__version__ = "1.0.0" diff --git a/unified_pipeline/src/analysis/__init__.py b/unified_pipeline/src/analysis/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..87a06c3e4f150c93b5862d13dc24699ded38ee76 --- /dev/null +++ b/unified_pipeline/src/analysis/__init__.py @@ -0,0 +1 @@ +"""Post-render geometry detection, difficulty scoring, and validation.""" diff --git a/unified_pipeline/src/analysis/difficulty_analyzer.py b/unified_pipeline/src/analysis/difficulty_analyzer.py new file mode 100644 index 0000000000000000000000000000000000000000..0fc1b95dadc05cc0c724dc409f85f369a9fe2741 --- /dev/null +++ b/unified_pipeline/src/analysis/difficulty_analyzer.py @@ -0,0 +1,81 @@ +"""Computes the deterministic difficulty score from post-render geometry metrics. + +Pure function of :class:`~src.analysis.geometry_analyzer.GeometryResult` and +the configured weights -- no randomness is involved, so the score is +trivially reproducible given identical geometry inputs. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +from src.analysis.geometry_analyzer import GeometryResult +from src.config.schema import DifficultyWeights +from src.utils.geometry_utils import clamp01 + + +@dataclass +class DifficultyResult: + """The five difficulty components, the weighted score, and its tier.""" + + misplacement: float + whitespace: float + faintness: float + smallness: float + low_texture: float + score: float + tier: str + + +class DifficultyAnalyzer: + """Computes the exact spec-defined difficulty score and tier. + + ``score = 0.35*misplacement + 0.20*whitespace + 0.20*faintness + 0.15*smallness + 0.10*low_texture`` + + Example: + >>> analyzer = DifficultyAnalyzer(cfg.difficulty) + >>> result = analyzer.analyze(geometry_result) + """ + + def __init__(self, weights: DifficultyWeights) -> None: + self.weights = weights + + def analyze(self, geometry: GeometryResult) -> DifficultyResult: + """Compute the difficulty components, weighted score, and tier for one sample.""" + w = self.weights + + faintness_range = w.faintness_light_ref - w.faintness_dark_ref + + misplacement = clamp01(1.0 - geometry.polygon_overlap_proxy) + whitespace = clamp01(geometry.whitespace_ratio) + faintness = clamp01((geometry.mean_dark_intensity - w.faintness_dark_ref) / faintness_range) + smallness = clamp01(1.0 - geometry.signature_area_ratio / w.smallness_reference_area_ratio) + low_texture = clamp01(1.0 - geometry.gray_stddev / w.low_texture_reference_stddev) + + score = ( + w.misplacement * misplacement + + w.whitespace * whitespace + + w.faintness * faintness + + w.smallness * smallness + + w.low_texture * low_texture + ) + score = float(np.clip(score, 0.0, 1.0)) + + if score < w.easy_threshold: + tier = "easy" + elif score < w.hard_threshold: + tier = "medium" + else: + tier = "hard" + + return DifficultyResult( + misplacement=round(misplacement, 4), + whitespace=round(whitespace, 4), + faintness=round(faintness, 4), + smallness=round(smallness, 4), + low_texture=round(low_texture, 4), + score=round(score, 4), + tier=tier, + ) diff --git a/unified_pipeline/src/analysis/geometry_analyzer.py b/unified_pipeline/src/analysis/geometry_analyzer.py new file mode 100644 index 0000000000000000000000000000000000000000..92aef6a529b771cec1ebc2498339baf3c860ddb0 --- /dev/null +++ b/unified_pipeline/src/analysis/geometry_analyzer.py @@ -0,0 +1,402 @@ +"""Post-render signature detection and geometry/coverage metric computation. + +Rather than searching the entire page (which would pick up printed field +labels like "By:" or "Print Name:"), detection is scoped to the known +signature field location -- exactly as a production pipeline would use the +document's layout annotation to know where to look. A tight pass is tried +first; if no ink is found (e.g. the signature was intentionally misplaced +far from its box) the search window progressively expands, bounded by the +page, before giving up. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field + +import cv2 +import numpy as np + +from src.utils.geometry_utils import ( + Box, box_center, box_to_polygon, centroid_of, containment_pct, + coverage_pct, distance_between, iou, margin_utilization, points_to_polygon, safe_area, +) +from src.utils.logging_utils import get_logger + +logger = get_logger(__name__) + +MIN_INK_PIXELS = 12 + + +@dataclass +class GeometryResult: + """Full geometric + photometric characterization of the detected signature.""" + + found: bool + signature_bbox_px: Box + min_rotated_rect: dict + signature_polygon_px: list[list[float]] + convex_hull_px: list[list[float]] + # One (or more, if the ink broke into disjoint blobs) real per-component + # boundary contour(s) -- for VISUALIZATION only (see + # src/visualization/before_after_preview.py), never used in scoring. + # signature_polygon_px/convex_hull_px above are the CONVEX HULL of every + # ink pixel (matches projectA's own computation, including its + # approxPolyDP simplification), correct for coverage/IoU/containment + # math but "bridges" gaps between separate ink blobs (e.g. between + # letters, or a flourish far from the main strokes) with straight + # lines -- confirmed, on direct user feedback, to still visibly fail + # to "hug" a signature with a real gap even after matching projectA's + # exact rendering (that's projectA's own known tradeoff, just not + # visible on a compact signature). These contours hug each real ink + # blob's own boundary instead, so a translucent fill of them only ever + # tints actual ink, never the whitespace between separate strokes. + display_contours: list[list[list[float]]] + centroid_px: tuple[float, float] + orientation_deg: float + ink_pixels: int + target_box: Box + actual_box: Box + search_box: Box + + box_coverage_pct: float + containment_pct: float + ink_fill_ratio: float + polygon_overlap_proxy: float + distance_from_box_center_px: float + margin_utilization: float + + mean_dark_intensity: float + gray_stddev: float + signature_area_ratio: float + whitespace_ratio: float + + +def _expand_box(box: Box, canvas_size: tuple[int, int], factor: float, min_pad: float) -> Box: + x1, y1, x2, y2 = box + w, h = canvas_size + bw, bh = (x2 - x1), (y2 - y1) + pad_x = max(bw * (factor - 1.0) / 2.0, min_pad) + pad_y = max(bh * (factor - 1.0) / 2.0, min_pad) + return ( + float(max(x1 - pad_x, 0)), float(max(y1 - pad_y, 0)), + float(min(x2 + pad_x, w)), float(min(y2 + pad_y, h)), + ) + + +def _union_box(a: Box, b: Box) -> Box: + return (min(a[0], b[0]), min(a[1], b[1]), max(a[2], b[2]), max(a[3], b[3])) + + +def _is_printed_rule_line(width: int, height: int, area: int) -> bool: + """Heuristic filter for printed "sign here" guideline rules vs. handwritten ink. + + Printed rules are thin (<=3px tall), very wide relative to their height, + and nearly solid (high fill ratio) -- unlike handwriting, which varies in + stroke height and leaves gaps even in a single connected component. + """ + if height > 3: + return False + if width < 8 * max(height, 1): + return False + fill_ratio = area / max(width * height, 1) + return fill_ratio > 0.6 + + +def _strip_printed_rule_line_pixels(binary: np.ndarray) -> np.ndarray: + """Removes long, unbroken horizontal-line pixels (the template's own + printed "sign here" rule) from an ink mask BEFORE connected-component + analysis groups pixels together. + + A pasted signature's ink very often sits directly on top of, and + touches, that printed line -- when it does, plain 8-connectivity merges + the line and the ink into ONE connected component, whose bounding box + then spans the printed line's full width (often the whole box) instead + of just where the ink actually is. `_is_printed_rule_line()` below + (per-component, post-hoc) only catches a line that stayed its own + separate component; it can't un-merge one that's already fused with + real ink. A wide horizontal morphological opening isolates only pixels + that are part of a long, uninterrupted straight run -- true of a + printed rule, essentially never true of handwriting for more than a + few pixels at a time (natural slant/curviness breaks a single-row run + quickly) -- so subtracting it erases the line's own pixels while + leaving the ink's pixels, even where they touched, intact. Deliberately + a small ABSOLUTE width, not a fraction of the crop: once a signature + crosses the middle of the line, the line survives as two SHORTER + sub-segments on either side of the crossing, and a proportionally-sized + kernel could exceed either sub-segment's length, letting both slip + through undetected. + + The 1-row-tall opening kernel only checks each row in isolation, so a + genuinely TALL solid block of ink (thick handwriting, or a filled test + fixture) satisfies the same "long unbroken run" test on every one of + its rows and would otherwise be erased in full, row by row -- that's + not what a printed rule looks like (``_is_printed_rule_line()`` + elsewhere in this module defines one as <=3px tall). So candidate line + pixels are re-checked per connected-component afterward: only ones + whose overall bounding height stays <=3px are actually subtracted. + """ + h, w = binary.shape[:2] + kernel_w = 20 + if kernel_w >= w: + return binary + line_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_w, 1)) + line_pixels = cv2.morphologyEx(binary, cv2.MORPH_OPEN, line_kernel, iterations=1) + if line_pixels.sum() == 0: + return binary + + n_labels, labels, stats, _ = cv2.connectedComponentsWithStats(line_pixels, connectivity=8) + thin_line_pixels = np.zeros_like(line_pixels) + for label in range(1, n_labels): + if stats[label, cv2.CC_STAT_HEIGHT] <= 3: + thin_line_pixels[labels == label] = 255 + return cv2.bitwise_and(binary, cv2.bitwise_not(thin_line_pixels)) + + +def _mask_already_dark_in_baseline(binary: np.ndarray, gray_crop: np.ndarray, baseline_crop: np.ndarray) -> np.ndarray: + """Removes pixels that were ALREADY dark in the clean, unsigned template + (printed field labels like "Bank Manager:", box borders, etc.) from an + ink mask -- these sit right next to, and sometimes touch, a short + signature box's search region, and get picked up as "ink" by simple + brightness thresholding otherwise. The clean template image is known in + advance (the same way a real production pipeline knows its own form + layouts, not the specific run's signature placement) so this isn't + using ground truth the way a real verifier couldn't -- it's exactly + what :meth:`_strip_printed_rule_line_pixels` already does for straight + rule lines, generalized to any pre-existing printed content shape.""" + if baseline_crop.shape != gray_crop.shape: + return binary + baseline_background = float(np.percentile(baseline_crop, 90)) + baseline_threshold = baseline_background - max(18.0, baseline_background * 0.15) + baseline_dark = baseline_crop.astype(np.float32) < baseline_threshold + out = binary.copy() + out[baseline_dark] = 0 + return out + + +def _ink_mask(gray_crop: np.ndarray, baseline_crop: "np.ndarray | None" = None) -> "np.ndarray | None": + if gray_crop.size == 0: + return None + background = float(np.percentile(gray_crop, 90)) + threshold = background - max(18.0, background * 0.15) + binary = (gray_crop.astype(np.float32) < threshold).astype(np.uint8) * 255 + if binary.sum() == 0: + return binary + binary = _strip_printed_rule_line_pixels(binary) + if baseline_crop is not None: + binary = _mask_already_dark_in_baseline(binary, gray_crop, baseline_crop) + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2, 2)) + opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) + n_labels, labels, stats, _ = cv2.connectedComponentsWithStats(opened, connectivity=8) + cleaned = np.zeros_like(opened) + for label in range(1, n_labels): + area = stats[label, cv2.CC_STAT_AREA] + w = stats[label, cv2.CC_STAT_WIDTH] + h = stats[label, cv2.CC_STAT_HEIGHT] + if area < 2: + continue + if _is_printed_rule_line(w, h, area): + continue + cleaned[labels == label] = 255 + return cleaned + + +class GeometryAnalyzer: + """Detects the rendered signature within its known field and computes all geometry metrics. + + Example: + >>> analyzer = GeometryAnalyzer() + >>> result = analyzer.analyze(final_image, target_box=(100, 100, 300, 160), + ... canvas_size=(1024, 559)) + """ + + def analyze( + self, + image_rgb: np.ndarray, + target_box: Box, + canvas_size: tuple[int, int], + placement_hint_bbox: "Box | None" = None, + baseline_image_rgb: "np.ndarray | None" = None, + expect_blank: bool = False, + ) -> GeometryResult: + """Detect the rendered signature and compute every geometry/coverage metric. + + Args: + image_rgb: The fully rendered (and augmented) document, ``(H, W, 3)`` uint8. + target_box: The annotated ``(x1, y1, x2, y2)`` signature field. + canvas_size: ``(width, height)`` of the document. + placement_hint_bbox: Optional pre-augmentation placement bounding + box, used to widen the search if a tight pass finds nothing + (e.g. after a large intentional misplacement). + baseline_image_rgb: Optional clean, unsigned template image + (same canvas size, before any signature/cosmetic text was + drawn) -- when given, pixels that were already dark in the + template itself (printed field labels, box borders) are + excluded from ink detection, so a signature box search + region that happens to sit close to printed text doesn't + pick that text up as "ink" (see + :func:`_mask_already_dark_in_baseline`). Not used if the + sample underwent a geometric document-level transform + (rotation/tilt/perspective) that could misalign it against + ``image_rgb`` -- caller's responsibility to omit it then. + expect_blank: Set when the caller already knows (from ground + truth, e.g. a 2a/5a case's target box) that NO signature was + placed here. Skips the second, widened retry pass entirely + -- that retry exists to chase a signature that was + deliberately misplaced far from its own box, not to search + a box we already know is empty. Without this, on a sample + where ``baseline_image_rgb`` had to be omitted (a page-level + geometric transform was applied, so a pixel diff would be + misaligned), the widened pass had nothing to exclude + already-printed content with, and could pick up a nearby + printed caption/label below the blank box as if it were a + wildly misplaced signature. + + Returns: + A :class:`GeometryResult` with detection status and every metric. + """ + gray = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY) + baseline_gray = cv2.cvtColor(baseline_image_rgb, cv2.COLOR_RGB2GRAY) if baseline_image_rgb is not None else None + + search_box = _expand_box(target_box, canvas_size, factor=1.15, min_pad=6) + mask, offset = self._try_detect(gray, search_box, baseline_gray) + + if not expect_blank and (mask is None or int((mask > 0).sum()) < MIN_INK_PIXELS): + union = _union_box(target_box, placement_hint_bbox) if placement_hint_bbox else target_box + search_box = _expand_box(union, canvas_size, factor=2.2, min_pad=50) + mask, offset = self._try_detect(gray, search_box, baseline_gray) + + if mask is None or int((mask > 0).sum()) < MIN_INK_PIXELS: + if not expect_blank: + logger.warning("No signature ink detected near target box %s.", target_box) + return self._empty_result(target_box, search_box, canvas_size) + + return self._build_result(mask, offset, gray, target_box, search_box, canvas_size) + + @staticmethod + def _try_detect( + gray: np.ndarray, box: Box, baseline_gray: "np.ndarray | None" = None, + ) -> tuple["np.ndarray | None", tuple[int, int]]: + x1, y1, x2, y2 = (int(round(v)) for v in box) + crop = gray[y1:y2, x1:x2] + baseline_crop = baseline_gray[y1:y2, x1:x2] if baseline_gray is not None else None + mask = _ink_mask(crop, baseline_crop) + return mask, (x1, y1) + + def _build_result( + self, mask: np.ndarray, offset: tuple[int, int], full_gray: np.ndarray, + target_box: Box, search_box: Box, canvas_size: tuple[int, int], + ) -> GeometryResult: + ox, oy = offset + ys, xs = np.where(mask > 0) + points = np.column_stack([xs + ox, ys + oy]).astype(np.float32) + ink_pixels = int(len(points)) + + x1, y1 = float(points[:, 0].min()), float(points[:, 1].min()) + x2, y2 = float(points[:, 0].max()) + 1.0, float(points[:, 1].max()) + 1.0 + bbox: Box = (x1, y1, x2, y2) + + rect = cv2.minAreaRect(points) + (rcx, rcy), (rw, rh), rangle = rect + rotated_rect = { + "center": [round(float(rcx), 2), round(float(rcy), 2)], + "size": [round(float(rw), 2), round(float(rh), 2)], + "angle_deg": round(float(rangle), 2), + } + + # Convex hull of every detected ink pixel, simplified with + # approxPolyDP (epsilon = 1% of hull perimeter) -- matches + # projectA's own signature_polygon_px computation exactly + # (geometry_metrics.py::compute_signature_placement_metrics()), + # so this pipeline's polygon behaves the same way projectA's does. + contour_points = points.astype(np.int32) + hull = cv2.convexHull(contour_points) + epsilon = 0.01 * cv2.arcLength(hull, True) + approx = cv2.approxPolyDP(hull, epsilon, True) + hull_xy = [[int(pt[0][0]), int(pt[0][1])] for pt in approx] + + # Real per-blob boundary contour(s) -- for the visual preview only + # (see the display_contours field docstring on GeometryResult). + # A small dilation first (on a COPY, never the scoring mask) merges + # adjacent pen-stroke fragments of the same letter into one smooth + # blob instead of a "polka dot" scatter of tiny separate islands -- + # a few px is nowhere near enough to bridge a real gap between + # separate letters or a flourish far from the main strokes, so + # genuine whitespace still stays outside every contour. + dilated = cv2.dilate(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)), iterations=1) + raw_contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + display_contours = [ + [[float(pt[0][0] + ox), float(pt[0][1] + oy)] for pt in c] + for c in raw_contours if len(c) >= 3 + ] + + mean_pt, eigvecs, eigvals = cv2.PCACompute2(points, mean=None) + orientation_deg = float(np.degrees(np.arctan2(eigvecs[0][1], eigvecs[0][0]))) + orientation_deg = ((orientation_deg + 90) % 180) - 90 + + centroid = (float(points[:, 0].mean()), float(points[:, 1].mean())) + + bbox_crop = full_gray[int(y1):int(y2), int(x1):int(x2)] + mean_dark_intensity = float(bbox_crop[mask.astype(bool)].mean()) if bbox_crop.shape == mask.shape else float( + full_gray[oy:oy + mask.shape[0], ox:ox + mask.shape[1]][mask.astype(bool)].mean() + ) + gray_stddev = float(bbox_crop.std()) if bbox_crop.size > 0 else 0.0 + + target_poly = box_to_polygon(target_box) + signature_poly = points_to_polygon(hull_xy) + + bbox_area = max((x2 - x1) * (y2 - y1), 1.0) + ink_fill_ratio = float(np.clip(ink_pixels / bbox_area, 0.0, 1.0)) + canvas_area = max(canvas_size[0] * canvas_size[1], 1) + signature_area_ratio = float(bbox_area / canvas_area) + + return GeometryResult( + found=True, + signature_bbox_px=bbox, + min_rotated_rect=rotated_rect, + signature_polygon_px=hull_xy, + convex_hull_px=hull_xy, + display_contours=display_contours, + centroid_px=(round(centroid[0], 2), round(centroid[1], 2)), + orientation_deg=round(orientation_deg, 2), + ink_pixels=ink_pixels, + target_box=target_box, + actual_box=bbox, + search_box=search_box, + box_coverage_pct=round(coverage_pct(target_poly, signature_poly), 3), + containment_pct=round(containment_pct(target_poly, signature_poly), 3), + ink_fill_ratio=round(ink_fill_ratio, 4), + polygon_overlap_proxy=round(iou(target_poly, signature_poly), 4), + distance_from_box_center_px=round(distance_between(box_center(target_box), centroid), 2), + margin_utilization=round(margin_utilization(target_box, signature_poly), 4), + mean_dark_intensity=round(mean_dark_intensity, 2), + gray_stddev=round(gray_stddev, 2), + signature_area_ratio=round(signature_area_ratio, 5), + whitespace_ratio=round(float(np.clip(1.0 - ink_fill_ratio, 0.0, 1.0)), 4), + ) + + @staticmethod + def _empty_result(target_box: Box, search_box: Box, canvas_size: tuple[int, int]) -> GeometryResult: + return GeometryResult( + found=False, + signature_bbox_px=target_box, + min_rotated_rect={"center": [0.0, 0.0], "size": [0.0, 0.0], "angle_deg": 0.0}, + signature_polygon_px=[], + convex_hull_px=[], + display_contours=[], + centroid_px=(float("nan"), float("nan")), + orientation_deg=0.0, + ink_pixels=0, + target_box=target_box, + actual_box=target_box, + search_box=search_box, + box_coverage_pct=0.0, + containment_pct=0.0, + ink_fill_ratio=0.0, + polygon_overlap_proxy=0.0, + distance_from_box_center_px=float("nan"), + margin_utilization=0.0, + mean_dark_intensity=255.0, + gray_stddev=0.0, + signature_area_ratio=0.0, + whitespace_ratio=1.0, + ) diff --git a/unified_pipeline/src/analysis/validation_engine.py b/unified_pipeline/src/analysis/validation_engine.py new file mode 100644 index 0000000000000000000000000000000000000000..f42e8431699e1ad8a85d1103be4e15f6c1085bc8 --- /dev/null +++ b/unified_pipeline/src/analysis/validation_engine.py @@ -0,0 +1,282 @@ +"""Post-generation validation of exported metadata. + +Checks that every sample's metadata is internally consistent and safe for +downstream ML consumption: the signature was actually detected, its polygon +is a valid non-degenerate geometry, every declared bounding box is +well-formed and lies on the page, no NaN/Inf values leaked into the JSON, +metadata is schema-complete, and the difficulty score is exactly +reproducible from its own recorded components and weights. +""" + +from __future__ import annotations + +import math +from dataclasses import dataclass, field +from typing import Any + +from shapely.geometry import Polygon + +from src.config.schema import DifficultyWeights, ValidationConfig +from src.utils.logging_utils import get_logger +from src.verification.case_types import CASE_TABLE + +logger = get_logger(__name__) + +REQUIRED_TOP_LEVEL_KEYS = ( + "document_id", "template", "page", "page_size", "signer", "signature_source", + "placement", "rendering", "difficulty", "quality", "verification", "augmentation", "geometry", "metrics", +) + +# Case types whose ground truth is an intentionally blank target box -- a +# real signature is not expected to be detected there. +_BLANK_CASE_TYPES = frozenset({"2a", "5a"}) + + +@dataclass +class ValidationIssue: + field: str + message: str + severity: str # "error" | "warning" + + +@dataclass +class ValidationReport: + sample_id: str + passed: bool + issues: list[ValidationIssue] = field(default_factory=list) + + @property + def errors(self) -> list[ValidationIssue]: + return [i for i in self.issues if i.severity == "error"] + + @property + def warnings(self) -> list[ValidationIssue]: + return [i for i in self.issues if i.severity == "warning"] + + +class ValidationEngine: + """Validates one sample's exported metadata for correctness and completeness. + + Example: + >>> engine = ValidationEngine(cfg.validation, cfg.difficulty) + >>> report = engine.validate(metadata) + >>> assert report.passed + """ + + def __init__(self, config: ValidationConfig, difficulty_weights: DifficultyWeights) -> None: + self.config = config + self.weights = difficulty_weights + + def validate(self, metadata: dict[str, Any]) -> ValidationReport: + """Run every validation check against one sample's metadata dict.""" + sample_id = str(metadata.get("document_id", "unknown")) + issues: list[ValidationIssue] = [] + + issues += self._check_completeness(metadata) + issues += self._check_no_nan(metadata) + issues += self._check_signature_exists(metadata) + issues += self._check_polygon_valid(metadata) + issues += self._check_boxes(metadata) + issues += self._check_difficulty_reproducible(metadata) + issues += self._check_verification_consistency(metadata) + + has_errors = any(i.severity == "error" for i in issues) + has_warnings = any(i.severity == "warning" for i in issues) + passed = not has_errors and not (self.config.strict and has_warnings) + + return ValidationReport(sample_id=sample_id, passed=passed, issues=issues) + + def _check_completeness(self, metadata: dict[str, Any]) -> list[ValidationIssue]: + issues = [] + for key in REQUIRED_TOP_LEVEL_KEYS: + if key not in metadata: + issues.append(ValidationIssue(key, f"Missing required top-level key '{key}'", "error")) + signer = metadata.get("signer", {}) + for key in ("name", "role"): + if not signer.get(key): + issues.append(ValidationIssue(f"signer.{key}", f"Missing signer.{key}", "error")) + return issues + + def _check_no_nan(self, metadata: dict[str, Any], path: str = "") -> list[ValidationIssue]: + issues: list[ValidationIssue] = [] + + # geometry.centroid_px / metrics.distance_from_box_center_px are + # deliberate NaN sentinels (see GeometryAnalyzer._empty_result) when + # nothing was detected -- not a data-quality bug, so they're + # excluded from the walk. Note distance_from_box_center_px is + # exported under "metrics", not "geometry" (see + # MetadataExporter.build) -- this previously said "geometry.distance_..." + # and so never actually matched anything, silently failing to skip + # it; invisible until a genuinely blank-target case (2a/5a) was + # ever correctly detected as such, since contamination in ink + # detection had been making even deliberately-blank boxes report + # SOME (spurious) ink found beforehand. + skip_paths: set = set() + if not metadata.get("geometry", {}).get("detected", True): + skip_paths = {"geometry.centroid_px[0]", "geometry.centroid_px[1]", "metrics.distance_from_box_center_px"} + + def walk(value: Any, cur_path: str) -> None: + if cur_path in skip_paths: + return + if isinstance(value, dict): + for k, v in value.items(): + walk(v, f"{cur_path}.{k}" if cur_path else str(k)) + elif isinstance(value, (list, tuple)): + for i, v in enumerate(value): + walk(v, f"{cur_path}[{i}]") + elif isinstance(value, float): + if math.isnan(value) or math.isinf(value): + issues.append(ValidationIssue(cur_path, f"Non-finite value ({value}) at {cur_path}", "error")) + + walk(metadata, path) + return issues + + @staticmethod + def _expects_blank_target(metadata: dict[str, Any]) -> bool: + verification = metadata.get("verification") or {} + return verification.get("case_type") in _BLANK_CASE_TYPES + + def _check_signature_exists(self, metadata: dict[str, Any]) -> list[ValidationIssue]: + geometry = metadata.get("geometry", {}) + if self._expects_blank_target(metadata): + return [] + if not geometry.get("detected", False): + return [ValidationIssue("geometry.detected", "Signature was not detected in the rendered document", "error")] + if geometry.get("ink_pixels", 0) <= 0: + return [ValidationIssue("geometry.ink_pixels", "Signature detected but has zero ink pixels", "error")] + return [] + + def _check_polygon_valid(self, metadata: dict[str, Any]) -> list[ValidationIssue]: + issues: list[ValidationIssue] = [] + if not metadata.get("geometry", {}).get("detected", False) and self._expects_blank_target(metadata): + return [] + poly_pts = metadata.get("geometry", {}).get("signature_polygon_px", []) + if len(poly_pts) < 3: + issues.append(ValidationIssue("geometry.signature_polygon_px", "Polygon has fewer than 3 points", "error")) + return issues + try: + poly = Polygon(poly_pts) + if not poly.is_valid: + issues.append(ValidationIssue("geometry.signature_polygon_px", "Polygon geometry is invalid", "error")) + elif poly.area <= 0: + issues.append(ValidationIssue("geometry.signature_polygon_px", "Polygon has zero area", "error")) + except Exception as exc: # pragma: no cover - defensive + issues.append(ValidationIssue("geometry.signature_polygon_px", f"Polygon construction failed: {exc}", "error")) + return issues + + def _check_boxes(self, metadata: dict[str, Any]) -> list[ValidationIssue]: + issues: list[ValidationIssue] = [] + geometry = metadata.get("geometry", {}) + page_w, page_h = metadata.get("page_size", [None, None]) + eps = self.config.max_coordinate_epsilon + + for field_name in ("target_box", "actual_box", "signature_bbox_px", "box_px"): + box = geometry.get(field_name) + if not box or len(box) != 4: + issues.append(ValidationIssue(f"geometry.{field_name}", "Box missing or malformed", "error")) + continue + x1, y1, x2, y2 = box + if x2 <= x1 or y2 <= y1: + issues.append(ValidationIssue(f"geometry.{field_name}", f"Box has non-positive extent: {box}", "error")) + if page_w is not None and page_h is not None: + if x1 < -eps or y1 < -eps or x2 > page_w + eps or y2 > page_h + eps: + issues.append(ValidationIssue( + f"geometry.{field_name}", + f"Box {box} lies outside page bounds (0,0,{page_w},{page_h})", + "warning", + )) + return issues + + def _check_difficulty_reproducible(self, metadata: dict[str, Any]) -> list[ValidationIssue]: + difficulty = metadata.get("difficulty", {}) + required = ("misplacement", "whitespace", "faintness", "smallness", "low_texture", "score", "tier") + if not all(k in difficulty for k in required): + return [ValidationIssue("difficulty", "Difficulty block incomplete", "error")] + + w = self.weights + recomputed = ( + w.misplacement * difficulty["misplacement"] + + w.whitespace * difficulty["whitespace"] + + w.faintness * difficulty["faintness"] + + w.smallness * difficulty["smallness"] + + w.low_texture * difficulty["low_texture"] + ) + if abs(recomputed - difficulty["score"]) > 1e-3: + return [ValidationIssue( + "difficulty.score", + f"Score {difficulty['score']} does not match recomputed weighted sum {recomputed:.4f}", + "error", + )] + + if recomputed < w.easy_threshold: + expected_tier = "easy" + elif recomputed < w.hard_threshold: + expected_tier = "medium" + else: + expected_tier = "hard" + if expected_tier != difficulty["tier"]: + return [ValidationIssue( + "difficulty.tier", f"Tier '{difficulty['tier']}' does not match expected '{expected_tier}'", "error", + )] + return [] + + def _check_verification_consistency(self, metadata: dict[str, Any]) -> list[ValidationIssue]: + verification = metadata.get("verification") or {} + case_type = verification.get("case_type") + if case_type is None: + return [] + + spec = CASE_TABLE.get(case_type) + if spec is None: + return [ValidationIssue("verification.case_type", f"Unknown case_type '{case_type}'", "error")] + + issues: list[ValidationIssue] = [] + if verification.get("verdict") != spec.verdict: + issues.append(ValidationIssue( + "verification.verdict", + f"verdict '{verification.get('verdict')}' does not match case_type '{case_type}' expected '{spec.verdict}'", + "error", + )) + if verification.get("reject_code") != spec.reject_code: + issues.append(ValidationIssue( + "verification.reject_code", + f"reject_code '{verification.get('reject_code')}' does not match case_type '{case_type}' expected '{spec.reject_code}'", + "error", + )) + return issues + + +@dataclass +class ValidationSummary: + """Aggregate statistics over a batch of per-sample validation reports.""" + + total: int + passed: int + failed: int + total_errors: int + total_warnings: int + failed_sample_ids: list[str] + + def as_dict(self) -> dict[str, Any]: + return { + "total": self.total, + "passed": self.passed, + "failed": self.failed, + "pass_rate": round(self.passed / self.total, 4) if self.total else 0.0, + "total_errors": self.total_errors, + "total_warnings": self.total_warnings, + "failed_sample_ids": self.failed_sample_ids, + } + + +def summarize(reports: list[ValidationReport]) -> ValidationSummary: + """Aggregate a list of per-sample validation reports into a batch summary.""" + passed = sum(1 for r in reports if r.passed) + return ValidationSummary( + total=len(reports), + passed=passed, + failed=len(reports) - passed, + total_errors=sum(len(r.errors) for r in reports), + total_warnings=sum(len(r.warnings) for r in reports), + failed_sample_ids=[r.sample_id for r in reports if not r.passed], + ) diff --git a/unified_pipeline/src/augmentation/__init__.py b/unified_pipeline/src/augmentation/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f4b5871f81f066d6496a9f7288a56653167fe601 --- /dev/null +++ b/unified_pipeline/src/augmentation/__init__.py @@ -0,0 +1 @@ +"""Configurable augmentation effects for signature layers and full documents.""" diff --git a/unified_pipeline/src/augmentation/albumentations_effects.py b/unified_pipeline/src/augmentation/albumentations_effects.py new file mode 100644 index 0000000000000000000000000000000000000000..9b80bff7650d13017fa8c50cdc90e487814262a1 --- /dev/null +++ b/unified_pipeline/src/augmentation/albumentations_effects.py @@ -0,0 +1,76 @@ +"""Thin, deterministic wrappers around Albumentations transforms. + +Every wrapper collapses the transform's own randomized magnitude range down +to values derived from our ``strength in [0, 1]`` convention, and executes +via a freshly seeded ``A.Compose`` so results are fully reproducible under +:mod:`src.utils.random_utils` seed derivation rather than Albumentations' +global random state. +""" + +from __future__ import annotations + +import albumentations as A +import numpy as np + + +def _run(transform: "A.BasicTransform", image: np.ndarray, seed: int) -> np.ndarray: + pipeline = A.Compose([transform], seed=seed, p=1.0) + return pipeline(image=image)["image"] + + +def motion_blur(image: np.ndarray, strength: float, seed: int) -> np.ndarray: + """Directional motion blur; ``strength`` controls kernel size (3..21 px).""" + k = int(round(3 + strength * 18)) + k = k + 1 if k % 2 == 0 else k + k = max(3, min(k, 21)) + return _run(A.MotionBlur(blur_limit=(k, k), p=1.0), image, seed) + + +def gaussian_blur(image: np.ndarray, strength: float, seed: int) -> tuple[np.ndarray, float]: + """Isotropic Gaussian blur; ``strength`` controls sigma (0.2..4.0 px).""" + sigma = round(0.2 + strength * 3.8, 3) + out = _run(A.GaussianBlur(blur_limit=0, sigma_limit=(sigma, sigma), p=1.0), image, seed) + return out, sigma + + +def perspective_warp(image: np.ndarray, strength: float, seed: int) -> np.ndarray: + """Subtle perspective distortion; ``strength`` controls corner-jitter scale.""" + scale = round(0.01 + strength * 0.09, 4) + return _run(A.Perspective(scale=(scale, scale), keep_size=True, fit_output=False, p=1.0), image, seed) + + +def contrast_shift(image: np.ndarray, strength: float, seed: int) -> np.ndarray: + """Global contrast shift; ``strength`` controls the max +/- magnitude.""" + mag = round(strength * 0.45, 4) + t = A.RandomBrightnessContrast(brightness_limit=(0.0, 0.0), contrast_limit=(-mag, mag), p=1.0) + return _run(t, image, seed) + + +def gamma_shift(image: np.ndarray, strength: float, seed: int) -> tuple[np.ndarray, int]: + """Gamma correction; ``strength`` controls deviation from gamma=100.""" + delta = int(round(strength * 45)) + lo, hi = max(100 - delta, 40), 100 + delta + out = _run(A.RandomGamma(gamma_limit=(lo, hi), p=1.0), image, seed) + return out, lo if lo != hi else lo + + +def jpeg_compression(image: np.ndarray, min_quality: int, max_quality: int, seed: int) -> tuple[np.ndarray, int]: + """JPEG re-compression artifact simulation over ``[min_quality, max_quality]``.""" + rng = np.random.default_rng(seed) + quality = int(rng.integers(min_quality, max_quality + 1)) + out = _run(A.ImageCompression(compression_type="jpeg", quality_range=(quality, quality), p=1.0), image, seed) + return out, quality + + +def salt_and_pepper(image: np.ndarray, strength: float, seed: int) -> np.ndarray: + """Salt-and-pepper impulse noise; ``strength`` controls the affected pixel fraction.""" + amount = round(0.002 + strength * 0.09, 5) + t = A.SaltAndPepper(amount=(amount, amount), salt_vs_pepper=(0.45, 0.55), p=1.0) + return _run(t, image, seed) + + +def sensor_noise(image: np.ndarray, strength: float, seed: int) -> np.ndarray: + """Gaussian sensor/scanner noise; ``strength`` controls the noise std as a fraction of range.""" + std = round(0.01 + strength * 0.12, 5) + t = A.GaussNoise(std_range=(std, std), mean_range=(0.0, 0.0), per_channel=False, p=1.0) + return _run(t, image, seed) diff --git a/unified_pipeline/src/augmentation/augmentation_engine.py b/unified_pipeline/src/augmentation/augmentation_engine.py new file mode 100644 index 0000000000000000000000000000000000000000..e418308ffe78c0e2a47070fcdb77b0f592e629b4 --- /dev/null +++ b/unified_pipeline/src/augmentation/augmentation_engine.py @@ -0,0 +1,290 @@ +"""Orchestrates every configurable augmentation effect for one sample. + +Two passes are performed: + +1. :meth:`AugmentationEngine.apply_to_signature` -- effects that only make + sense applied to the ink itself (stroke geometry, ink density, localized + blur/jitter), run on the RGBA signature layer *before* blending. +2. :meth:`AugmentationEngine.apply_to_document` -- effects that simulate + physical document/scanner degradation, run on the fully blended page. + +Every effect is gated independently by its own ``enabled`` flag, a +probability roll, and a strength value -- each resolved from a seed derived +deterministically from ``(sample_seed, augmentation_name)`` (or an explicit +per-effect ``seed`` override in config), guaranteeing reproducibility. +Every effect's resolved parameters are collected into an +:class:`AugmentationRecord` for full metadata traceability, even when the +effect was not applied. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + +import numpy as np + +from src.augmentation import albumentations_effects as AE +from src.augmentation import document_effects as DE +from src.augmentation import signature_effects as SE +from src.config.schema import AugmentationParam, AugmentationsConfig +from src.utils.logging_utils import get_logger +from src.utils.random_utils import SeedManager, resolve_scalar_or_range + +logger = get_logger(__name__) + + +@dataclass +class AugmentationRecord: + """The resolved, applied-or-not state of a single augmentation effect.""" + + name: str + enabled: bool + applied: bool + strength: float + probability: float + seed: int + params: dict[str, Any] = field(default_factory=dict) + + +def _prepare(name: str, cfg: AugmentationParam, seed_manager: SeedManager) -> tuple[bool, np.random.Generator, int]: + seed = cfg.seed if cfg.seed is not None else seed_manager.seed_for(f"augmentation:{name}") + rng = np.random.default_rng(seed) + roll = float(rng.uniform(0.0, 1.0)) + applied = bool(cfg.enabled and roll < cfg.probability) + return applied, rng, seed + + +def _record(name: str, cfg: AugmentationParam, applied: bool, seed: int, + strength: float = 0.0, params: "dict | None" = None) -> AugmentationRecord: + return AugmentationRecord( + name=name, enabled=cfg.enabled, applied=applied, strength=round(strength, 4), + probability=cfg.probability, seed=seed, params=params or {}, + ) + + +class AugmentationEngine: + """Applies the full configurable augmentation suite to a signature layer and a document. + + Example: + >>> engine = AugmentationEngine(cfg.augmentation) + >>> rgba, sig_records = engine.apply_to_signature(placed_rgba, seed_manager) + >>> doc, doc_records, warped_baseline = engine.apply_to_document(blended_doc, seed_manager, baseline_image) + """ + + def __init__(self, config: AugmentationsConfig) -> None: + self.config = config + + # ------------------------------------------------------------------ # + # Signature-layer effects + # ------------------------------------------------------------------ # + def apply_to_signature( + self, rgba: np.ndarray, seed_manager: SeedManager, + ) -> tuple[np.ndarray, dict[str, AugmentationRecord]]: + cfg = self.config + records: dict[str, AugmentationRecord] = {} + out = rgba + + applied, rng, seed = _prepare("rotation", cfg.rotation, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.rotation.strength, rng) + extra_deg = float(rng.uniform(-1, 1)) * strength * 8.0 + out = SE.jitter_rotation(out, extra_deg) + records["rotation"] = _record("rotation", cfg.rotation, True, seed, strength, {"extra_deg": round(extra_deg, 3)}) + else: + records["rotation"] = _record("rotation", cfg.rotation, False, seed) + + applied, rng, seed = _prepare("stroke_dilation", cfg.stroke_dilation, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.stroke_dilation.strength, rng) + out = SE.stroke_morphology(out, strength, thicken=True) + records["stroke_dilation"] = _record("stroke_dilation", cfg.stroke_dilation, True, seed, strength, {"thicken": True}) + else: + records["stroke_dilation"] = _record("stroke_dilation", cfg.stroke_dilation, False, seed) + + applied, rng, seed = _prepare("ink_fading", cfg.ink_fading, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.ink_fading.strength, rng) + out = SE.ink_fading(out, strength, rng) + records["ink_fading"] = _record("ink_fading", cfg.ink_fading, True, seed, strength) + else: + records["ink_fading"] = _record("ink_fading", cfg.ink_fading, False, seed) + + applied, rng, seed = _prepare("ink_bleed", cfg.ink_bleed, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.ink_bleed.strength, rng) + out = SE.ink_bleed(out, strength) + records["ink_bleed"] = _record("ink_bleed", cfg.ink_bleed, True, seed, strength) + else: + records["ink_bleed"] = _record("ink_bleed", cfg.ink_bleed, False, seed) + + applied, rng, seed = _prepare("motion_blur", cfg.motion_blur, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.motion_blur.strength, rng) + angle = float(rng.uniform(0, 180)) + out = SE.motion_blur_signature(out, strength, angle) + records["motion_blur"] = _record("motion_blur", cfg.motion_blur, True, seed, strength, {"angle_deg": round(angle, 2)}) + else: + records["motion_blur"] = _record("motion_blur", cfg.motion_blur, False, seed) + + applied, rng, seed = _prepare("gaussian_blur", cfg.gaussian_blur, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.gaussian_blur.strength, rng) + sigma = 0.2 + strength * 2.2 + out = SE.gaussian_blur_signature(out, sigma) + records["gaussian_blur"] = _record("gaussian_blur", cfg.gaussian_blur, True, seed, strength, {"sigma": round(sigma, 3)}) + else: + records["gaussian_blur"] = _record("gaussian_blur", cfg.gaussian_blur, False, seed) + + return out, records + + # ------------------------------------------------------------------ # + # Document-level effects + # ------------------------------------------------------------------ # + def apply_to_document( + self, image: np.ndarray, seed_manager: SeedManager, baseline_image: "np.ndarray | None" = None, + ) -> tuple[np.ndarray, dict[str, AugmentationRecord], "np.ndarray | None"]: + """Runs the full document-level effect suite. + + Args: + image: The fully-signed document to degrade. + seed_manager: Derives each effect's seed deterministically. + baseline_image: Optional blank/unsigned template, same canvas + size as ``image`` -- when given, the 3 geometric effects + (perspective/crumple/page_tilt) apply the exact same + transform to it too, so the returned ``warped_baseline`` (3rd + tuple element) stays pixel-aligned with ``image`` even after + a rotation/warp, and callers can keep using baseline-based + ink-exclusion instead of having to skip it. ``None`` in, + ``None`` out. + + Returns: + ``(degraded_image, per_effect_records, warped_baseline)``. + """ + cfg = self.config + records: dict[str, AugmentationRecord] = {} + out = image + baseline_out = baseline_image + + applied, rng, seed = _prepare("perspective", cfg.perspective, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.perspective.strength, rng) + out = AE.perspective_warp(out, strength, seed) + if baseline_out is not None: + # Deterministic from (strength, seed) alone (Albumentations + # reseeds its own RNG from `seed` each call) -- calling it + # again on the baseline reproduces the identical warp. + baseline_out = AE.perspective_warp(baseline_out, strength, seed) + records["perspective"] = _record("perspective", cfg.perspective, True, seed, strength) + else: + records["perspective"] = _record("perspective", cfg.perspective, False, seed) + + applied, rng, seed = _prepare("contrast", cfg.contrast, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.contrast.strength, rng) + out = AE.contrast_shift(out, strength, seed) + records["contrast"] = _record("contrast", cfg.contrast, True, seed, strength) + else: + records["contrast"] = _record("contrast", cfg.contrast, False, seed) + + applied, rng, seed = _prepare("gamma", cfg.gamma, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.gamma.strength, rng) + out, gamma_value = AE.gamma_shift(out, strength, seed) + records["gamma"] = _record("gamma", cfg.gamma, True, seed, strength, {"gamma": gamma_value}) + else: + records["gamma"] = _record("gamma", cfg.gamma, False, seed) + + applied, rng, seed = _prepare("coffee_stain", cfg.coffee_stain, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.coffee_stain.strength, rng) + out, params = DE.coffee_stain(out, strength, rng) + records["coffee_stain"] = _record("coffee_stain", cfg.coffee_stain, True, seed, strength, params) + else: + records["coffee_stain"] = _record("coffee_stain", cfg.coffee_stain, False, seed) + + applied, rng, seed = _prepare("stain", cfg.stain, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.stain.strength, rng) + out, params = DE.stain(out, strength, cfg.stain.type, rng) + records["stain"] = _record("stain", cfg.stain, True, seed, strength, params) + else: + records["stain"] = _record("stain", cfg.stain, False, seed) + + applied, rng, seed = _prepare("shadow", cfg.shadow, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.shadow.strength, rng) + out, params = DE.shadow(out, strength, cfg.shadow.type, rng) + records["shadow"] = _record("shadow", cfg.shadow, True, seed, strength, params) + else: + records["shadow"] = _record("shadow", cfg.shadow, False, seed) + + applied, rng, seed = _prepare("crumple", cfg.crumple, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.crumple.strength, rng) + density = resolve_scalar_or_range(cfg.crumple.wrinkle_density, rng) + out, params, baseline_out = DE.crumple(out, strength, density, rng, companion=baseline_out) + records["crumple"] = _record("crumple", cfg.crumple, True, seed, strength, {**params, "wrinkle_density": round(density, 3)}) + else: + records["crumple"] = _record("crumple", cfg.crumple, False, seed) + + applied, rng, seed = _prepare("discoloration", cfg.discoloration, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.discoloration.strength, rng) + out, params = DE.discoloration(out, strength, cfg.discoloration.tone, rng) + records["discoloration"] = _record("discoloration", cfg.discoloration, True, seed, strength, params) + else: + records["discoloration"] = _record("discoloration", cfg.discoloration, False, seed) + + applied, rng, seed = _prepare("page_tilt", cfg.page_tilt, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.page_tilt.strength, rng) + out, params, baseline_out = DE.page_tilt(out, strength, rng, companion=baseline_out) + records["page_tilt"] = _record("page_tilt", cfg.page_tilt, True, seed, strength, params) + else: + records["page_tilt"] = _record("page_tilt", cfg.page_tilt, False, seed) + + applied, rng, seed = _prepare("dust", cfg.dust, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.dust.strength, rng) + out, params = DE.dust(out, strength, rng) + records["dust"] = _record("dust", cfg.dust, True, seed, strength, params) + else: + records["dust"] = _record("dust", cfg.dust, False, seed) + + applied, rng, seed = _prepare("scanner_streaks", cfg.scanner_streaks, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.scanner_streaks.strength, rng) + out, params = DE.scanner_streaks(out, strength, rng) + records["scanner_streaks"] = _record("scanner_streaks", cfg.scanner_streaks, True, seed, strength, params) + else: + records["scanner_streaks"] = _record("scanner_streaks", cfg.scanner_streaks, False, seed) + + applied, rng, seed = _prepare("scanner_noise", cfg.scanner_noise, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.scanner_noise.strength, rng) + out = AE.sensor_noise(out, strength, seed) + records["scanner_noise"] = _record("scanner_noise", cfg.scanner_noise, True, seed, strength) + else: + records["scanner_noise"] = _record("scanner_noise", cfg.scanner_noise, False, seed) + + applied, rng, seed = _prepare("salt_pepper", cfg.salt_pepper, seed_manager) + if applied: + strength = resolve_scalar_or_range(cfg.salt_pepper.strength, rng) + out = AE.salt_and_pepper(out, strength, seed) + records["salt_pepper"] = _record("salt_pepper", cfg.salt_pepper, True, seed, strength) + else: + records["salt_pepper"] = _record("salt_pepper", cfg.salt_pepper, False, seed) + + # JPEG compression must run last: it is a lossy re-encode and every + # subsequent pixel-level effect would otherwise mask its artifacts. + jpeg_cfg = cfg.jpeg_compression + applied, rng, seed = _prepare("jpeg_compression", jpeg_cfg, seed_manager) + if applied: + strength = resolve_scalar_or_range(jpeg_cfg.strength, rng) + out, quality = AE.jpeg_compression(out, jpeg_cfg.min_quality, jpeg_cfg.max_quality, seed) + records["jpeg_compression"] = _record("jpeg_compression", jpeg_cfg, True, seed, strength, {"quality": quality}) + else: + records["jpeg_compression"] = _record("jpeg_compression", jpeg_cfg, False, seed) + + return out, records, baseline_out diff --git a/unified_pipeline/src/augmentation/document_effects.py b/unified_pipeline/src/augmentation/document_effects.py new file mode 100644 index 0000000000000000000000000000000000000000..ab3a44c1c2a0386d90dc90bfff01ad44c451e4b3 --- /dev/null +++ b/unified_pipeline/src/augmentation/document_effects.py @@ -0,0 +1,267 @@ +"""Custom augmentation effects operating on the full, already-blended document image. + +These simulate physical document degradation and scanning artifacts that +Albumentations has no domain-specific transform for: coffee/water/paper +stains, directional shadows, page creasing, discoloration, dust, and +scanner streaks. Each function returns ``(image, actual_params)`` so the +exact resolved parameters (e.g. which corner a shadow fell in) can be +recorded in metadata. +""" + +from __future__ import annotations + +import cv2 +import numpy as np + +from src.utils.transform_utils import smooth_noise_field + + +def _blob_mask(shape: tuple[int, int], center: tuple[float, float], radii: tuple[float, float], + angle_deg: float, blur_sigma: float) -> np.ndarray: + """Build a soft-edged elliptical blob mask in ``[0, 1]``.""" + h, w = shape + mask = np.zeros((h, w), dtype=np.uint8) + cv2.ellipse(mask, (int(center[0]), int(center[1])), (int(radii[0]), int(radii[1])), angle_deg, 0, 360, 255, -1) + mask_f = cv2.GaussianBlur(mask.astype(np.float32), (0, 0), sigmaX=blur_sigma) / 255.0 + return np.clip(mask_f, 0, 1) + + +def coffee_stain(image: np.ndarray, strength: float, rng: np.random.Generator) -> tuple[np.ndarray, dict]: + """Overlay 1-3 translucent brown ring-edged blotches, like a coffee cup mark.""" + h, w = image.shape[:2] + out = image.astype(np.float32) + n_blobs = 1 + int(rng.integers(0, 1 + int(round(strength * 3)))) + centers = [] + for _ in range(max(n_blobs, 1)): + cx, cy = rng.uniform(0.1, 0.9) * w, rng.uniform(0.1, 0.9) * h + base_r = (0.03 + strength * 0.10) * min(w, h) + rx, ry = base_r * rng.uniform(0.8, 1.3), base_r * rng.uniform(0.7, 1.2) + angle = float(rng.uniform(0, 180)) + fill = _blob_mask((h, w), (cx, cy), (rx, ry), angle, blur_sigma=base_r * 0.35) + rim = fill - _blob_mask((h, w), (cx, cy), (rx * 0.82, ry * 0.82), angle, blur_sigma=base_r * 0.2) + color = np.array([139, 98, 60], dtype=np.float32) + opacity = 0.10 + strength * 0.30 + for c in range(3): + out[:, :, c] = out[:, :, c] * (1 - fill * opacity * 0.5) + color[c] * fill * opacity * 0.5 + out[:, :, c] = out[:, :, c] * (1 - rim * opacity * 0.9) + color[c] * 0.7 * rim * opacity * 0.9 + centers.append((round(float(cx), 1), round(float(cy), 1))) + return np.clip(out, 0, 255).astype(np.uint8), {"blob_count": len(centers), "centers": centers} + + +def stain(image: np.ndarray, strength: float, stain_type: str, rng: np.random.Generator) -> tuple[np.ndarray, dict]: + """Overlay a paper or water stain blotch. + + ``paper`` stains are mottled brownish-grey discolorations; ``water`` + stains are pale, soft-edged, faintly blue-grey tide marks. + """ + h, w = image.shape[:2] + out = image.astype(np.float32) + resolved_type = stain_type if stain_type != "random" else rng.choice(["paper", "water"]) + cx, cy = rng.uniform(0.15, 0.85) * w, rng.uniform(0.15, 0.85) * h + base_r = (0.06 + strength * 0.16) * min(w, h) + + if resolved_type == "water": + color = np.array([170, 178, 182], dtype=np.float32) + opacity = 0.06 + strength * 0.18 + rx, ry = base_r * rng.uniform(1.0, 1.6), base_r * rng.uniform(0.8, 1.3) + fill = _blob_mask((h, w), (cx, cy), (rx, ry), float(rng.uniform(0, 180)), blur_sigma=base_r * 0.6) + edge = fill - _blob_mask((h, w), (cx, cy), (rx * 0.9, ry * 0.9), 0, blur_sigma=base_r * 0.3) + for c in range(3): + out[:, :, c] = out[:, :, c] * (1 - fill * opacity * 0.4) + color[c] * fill * opacity * 0.4 + out[:, :, c] = out[:, :, c] * (1 - edge * opacity) + color[c] * edge * opacity + else: + color = np.array([120, 106, 84], dtype=np.float32) + opacity = 0.08 + strength * 0.22 + for _ in range(3): + rr = base_r * rng.uniform(0.4, 1.0) + ox, oy = cx + rng.uniform(-base_r, base_r) * 0.5, cy + rng.uniform(-base_r, base_r) * 0.5 + fill = _blob_mask((h, w), (ox, oy), (rr, rr * rng.uniform(0.7, 1.1)), float(rng.uniform(0, 180)), + blur_sigma=rr * 0.5) + for c in range(3): + out[:, :, c] = out[:, :, c] * (1 - fill * opacity * 0.5) + color[c] * fill * opacity * 0.5 + + return np.clip(out, 0, 255).astype(np.uint8), { + "resolved_type": resolved_type, "center": (round(float(cx), 1), round(float(cy), 1)), + } + + +def shadow(image: np.ndarray, strength: float, shadow_type: str, rng: np.random.Generator) -> tuple[np.ndarray, dict]: + """Apply an edge, corner, or fold shadow gradient.""" + h, w = image.shape[:2] + resolved_type = shadow_type if shadow_type != "random" else rng.choice(["edge", "corner", "fold"]) + out = image.astype(np.float32) + darkness = 0.15 + strength * 0.55 + + if resolved_type == "edge": + side = rng.choice(["top", "bottom", "left", "right"]) + band_frac = 0.12 + strength * 0.18 + yy, xx = np.mgrid[0:h, 0:w].astype(np.float32) + if side == "top": + grad = np.clip(1 - yy / (h * band_frac), 0, 1) + elif side == "bottom": + grad = np.clip(1 - (h - 1 - yy) / (h * band_frac), 0, 1) + elif side == "left": + grad = np.clip(1 - xx / (w * band_frac), 0, 1) + else: + grad = np.clip(1 - (w - 1 - xx) / (w * band_frac), 0, 1) + gain = 1 - grad * darkness + out *= gain[:, :, None] + extra = {"side": side} + elif resolved_type == "corner": + corner = rng.choice(["top_left", "top_right", "bottom_left", "bottom_right"]) + cx = 0 if "left" in corner else w + cy = 0 if "top" in corner else h + yy, xx = np.mgrid[0:h, 0:w].astype(np.float32) + dist = np.hypot(xx - cx, yy - cy) + radius = 0.55 * min(w, h) * (0.7 + strength * 0.6) + grad = np.clip(1 - dist / radius, 0, 1) + gain = 1 - grad * darkness + out *= gain[:, :, None] + extra = {"corner": corner} + else: # fold + n_folds = 1 + int(strength * 2) + extra_lines = [] + for _ in range(n_folds): + vertical = bool(rng.integers(0, 2)) + pos = rng.uniform(0.25, 0.75) + width = (0.015 + strength * 0.03) * (w if vertical else h) + yy, xx = np.mgrid[0:h, 0:w].astype(np.float32) + axis = xx if vertical else yy + center = pos * (w if vertical else h) + dist = np.abs(axis - center) + grad = np.clip(1 - dist / max(width, 1.0), 0, 1) + gain = 1 - grad * darkness * 0.7 + out *= gain[:, :, None] + extra_lines.append({"vertical": vertical, "position_frac": round(float(pos), 3)}) + extra = {"folds": extra_lines} + + return np.clip(out, 0, 255).astype(np.uint8), {"resolved_type": resolved_type, **extra} + + +def crumple( + image: np.ndarray, strength: float, wrinkle_density: float, rng: np.random.Generator, + companion: "np.ndarray | None" = None, +) -> tuple[np.ndarray, dict, "np.ndarray | None"]: + """Simulate paper crumpling: subtle geometric displacement plus faint wrinkle-line shading. + + ``companion``, if given, gets the exact same geometric displacement + remap applied (using the identical ``map_x``/``map_y`` computed from + this call's own random draws, before any further rng consumption for + shading/wrinkle-lines below) -- no shading, since only spatial + alignment matters for a baseline reference image, not tone. See + :func:`page_tilt`'s ``companion`` docstring for why this exists. + """ + h, w = image.shape[:2] + field_x = smooth_noise_field((h, w), rng, blur_frac=0.06) - 0.5 + field_y = smooth_noise_field((h, w), rng, blur_frac=0.06) - 0.5 + amplitude = strength * min(w, h) * 0.015 + + yy, xx = np.mgrid[0:h, 0:w].astype(np.float32) + map_x = xx + field_x * amplitude + map_y = yy + field_y * amplitude + warped = cv2.remap(image, map_x, map_y, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE) + + warped_companion = None + if companion is not None: + warped_companion = cv2.remap(companion, map_x, map_y, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE) + + shade_field = smooth_noise_field((h, w), rng, blur_frac=0.03) + n_lines = int(3 + wrinkle_density * 12) + line_mask = np.zeros((h, w), dtype=np.float32) + for _ in range(n_lines): + x1, y1 = rng.uniform(0, w), rng.uniform(0, h) + angle = rng.uniform(0, 2 * np.pi) + length = rng.uniform(0.15, 0.5) * min(w, h) + x2, y2 = x1 + np.cos(angle) * length, y1 + np.sin(angle) * length + cv2.line(line_mask, (int(x1), int(y1)), (int(x2), int(y2)), 1.0, thickness=1) + line_mask = cv2.GaussianBlur(line_mask, (0, 0), sigmaX=1.0) + + shading = (shade_field - 0.5) * strength * 0.25 - line_mask * strength * 0.18 + out = warped.astype(np.float32) * (1 + shading[:, :, None]) + return ( + np.clip(out, 0, 255).astype(np.uint8), + {"wrinkle_lines": n_lines, "displacement_amplitude_px": round(float(amplitude), 3)}, + np.clip(warped_companion, 0, 255).astype(np.uint8) if warped_companion is not None else None, + ) + + +_SEPIA_MATRIX = np.array([ + [0.393, 0.769, 0.189], + [0.349, 0.686, 0.168], + [0.272, 0.534, 0.131], +], dtype=np.float32) + + +def discoloration(image: np.ndarray, strength: float, tone: str, rng: np.random.Generator) -> tuple[np.ndarray, dict]: + """Apply an overall color-cast discoloration: yellowing, greying, or sepia toning.""" + resolved_tone = tone if tone != "random" else rng.choice(["yellow", "grey", "sepia"]) + img_f = image.astype(np.float32) + + if resolved_tone == "yellow": + tint = np.array([1.0 + strength * 0.12, 1.0 + strength * 0.04, 1.0 - strength * 0.28], dtype=np.float32) + out = img_f * tint[None, None, :] + elif resolved_tone == "grey": + gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY).astype(np.float32) + gray3 = np.repeat(gray[:, :, None], 3, axis=2) + out = img_f * (1 - strength * 0.6) + gray3 * (strength * 0.6) + else: # sepia + sepia = img_f @ _SEPIA_MATRIX.T + out = img_f * (1 - strength * 0.7) + sepia * (strength * 0.7) + + unevenness = smooth_noise_field(image.shape[:2], rng, blur_frac=0.05) + gain = 1 - (1 - unevenness) * strength * 0.08 + out *= gain[:, :, None] + + return np.clip(out, 0, 255).astype(np.uint8), {"resolved_tone": resolved_tone} + + +def page_tilt( + image: np.ndarray, strength: float, rng: np.random.Generator, companion: "np.ndarray | None" = None, +) -> tuple[np.ndarray, dict, "np.ndarray | None"]: + """Rotate the whole page by a small angle, simulating a crooked scanner feed. + + ``companion``, if given, gets the exact same rotation matrix applied + (no additional randomness/shading) -- used to keep a baseline + reference image spatially aligned with the rotated document, so ink + detection's "already dark in the blank template" exclusion still + works after a geometric transform instead of having to be skipped + entirely (see :func:`~src.analysis.geometry_analyzer._mask_already_dark_in_baseline`). + """ + h, w = image.shape[:2] + angle = float(rng.uniform(-1.0, 1.0) * (1.5 + strength * 6.5)) + matrix = cv2.getRotationMatrix2D((w / 2.0, h / 2.0), angle, 1.0) + rotated = cv2.warpAffine(image, matrix, (w, h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE) + rotated_companion = None + if companion is not None: + rotated_companion = cv2.warpAffine(companion, matrix, (w, h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE) + return rotated, {"tilt_deg": round(angle, 3)}, rotated_companion + + +def dust(image: np.ndarray, strength: float, rng: np.random.Generator) -> tuple[np.ndarray, dict]: + """Scatter small light/dark specks simulating dust and sensor debris.""" + h, w = image.shape[:2] + out = image.astype(np.float32) + n_specks = int((h * w) * 0.00003 * (1 + strength * 6)) + xs = rng.integers(0, w, size=n_specks) + ys = rng.integers(0, h, size=n_specks) + for x, y in zip(xs, ys): + r = int(rng.integers(1, 3)) + val = float(rng.choice([255.0, 40.0])) + cv2.circle(out, (int(x), int(y)), r, (val, val, val), -1) + return np.clip(out, 0, 255).astype(np.uint8), {"speck_count": int(n_specks)} + + +def scanner_streaks(image: np.ndarray, strength: float, rng: np.random.Generator) -> tuple[np.ndarray, dict]: + """Add faint vertical scan-line streaks, simulating a dirty scanner sensor/glass.""" + h, w = image.shape[:2] + out = image.astype(np.float32) + n_streaks = 1 + int(strength * 6) + xs = sorted(int(v) for v in rng.integers(0, w, size=n_streaks)) + for x in xs: + width = int(rng.integers(1, 3)) + delta = float(rng.uniform(-1, 1)) * (10 + strength * 35) + x1, x2 = max(x - width, 0), min(x + width, w) + out[:, x1:x2, :] += delta + return np.clip(out, 0, 255).astype(np.uint8), {"streak_count": n_streaks, "positions_x": xs} + + diff --git a/unified_pipeline/src/augmentation/signature_effects.py b/unified_pipeline/src/augmentation/signature_effects.py new file mode 100644 index 0000000000000000000000000000000000000000..ec1ff9d2fdff4e58eef109e49afb01846ff77348 --- /dev/null +++ b/unified_pipeline/src/augmentation/signature_effects.py @@ -0,0 +1,106 @@ +"""Custom augmentation effects operating on the RGBA signature layer. + +These act on the placed signature *before* it is blended onto the document, +so they affect only the ink itself (stroke geometry, ink density) rather +than the surrounding paper. +""" + +from __future__ import annotations + +import cv2 +import numpy as np + +from src.utils.transform_utils import rotate_and_scale_rgba, smooth_noise_field + + +def stroke_morphology(rgba: np.ndarray, strength: float, thicken: bool) -> np.ndarray: + """Thicken or thin ink strokes via morphological dilation/erosion of the alpha channel.""" + ksize = max(1, int(round(1 + strength * 5))) + ksize = ksize + 1 if ksize % 2 == 0 else ksize + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (ksize, ksize)) + alpha = rgba[:, :, 3] + op = cv2.dilate if thicken else cv2.erode + new_alpha = op(alpha, kernel, iterations=1) + out = rgba.copy() + out[:, :, 3] = new_alpha + return out + + +def motion_blur_signature(rgba: np.ndarray, strength: float, angle_deg: float) -> np.ndarray: + """Directional (hand-shake style) motion blur applied to the signature stamp.""" + length = max(3, int(round(3 + strength * 16))) + kernel = np.zeros((length, length), dtype=np.float32) + kernel[length // 2, :] = 1.0 + center = (length / 2 - 0.5, length / 2 - 0.5) + rot = cv2.getRotationMatrix2D(center, angle_deg, 1.0) + kernel = cv2.warpAffine(kernel, rot, (length, length)) + kernel_sum = kernel.sum() + if kernel_sum > 1e-6: + kernel /= kernel_sum + + out = rgba.astype(np.float32) + for c in range(4): + out[:, :, c] = cv2.filter2D(out[:, :, c], -1, kernel, borderType=cv2.BORDER_CONSTANT) + return np.clip(out, 0, 255).astype(np.uint8) + + +def gaussian_blur_signature(rgba: np.ndarray, sigma: float) -> np.ndarray: + """Isotropic blur of the signature stamp (softens edges, simulates scan blur/feathering).""" + if sigma <= 0: + return rgba + blurred = cv2.GaussianBlur(rgba.astype(np.float32), (0, 0), sigmaX=sigma) + return np.clip(blurred, 0, 255).astype(np.uint8) + + +def ink_fading(rgba: np.ndarray, strength: float, rng: np.random.Generator) -> np.ndarray: + """Fade ink unevenly, simulating a pen running low on ink mid-stroke.""" + h, w = rgba.shape[:2] + if h < 2 or w < 2: + return rgba + field = smooth_noise_field((h, w), rng, blur_frac=0.15) + multiplier = 1.0 - strength * (1.0 - field) * 0.9 + multiplier = np.clip(multiplier, 0.05, 1.0) + out = rgba.copy() + out[:, :, 3] = np.clip(out[:, :, 3].astype(np.float32) * multiplier, 0, 255).astype(np.uint8) + return out + + +def ink_bleed(rgba: np.ndarray, strength: float) -> np.ndarray: + """Simulate ink bleeding/feathering into paper fibers around stroke edges.""" + ksize = max(1, int(round(1 + strength * 4))) + ksize = ksize + 1 if ksize % 2 == 0 else ksize + alpha = rgba[:, :, 3].astype(np.float32) + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (ksize, ksize)) + dilated = cv2.dilate(alpha, kernel, iterations=1) + halo = cv2.GaussianBlur(dilated, (0, 0), sigmaX=max(ksize * 0.6, 0.8)) + bled = np.maximum(alpha, halo * (0.35 + 0.35 * strength)) + out = rgba.copy() + out[:, :, 3] = np.clip(bled, 0, 255).astype(np.uint8) + return out + + +def _fit_to_canvas(rgba: np.ndarray, canvas_h: int, canvas_w: int) -> np.ndarray: + """Center-crop/pad ``rgba`` back to ``(canvas_h, canvas_w)``. + + ``rotate_and_scale_rgba`` auto-expands its output to avoid clipping, + which is correct for a standalone stamp but not here: these jitter + effects run on the full-canvas placement layer, and every downstream + stage (blending, geometry detection) assumes that layer stays exactly + canvas-sized. + """ + h, w = rgba.shape[:2] + if h == canvas_h and w == canvas_w: + return rgba + out = np.zeros((canvas_h, canvas_w, 4), dtype=rgba.dtype) + src_x1, src_y1 = max((w - canvas_w) // 2, 0), max((h - canvas_h) // 2, 0) + src = rgba[src_y1:src_y1 + min(h, canvas_h), src_x1:src_x1 + min(w, canvas_w)] + sh, sw = src.shape[:2] + dst_x1, dst_y1 = max((canvas_w - w) // 2, 0), max((canvas_h - h) // 2, 0) + out[dst_y1:dst_y1 + sh, dst_x1:dst_x1 + sw] = src + return out + + +def jitter_rotation(rgba: np.ndarray, extra_deg: float) -> np.ndarray: + """Apply a small extra rotation on top of the base placement rotation.""" + h, w = rgba.shape[:2] + return _fit_to_canvas(rotate_and_scale_rgba(rgba, extra_deg, 1.0), h, w) diff --git a/unified_pipeline/src/config/__init__.py b/unified_pipeline/src/config/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a6ab4775e8b62710d3b6d0e865cd016570d156d7 --- /dev/null +++ b/unified_pipeline/src/config/__init__.py @@ -0,0 +1,6 @@ +"""Configuration schema (Pydantic) and OmegaConf-based YAML loading.""" + +from src.config.schema import PipelineConfig +from src.config.loader import load_config + +__all__ = ["PipelineConfig", "load_config"] diff --git a/unified_pipeline/src/config/loader.py b/unified_pipeline/src/config/loader.py new file mode 100644 index 0000000000000000000000000000000000000000..f7f0971e73e14c338f3db388bfdd0bfe8b2516a7 --- /dev/null +++ b/unified_pipeline/src/config/loader.py @@ -0,0 +1,70 @@ +"""OmegaConf-based configuration loading, merging, and Pydantic validation. + +Supports: + +* A base YAML file (typically ``configs/default.yaml``). +* An optional user YAML file merged on top of the base. +* Dot-list CLI overrides (``key.subkey=value``), e.g. as produced by + ``OmegaConf.from_dotlist(["placement.rotation_deg.min=-5"])``. + +The merged structure is converted to a plain Python container and validated +against :class:`~src.config.schema.PipelineConfig`, so any typo or +out-of-range value fails fast with a clear error instead of silently being +ignored. +""" + +from __future__ import annotations + +from pathlib import Path + +from omegaconf import OmegaConf + +from src.config.schema import PipelineConfig + + +def _default_config_path() -> Path: + return Path(__file__).resolve().parents[2] / "configs" / "default.yaml" + + +def load_config( + config_path: "str | Path | None" = None, + overrides: "list[str] | None" = None, +) -> PipelineConfig: + """Load, merge, and validate the pipeline configuration. + + Args: + config_path: Path to a user YAML config. If ``None``, only the + built-in ``configs/default.yaml`` is used. + overrides: Optional list of ``"dotted.key=value"`` CLI-style + overrides, applied last (highest precedence). + + Returns: + A fully validated :class:`~src.config.schema.PipelineConfig`. + + Raises: + pydantic.ValidationError: If the merged configuration is invalid. + FileNotFoundError: If ``config_path`` is given but does not exist. + """ + base = OmegaConf.load(_default_config_path()) + + merged = base + if config_path is not None: + config_path = Path(config_path) + if not config_path.exists(): + raise FileNotFoundError(f"Config file not found: {config_path}") + user_conf = OmegaConf.load(config_path) + merged = OmegaConf.merge(base, user_conf) + + if overrides: + cli_conf = OmegaConf.from_dotlist(overrides) + merged = OmegaConf.merge(merged, cli_conf) + + resolved = OmegaConf.to_container(merged, resolve=True) + return PipelineConfig.model_validate(resolved) + + +def save_resolved_config(config: PipelineConfig, path: "str | Path") -> None: + """Write a fully-resolved configuration (as actually used) to YAML for provenance.""" + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + OmegaConf.save(OmegaConf.create(config.model_dump()), path) diff --git a/unified_pipeline/src/config/schema.py b/unified_pipeline/src/config/schema.py new file mode 100644 index 0000000000000000000000000000000000000000..36dc1d405bbbbc8ed88a66aa1e6f0dd89b8347e5 --- /dev/null +++ b/unified_pipeline/src/config/schema.py @@ -0,0 +1,330 @@ +"""Pydantic configuration schema for the synthetic document generation pipeline. + +Every user-facing parameter is validated here. Numeric placement/augmentation +parameters accept either a fixed scalar or a ``{min, max}`` range (resolved +per-sample by a seeded RNG, see :mod:`src.utils.random_utils`). + +The individual augmentation effects requested by the product spec are +consolidated into configurable *families* where an effect is a strict +specialization of another (paper stains + water stains -> ``stain`` with a +``type`` selector; edge/corner/fold shadows -> ``shadow`` with a ``type`` +selector; crumpling + wrinkles -> ``crumple``; general discoloration + paper +yellowing -> ``discoloration`` with a ``tone`` selector). This keeps the +config surface coherent while still reaching every requested visual effect. +""" + +from __future__ import annotations + +from typing import Literal, Union + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator + +NumericOrRange_T = Union[float, "Range"] + + +class Range(BaseModel): + """An inclusive ``[min, max]`` range a value is uniformly sampled from.""" + + model_config = ConfigDict(extra="forbid") + + min: float + max: float + + @model_validator(mode="after") + def _check_order(self) -> "Range": + if self.max < self.min: + raise ValueError(f"Range max ({self.max}) must be >= min ({self.min})") + return self + + +NumericOrRange = Union[float, Range] + + +class AugmentationParam(BaseModel): + """Base schema shared by every augmentation effect. + + Extra keys are allowed so effect-specific parameters (e.g. ``type`` for + :class:`ShadowParam`, ``tone`` for discoloration) can be declared inline + in YAML without needing a bespoke subclass for every single effect. + """ + + model_config = ConfigDict(extra="allow") + + enabled: bool = False + strength: NumericOrRange = 0.0 + probability: float = Field(default=1.0, ge=0.0, le=1.0) + seed: "int | None" = None + + @field_validator("strength") + @classmethod + def _strength_bounds(cls, v: NumericOrRange) -> NumericOrRange: + if isinstance(v, (int, float)) and not (0.0 <= float(v) <= 1.0): + raise ValueError("strength must be within [0, 1]") + return v + + +class ShadowParam(AugmentationParam): + type: Literal["edge", "corner", "fold", "random"] = "random" + + +class StainParam(AugmentationParam): + type: Literal["paper", "water", "random"] = "random" + + +class DiscolorationParam(AugmentationParam): + tone: Literal["yellow", "grey", "sepia", "random"] = "random" + + +class CrumpleParam(AugmentationParam): + wrinkle_density: NumericOrRange = 0.4 + + +class JpegCompressionParam(AugmentationParam): + min_quality: int = Field(default=35, ge=1, le=100) + max_quality: int = Field(default=90, ge=1, le=100) + + +class AugmentationsConfig(BaseModel): + """The full family of configurable document/signature augmentations.""" + + model_config = ConfigDict(extra="forbid") + + rotation: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + stroke_dilation: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + motion_blur: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + gaussian_blur: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + perspective: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + scanner_noise: AugmentationParam = Field(default_factory=lambda: AugmentationParam(enabled=True, strength=0.06, probability=1.0)) + salt_pepper: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + jpeg_compression: JpegCompressionParam = Field(default_factory=lambda: JpegCompressionParam()) + contrast: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + gamma: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + coffee_stain: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + stain: StainParam = Field(default_factory=lambda: StainParam()) + shadow: ShadowParam = Field(default_factory=lambda: ShadowParam()) + crumple: CrumpleParam = Field(default_factory=lambda: CrumpleParam()) + discoloration: DiscolorationParam = Field(default_factory=lambda: DiscolorationParam()) + page_tilt: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + dust: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + scanner_streaks: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + ink_fading: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + ink_bleed: AugmentationParam = Field(default_factory=lambda: AugmentationParam()) + + def items(self): + """Yield ``(name, AugmentationParam)`` pairs for every configured effect.""" + for name in self.model_fields: + yield name, getattr(self, name) + + +class PlacementConfig(BaseModel): + """Controls where and how the signature is positioned inside its target box.""" + + model_config = ConfigDict(extra="forbid") + + x_offset: NumericOrRange = 0.0 + y_offset: NumericOrRange = 0.0 + rotation_deg: NumericOrRange = 0.0 + scale_factor: NumericOrRange = 0.9 + anchor_point: Literal[ + "center", "top_left", "top_right", "bottom_left", "bottom_right", + "top_center", "bottom_center", "left_center", "right_center", + ] = "center" + margin: float = Field(default=0.06, ge=0.0, le=0.45) + misplacement_probability: float = Field(default=0.0, ge=0.0, le=1.0) + misplacement_strength: NumericOrRange = 0.3 + allow_partial_outside: bool = False + # 28px, not the deck's 16px (see placement_engine.py's own docstring history + # for the original 24px-vs-16px measurement) -- raised again 2026-08-11 after + # a real signature placed into a genuine 18px "sign here" line was measured + # at each candidate floor: 24px -> 15.1% fully-opaque ink, 28px -> 16.8%, + # with only a mild overflow cost (median 4% at 28px vs 0% at 24px) -- + # pushing further (32px+) escalates overflow much faster than legibility + # gain (20%+ median overflow by 32px) so 28px was kept as the balance point. + min_signature_height_px: float = Field(default=28.0, gt=0.0) + + +class RenderingConfig(BaseModel): + """Controls how the signature layer is composited onto the document.""" + + model_config = ConfigDict(extra="forbid") + + blend_mode: Literal["multiply", "darken", "alpha"] = "multiply" + ink_opacity: NumericOrRange = 0.88 + blur_sigma: NumericOrRange = 0.35 + feather_px: NumericOrRange = 1.2 + scanner_noise: NumericOrRange = 0.02 + anti_alias: bool = True + + +class SignerConfig(BaseModel): + """Optional overrides for the assigned signer identity.""" + + model_config = ConfigDict(extra="forbid") + + name: "str | None" = None + role: "str | None" = None + + +class SignatureSelectionConfig(BaseModel): + """Controls how a signature image is drawn from the discovered dataset.""" + + model_config = ConfigDict(extra="forbid") + + source_filter: Literal["genuine", "forged", "any"] = "genuine" + signature_id: "str | None" = None + + +class DocumentConfig(BaseModel): + """Selects which template (and page) to render.""" + + model_config = ConfigDict(extra="forbid") + + template: "str | None" = None + page: "int | None" = None + # Cosmetic (non-scored) field filling -- SWIFT/bank code, NRIC, UEN, + # amounts, dates. Only meaningful for procedurally-drawn documents: + # scanned templates are static photos with no text-region annotations + # to inject synthetic text into. See src/cosmetics/cosmetic_fields.py. + enable_cosmetic_fields: bool = False + + +class QualityConfig(BaseModel): + """Document quality-flag inputs (feed into the ``quality`` metadata block).""" + + model_config = ConfigDict(extra="forbid") + + case_type: "str | None" = None + notes: "str | None" = None + scanner_quality: NumericOrRange = 0.85 + paper_quality: NumericOrRange = 0.85 + render_quality: NumericOrRange = 0.85 + + +class DifficultyWeights(BaseModel): + """Weights and tier thresholds for the difficulty score. + + Defaults match the specification exactly: + ``0.35*misplacement + 0.20*whitespace + 0.20*faintness + 0.15*smallness + 0.10*low_texture``. + """ + + model_config = ConfigDict(extra="forbid") + + misplacement: float = 0.35 + whitespace: float = 0.20 + faintness: float = 0.20 + smallness: float = 0.15 + low_texture: float = 0.10 + easy_threshold: float = 0.33 + hard_threshold: float = 0.66 + + # Reference constants each component is normalized against before + # weighting. Defaults match the spec deck's generic literal values + # (verified against the scanned bank-document templates). Procedural + # templates measure much smaller/lighter real signature ink than these + # generic references assume, so `configs/procedural_templates.yaml` + # overrides these three to values refit against that template family's + # own measured proportions -- see `unified_pipeline/README.md`. + smallness_reference_area_ratio: float = 0.04 + faintness_dark_ref: float = 90.0 + faintness_light_ref: float = 210.0 + low_texture_reference_stddev: float = 60.0 + + @model_validator(mode="after") + def _check_weights(self) -> "DifficultyWeights": + total = self.misplacement + self.whitespace + self.faintness + self.smallness + self.low_texture + if abs(total - 1.0) > 1e-6: + raise ValueError(f"Difficulty weights must sum to 1.0, got {total}") + return self + + +class PathsConfig(BaseModel): + """Filesystem locations for inputs/outputs.""" + + model_config = ConfigDict(extra="forbid") + + image_dir: str = "templates/scanned" + dataset_dir: str = "dataset" + annotations_dir: str = "annotations" + output_dir: str = "output" + cache_dir: str = "templates/procedural" + + # Additive signature-image sources beyond `dataset_dir` (which already + # covers CEDAR genuine/forged + the curated hindi_bengali sample). Each + # is optional -- `None`/missing-on-disk sources are skipped gracefully, + # same policy as the original BHSig260 wiring in projectA. See + # SignatureDataset.discover() in src/signatures/signature_dataset.py. + signet_dir: "str | None" = None + gpds_dir: "str | None" = None + bhsig260_hindi_dir: "str | None" = None + bhsig260_bengali_dir: "str | None" = None + + # Which TemplateProvider(s) supply documents: pre-made scanned images + # with hand-measured box annotations ("scanned", the original behavior + # of this repo), matplotlib-drawn documents ("procedural", ported from + # projectA/utils/templatemaker.py), or both merged into one id space + # ("both"). See src/templates/template_provider.py. + template_provider: Literal["scanned", "procedural", "both"] = "scanned" + + # Scanned template ids to leave out of the pool entirely (e.g. + # ["bank_doc_08"]). Ids are assigned before filtering, so excluding one + # never renumbers/shifts any other template's id -- see + # src/templates/template_loader.py::TemplateLoader. + excluded_template_ids: "list[str]" = Field(default_factory=list) + + +class GenerationConfig(BaseModel): + """Batch-generation controls.""" + + model_config = ConfigDict(extra="forbid") + + count: int = Field(default=10, ge=1) + workers: int = Field(default=1, ge=1) + save_visualization: bool = False + # Before/after degradation comparison PNG per sample (clean render vs. + # final look, labeled banners) -- ported from projectA's + # metrics_overlay.py::build_before_after_preview(). See + # src/visualization/before_after_preview.py. + save_before_after_preview: bool = False + overwrite: bool = False + run_validation: bool = True + start_index: int = Field(default=1, ge=0) + filename_prefix: str = "case" + filename_digits: int = Field(default=6, ge=1) + + # Difficulty-score targeting: request a specific numeric score (e.g. + # 0.55) instead of a coarse easy/medium/hard tier -- ported from + # projectA's cli.py --difficulty {easy,medium,hard} retry-loop + # (generate_case_at_difficulty()), generalized to a float target since + # this pipeline's difficulty score is already continuous. `None` = off, + # natural generation (default). See GeneratorContext.run_sample_targeting_difficulty(). + target_difficulty: "float | None" = Field(default=None, ge=0.0, le=1.0) + difficulty_tolerance: float = Field(default=0.05, ge=0.0, le=1.0) + difficulty_max_attempts: int = Field(default=5, ge=1) + + +class ValidationConfig(BaseModel): + """Controls strictness of the post-generation ValidationEngine.""" + + model_config = ConfigDict(extra="forbid") + + strict: bool = False + max_coordinate_epsilon: float = 0.5 + + +class PipelineConfig(BaseModel): + """Top-level configuration for a full generation run.""" + + model_config = ConfigDict(extra="forbid") + + seed: int = 1234 + paths: PathsConfig = Field(default_factory=PathsConfig) + generation: GenerationConfig = Field(default_factory=GenerationConfig) + document: DocumentConfig = Field(default_factory=DocumentConfig) + signer: SignerConfig = Field(default_factory=SignerConfig) + signature_selection: SignatureSelectionConfig = Field(default_factory=SignatureSelectionConfig) + placement: PlacementConfig = Field(default_factory=PlacementConfig) + rendering: RenderingConfig = Field(default_factory=RenderingConfig) + augmentation: AugmentationsConfig = Field(default_factory=AugmentationsConfig) + quality: QualityConfig = Field(default_factory=QualityConfig) + difficulty: DifficultyWeights = Field(default_factory=DifficultyWeights) + validation: ValidationConfig = Field(default_factory=ValidationConfig) diff --git a/unified_pipeline/src/cosmetics/__init__.py b/unified_pipeline/src/cosmetics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/unified_pipeline/src/cosmetics/cosmetic_fields.py b/unified_pipeline/src/cosmetics/cosmetic_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..4532e91928592f06244448a8cc21196f056b6038 --- /dev/null +++ b/unified_pipeline/src/cosmetics/cosmetic_fields.py @@ -0,0 +1,255 @@ +"""Cosmetic (non-scored) field filling for procedurally-drawn documents. + +Ported from ``projectA/utils/cosmetic_fields.py``. Fills in the parts of a +bank document that make it look like a real filled-in form -- entity name, +registration number, SWIFT/routing code, amounts, dates -- none of which +factor into :mod:`src.analysis.difficulty_analyzer` or any other scored +metric. Only meaningful for :class:`~src.templates.procedural_template_provider.ProceduralTemplateProvider` +documents: scanned templates are static photos with no text-region +annotations to inject synthetic text into (see ``paths.enable_cosmetic_fields`` +in ``src/config/schema.py``). + +Operates on the same ``(H, W, 3)`` uint8 RGB ``numpy`` arrays the rest of the +pipeline uses (:mod:`src.utils.image_io`), converting to/from a PIL image +internally since text rendering needs ``ImageDraw``. + +Every random draw here takes an explicit ``random.Random`` (the caller +passes ``seed_manager.python_rng("cosmetic_fields")`` -- see +``src/generator.py::_maybe_fill_cosmetic_fields``) rather than using the +global ``random`` module. An earlier version used the global module +directly, which silently broke this pipeline's core "same seed -> same +output" guarantee for every sample with cosmetic fields enabled (invisible +while the feature defaulted to off; surfaced as a real +``test_run_sample_is_fully_reproducible`` failure the moment it became +default-on). +""" + +from __future__ import annotations + +import random + +import numpy as np +from PIL import Image, ImageDraw, ImageFont + +from src.templates.procedural_template_provider import _box_px, _to_px, get_bank_info, get_field_boxes + +_CHEQUE_FIELD_TARGETS_IN = {entry["field"]: entry["box_in"] for entry in get_field_boxes("Cheque Voucher")} + + +def _get_font(size: int = 14, bold: bool = False) -> "ImageFont.FreeTypeFont": + candidates = [ + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/Library/Fonts/Arial Bold.ttf" if bold else "/Library/Fonts/Arial.ttf", + "/System/Library/Fonts/Supplemental/Arial Bold.ttf" if bold else "/System/Library/Fonts/Supplemental/Arial.ttf", + "C:\\Windows\\Fonts\\arialbd.ttf" if bold else "C:\\Windows\\Fonts\\arial.ttf", + "arialbd.ttf" if bold else "arial.ttf", + "DejaVuSans-Bold.ttf" if bold else "DejaVuSans.ttf", + ] + for path in candidates: + try: + return ImageFont.truetype(path, size) + except Exception: + continue + try: + return ImageFont.load_default(size=size) + except TypeError: + return ImageFont.load_default() + + +def _fill_name_field(draw, name): + x, y = _to_px(0.6, 8.5) + font = _get_font(24) + draw.text((x, y), name, fill=(25, 25, 25), font=font) + return {"full_legal_name": list(draw.textbbox((x, y), name, font=font))} + + +def _fill_cheque_payee(draw, name): + x1, y1, x2, y2 = _box_px(*_CHEQUE_FIELD_TARGETS_IN["payee_name"]) + x, y = x1 + 6, y1 + 8 + font = _get_font(24) + draw.text((x, y), name, fill=(20, 20, 20), font=font) + return {"payee_name": list(draw.textbbox((x, y), name, font=font))} + + +def generate_nric(rng: "random.Random") -> str: + """A plausible Singapore NRIC (S/T + 7 digits + real checksum letter).""" + prefix = rng.choice(["S", "T"]) + digits = [rng.randint(0, 9) for _ in range(7)] + weights = [2, 7, 6, 5, 4, 3, 2] + total = sum(d * w for d, w in zip(digits, weights)) + if prefix == "T": + total += 4 + remainder = total % 11 + st_table = ["J", "Z", "I", "H", "G", "F", "E", "D", "C", "B", "A"] + checksum_letter = st_table[remainder] + return f"{prefix}{''.join(str(d) for d in digits)}{checksum_letter}" + + +_COMPANY_NAME_PREFIXES = [ + "Marina Bay", "Orchard Road", "Raffles Place", "Sentosa Cove", + "Jurong Industrial", "Tanjong Pagar", "Bugis Junction", "Clarke Quay", + "Shenton Way", "Changi Business", "Woodlands", "Paya Lebar", +] +_COMPANY_NAME_SUFFIXES = [ + "Holdings", "Trading", "Ventures", "Capital", "Enterprises", + "Investments", "Logistics", "Industries", "Resources", "Partners", +] + + +def generate_company_name(rng: "random.Random") -> str: + """A plausible Singapore-style corporate entity name.""" + prefix = rng.choice(_COMPANY_NAME_PREFIXES) + suffix = rng.choice(_COMPANY_NAME_SUFFIXES) + return f"{prefix} {suffix} Pte Ltd" + + +def generate_uen(rng: "random.Random") -> str: + """A format-correct (not checksum-validated) Singapore UEN.""" + year = rng.randint(1990, 2026) + serial = rng.randint(10000, 99999) + check_letter = rng.choice("ABCDEFGHJKLMNPQRSTUVWXYZ") + return f"{year}{serial}{check_letter}" + + +def _fill_corporate_cosmetic_fields(draw, rng, reg_no=None, country="Singapore", tax_jurisdiction="SG", address="12 Marina Blvd, Singapore"): + font = _get_font(24) + reg_no = reg_no or generate_nric(rng) + field_boxes = {} + for field_name, (x_in, y_in), text in [ + ("registration_no", (4.35, 8.5), reg_no), + ("country_of_incorporation", (0.6, 7.85), country), + ("tax_jurisdiction", (4.35, 7.85), tax_jurisdiction), + ("registered_address", (0.6, 6.6), address), + ]: + x, y = _to_px(x_in, y_in) + draw.text((x, y), text, fill=(25, 25, 25), font=font) + field_boxes[field_name] = list(draw.textbbox((x, y), text, font=font)) + return field_boxes + + +def _fill_section2_row(draw, rng, row_index, swift, currency="SGD", amount=None, checked=True): + font = _get_font(24) + y_row = 5.4 - 0.45 * row_index + amount = amount or f"{rng.randint(5_000, 500_000):,}.00" + field_boxes = {} + if not checked: + return field_boxes + cb_x1, cb_y1, cb_x2, cb_y2 = _box_px(0.6, y_row, 0.72, y_row + 0.12) + draw.line([(cb_x1, cb_y1), (cb_x2, cb_y2)], fill=(20, 20, 20), width=2) + draw.line([(cb_x2, cb_y1), (cb_x1, cb_y2)], fill=(20, 20, 20), width=2) + field_boxes["checkbox"] = [cb_x1, cb_y1, cb_x2, cb_y2] + for field_name, (x_in, y_in), text in [ + ("swift", (3.5, y_row + 0.1), swift), ("currency", (5.5, y_row + 0.1), currency), ("amount", (6.7, y_row + 0.1), amount), + ]: + x, y = _to_px(x_in, y_in) + draw.text((x, y), text, fill=(25, 25, 25), font=font) + field_boxes[field_name] = list(draw.textbbox((x, y), text, font=font)) + return field_boxes + + +def _fill_cheque_cosmetic_fields(draw, rng, date_str=None, amount_words=None, amount_num=None, bearer=False): + font = _get_font(24) + field_boxes = {} + # A synthetic date derived from rng, NOT datetime.date.today() -- the + # real wall-clock date would make this field (and therefore the whole + # rendered image) reproducible only within a single calendar day, not + # actually reproducible from the seed the way every other field here is. + date_str = date_str or f"{rng.randint(1, 28):02d}{rng.randint(1, 12):02d}{rng.randint(2023, 2026)}" + + dx1, dy1, dx2, dy2 = _box_px(*_CHEQUE_FIELD_TARGETS_IN["date"]) + n_chars = 8 + cell_w = (dx2 - dx1) / n_chars + char_boxes = [] + for i, ch in enumerate(date_str[:n_chars]): + x = int(dx1 + i * cell_w + cell_w * 0.3) + y = dy1 + 3 + draw.text((x, y), ch, fill=(20, 20, 20), font=font) + char_boxes.append(draw.textbbox((x, y), ch, font=font)) + if char_boxes: + xs = [b[0] for b in char_boxes] + [b[2] for b in char_boxes] + ys = [b[1] for b in char_boxes] + [b[3] for b in char_boxes] + field_boxes["date"] = [min(xs), min(ys), max(xs), max(ys)] + + amount_num = amount_num if amount_num is not None else rng.randint(500, 50000) + if amount_words is None: + amount_words = f"{amount_num:,} DOLLARS ONLY" + ax1, ay1, _, _ = _box_px(*_CHEQUE_FIELD_TARGETS_IN["amount_words"]) + x, y = ax1 + 6, ay1 + 6 + draw.text((x, y), amount_words.upper(), fill=(20, 20, 20), font=font) + field_boxes["amount_words"] = list(draw.textbbox((x, y), amount_words.upper(), font=font)) + + amount_text = f"{amount_num:,.2f}" + nx1, ny1, nx2, ny2 = _box_px(*_CHEQUE_FIELD_TARGETS_IN["amount_numeric"]) + box_h = ny2 - ny1 + tmp_bbox = draw.textbbox((0, 0), amount_text, font=font) + text_h = tmp_bbox[3] - tmp_bbox[1] + x = nx1 + 110 + y = ny1 + (box_h - text_h) // 2 + draw.text((x, y), amount_text, fill=(20, 20, 20), font=font) + field_boxes["amount_numeric"] = list(draw.textbbox((x, y), amount_text, font=font)) + + if bearer: + x, y = _to_px(6.805, 8.005) + draw.line([(x, y), (x + 18, y + 18)], fill=(20, 20, 20), width=2) + draw.line([(x + 18, y), (x, y + 18)], fill=(20, 20, 20), width=2) + field_boxes["bearer_checkbox"] = [x, y, x + 18, y + 18] + return field_boxes + + +def fill_all_cosmetic_fields( + document_image: "np.ndarray", template_cfg: dict, primary_person_name: str, rng: "random.Random", +) -> "tuple[np.ndarray, dict]": + """Single dispatch point: fills in cosmetic fields for one document. + + Args: + document_image: ``(H, W, 3)`` uint8 RGB array (e.g. from + ``load_image_rgb``), the clean procedural template BEFORE + signature placement. + template_cfg: the ``dataset_directory``-style config for this + template, from ``ProceduralTemplateProvider.field_boxes_for(id)`` + (has ``bank``, ``type``, ``entity_type``). + primary_person_name: the signer's display name, shown as the + entity/payee name on "individual"-type templates. + rng: a seeded ``random.Random`` (e.g. + ``seed_manager.python_rng("cosmetic_fields")``) -- every random + draw in this module goes through it, never the global ``random`` + module, so the same seed always produces the same fields. + + Returns: + ``(document_image, field_boxes)`` -- a NEW array (the input is not + mutated) and a dict of ``field_name -> {"actual_box", "target_box"}``, + matching ``projectA``'s ``ground_truth.json`` ``metrics.field_boxes`` + shape (``target_box`` is ``None`` where no analytic target exists). + """ + pil_img = Image.fromarray(document_image.astype(np.uint8)).convert("RGB") + draw = ImageDraw.Draw(pil_img) + field_boxes: "dict[str, list]" = {} + + if template_cfg["type"].lower() == "cheque voucher": + field_boxes.update(_fill_cheque_payee(draw, primary_person_name)) + field_boxes.update(_fill_cheque_cosmetic_fields(draw, rng, bearer=rng.random() < 0.15)) + else: + entity_type = template_cfg.get("entity_type", "individual") + if entity_type == "corporate": + entity_name = generate_company_name(rng) + reg_no = generate_uen(rng) + else: + entity_name = primary_person_name + reg_no = generate_nric(rng) + field_boxes.update(_fill_name_field(draw, entity_name)) + field_boxes.update(_fill_corporate_cosmetic_fields(draw, rng, reg_no=reg_no)) + swift = get_bank_info(template_cfg["bank"])["swift"] + for row_index in range(3): + fb = _fill_section2_row(draw, rng, row_index, swift=swift, checked=(row_index == 0)) + field_boxes.update({f"{k}_row{row_index}": v for k, v in fb.items()}) + + target_boxes_in = {entry["field"]: entry["box_in"] for entry in get_field_boxes(template_cfg["type"])} + field_boxes = { + name: { + "actual_box": actual_box, + "target_box": list(_box_px(*target_boxes_in[name])) if name in target_boxes_in else None, + } + for name, actual_box in field_boxes.items() + } + return np.array(pil_img), field_boxes diff --git a/unified_pipeline/src/cosmetics/scanned_cosmetic_fields.py b/unified_pipeline/src/cosmetics/scanned_cosmetic_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..d55ca64703b20931011189a09dfe49784847c962 --- /dev/null +++ b/unified_pipeline/src/cosmetics/scanned_cosmetic_fields.py @@ -0,0 +1,96 @@ +"""Cosmetic (non-scored) name/company-name filling for SCANNED templates. + +Reads pre-measured field coordinates straight from the template's own +annotation (``template.annotation.pages[N].cosmetic_fields`, see +:class:`~src.templates.annotation_schema.CosmeticFieldAnnotation``) -- +exactly like :mod:`src.placement.placement_engine` already reads +``signature_boxes`` from the same file, no image analysis at generation +time. Those coordinates are measured ONCE by +``scripts/detect_cosmetic_field_coordinates.py`` (search a hand-picked +region for the real blank line/table-divider via the same classical-CV +line detector already used for signature-box auto-detection, then cache +the exact result) -- see that script's docstring for the full method. + +Two of the nine bundled templates (``bank_doc_01``, ``bank_doc_02``) are +stock "sample" images with fabricated content already baked into the photo +itself (bracketed ``[PLACEHOLDER ...]`` text, or an already-fully-filled +specimen letter) -- there is no genuine blank field to fill on those two +without inpainting over existing print, so their annotations simply have +no ``cosmetic_fields`` entries and this module is a no-op for them. +""" + +from __future__ import annotations + +import random +from typing import TYPE_CHECKING + +import numpy as np +from PIL import Image, ImageDraw + +from src.cosmetics.cosmetic_fields import _get_font, generate_company_name + +if TYPE_CHECKING: + from src.templates.annotation_schema import CosmeticFieldAnnotation + +# Fields whose text must be GENERATED (not the signer's own name) -- keyed +# the same as each CosmeticFieldAnnotation.field value. Anything not listed +# here just uses the signer's display name directly (correct for every +# "Print Name:"/"Mr. Ms. ___"/"Name of Account Holder:"-style field). +# "company_name" (bank_doc_09's "Company Name" table cell, under ENTITY +# DETAILS next to Registration Number) is the one field that is NOT a +# personal-name field -- pasting a person's name there (e.g. "Nancy +# Ivanov") under a column header literally reading "Company Name" was a +# real bug: right position, wrong kind of text for that field. +_GENERATED_FIELD_TEXT = { + "company_name": generate_company_name, +} + + +def fill_scanned_cosmetic_fields( + document_image: "np.ndarray", + cosmetic_fields: "list[CosmeticFieldAnnotation]", + primary_person_name: str, + rng: "random.Random", +) -> "tuple[np.ndarray, dict]": + """Fill in this scanned template's pre-measured cosmetic fields. + + Args: + document_image: ``(H, W, 3)`` uint8 RGB array (the loaded scanned + template page, before signature placement). + cosmetic_fields: ``template.annotation.pages[N].cosmetic_fields`` + -- empty for templates with no genuine blank field (this + function is then a no-op). + primary_person_name: the signer's display name -- used for every + field except the ones in ``_GENERATED_FIELD_TEXT``. + rng: a seeded ``random.Random`` (e.g. + ``seed_manager.python_rng("cosmetic_fields")``), passed through + to :func:`~src.cosmetics.cosmetic_fields.generate_company_name` + so this stays reproducible from the pipeline's seed. + + Returns: + ``(document_image, field_boxes)`` -- a NEW array (input untouched) + and a dict of ``field_name -> {"actual_box": [...], "target_box": None}`` + for whichever fields were drawn (empty dict if ``cosmetic_fields`` is empty). + """ + if not cosmetic_fields: + return document_image, {} + + pil_img = Image.fromarray(document_image.astype(np.uint8)).convert("RGB") + draw = ImageDraw.Draw(pil_img) + h = pil_img.height + font = _get_font(max(14, int(h * 0.014))) + field_boxes: "dict[str, dict]" = {} + + for spec in cosmetic_fields: + generator = _GENERATED_FIELD_TEXT.get(spec.field) + text = generator(rng) if generator else primary_person_name + pad = max(4, int(spec.line_w * 0.02)) + if spec.mode == "above_line": + x, y = spec.line_x + pad, spec.line_y - font.size - 2 + else: # "below_line" -- a table row-divider; write inside the cell below it + x, y = spec.line_x + pad, spec.line_y + max(3, int(font.size * 0.15)) + + draw.text((x, y), text, fill=(25, 25, 25), font=font) + field_boxes[spec.field] = {"actual_box": list(draw.textbbox((x, y), text, font=font)), "target_box": None} + + return np.array(pil_img), field_boxes diff --git a/unified_pipeline/src/generator.py b/unified_pipeline/src/generator.py new file mode 100644 index 0000000000000000000000000000000000000000..919ac001ca9c7785b2844484991f2342fd5e8c8c --- /dev/null +++ b/unified_pipeline/src/generator.py @@ -0,0 +1,1167 @@ +"""Top-level orchestration: DatasetGenerator drives the full per-sample pipeline. + +For every sample this wires together, in order: + +1. :class:`~src.templates.template_loader.TemplateLoader` -- pick a template + signature box. +2. :class:`~src.signatures.signature_dataset.SignatureDataset` -- pick a signature image. +3. :class:`~src.rendering.signature_preprocessor.SignaturePreprocessor` -- clean it into an RGBA stamp. +4. :class:`~src.placement.placement_engine.PlacementEngine` -- fit/rotate/position it. +5. :class:`~src.augmentation.augmentation_engine.AugmentationEngine` -- signature-layer effects. +6. :class:`~src.rendering.renderer.Renderer` -- blend onto the document. +7. :class:`~src.augmentation.augmentation_engine.AugmentationEngine` -- document-level effects. +8. :class:`~src.analysis.geometry_analyzer.GeometryAnalyzer` -- detect + measure. +9. :class:`~src.analysis.difficulty_analyzer.DifficultyAnalyzer` -- score. +10. :class:`~src.metadata.metadata_exporter.MetadataExporter` -- assemble + write JSON. +11. :class:`~src.analysis.validation_engine.ValidationEngine` -- verify. + +A single :class:`GeneratorContext` holds all stateless-between-calls engine +instances for one process; :class:`DatasetGenerator` either drives it +directly (``workers=1``) or fans it out across a +:class:`concurrent.futures.ProcessPoolExecutor` pool, where each worker +process builds exactly one context via ``_init_worker`` and reuses it for +every sample it is assigned. Because every random draw is derived purely +from ``(master_seed, sample_index, stage)``, results are identical +regardless of worker count or scheduling order. +""" + +from __future__ import annotations + +import csv +import shutil +from concurrent.futures import ProcessPoolExecutor, as_completed +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import numpy as np +from PIL import Image +from tqdm import tqdm + +from src.analysis.difficulty_analyzer import DifficultyAnalyzer +from src.analysis.geometry_analyzer import GeometryAnalyzer +from src.analysis.validation_engine import ValidationEngine, ValidationReport, summarize +from src.augmentation.augmentation_engine import AugmentationEngine +from src.config.schema import DifficultyWeights, PipelineConfig, PlacementConfig, Range +from src.cosmetics.cosmetic_fields import fill_all_cosmetic_fields +from src.cosmetics.scanned_cosmetic_fields import fill_scanned_cosmetic_fields +from src.metadata.metadata_exporter import MetadataExporter +from src.placement.placement_engine import PlacementEngine, PlacementResult +from src.rendering.printed_text import render_printed_name_stamp +from src.rendering.renderer import Renderer +from src.rendering.signature_preprocessor import SignaturePreprocessor +from src.signatures.signature_dataset import SignatureDataset, SignatureRecord +from src.templates.annotation_schema import SignatureBoxAnnotation, TemplatePageAnnotation +from src.templates.procedural_template_provider import ProceduralTemplateProvider +from src.templates.template_loader import Template, TemplateLoader +from src.templates.template_provider import CompositeTemplateProvider, TemplateProvider, template_kind +from src.utils.image_io import load_image_any, load_image_rgb, save_image_rgb +from src.utils.logging_utils import get_logger +from src.utils.names import generate_name, generate_role +from src.utils.random_utils import SeedManager, resolve_scalar_or_range +from src.verification.case_types import CASE_TABLE, CaseSpec +from src.verification.registry import SignerIdentityRegistry +from src.visualization.before_after_preview import save_before_after_preview +from src.visualization.visualizer import save_visualization + +logger = get_logger(__name__) + +INK_COLOR_POOL: tuple[tuple[int, int, int], ...] = ( + (15, 15, 20), # black + (18, 22, 58), # blue-black + (24, 34, 96), # ballpoint blue + (12, 12, 14), # deep black +) + +# DifficultyWeights' schema defaults for these fields are the deck's +# literal, generic reference constants (0.04 / 90 / 210 / 60) -- measured +# (see configs/procedural_templates.yaml's own history) to peg smallness/ +# faintness near their ceiling for almost any real signature on this +# pipeline's own templates, especially procedural ones (real signature area +# is ~0.2-1.5% of the page vs. the deck's assumed 4% "normal"). Previously +# this recalibration only applied if the whole run used +# `--config configs/procedural_templates.yaml`, which meant a +# `template_provider: both` batch (or the plain CLI default) silently used +# the deck-literal, too-aggressive constants for procedural samples anyway. +# Applied automatically per-sample below, keyed on template_kind(), instead. +# +# faintness_dark_ref and low_texture_reference_stddev refit 2026-08-11 +# against a fresh 60-sample natural draw of real procedural output +# (mean_dark_intensity: p10=164.0/median=180.6/p90=195.7; gray_stddev: +# p10=25.3/median=34.6/p90=46.5) -- the prior faintness_dark_ref (184) sat +# ABOVE the real median (180.6), so `clamp01((dark_intensity-dark_ref)/...)` +# floored to exactly 0.000 for every sample in a fresh measurement (20% of +# the total score, contributing nothing, discriminating nothing, for the +# entire procedural template family). Re-anchored to real p10 (~164, rounded +# to 165) so faintness has genuine spread across the real distribution +# instead of a constant zero: median sample now scores ~0.30, darkest +# decile ~0, lightest observed (~208) ~0.79. low_texture_reference_stddev +# was never split from the deck-literal default at all (only smallness/ +# faintness were, historically) despite the same underlying asymmetry +# existing here too -- anchored to real p90 (~46.5, rounded to 46) so the +# best-textured decile of real samples can reach low_texture=0, the same +# treatment already given to smallness_reference_area_ratio. +_PROCEDURAL_DIFFICULTY_RECALIBRATION = { + "smallness_reference_area_ratio": 0.003, + "faintness_dark_ref": 165.0, + "faintness_light_ref": 220.0, + "low_texture_reference_stddev": 46.0, +} + +# configs/default.yaml's own shipped baseline for placement.scale_factor -- +# NOT the bare schema class default (a flat 0.9), the actual range every +# real run resolves to unless a config/CLI override says otherwise. Used +# below to detect "nobody customized this" without relying on +# model_fields_set, which can't tell configs/default.yaml's own baseline +# apart from a genuine user override (both look identical once merged). +_DEFAULT_SCALE_FACTOR = Range(min=0.78, max=0.95) + +# scale_factor=1.15 (flat), not the ~0.78-0.95 shipped range -- procedural's +# boxes (200x80px) have real headroom scanned's cramped real "sign here" +# lines don't, measured 2026-08-11 across 20 real placements per candidate: +# scale_factor=1.1 already gave a meaningful legibility gain (opaque-ink +# fraction 0.238 -> 0.280) with ZERO overflow (median and p90 both 0%); 1.3 +# starts trading further gains for real overflow (6-7%). 1.15 sits just past +# the confirmed free win. Scanned was tested too (0.9 through 1.2) and +# showed NO change at all in that range -- `min_signature_height_px` (see +# PlacementConfig), not scale_factor, is the binding constraint there, so +# scanned keeps configs/default.yaml's own range unchanged. +_PROCEDURAL_PLACEMENT_RECALIBRATION = { + "scale_factor": 1.15, +} + +# perspective/page_tilt/crumple spatially warp the whole page, which used +# to force baseline-diff ink detection to be skipped entirely for any +# sample where one fired (a pixel in the final image no longer sat at the +# same (x, y) it occupied in the clean baseline, so a naive diff would +# compare misaligned pixels) -- this silently let large blocks of printed +# text get misdetected as "ink" whenever a search box's expanded region +# rotated/warped into them, corrupting difficulty scores for those samples. +# Fixed for the SCORED target box: AugmentationEngine.apply_to_document()'s +# optional `baseline_image` param now applies the exact same geometric +# transform to a baseline companion image too, so the `warped_baseline` used +# below stays pixel-aligned with `final_image` even after a transform -- +# baseline exclusion no longer needs to be skipped there. This constant is +# still used by compute_extra_signature_overlays() below, which re-detects +# OTHER (non-scored, preview-only) boxes after generation already finished +# and doesn't have a correspondingly-warped baseline on hand -- skipping +# baseline exclusion for those is an accepted, lower-stakes fallback since +# they never feed difficulty/verdict, only the before/after preview image. +_GEOMETRIC_DOCUMENT_EFFECTS = ("perspective", "page_tilt", "crumple") + + +@dataclass +class SampleOutcome: + """Lightweight, picklable result of generating one sample (used across process boundaries).""" + + sample_id: str + success: bool + error: "str | None" = None + difficulty_tier: "str | None" = None + difficulty_score: "float | None" = None + validation_passed: "bool | None" = None + validation_errors: int = 0 + validation_warnings: int = 0 + # Populated on success -- feeds dataset_summary.csv (see DatasetGenerator.generate()). + case_folder: "str | None" = None + template_id: "str | None" = None + template_kind: "str | None" = None + case_type: "str | None" = None + verdict: "str | None" = None + reject_code: "str | None" = None + signer_name: "str | None" = None + + +class GeneratorContext: + """Holds every stateless-between-calls engine instance needed to generate a sample.""" + + def __init__(self, config: PipelineConfig) -> None: + self.config = config + self.template_provider = self._build_template_provider(config) + self.signature_dataset = SignatureDataset( + config.paths.dataset_dir, + signet_dir=config.paths.signet_dir, + gpds_dir=config.paths.gpds_dir, + bhsig260_hindi_dir=config.paths.bhsig260_hindi_dir, + bhsig260_bengali_dir=config.paths.bhsig260_bengali_dir, + ) + self.preprocessor = SignaturePreprocessor() + self.placement_engine = PlacementEngine(config.placement) + self._placement_engine_procedural = self._build_procedural_placement_engine(config.placement) + self.augmentation_engine = AugmentationEngine(config.augmentation) + self.renderer = Renderer(config.rendering) + self.geometry_analyzer = GeometryAnalyzer() + self.difficulty_analyzer = DifficultyAnalyzer(config.difficulty) + self._difficulty_analyzer_procedural = self._build_procedural_difficulty_analyzer(config.difficulty) + self.metadata_exporter = MetadataExporter() + self.validation_engine = ValidationEngine(config.validation, config.difficulty) + + # Trigger discovery eagerly so the first sample doesn't pay (and doesn't + # race another worker on) the filesystem-scan + annotation-cache cost. + self.template_provider.discover() + self.signature_dataset.discover() + self.identity_registry = SignerIdentityRegistry(self.template_provider, self.signature_dataset, config.seed) + + # Set by _run_generic_sample/_run_case_sample on every call: the + # document right after signature placement, before document-level + # augmentation. run_sample()'s return signature is unchanged (still + # just metadata/image/report, matching every existing caller), so + # this is how the "before" image for save_before_after_preview + # escapes to _generate_and_export without a breaking API change. + # One GeneratorContext is used sequentially within a single worker + # process/sample at a time (see DatasetGenerator/_init_worker), so + # this is safe -- it always reflects the most recent run_sample() + # call by the time that call returns. + self.last_clean_image: "np.ndarray | None" = None + + # Set alongside last_clean_image, same call sites, same lifetime + # caveat -- lets a caller (e.g. streamlit_app.py's interactive + # degradation lever) re-run GeometryAnalyzer/DifficultyAnalyzer on a + # hypothetically-more-or-less-degraded version of last_clean_image + # and get a live difficulty score, the same way the real + # generation pipeline would have scored it. + self.last_baseline_image: "np.ndarray | None" = None + self.last_target_box: "tuple[float, float, float, float] | None" = None + self.last_canvas_size: "tuple[int, int] | None" = None + + @staticmethod + def _build_procedural_difficulty_analyzer(base_weights: DifficultyWeights) -> DifficultyAnalyzer: + """A second :class:`DifficultyAnalyzer`, sharing every field of + ``base_weights`` except the 3 smallness/faintness reference + constants -- used only for procedural-template samples (see + :meth:`_difficulty_analyzer_for`). If the caller already explicitly + set any of those 3 fields (a CLI ``--set difficulty....=`` override, + or a whole config profile like ``configs/procedural_templates.yaml`` + that sets them itself), that choice is respected as-is and applied + to every sample regardless of template kind -- explicit + configuration always wins over this automatic per-kind default. + """ + explicitly_set = base_weights.model_fields_set + overrides = { + key: value for key, value in _PROCEDURAL_DIFFICULTY_RECALIBRATION.items() if key not in explicitly_set + } + if not overrides: + return DifficultyAnalyzer(base_weights) + return DifficultyAnalyzer(base_weights.model_copy(update=overrides)) + + def _difficulty_analyzer_for(self, template_id: str) -> DifficultyAnalyzer: + """Picks the deck-literal or procedural-recalibrated analyzer for + this sample's template -- keyed per-sample (not per-run), so a + ``template_provider: both`` batch scores each sample against the + constants that actually fit its template family, instead of one + global setting silently misgrading half the batch.""" + if template_kind(template_id) == "procedural": + return self._difficulty_analyzer_procedural + return self.difficulty_analyzer + + @staticmethod + def _build_procedural_placement_engine(base_placement: PlacementConfig) -> PlacementEngine: + """A second :class:`PlacementEngine`, sharing every field of + ``base_placement`` except ``scale_factor`` -- used only for + procedural-template samples (see :meth:`_placement_engine_for`). + + Deliberately does NOT use ``model_fields_set`` the way + :meth:`_build_procedural_difficulty_analyzer` does: unlike the + difficulty reference constants (never set by + ``configs/default.yaml``, so ``model_fields_set`` cleanly means + "a real user override"), ``configs/default.yaml`` itself always + sets ``placement.scale_factor`` (to its own natural-variation range, + ``{min: 0.78, max: 0.95}``) -- so ``model_fields_set`` would always + contain ``scale_factor`` on every run, silently defeating this + recalibration 100% of the time regardless of whether a real user + override exists. Compares the RESOLVED value against that known + shipped baseline instead: matches it -> safe to recalibrate; + differs (a real ``--set``/custom-profile override) -> respected + as-is for every template, same intent as the difficulty version. + """ + if base_placement.scale_factor == _DEFAULT_SCALE_FACTOR: + return PlacementEngine(base_placement.model_copy(update=_PROCEDURAL_PLACEMENT_RECALIBRATION)) + return PlacementEngine(base_placement) + + def _placement_engine_for(self, template_id: str) -> PlacementEngine: + """Picks the base or procedural-recalibrated placement engine for + this sample's template -- same per-sample keying as + :meth:`_difficulty_analyzer_for`, for the same reason (a + ``template_provider: both`` batch shouldn't apply one family's + placement tuning to the other's templates).""" + if template_kind(template_id) == "procedural": + return self._placement_engine_procedural + return self.placement_engine + + @staticmethod + def _build_template_provider(config: PipelineConfig) -> "TemplateProvider": + """Builds the configured template source(s) -- see ``paths.template_provider`` + in ``src/config/schema.py``. ``TemplateLoader`` (scanned) and + ``ProceduralTemplateProvider`` are structurally interchangeable + (:class:`~src.templates.template_provider.TemplateProvider`); "both" + merges their id spaces via :class:`CompositeTemplateProvider`. + """ + kind = config.paths.template_provider + excluded = frozenset(config.paths.excluded_template_ids) + if kind == "scanned": + return TemplateLoader(config.paths.image_dir, config.paths.annotations_dir, excluded_ids=excluded) + if kind == "procedural": + return ProceduralTemplateProvider(config.paths.cache_dir) + scanned = TemplateLoader(config.paths.image_dir, config.paths.annotations_dir, excluded_ids=excluded) + procedural = ProceduralTemplateProvider(config.paths.cache_dir) + return CompositeTemplateProvider([scanned, procedural]) + + def run_sample( + self, sample_index: int, seed_suffix: "str | None" = None, + ) -> tuple[dict[str, Any], "np.ndarray", "ValidationReport | None"]: + """Generate one sample. + + Args: + sample_index: Used for both the output filename (``case_NNNNNN``) + and, when ``seed_suffix`` is ``None``, the seed itself. + seed_suffix: When given, the seed is derived from + ``f"{sample_index}_{seed_suffix}"`` instead of the bare + index -- a different, still-fully-deterministic draw for the + same nominal sample, used by + :meth:`run_sample_targeting_difficulty`'s retry loop so a + retry doesn't just reproduce the exact same result. + """ + cfg = self.config + seed_key = sample_index if seed_suffix is None else f"{sample_index}_{seed_suffix}" + sm = SeedManager(cfg.seed, seed_key) + prefix = cfg.generation.filename_prefix + digits = cfg.generation.filename_digits + sample_id = f"{prefix}_{sample_index:0{digits}d}" + + case_spec = CASE_TABLE.get(cfg.quality.case_type) + if case_spec is not None: + return self._run_case_sample(sample_id, sm, case_spec) + return self._run_generic_sample(sample_id, sm) + + def run_sample_targeting_difficulty( + self, sample_index: int, target_score: float, tolerance: float = 0.05, max_attempts: int = 5, + ) -> tuple[dict[str, Any], "np.ndarray", "ValidationReport | None"]: + """Generate a sample whose ``difficulty.score`` lands within + ``tolerance`` of ``target_score``, retrying with fresh (but still + deterministic) randomness up to ``max_attempts`` times. + + Ported from projectA's ``cli.py --difficulty {easy,medium,hard}`` + retry-loop (``case_generation.py::generate_case_at_difficulty()``), + generalized from a coarse tier request to this pipeline's own + continuous ``[0, 1]`` score -- request ``0.55``, not ``"medium"``. + Every attempt is still fully reproducible from ``(seed, + sample_index, attempt)``; the closest-scoring attempt is kept even + if none lands within tolerance (metadata's ``difficulty_targeting`` + block records whether it actually matched). + """ + # Tracks last_clean_image alongside each attempt's result -- it's + # otherwise only ever set to the MOST RECENT run_sample() call + # (see the attribute's docstring in __init__), which would silently + # mismatch the returned metadata/image whenever the winning attempt + # (best-so-far, or the final match) isn't the last one tried. + best: "tuple[dict, np.ndarray, ValidationReport | None, float, np.ndarray | None, np.ndarray | None, tuple | None, tuple | None] | None" = None + for attempt in range(max_attempts): + suffix = None if attempt == 0 else f"difficulty_retry{attempt}" + metadata, image, report = self.run_sample(sample_index, seed_suffix=suffix) + score = metadata["difficulty"]["score"] + delta = abs(score - target_score) + if best is None or delta < best[3]: + best = ( + metadata, image, report, delta, self.last_clean_image, + self.last_baseline_image, self.last_target_box, self.last_canvas_size, + ) + if delta <= tolerance: + metadata["difficulty_targeting"] = { + "requested_score": target_score, "tolerance": tolerance, + "attempts_used": attempt + 1, "matched": True, + } + return metadata, image, report + + metadata, image, report, delta, clean_image, baseline_image, target_box, canvas_size = best + self.last_clean_image = clean_image + self.last_baseline_image = baseline_image + self.last_target_box = target_box + self.last_canvas_size = canvas_size + metadata["difficulty_targeting"] = { + "requested_score": target_score, "tolerance": tolerance, + "attempts_used": max_attempts, "matched": False, + } + logger.warning( + "Sample %d: could not reach difficulty %.3f (+/-%.3f) in %d attempts; closest was %.3f (delta=%.3f).", + sample_index, target_score, tolerance, max_attempts, metadata["difficulty"]["score"], delta, + ) + return metadata, image, report + + def _run_generic_sample(self, sample_id: str, sm: SeedManager) -> tuple[dict[str, Any], "np.ndarray", "ValidationReport | None"]: + cfg = self.config + template = self._select_template(sm) + page_annotation = self._select_page(template) + box = self._select_box(page_annotation, sm) + + document_image = load_image_rgb(template.image_path) + baseline_image = document_image # pristine, pre-cosmetic-fields/pre-signature -- see analyze()'s baseline_image_rgb + canvas_h, canvas_w = document_image.shape[:2] + target_box = box.as_tuple() + + signer_name = cfg.signer.name or generate_name(sm.python_rng("signer_name")) + signer_role = cfg.signer.role or box.role or generate_role(sm.python_rng("signer_role")) + + document_image, cosmetic_field_boxes = self._maybe_fill_cosmetic_fields(document_image, template, page_annotation, box.id, signer_name, sm) + + signature_record = self._select_signature(sm) + raw_signature = load_image_any(signature_record.path) + ink_color = INK_COLOR_POOL[int(sm.numpy_rng("ink_color").integers(0, len(INK_COLOR_POOL)))] + signature_rgba = self.preprocessor.process(raw_signature, ink_color=ink_color) + + placement = self._placement_engine_for(template.template_id).place( + signature_rgba, target_box=target_box, canvas_size=(canvas_w, canvas_h), seed_manager=sm, + ) + signature_layer, signature_aug_records = self.augmentation_engine.apply_to_signature(placement.layer, sm) + render_result = self.renderer.blend(document_image, signature_layer, sm) + + # If this template has more than one signature box, fill every + # OTHER box with its own genuine signature too -- a "generic" + # (non-case-type) sample is meant to represent a normal, properly + # executed document, so leaving a second box conspicuously blank + # looked like a broken render rather than a deliberate test case. + # Only quality.case_type-driven samples (via _run_case_sample, + # e.g. 5a/5b) are meant to leave a box blank/wrong, each with its + # own labeled verdict/reject_code explaining why. These extra + # signatures aren't scored (geometry/difficulty stay keyed to the + # single primary `box` selected above, matching the rest of this + # method's schema), just rendered -- recorded under + # metadata["additional_signatures"] for traceability. + other_boxes = [b for b in page_annotation.signature_boxes if b.id != box.id] + additional_signatures: "list[dict] | None" = None + document_with_all_signatures = render_result.image + if other_boxes: + additional_signatures = [] + for other_box in other_boxes: + sm_other = SeedManager(cfg.seed, f"{sm.sample_index}_box_{other_box.id}") + other_record = self._select_signature(sm_other) + other_raw = load_image_any(other_record.path) + other_ink_color = INK_COLOR_POOL[int(sm_other.numpy_rng("ink_color").integers(0, len(INK_COLOR_POOL)))] + other_rgba = self.preprocessor.process(other_raw, ink_color=other_ink_color) + other_placement = self._placement_engine_for(template.template_id).place( + other_rgba, target_box=other_box.as_tuple(), canvas_size=(canvas_w, canvas_h), seed_manager=sm_other, + ) + other_layer, _ = self.augmentation_engine.apply_to_signature(other_placement.layer, sm_other) + other_render = self.renderer.blend(document_with_all_signatures, other_layer, sm_other) + document_with_all_signatures = other_render.image + other_name = self.identity_registry.display_name(other_record.signer_group) + document_with_all_signatures, other_field_boxes = self._fill_box_cosmetic_field( + document_with_all_signatures, template, page_annotation, other_box.id, other_name, sm_other, + ) + if other_field_boxes: + cosmetic_field_boxes = {**(cosmetic_field_boxes or {}), **other_field_boxes} + additional_signatures.append({ + "box_id": other_box.id, "role": other_box.role, + "signature_id": other_record.signature_id, "signer_group": other_record.signer_group, + "source_type": other_record.source_type, "script": other_record.script, + "ref_path": other_record.path.as_posix(), + }) + + self.last_clean_image = document_with_all_signatures.copy() + self.last_baseline_image = baseline_image + self.last_target_box = target_box + self.last_canvas_size = (canvas_w, canvas_h) + final_image, document_aug_records, warped_baseline = self.augmentation_engine.apply_to_document( + document_with_all_signatures, sm, baseline_image=baseline_image, + ) + + geometry = self.geometry_analyzer.analyze( + final_image, target_box=target_box, canvas_size=(canvas_w, canvas_h), + placement_hint_bbox=placement.intended_bbox, + baseline_image_rgb=warped_baseline, + ) + difficulty = self._difficulty_analyzer_for(template.template_id).analyze(geometry) + + quality_rng = sm.numpy_rng("quality") + quality_values = { + "scanner_quality": round(resolve_scalar_or_range(cfg.quality.scanner_quality, quality_rng), 4), + "paper_quality": round(resolve_scalar_or_range(cfg.quality.paper_quality, quality_rng), 4), + "render_quality": round(resolve_scalar_or_range(cfg.quality.render_quality, quality_rng), 4), + } + + metadata = self.metadata_exporter.build( + document_id=sample_id, + template_id=template.template_id, + document_type=template.annotation.document_type, + page=page_annotation.page, + page_size=(canvas_w, canvas_h), + dpi=page_annotation.dpi, + signer_name=signer_name, + signer_role=signer_role, + signature_source=signature_record, + placement=placement, + render=render_result, + signature_aug=signature_aug_records, + document_aug=document_aug_records, + geometry=geometry, + difficulty=difficulty, + quality_cfg=cfg.quality, + quality_values=quality_values, + seed=cfg.seed, + sample_index=sm.sample_index, + verification=None, + ) + if cosmetic_field_boxes: + metadata["cosmetics"] = {"field_boxes": cosmetic_field_boxes} + if additional_signatures: + metadata["additional_signatures"] = additional_signatures + + validation_report = None + if cfg.generation.run_validation: + validation_report = self.validation_engine.validate(metadata) + + return metadata, final_image, validation_report + + # ------------------------------------------------------------------ # + # Verification test-case generation (case_type-driven) + # ------------------------------------------------------------------ # + + def _run_case_sample( + self, sample_id: str, sm: SeedManager, case_spec: CaseSpec, + ) -> tuple[dict[str, Any], "np.ndarray", "ValidationReport | None"]: + cfg = self.config + template = self._select_template_for_case(sm, case_spec) + page_annotation = self._select_page(template) + boxes = page_annotation.signature_boxes + if not boxes: + raise ValueError(f"Page {page_annotation.page} has no signature boxes defined.") + target_box, companion_box = self._select_case_boxes(boxes, sm) + + document_image = load_image_rgb(template.image_path) + baseline_image = document_image # pristine, pre-cosmetic-fields/pre-signature -- see analyze()'s baseline_image_rgb + canvas_h, canvas_w = document_image.shape[:2] + target_box_tuple = target_box.as_tuple() + canvas_size = (canvas_w, canvas_h) + + expected_group = self.identity_registry.authorized_signer_group(template.template_id, target_box.id) + signer_name = cfg.signer.name or self.identity_registry.display_name(expected_group) + signer_role = cfg.signer.role or target_box.role + + document_image, cosmetic_field_boxes = self._maybe_fill_cosmetic_fields(document_image, template, page_annotation, target_box.id, signer_name, sm) + + ink_color = INK_COLOR_POOL[int(sm.numpy_rng("ink_color").integers(0, len(INK_COLOR_POOL)))] + + document_after_companion = document_image + companion_info: "dict[str, Any] | None" = None + additional_signatures: "list[dict] | None" = None + if case_spec.fill_companion: + if companion_box is None: + logger.warning( + "Case %s wants a companion signer but template %s has only one signature box; skipping.", + case_spec.code, template.template_id, + ) + else: + companion_group = self.identity_registry.authorized_signer_group(template.template_id, companion_box.id) + sm_companion = SeedManager(cfg.seed, f"{sm.sample_index}_companion") + companion_record = self.signature_dataset.sample_from_group( + sm_companion.numpy_rng("case:companion_signature"), companion_group, source_filter="genuine", + ) + companion_raw = load_image_any(companion_record.path) + companion_rgba = self.preprocessor.process(companion_raw, ink_color=ink_color) + companion_placement = self._placement_engine_for(template.template_id).place( + companion_rgba, target_box=companion_box.as_tuple(), canvas_size=canvas_size, seed_manager=sm_companion, + ) + companion_layer, _ = self.augmentation_engine.apply_to_signature(companion_placement.layer, sm_companion) + companion_render = self.renderer.blend(document_after_companion, companion_layer, sm_companion) + document_after_companion = companion_render.image + companion_name = self.identity_registry.display_name(companion_group) + document_after_companion, companion_field_boxes = self._fill_box_cosmetic_field( + document_after_companion, template, page_annotation, companion_box.id, companion_name, sm_companion, + ) + if companion_field_boxes: + cosmetic_field_boxes = {**(cosmetic_field_boxes or {}), **companion_field_boxes} + companion_info = { + "box_id": companion_box.id, "filled": True, + "signer_group": companion_group, "script": self.identity_registry.script_for(companion_group), + "matches_expected": True, "ref_path": str(companion_record.path), + } + elif companion_box is not None: + # This case type doesn't officially test a second signer (e.g. + # 1a/1b/1d/2a/2b/3a/3b -- fill_companion=False), but the + # template it landed on still has a second signature box. Fill + # it correctly anyway, same reasoning as the generic-sample + # path (_run_generic_sample): a properly executed document + # shouldn't have a conspicuously blank second box with no + # verdict/reject_code explaining why -- only fill_companion=True + # types (5a/5b) are meant to leave a required box blank/wrong, + # each with its own labeled verdict. Tracked separately from + # `companion_info`/`verification.companion` (which describes + # this case type's OWN tested companion behavior) under the + # same "additional_signatures" key the generic path uses. + extra_group = self.identity_registry.authorized_signer_group(template.template_id, companion_box.id) + sm_extra = SeedManager(cfg.seed, f"{sm.sample_index}_extra_{companion_box.id}") + extra_record = self.signature_dataset.sample_from_group( + sm_extra.numpy_rng("case:extra_signature"), extra_group, source_filter="genuine", + ) + extra_raw = load_image_any(extra_record.path) + extra_rgba = self.preprocessor.process(extra_raw, ink_color=ink_color) + extra_placement = self._placement_engine_for(template.template_id).place( + extra_rgba, target_box=companion_box.as_tuple(), canvas_size=canvas_size, seed_manager=sm_extra, + ) + extra_layer, _ = self.augmentation_engine.apply_to_signature(extra_placement.layer, sm_extra) + extra_render = self.renderer.blend(document_after_companion, extra_layer, sm_extra) + document_after_companion = extra_render.image + extra_name = self.identity_registry.display_name(extra_group) + document_after_companion, extra_field_boxes = self._fill_box_cosmetic_field( + document_after_companion, template, page_annotation, companion_box.id, extra_name, sm_extra, + ) + if extra_field_boxes: + cosmetic_field_boxes = {**(cosmetic_field_boxes or {}), **extra_field_boxes} + additional_signatures = [{ + "box_id": companion_box.id, "role": companion_box.role, + "signature_id": extra_record.signature_id, "signer_group": extra_group, + "source_type": extra_record.source_type, "script": self.identity_registry.script_for(extra_group), + "ref_path": extra_record.path.as_posix(), + }] + + actual_record, actual_group, actual_source_type, target_rgba = self._resolve_case_signature( + case_spec, template, target_box, companion_box, expected_group, signer_name, ink_color, sm, + ) + + if target_rgba is None: + placement = PlacementResult( + layer=np.zeros((canvas_h, canvas_w, 4), dtype=np.uint8), + target_box=target_box_tuple, intended_bbox=target_box_tuple, + x_offset=0.0, y_offset=0.0, rotation_deg=0.0, scale_factor=0.0, + anchor_point=cfg.placement.anchor_point, misplaced=False, + misplacement_dx=0.0, misplacement_dy=0.0, + ) + else: + placement = self._placement_engine_for(template.template_id).place( + target_rgba, target_box=target_box_tuple, canvas_size=canvas_size, seed_manager=sm, + ) + signature_layer, signature_aug_records = self.augmentation_engine.apply_to_signature(placement.layer, sm) + render_result = self.renderer.blend(document_after_companion, signature_layer, sm) + self.last_clean_image = render_result.image.copy() + self.last_baseline_image = baseline_image + self.last_target_box = target_box_tuple + self.last_canvas_size = canvas_size + final_image, document_aug_records, warped_baseline = self.augmentation_engine.apply_to_document( + render_result.image, sm, baseline_image=baseline_image, + ) + + geometry = self.geometry_analyzer.analyze( + final_image, target_box=target_box_tuple, canvas_size=canvas_size, + placement_hint_bbox=placement.intended_bbox, + baseline_image_rgb=warped_baseline, + expect_blank=target_rgba is None, + ) + difficulty = self._difficulty_analyzer_for(template.template_id).analyze(geometry) + + quality_rng = sm.numpy_rng("quality") + quality_values = { + "scanner_quality": round(resolve_scalar_or_range(cfg.quality.scanner_quality, quality_rng), 4), + "paper_quality": round(resolve_scalar_or_range(cfg.quality.paper_quality, quality_rng), 4), + "render_quality": round(resolve_scalar_or_range(cfg.quality.render_quality, quality_rng), 4), + } + + verification_record = { + "case_type": case_spec.code, + "verdict": case_spec.verdict, + "reject_code": case_spec.reject_code, + "target_box_id": target_box.id, + "expected_signer_group": expected_group, + "expected_signer_script": self.identity_registry.script_for(expected_group), + "actual_signer_group": actual_group, + "actual_source_type": actual_source_type, + "actual_signer_script": self.identity_registry.script_for(actual_group) if actual_group else None, + "companion": companion_info, + } + + metadata = self.metadata_exporter.build( + document_id=sample_id, + template_id=template.template_id, + document_type=template.annotation.document_type, + page=page_annotation.page, + page_size=(canvas_w, canvas_h), + dpi=page_annotation.dpi, + signer_name=signer_name, + signer_role=signer_role, + signature_source=actual_record, + placement=placement, + render=render_result, + signature_aug=signature_aug_records, + document_aug=document_aug_records, + geometry=geometry, + difficulty=difficulty, + quality_cfg=cfg.quality, + quality_values=quality_values, + seed=cfg.seed, + sample_index=sm.sample_index, + verification=verification_record, + ) + if cosmetic_field_boxes: + metadata["cosmetics"] = {"field_boxes": cosmetic_field_boxes} + if additional_signatures: + metadata["additional_signatures"] = additional_signatures + + validation_report = None + if cfg.generation.run_validation: + validation_report = self.validation_engine.validate(metadata) + + return metadata, final_image, validation_report + + def _maybe_fill_cosmetic_fields( + self, document_image: "np.ndarray", template: Template, page_annotation: TemplatePageAnnotation, + primary_box_id: str, signer_name: str, sm: SeedManager, + ) -> "tuple[np.ndarray, dict | None]": + """Fills non-scored cosmetic fields if enabled -- SWIFT/NRIC/UEN/amounts + on procedural templates (full field set, exact coordinates known from + the drawing spec -- see :mod:`src.cosmetics.cosmetic_fields`), or a + pre-measured name/company-name on scanned templates (coordinates + cached in the template's own annotation file -- see + :mod:`src.cosmetics.scanned_cosmetic_fields` and + ``scripts/detect_cosmetic_field_coordinates.py`` for how they were + measured -- and the 2 of 9 scanned templates that are stock photos + with no genuine blank field to fill, which just have no + ``cosmetic_fields`` entries). No-op (image unchanged, ``None`` + boxes) when disabled, or when a scanned template has none configured. + + Only fills fields with no ``box_id`` (entity-level, e.g. + ``company_name``) or tied to ``primary_box_id`` -- on a + multi-signature-box template, a field belonging to a DIFFERENT box + (e.g. a companion signatory's own "Print Name:" line) is filled + separately once that signer's identity is known, by + :meth:`_fill_box_cosmetic_field`. This method runs early, before + any companion/additional signer is resolved, so it can only ever + speak for the box it's actually given. + """ + if not self.config.document.enable_cosmetic_fields: + return document_image, None + + if template_kind(template.template_id) == "scanned": + fields = [f for f in page_annotation.cosmetic_fields if f.box_id in (None, primary_box_id)] + image, boxes = fill_scanned_cosmetic_fields( + document_image, fields, signer_name, sm.python_rng("cosmetic_fields"), + ) + return image, (boxes or None) + + field_boxes_fn = getattr(self.template_provider, "field_boxes_for", None) + if field_boxes_fn is None: + return document_image, None + template_cfg = field_boxes_fn(template.template_id) + if template_cfg is None: + return document_image, None + return fill_all_cosmetic_fields(document_image, template_cfg, signer_name, sm.python_rng("cosmetic_fields")) + + def _fill_box_cosmetic_field( + self, document_image: "np.ndarray", template: Template, page_annotation: TemplatePageAnnotation, + box_id: str, name: str, sm: SeedManager, + ) -> "tuple[np.ndarray, dict | None]": + """Second, later pass for a companion/additional signature box's own + cosmetic field (e.g. bank_doc_04's LENDER "Print Name:" line, + distinct from the BORROWER field :meth:`_maybe_fill_cosmetic_fields` + already handled) -- called once that box's actual signer identity + is known, which is always *after* the first pass already ran (see + ``_run_case_sample``/``_run_generic_sample``). No-op if cosmetic + fields are disabled, the template is procedural (no box-tied + fields exist there), or this particular box has no such field + (true for most single-name-field scanned templates). + """ + if not self.config.document.enable_cosmetic_fields or template_kind(template.template_id) != "scanned": + return document_image, None + fields = [f for f in page_annotation.cosmetic_fields if f.box_id == box_id] + if not fields: + return document_image, None + image, boxes = fill_scanned_cosmetic_fields(document_image, fields, name, sm.python_rng("cosmetic_fields")) + return image, (boxes or None) + + def _select_template_for_case(self, sm: SeedManager, case_spec: CaseSpec) -> Template: + cfg = self.config + if cfg.document.template: + return self.template_provider.get(cfg.document.template) + + templates = self.template_provider.discover() + if case_spec.fill_companion: + candidates = sorted( + tid for tid, t in templates.items() if len(t.annotation.pages[0].signature_boxes) >= 2 + ) + if not candidates: + logger.warning( + "Case %s needs a multi-box template but none were found; using the full template pool.", + case_spec.code, + ) + candidates = sorted(templates.keys()) + else: + candidates = sorted(templates.keys()) + + idx = int(sm.numpy_rng("template_selection").integers(0, len(candidates))) + return self.template_provider.get(candidates[idx]) + + def _select_case_boxes( + self, boxes: "list[SignatureBoxAnnotation]", sm: SeedManager, + ) -> "tuple[SignatureBoxAnnotation, SignatureBoxAnnotation | None]": + idx = int(sm.numpy_rng("box_selection").integers(0, len(boxes))) + target = boxes[idx] + others = [b for i, b in enumerate(boxes) if i != idx] + companion = others[0] if others else None + return target, companion + + def _resolve_case_signature( + self, case_spec: CaseSpec, template: Template, target_box: "SignatureBoxAnnotation", + companion_box: "SignatureBoxAnnotation | None", expected_group: str, expected_name: str, + ink_color: tuple[int, int, int], sm: SeedManager, + ) -> "tuple[SignatureRecord | None, str | None, str, np.ndarray | None]": + """Resolve what actually goes in the target box for one case type. + + Returns ``(signature_record_or_none, actual_signer_group_or_none, + actual_source_type, rgba_layer_or_none)``. + """ + mode = case_spec.signature_mode + + if mode == "blank": + return None, None, "none", None + + if mode == "printed_text": + layer_rgba = render_printed_name_stamp(expected_name, ink_color=ink_color) + return None, None, "printed", layer_rgba + + if mode == "genuine_correct": + group, source_filter, stage = expected_group, "genuine", "case:target_signature" + elif mode == "genuine_variation": + group, source_filter, stage = expected_group, "genuine", "case:target_signature_variant" + elif mode == "forged_skilled": + group, source_filter, stage = expected_group, "forged", "case:forged_signature" + elif mode == "wrong_signer_genuine": + wrong_group = self.identity_registry.random_wrong_signer_group( + sm.python_rng("case:wrong_signer"), expected_group, + ) + group, source_filter, stage = wrong_group, "genuine", "case:wrong_signature" + elif mode == "role_swap": + if companion_box is None: + raise ValueError(f"Case {case_spec.code} requires a companion box but none is available.") + group = self.identity_registry.authorized_signer_group(template.template_id, companion_box.id) + source_filter, stage = "genuine", "case:role_swap_signature" + elif mode == "foreign_template_signer": + group, _foreign_template_id, _foreign_box_id = self.identity_registry.foreign_authorized(template.template_id) + source_filter, stage = "genuine", "case:foreign_signature" + else: + raise ValueError(f"Unknown signature_mode: {mode!r}") + + record = self.signature_dataset.sample_from_group(sm.numpy_rng(stage), group, source_filter) + raw = load_image_any(record.path) + layer_rgba = self.preprocessor.process(raw, ink_color=ink_color) + return record, group, record.source_type, layer_rgba + + def _select_template(self, sm: SeedManager): + cfg = self.config + if cfg.document.template: + return self.template_provider.get(cfg.document.template) + return self.template_provider.random_template(sm.numpy_rng("template_selection")) + + def _select_page(self, template): + cfg = self.config + if cfg.document.page is not None: + return template.annotation.page_by_number(cfg.document.page) + return template.annotation.pages[0] + + def _select_box(self, page_annotation, sm: SeedManager): + boxes = page_annotation.signature_boxes + if not boxes: + raise ValueError(f"Page {page_annotation.page} has no signature boxes defined.") + idx = int(sm.numpy_rng("box_selection").integers(0, len(boxes))) + return boxes[idx] + + def _select_signature(self, sm: SeedManager): + cfg = self.config + if cfg.signature_selection.signature_id: + return self.signature_dataset.get(cfg.signature_selection.signature_id) + return self.signature_dataset.sample( + sm.numpy_rng("signature_selection"), source_filter=cfg.signature_selection.source_filter, + ) + + +_WORKER_CONTEXT: "GeneratorContext | None" = None +_WORKER_CONFIG: "PipelineConfig | None" = None + + +def _init_worker(config: PipelineConfig) -> None: + global _WORKER_CONTEXT, _WORKER_CONFIG + _WORKER_CONFIG = config + _WORKER_CONTEXT = GeneratorContext(config) + + +def compute_extra_signature_overlays( + ctx: "GeneratorContext", metadata: dict, final_image: "np.ndarray", +) -> "list[dict]": + """Re-runs :class:`~src.analysis.geometry_analyzer.GeometryAnalyzer` on + every OTHER signed box in this sample (companion + additional_signatures) + -- purely for the before/after metrics-preview overlay, never for + scoring (geometry/difficulty stay keyed to the one target box the rest + of the pipeline scores). Without this, a preview of a multi-box + template shows one fully-explained scored signature and one + unexplained, unlabeled scribble elsewhere on the page. See + :func:`src.visualization.before_after_preview.save_before_after_preview`'s + ``extra_boxes`` parameter. + """ + template = ctx.template_provider.get(metadata["template"]) + boxes_by_id = {b.id: b for b in template.annotation.pages[0].signature_boxes} + canvas_size = (metadata["page_size"][0], metadata["page_size"][1]) + + effects = (metadata.get("augmentation") or {}).get("effects") or {} + geometric_applied = any((effects.get(name) or {}).get("applied") for name in _GEOMETRIC_DOCUMENT_EFFECTS) + baseline = None if geometric_applied else load_image_rgb(template.image_path) + + entries: "list[tuple[str, str]]" = [] + companion = (metadata.get("verification") or {}).get("companion") + if companion and companion.get("filled") and companion.get("box_id") in boxes_by_id: + entries.append((companion["box_id"], f"{boxes_by_id[companion['box_id']].role} (companion)")) + for extra in metadata.get("additional_signatures") or []: + if extra["box_id"] in boxes_by_id and extra["box_id"] not in {e[0] for e in entries}: + entries.append((extra["box_id"], f"{extra.get('role') or extra['box_id']} (other box)")) + + overlays = [] + for box_id, label in entries: + box = boxes_by_id[box_id] + result = ctx.geometry_analyzer.analyze( + final_image, target_box=box.as_tuple(), canvas_size=canvas_size, baseline_image_rgb=baseline, + ) + overlays.append({ + "label": label, + "geometry": { + "target_box": list(result.target_box), "actual_box": list(result.actual_box), + "signature_polygon_px": result.signature_polygon_px, + "display_contours": result.display_contours, + }, + "metrics": { + "box_coverage_pct": result.box_coverage_pct, "containment_pct": result.containment_pct, + "ink_fill_ratio": result.ink_fill_ratio, "polygon_overlap_proxy": result.polygon_overlap_proxy, + "mean_dark_intensity": result.mean_dark_intensity, "margin_utilization": result.margin_utilization, + } if result.found else None, + }) + return overlays + + +def _case_folder_name(sample_id: str, case_type: "str | None") -> str: + """Every sample gets its own folder (ported from projectA's per-case-folder + convention -- see the module docstring's "Output layout" note) rather than + scattering flat sample_NNNNNN.png/.json pairs across output_dir. The + template kind (scanned/procedural) isn't knowable until after the sample + is actually generated (template selection is random per sample unless + `document.template` pins it), so it can't be part of a name computed + up-front for the overwrite-skip check below -- it's still recorded + prominently in the ground-truth JSON's `template_kind` field and in + dataset_summary.csv instead. + """ + return f"{sample_id}_{case_type}" if case_type else sample_id + + +def _export_reference_signature(src_path: str, dst_path: Path) -> None: + """Re-encode a reference signature image as a REAL PNG, regardless of + its source format. BHSig260 ships ``.tif``; CEDAR/SigNet are already + ``.png``. A previous version of this just ``shutil.copy2``'d the raw + bytes under a ``*_reference_signature.png`` name -- for BHSig260-sourced + signatures that left actual TIFF bytes inside a file misleadingly named + ``.png``, which any standards-compliant image viewer correctly refuses + to open ("error occurred while loading image"). Decoding with Pillow + and re-saving guarantees the extension always matches the real content. + """ + with Image.open(src_path) as im: + im.convert("RGB").save(dst_path) + + +def _generate_and_export(sample_index: int) -> SampleOutcome: + """Module-level worker entry point: generate one sample and write it to disk.""" + assert _WORKER_CONTEXT is not None and _WORKER_CONFIG is not None, "Worker not initialized" + cfg = _WORKER_CONFIG + ctx = _WORKER_CONTEXT + prefix = cfg.generation.filename_prefix + digits = cfg.generation.filename_digits + sample_id = f"{prefix}_{sample_index:0{digits}d}" + output_dir = Path(cfg.paths.output_dir) + case_folder = _case_folder_name(sample_id, cfg.quality.case_type) + case_dir = output_dir / case_folder + png_path = case_dir / f"{sample_id}_document.png" + json_path = case_dir / f"{sample_id}_ground_truth.json" + + if not cfg.generation.overwrite and png_path.exists() and json_path.exists(): + return SampleOutcome(sample_id=sample_id, success=True, error="skipped_existing", case_folder=case_folder) + + try: + if cfg.generation.target_difficulty is not None: + metadata, final_image, validation_report = ctx.run_sample_targeting_difficulty( + sample_index, cfg.generation.target_difficulty, + cfg.generation.difficulty_tolerance, cfg.generation.difficulty_max_attempts, + ) + else: + metadata, final_image, validation_report = ctx.run_sample(sample_index) + case_dir.mkdir(parents=True, exist_ok=True) + save_image_rgb(png_path, final_image) + + # Copy the actual reference signature image(s) used INTO the case + # folder (not just a path pointing back at the raw dataset) -- + # ported from projectA's case_generation.py::save_case(), so each + # case folder is a fully self-contained, portable unit: open it, + # see the rendered document, the exact reference signature(s) it + # was compared against, and the ground truth, with no dependency on + # the raw dataset being present. `None`/missing for signature_modes + # with no real ink (blank box, printed-text-only). + sig_source = metadata.get("signature_source") or {} + if sig_source.get("path"): + try: + _export_reference_signature(sig_source["path"], case_dir / f"{sample_id}_reference_signature.png") + except OSError: + logger.warning("Sample %s: could not copy reference signature from %s", sample_id, sig_source["path"]) + companion = (metadata.get("verification") or {}).get("companion") or {} + if companion.get("ref_path"): + try: + _export_reference_signature(companion["ref_path"], case_dir / f"{sample_id}_companion_signature.png") + except OSError: + logger.warning("Sample %s: could not copy companion signature from %s", sample_id, companion["ref_path"]) + for extra in metadata.get("additional_signatures") or []: + try: + _export_reference_signature(extra["ref_path"], case_dir / f"{sample_id}_{extra['box_id']}_signature.png") + except OSError: + logger.warning("Sample %s: could not copy additional signature from %s", sample_id, extra["ref_path"]) + + ctx.metadata_exporter.export(metadata, json_path) + + if cfg.generation.save_visualization: + viz_path = case_dir / f"{sample_id}_visualization.png" + save_visualization(viz_path, final_image, metadata) + + if cfg.generation.save_before_after_preview and ctx.last_clean_image is not None: + # Grouped in one shared folder rather than scattered one-per-case -- + # easier to flip through every sample's metrics preview in a row + # without opening each case folder individually. + before_after_path = output_dir / "before_after" / f"{sample_id}_before_after.png" + extra_boxes = compute_extra_signature_overlays(ctx, metadata, final_image) + save_before_after_preview(before_after_path, ctx.last_clean_image, final_image, metadata, cfg.difficulty, extra_boxes) + + verification = metadata.get("verification") or {} + outcome = SampleOutcome( + sample_id=sample_id, success=True, + difficulty_tier=metadata["difficulty"]["tier"], + difficulty_score=metadata["difficulty"]["score"], + case_folder=case_folder, + template_id=metadata["template"], + template_kind=metadata["template_kind"], + case_type=verification.get("case_type"), + verdict=verification.get("verdict"), + reject_code=verification.get("reject_code"), + signer_name=metadata["signer"]["name"], + ) + if validation_report is not None: + outcome.validation_passed = validation_report.passed + outcome.validation_errors = len(validation_report.errors) + outcome.validation_warnings = len(validation_report.warnings) + if not validation_report.passed: + logger.warning( + "Sample %s failed validation: %s", sample_id, + "; ".join(i.message for i in validation_report.errors), + ) + return outcome + except Exception as exc: # noqa: BLE001 - report and continue the batch + logger.exception("Failed to generate sample %s", sample_id) + return SampleOutcome(sample_id=sample_id, success=False, error=str(exc), case_folder=case_folder) + + +@dataclass +class GenerationSummary: + """Batch-level summary returned by :meth:`DatasetGenerator.generate`.""" + + total: int + succeeded: int + failed: int + skipped_existing: int + validation_passed: int + validation_failed: int + tier_counts: dict[str, int] + failed_samples: list[str] + + def as_dict(self) -> dict[str, Any]: + return { + "total": self.total, + "succeeded": self.succeeded, + "failed": self.failed, + "skipped_existing": self.skipped_existing, + "validation_passed": self.validation_passed, + "validation_failed": self.validation_failed, + "tier_counts": self.tier_counts, + "failed_samples": self.failed_samples, + } + + +class DatasetGenerator: + """Drives batch generation of a synthetic signed-document dataset. + + Example: + >>> generator = DatasetGenerator(config) + >>> summary = generator.generate() + """ + + def __init__(self, config: PipelineConfig) -> None: + self.config = config + Path(config.paths.output_dir).mkdir(parents=True, exist_ok=True) + + def generate(self) -> GenerationSummary: + """Generate ``config.generation.count`` samples, sequentially or via a worker pool.""" + cfg = self.config + indices = list(range(cfg.generation.start_index, cfg.generation.start_index + cfg.generation.count)) + outcomes: list[SampleOutcome] = [] + + if cfg.generation.workers <= 1: + _init_worker(cfg) + for idx in tqdm(indices, desc="Generating samples", unit="doc"): + outcomes.append(_generate_and_export(idx)) + else: + with ProcessPoolExecutor( + max_workers=cfg.generation.workers, initializer=_init_worker, initargs=(cfg,), + ) as pool: + futures = {pool.submit(_generate_and_export, idx): idx for idx in indices} + for future in tqdm(as_completed(futures), total=len(futures), desc="Generating samples", unit="doc"): + outcomes.append(future.result()) + + self._write_summary_csv(outcomes) + return self._summarize(outcomes) + + def _write_summary_csv(self, outcomes: "list[SampleOutcome]") -> None: + """One row per sample -- a human-scannable overview of the whole batch + (case folder, which template/kind it landed on, verdict, difficulty) + without opening every ground_truth.json individually. Written + regardless of workers=1 vs multi-process, since it's built from the + already-collected SampleOutcome list, not during generation itself. + """ + path = Path(self.config.paths.output_dir) / "dataset_summary.csv" + fieldnames = [ + "sample_id", "case_folder", "success", "template_id", "template_kind", + "case_type", "verdict", "reject_code", "signer_name", + "difficulty_tier", "difficulty_score", + ] + with path.open("w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + for o in outcomes: + writer.writerow({ + "sample_id": o.sample_id, "case_folder": o.case_folder, "success": o.success, + "template_id": o.template_id, "template_kind": o.template_kind, + "case_type": o.case_type, "verdict": o.verdict, "reject_code": o.reject_code, + "signer_name": o.signer_name, + "difficulty_tier": o.difficulty_tier, "difficulty_score": o.difficulty_score, + }) + logger.info("Wrote dataset summary to %s", path) + + @staticmethod + def _summarize(outcomes: list[SampleOutcome]) -> GenerationSummary: + succeeded = [o for o in outcomes if o.success] + failed = [o for o in outcomes if not o.success] + skipped = [o for o in succeeded if o.error == "skipped_existing"] + validated = [o for o in succeeded if o.validation_passed is not None] + validation_passed = sum(1 for o in validated if o.validation_passed) + validation_failed = sum(1 for o in validated if not o.validation_passed) + + tier_counts: dict[str, int] = {} + for o in succeeded: + if o.difficulty_tier: + tier_counts[o.difficulty_tier] = tier_counts.get(o.difficulty_tier, 0) + 1 + + summary = GenerationSummary( + total=len(outcomes), + succeeded=len(succeeded), + failed=len(failed), + skipped_existing=len(skipped), + validation_passed=validation_passed, + validation_failed=validation_failed, + tier_counts=tier_counts, + failed_samples=[o.sample_id for o in failed], + ) + logger.info("Generation summary: %s", summary.as_dict()) + return summary diff --git a/unified_pipeline/src/metadata/__init__.py b/unified_pipeline/src/metadata/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4649838c4977c52e729c858949c582fe71e92605 --- /dev/null +++ b/unified_pipeline/src/metadata/__init__.py @@ -0,0 +1 @@ +"""Metadata assembly and export.""" diff --git a/unified_pipeline/src/metadata/metadata_exporter.py b/unified_pipeline/src/metadata/metadata_exporter.py new file mode 100644 index 0000000000000000000000000000000000000000..2a96d149a48003ae321cd730080b6e2cb3b391e5 --- /dev/null +++ b/unified_pipeline/src/metadata/metadata_exporter.py @@ -0,0 +1,257 @@ +"""Assembles and writes the complete per-sample metadata JSON. + +The exported schema always includes every documented field -- augmentation +parameters that were not applied are still present, with a neutral value +(``0``/``null``/``false``) rather than being omitted, so downstream +consumers can rely on a stable schema across the whole dataset. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import numpy as np + +from src.analysis.difficulty_analyzer import DifficultyResult +from src.analysis.geometry_analyzer import GeometryResult +from src.augmentation.augmentation_engine import AugmentationRecord +from src.config.schema import QualityConfig +from src.placement.placement_engine import PlacementResult +from src.rendering.renderer import RenderResult +from src.signatures.signature_dataset import SignatureRecord +from src.templates.template_provider import template_kind +from src.utils.logging_utils import get_logger + +logger = get_logger(__name__) + + +def _json_default(obj: Any) -> Any: + if isinstance(obj, np.integer): + return int(obj) + if isinstance(obj, np.floating): + return float(obj) + if isinstance(obj, np.bool_): + return bool(obj) + if isinstance(obj, np.ndarray): + return obj.tolist() + if isinstance(obj, Path): + return obj.as_posix() + return str(obj) + + +class MetadataExporter: + """Builds and writes the full per-sample metadata JSON document. + + Example: + >>> exporter = MetadataExporter() + >>> metadata = exporter.build(...) + >>> exporter.export(metadata, "output/sample_000001.json") + """ + + def build( + self, + document_id: str, + template_id: str, + document_type: "str | None", + page: int, + page_size: tuple[int, int], + dpi: int, + signer_name: str, + signer_role: str, + signature_source: "SignatureRecord | None", + placement: PlacementResult, + render: RenderResult, + signature_aug: dict[str, AugmentationRecord], + document_aug: dict[str, AugmentationRecord], + geometry: GeometryResult, + difficulty: DifficultyResult, + quality_cfg: QualityConfig, + quality_values: dict[str, float], + seed: int, + sample_index: int, + verification: "dict[str, Any] | None" = None, + ) -> dict[str, Any]: + """Assemble the complete metadata dict for one generated sample.""" + degraded = self._compute_degraded(quality_cfg) + + augmentation_section = self._build_augmentation_section(placement, render, signature_aug, document_aug) + + # Top-level convenience aliases, ported from projectA's leaner + # case-centric ground_truth.json schema (case_id/expected_verdict/ + # expected_reject_code) so a reader doesn't have to know Venket's + # nested "verification.verdict" shape to answer "what should a + # verifier conclude about this document" -- the single most common + # question when reviewing generated test cases. Additive only: every + # field Venket's schema already had is still present unchanged + # below, this just surfaces a few of them a second time at the top + # level. `None` for generic (non-case-type) samples, same as + # `verification` itself being `None` in that situation. + expected_verdict = verification["verdict"] if verification else None + expected_reject_code = verification["reject_code"] if verification else None + + metadata: dict[str, Any] = { + "case_id": document_id, + "expected_verdict": expected_verdict, + "expected_reject_code": expected_reject_code, + "document_id": document_id, + "template": template_id, + "template_kind": template_kind(template_id), + "document_type": document_type, + "page": page, + "page_size": list(page_size), + "dpi": dpi, + "signer": { + "name": signer_name, + "role": signer_role, + }, + "signature_source": ( + { + "signature_id": signature_source.signature_id, + "path": signature_source.path.as_posix(), + "signer_group": signature_source.signer_group, + "source_type": signature_source.source_type, + "script": signature_source.script, + } + if signature_source is not None + else {"signature_id": None, "path": None, "signer_group": None, "source_type": "none", "script": None} + ), + "placement": { + "x_offset": placement.x_offset, + "y_offset": placement.y_offset, + "rotation_deg": round(placement.rotation_deg, 3), + "scale_factor": round(placement.scale_factor, 4), + "anchor_point": placement.anchor_point, + "margin_target_box": list(placement.target_box), + "misplaced": placement.misplaced, + "misplacement_dx": round(placement.misplacement_dx, 2), + "misplacement_dy": round(placement.misplacement_dy, 2), + }, + "rendering": { + "blend_mode": render.blend_mode, + "ink_opacity": render.ink_opacity, + "blur_sigma": render.blur_sigma, + "feather_px": render.feather_px, + "scanner_noise": render.scanner_noise, + }, + "difficulty": { + "score": difficulty.score, + "tier": difficulty.tier, + "misplacement": difficulty.misplacement, + "whitespace": difficulty.whitespace, + "faintness": difficulty.faintness, + "smallness": difficulty.smallness, + "low_texture": difficulty.low_texture, + }, + "quality": { + "degraded": degraded, + "case_type": quality_cfg.case_type, + "notes": quality_cfg.notes, + "scanner_quality": quality_values["scanner_quality"], + "paper_quality": quality_values["paper_quality"], + "render_quality": quality_values["render_quality"], + }, + "verification": verification or { + "case_type": None, "verdict": None, "reject_code": None, + "target_box_id": None, "expected_signer_group": None, "expected_signer_script": None, + "actual_signer_group": None, "actual_source_type": None, "actual_signer_script": None, + "companion": None, + }, + "augmentation": augmentation_section, + "geometry": { + "box_px": list(geometry.target_box), + "signature_bbox_px": list(geometry.signature_bbox_px), + "target_box": list(geometry.target_box), + "actual_box": list(geometry.actual_box), + "search_box_px": list(geometry.search_box), + "signature_polygon_px": geometry.signature_polygon_px, + "convex_hull_px": geometry.convex_hull_px, + # Real per-ink-blob boundary contour(s) -- for visualization + # only (see GeometryResult.display_contours' docstring), + # never used in scoring; signature_polygon_px/convex_hull_px + # above remain the source of truth for every coverage metric. + "display_contours": geometry.display_contours, + "min_rotated_rect": geometry.min_rotated_rect, + "centroid_px": list(geometry.centroid_px), + "orientation_deg": geometry.orientation_deg, + "ink_pixels": geometry.ink_pixels, + "detected": geometry.found, + }, + "metrics": { + "box_coverage_pct": geometry.box_coverage_pct, + "containment_pct": geometry.containment_pct, + "ink_fill_ratio": geometry.ink_fill_ratio, + "polygon_overlap_proxy": geometry.polygon_overlap_proxy, + "distance_from_box_center_px": geometry.distance_from_box_center_px, + "margin_utilization": geometry.margin_utilization, + "mean_dark_intensity": geometry.mean_dark_intensity, + "gray_stddev": geometry.gray_stddev, + "signature_area_ratio": geometry.signature_area_ratio, + }, + "provenance": { + "master_seed": seed, + "sample_index": sample_index, + }, + } + return metadata + + @staticmethod + def _compute_degraded(quality_cfg: QualityConfig) -> bool: + case_type_flag = quality_cfg.case_type == "1d" + notes_flag = bool(quality_cfg.notes and "low-quality" in quality_cfg.notes.lower()) + return bool(case_type_flag or notes_flag) + + @staticmethod + def _build_augmentation_section( + placement: PlacementResult, render: RenderResult, + signature_aug: dict[str, AugmentationRecord], document_aug: dict[str, AugmentationRecord], + ) -> dict[str, Any]: + def rec(records: dict[str, AugmentationRecord], name: str) -> AugmentationRecord: + return records[name] + + stroke_rec = rec(signature_aug, "stroke_dilation") + noise_rec = rec(document_aug, "scanner_noise") + stain_rec = rec(document_aug, "stain") + shadow_rec = rec(document_aug, "shadow") + coffee_rec = rec(document_aug, "coffee_stain") + crumple_rec = rec(document_aug, "crumple") + tilt_rec = rec(document_aug, "page_tilt") + discoloration_rec = rec(document_aug, "discoloration") + jpeg_rec = rec(document_aug, "jpeg_compression") + + effects: dict[str, Any] = {} + for name, r in {**signature_aug, **document_aug}.items(): + effects[name] = { + "enabled": r.enabled, + "applied": r.applied, + "strength": r.strength, + "probability": r.probability, + "seed": r.seed, + "params": r.params, + } + + return { + "rotate_deg": round(placement.rotation_deg, 3), + "scale_factor": round(placement.scale_factor, 4), + "stroke": stroke_rec.strength if stroke_rec.applied else 0.0, + "noise_amount": noise_rec.strength if noise_rec.applied else 0.0, + "stain_type": stain_rec.params.get("resolved_type") if stain_rec.applied else None, + "stain_strength": stain_rec.strength if stain_rec.applied else 0.0, + "shadow_type": shadow_rec.params.get("resolved_type") if shadow_rec.applied else None, + "shadow_strength": shadow_rec.strength if shadow_rec.applied else 0.0, + "coffee_stain_strength": coffee_rec.strength if coffee_rec.applied else 0.0, + "crumple_strength": crumple_rec.strength if crumple_rec.applied else 0.0, + "tilt_deg": tilt_rec.params.get("tilt_deg", 0.0) if tilt_rec.applied else 0.0, + "discoloration_strength": discoloration_rec.strength if discoloration_rec.applied else 0.0, + "blur_sigma": render.blur_sigma, + "jpeg_quality": jpeg_rec.params.get("quality") if jpeg_rec.applied else None, + "scanner_noise": render.scanner_noise, + "effects": effects, + } + + def export(self, metadata: dict[str, Any], path: "str | Path") -> None: + """Write the metadata dict to disk as pretty-printed JSON.""" + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(metadata, indent=2, default=_json_default), encoding="utf-8") diff --git a/unified_pipeline/src/placement/__init__.py b/unified_pipeline/src/placement/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..00452f9f7ce99c5345f13a5f3a0927076813dbc5 --- /dev/null +++ b/unified_pipeline/src/placement/__init__.py @@ -0,0 +1 @@ +"""Signature placement: fitting, transforming, and positioning a signature stamp inside a target box.""" diff --git a/unified_pipeline/src/placement/placement_engine.py b/unified_pipeline/src/placement/placement_engine.py new file mode 100644 index 0000000000000000000000000000000000000000..5d3cf017fe6972857575ec6465ef3e2e96904a8a --- /dev/null +++ b/unified_pipeline/src/placement/placement_engine.py @@ -0,0 +1,226 @@ +"""Places a preprocessed signature stamp inside (or intentionally near) a target signature box. + +Handles aspect-ratio-preserving scale-to-fit, rotation, anchor-based +positioning, sub-pixel offsets, and controlled "misplacement" (an +intentional, probability-gated extra translation used to generate +hard/misaligned samples for benchmarking). +""" + +from __future__ import annotations + +from dataclasses import dataclass + +import cv2 +import numpy as np + +from src.config.schema import PlacementConfig +from src.utils.geometry_utils import Box +from src.utils.logging_utils import get_logger +from src.utils.random_utils import SeedManager, resolve_scalar_or_range +from src.utils.transform_utils import rotate_and_scale_rgba + +logger = get_logger(__name__) + +AnchorOffsetFn = "dict[str, callable]" + +# The legibility floor itself now lives on PlacementConfig +# (src/config/schema.py::PlacementConfig.min_signature_height_px, default +# 28px -- see that field's docstring for the full deck-16px-vs-measured-28px +# history) so it can be overridden the same config-driven way as every other +# placement knob, instead of a module-level constant only this file could see. + + +@dataclass +class PlacementResult: + """Everything downstream stages need to know about how the signature was placed.""" + + layer: np.ndarray # full-canvas-sized RGBA layer, transparent outside the signature + target_box: Box + intended_bbox: Box # the signature's transformed bounding box in canvas coordinates + x_offset: float + y_offset: float + rotation_deg: float + scale_factor: float + anchor_point: str + misplaced: bool + misplacement_dx: float + misplacement_dy: float + + +def _anchor_top_left(anchor: str, box_w: float, box_h: float, sig_w: float, sig_h: float) -> tuple[float, float]: + positions = { + "center": ((box_w - sig_w) / 2.0, (box_h - sig_h) / 2.0), + "top_left": (0.0, 0.0), + "top_right": (box_w - sig_w, 0.0), + "bottom_left": (0.0, box_h - sig_h), + "bottom_right": (box_w - sig_w, box_h - sig_h), + "top_center": ((box_w - sig_w) / 2.0, 0.0), + "bottom_center": ((box_w - sig_w) / 2.0, box_h - sig_h), + "left_center": (0.0, (box_h - sig_h) / 2.0), + "right_center": (box_w - sig_w, (box_h - sig_h) / 2.0), + } + return positions.get(anchor, positions["center"]) + + +class PlacementEngine: + """Fits, transforms, and positions a signature stamp relative to a target box. + + Example: + >>> engine = PlacementEngine(cfg.placement) + >>> result = engine.place(signature_rgba, target_box=(100, 100, 300, 160), + ... canvas_size=(1024, 559), seed_manager=sm) + """ + + def __init__(self, config: PlacementConfig) -> None: + self.config = config + + def place( + self, + signature_rgba: np.ndarray, + target_box: Box, + canvas_size: tuple[int, int], + seed_manager: SeedManager, + ) -> PlacementResult: + """Compute the placed signature layer for one sample. + + Args: + signature_rgba: The tightly-cropped RGBA signature stamp from + :class:`~src.rendering.signature_preprocessor.SignaturePreprocessor`. + target_box: The ``(x1, y1, x2, y2)`` target signature field, in + canvas pixel coordinates. + canvas_size: ``(width, height)`` of the full document canvas. + seed_manager: Per-sample deterministic RNG provider. + + Returns: + A :class:`PlacementResult` with the full-canvas RGBA layer ready + for blending, plus every resolved placement parameter for + metadata export. + """ + cfg = self.config + rng = seed_manager.numpy_rng("placement") + canvas_w, canvas_h = canvas_size + + rotation_deg = resolve_scalar_or_range(cfg.rotation_deg, rng) + scale_factor = resolve_scalar_or_range(cfg.scale_factor, rng) + x_offset = resolve_scalar_or_range(cfg.x_offset, rng) + y_offset = resolve_scalar_or_range(cfg.y_offset, rng) + + x1, y1, x2, y2 = target_box + box_w, box_h = (x2 - x1), (y2 - y1) + margin_x, margin_y = box_w * cfg.margin, box_h * cfg.margin + inner_x1, inner_y1 = x1 + margin_x, y1 + margin_y + inner_w = max(box_w - 2 * margin_x, 1.0) + inner_h = max(box_h - 2 * margin_y, 1.0) + + rotated = rotate_and_scale_rgba(signature_rgba, rotation_deg, 1.0) + sig_h, sig_w = rotated.shape[:2] + + fit_scale = min(inner_w / max(sig_w, 1), inner_h / max(sig_h, 1)) + final_scale = max(fit_scale * scale_factor, 1e-3) + + # Never let the signature shrink below a legible minimum height, even + # inside a very short box (real "sign here" lines are often only + # 12-20px tall at document resolution) -- a genuinely tiny box should + # make the signature overflow it, not vanish into sub-pixel noise. + min_height_scale = cfg.min_signature_height_px / max(sig_h, 1) + final_scale = max(final_scale, min_height_scale) + + placed = cv2.resize( + rotated, (max(int(round(sig_w * final_scale)), 1), max(int(round(sig_h * final_scale)), 1)), + interpolation=cv2.INTER_AREA if final_scale < 1.0 else cv2.INTER_CUBIC, + ) + placed_h, placed_w = placed.shape[:2] + + # A short "sign here" line (real bank forms are often only ~15-25px + # tall at this pipeline's working resolution) forces a large + # downscale of the signature stamp -- sometimes 15-20x when the + # source crop is a typical few-hundred-px-tall dataset image. + # INTER_AREA area-averages a multi-pixel-wide source stroke into a + # sub-pixel-wide destination stroke, which is the geometrically + # correct way to downscale, but it also dilutes that stroke's alpha + # well below fully opaque across most of its footprint -- a genuine + # ink stroke that was solidly opaque in the source crop can come + # out mostly under 30-40% opacity, rendering as a faint gray smudge + # rather than a smaller-but-still-dark signature (real signatures + # squeezed onto a short line stay dark, just compact). A plain + # linear rescale (matching a percentile of the resized alpha back + # up to the source's own level) was tried first and wasn't strong + # enough: even the SOURCE crop's own mid-tone pixels are already + # somewhat soft/anti-aliased from the original scan, so "matching + # the source" still left the bulk of the stroke under ~50% opacity. + # A gamma correction on the alpha channel instead boosts every + # partially-transparent pixel non-linearly (a 50%-opaque pixel + # becomes much more than 50% more opaque), which is what actually + # restores legibility. Gamma scales with how aggressive the + # downscale was (``final_scale`` itself, clamped) -- a sample that + # barely shrank gets ~no correction (gamma near 1, alpha^1 = alpha); + # one squeezed 10-20x onto a short line gets the strongest + # correction. Applied to alpha only, never RGB, so ink color is + # unaffected -- this makes thin/faint strokes fully opaque sooner, + # not darker in hue. + if final_scale < 1.0: + gamma = float(np.clip(final_scale * 3.0, 0.18, 1.0)) + alpha_norm = placed[:, :, 3].astype(np.float32) / 255.0 + placed = placed.copy() + placed[:, :, 3] = np.clip((alpha_norm**gamma) * 255.0, 0, 255).astype(np.uint8) + + ax, ay = _anchor_top_left(cfg.anchor_point, inner_w, inner_h, placed_w, placed_h) + base_x = inner_x1 + ax + x_offset + base_y = inner_y1 + ay + y_offset + + misplaced = bool(rng.uniform(0.0, 1.0) < cfg.misplacement_probability) + misplacement_dx = misplacement_dy = 0.0 + if misplaced: + strength = resolve_scalar_or_range(cfg.misplacement_strength, rng) + max_shift = strength * max(box_w, box_h) + angle = rng.uniform(0, 2 * np.pi) + radius = rng.uniform(0.3, 1.0) * max_shift + misplacement_dx = float(np.cos(angle) * radius) + misplacement_dy = float(np.sin(angle) * radius) + + final_x = base_x + misplacement_dx + final_y = base_y + misplacement_dy + + if not cfg.allow_partial_outside and not misplaced: + final_x = float(np.clip(final_x, inner_x1, inner_x1 + max(inner_w - placed_w, 0))) + final_y = float(np.clip(final_y, inner_y1, inner_y1 + max(inner_h - placed_h, 0))) + + # Hard safety clamp: never let the signature drift entirely off the physical page. + final_x = float(np.clip(final_x, -placed_w * 0.6, canvas_w - placed_w * 0.4)) + final_y = float(np.clip(final_y, -placed_h * 0.6, canvas_h - placed_h * 0.4)) + + layer = np.zeros((canvas_h, canvas_w, 4), dtype=np.uint8) + self._paste(layer, placed, int(round(final_x)), int(round(final_y))) + + intended_bbox: Box = (final_x, final_y, final_x + placed_w, final_y + placed_h) + + return PlacementResult( + layer=layer, + target_box=target_box, + intended_bbox=intended_bbox, + x_offset=x_offset, + y_offset=y_offset, + rotation_deg=rotation_deg, + scale_factor=scale_factor, + anchor_point=cfg.anchor_point, + misplaced=misplaced, + misplacement_dx=misplacement_dx, + misplacement_dy=misplacement_dy, + ) + + @staticmethod + def _paste(canvas: np.ndarray, sprite: np.ndarray, x: int, y: int) -> None: + """Alpha-aware paste of ``sprite`` onto ``canvas`` at ``(x, y)``, clipped to bounds.""" + canvas_h, canvas_w = canvas.shape[:2] + sprite_h, sprite_w = sprite.shape[:2] + + dst_x1, dst_y1 = max(x, 0), max(y, 0) + dst_x2, dst_y2 = min(x + sprite_w, canvas_w), min(y + sprite_h, canvas_h) + if dst_x2 <= dst_x1 or dst_y2 <= dst_y1: + logger.warning("Placed signature falls entirely outside the canvas; skipping paste.") + return + + src_x1, src_y1 = dst_x1 - x, dst_y1 - y + src_x2, src_y2 = src_x1 + (dst_x2 - dst_x1), src_y1 + (dst_y2 - dst_y1) + + canvas[dst_y1:dst_y2, dst_x1:dst_x2] = sprite[src_y1:src_y2, src_x1:src_x2] diff --git a/unified_pipeline/src/rendering/__init__.py b/unified_pipeline/src/rendering/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..77cd55b43955ae22b094a870b8c0f9bf3276848a --- /dev/null +++ b/unified_pipeline/src/rendering/__init__.py @@ -0,0 +1 @@ +"""Signature preprocessing and document/signature compositing.""" diff --git a/unified_pipeline/src/rendering/printed_text.py b/unified_pipeline/src/rendering/printed_text.py new file mode 100644 index 0000000000000000000000000000000000000000..35242b6c3cf8a00aec034efa44b9bb9d55de67ea --- /dev/null +++ b/unified_pipeline/src/rendering/printed_text.py @@ -0,0 +1,64 @@ +"""Renders a plain typed/printed name as an RGBA stamp (case_type "2b": no handwriting). + +Produces a tightly-cropped RGBA image shaped exactly like a +:class:`~src.rendering.signature_preprocessor.SignaturePreprocessor` output, +so it can flow through the existing +:class:`~src.placement.placement_engine.PlacementEngine` and +:class:`~src.rendering.renderer.Renderer` unchanged. +""" + +from __future__ import annotations + +import numpy as np +from PIL import Image, ImageDraw, ImageFont + +from src.utils.logging_utils import get_logger + +logger = get_logger(__name__) + +_FONT_CANDIDATES: tuple[str, ...] = ( + "C:/Windows/Fonts/arial.ttf", + "C:/Windows/Fonts/calibri.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", + "/System/Library/Fonts/Helvetica.ttc", +) + +_FONT_SIZE_PX = 64 +_PAD_PX = 6 + + +def _load_font(size: int = _FONT_SIZE_PX) -> "ImageFont.ImageFont": + for path in _FONT_CANDIDATES: + try: + return ImageFont.truetype(path, size) + except OSError: + continue + logger.warning("No truetype font found; falling back to PIL's bitmap default font.") + return ImageFont.load_default() + + +def render_printed_name_stamp(name: str, ink_color: tuple[int, int, int] = (15, 15, 20)) -> np.ndarray: + """Render ``name`` as plain typed text, returned as a tightly-cropped RGBA stamp. + + Args: + name: The printed/typed name to render (no handwriting stroke texture). + ink_color: Flat RGB color for the text (matches the pipeline's ink color pool). + + Returns: + An ``(H, W, 4)`` uint8 RGBA array: opaque text on a transparent background. + """ + font = _load_font() + + scratch = Image.new("RGBA", (8, 8), (0, 0, 0, 0)) + scratch_draw = ImageDraw.Draw(scratch) + text_bbox = scratch_draw.textbbox((0, 0), name, font=font) + text_w = max(text_bbox[2] - text_bbox[0], 1) + text_h = max(text_bbox[3] - text_bbox[1], 1) + + canvas_w, canvas_h = text_w + 2 * _PAD_PX, text_h + 2 * _PAD_PX + image = Image.new("RGBA", (canvas_w, canvas_h), (0, 0, 0, 0)) + draw = ImageDraw.Draw(image) + draw.text((_PAD_PX - text_bbox[0], _PAD_PX - text_bbox[1]), name, font=font, fill=(*ink_color, 255)) + + return np.array(image, dtype=np.uint8) diff --git a/unified_pipeline/src/rendering/renderer.py b/unified_pipeline/src/rendering/renderer.py new file mode 100644 index 0000000000000000000000000000000000000000..5905e1a133d25bfd599c04d213bc08f540117e8a --- /dev/null +++ b/unified_pipeline/src/rendering/renderer.py @@ -0,0 +1,105 @@ +"""Composites the placed (and signature-augmented) RGBA signature layer onto the document. + +Implements the blend modes and realism passes required by the spec: multiply +blend, darken blend, alpha blend, configurable ink opacity, feathering +(alpha-edge softening), a slight blur to match scan resolution, sensor/scan +noise, and anti-aliasing. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +import cv2 +import numpy as np + +from src.config.schema import RenderingConfig +from src.utils.logging_utils import get_logger +from src.utils.random_utils import SeedManager, resolve_scalar_or_range + +logger = get_logger(__name__) + + +@dataclass +class RenderResult: + """The blended document image plus the resolved rendering parameters.""" + + image: np.ndarray + blend_mode: str + ink_opacity: float + blur_sigma: float + feather_px: float + scanner_noise: float + + +class Renderer: + """Blends a signature RGBA layer onto a document canvas with configurable realism. + + Example: + >>> renderer = Renderer(cfg.rendering) + >>> result = renderer.blend(document_rgb, signature_layer_rgba, seed_manager) + """ + + def __init__(self, config: RenderingConfig) -> None: + self.config = config + + def blend( + self, document_rgb: np.ndarray, signature_layer_rgba: np.ndarray, seed_manager: SeedManager, + ) -> RenderResult: + """Blend the signature layer onto the document and return the composited image. + + Args: + document_rgb: The scanned document canvas, ``(H, W, 3)`` uint8. + signature_layer_rgba: A full-canvas-sized RGBA layer (as produced + by :class:`~src.placement.placement_engine.PlacementEngine`, + after any signature-level augmentation), transparent outside + the signature. + seed_manager: Per-sample deterministic RNG provider. + + Returns: + A :class:`RenderResult` with the composited uint8 RGB image and + the resolved rendering parameters (for metadata export). + """ + cfg = self.config + rng = seed_manager.numpy_rng("rendering") + + ink_opacity = resolve_scalar_or_range(cfg.ink_opacity, rng) + blur_sigma = resolve_scalar_or_range(cfg.blur_sigma, rng) + feather_px = resolve_scalar_or_range(cfg.feather_px, rng) + scanner_noise = resolve_scalar_or_range(cfg.scanner_noise, rng) + + layer = signature_layer_rgba.astype(np.float32) + if feather_px > 0: + layer[:, :, 3] = cv2.GaussianBlur(layer[:, :, 3], (0, 0), sigmaX=feather_px) + if blur_sigma > 0: + layer = cv2.GaussianBlur(layer, (0, 0), sigmaX=blur_sigma) + + sig_rgb = layer[:, :, :3] + alpha = np.clip(layer[:, :, 3] / 255.0, 0.0, 1.0) * float(np.clip(ink_opacity, 0.0, 1.0)) + alpha3 = alpha[:, :, None] + + base = document_rgb.astype(np.float32) + + if cfg.blend_mode == "multiply": + blended_pixel = base * (sig_rgb / 255.0) + elif cfg.blend_mode == "darken": + blended_pixel = np.minimum(base, sig_rgb) + else: # alpha + blended_pixel = sig_rgb + + composited = base * (1 - alpha3) + blended_pixel * alpha3 + + if scanner_noise > 0: + noise = rng.normal(0.0, scanner_noise * 255.0, size=composited.shape).astype(np.float32) + composited += noise + + if cfg.anti_alias: + composited = cv2.GaussianBlur(composited, (0, 0), sigmaX=0.3) + + out = np.clip(composited, 0, 255).astype(np.uint8) + + return RenderResult( + image=out, blend_mode=cfg.blend_mode, ink_opacity=round(ink_opacity, 4), + blur_sigma=round(blur_sigma, 4), feather_px=round(feather_px, 4), + scanner_noise=round(scanner_noise, 4), + ) diff --git a/unified_pipeline/src/rendering/signature_preprocessor.py b/unified_pipeline/src/rendering/signature_preprocessor.py new file mode 100644 index 0000000000000000000000000000000000000000..2b7c03b19c25ab6aef249b93a73f3800b3f56336 --- /dev/null +++ b/unified_pipeline/src/rendering/signature_preprocessor.py @@ -0,0 +1,123 @@ +"""Signature cleaning: white-background removal and alpha-mask extraction. + +Public handwritten signature datasets are typically flat scans: dark ink +strokes on an off-white/paper background, with no alpha channel. This module +converts such a scan into a clean RGBA "stamp" -- background fully +transparent, ink strokes opaque-to-translucent according to how dark the +original pixel was (preserving natural stroke texture: thin/faint pen +lift-offs stay translucent, heavy strokes stay opaque) -- ready to be +transformed and composited onto a document by :class:`~src.placement.placement_engine.PlacementEngine` +and :class:`~src.rendering.renderer.Renderer`. +""" + +from __future__ import annotations + +import cv2 +import numpy as np + +from src.utils.logging_utils import get_logger + +logger = get_logger(__name__) + +DEFAULT_INK_COLOR: tuple[int, int, int] = (18, 18, 24) + + +class SignaturePreprocessor: + """Cleans a raw signature scan into a tightly-cropped, transparent RGBA stamp. + + Example: + >>> pre = SignaturePreprocessor() + >>> rgba = pre.process(raw_signature_array) + """ + + def __init__( + self, + ink_color: tuple[int, int, int] = DEFAULT_INK_COLOR, + min_component_area: int = 4, + pad_px: int = 6, + alpha_gamma: float = 0.85, + ) -> None: + self.ink_color = ink_color + self.min_component_area = min_component_area + self.pad_px = pad_px + self.alpha_gamma = alpha_gamma + + def process(self, raw_image: np.ndarray, ink_color: "tuple[int, int, int] | None" = None) -> np.ndarray: + """Convert a raw signature scan into a clean, tightly-cropped RGBA stamp. + + Args: + raw_image: The raw signature image as loaded (grayscale ``(H, W)``, + RGB ``(H, W, 3)``, or RGBA ``(H, W, 4)`` uint8). + ink_color: Optional override for the flat ink color used for the + RGB channels (alpha still carries the natural stroke texture). + + Returns: + An ``(H', W', 4)`` uint8 RGBA array, cropped tightly to the ink + content with a small transparent pad. + """ + gray, existing_alpha = self._to_gray(raw_image) + gray = cv2.medianBlur(gray, 3) + alpha = self._compute_alpha(gray) + if existing_alpha is not None: + alpha = np.minimum(alpha, existing_alpha) + alpha = self._remove_speckles(alpha) + rgba = self._compose_rgba(alpha, ink_color or self.ink_color) + rgba = self._crop_to_content(rgba, pad=self.pad_px) + return rgba + + @staticmethod + def _to_gray(raw_image: np.ndarray) -> tuple[np.ndarray, "np.ndarray | None"]: + if raw_image.ndim == 2: + return raw_image.copy(), None + if raw_image.shape[2] == 4: + rgb = raw_image[:, :, :3] + alpha = raw_image[:, :, 3] + gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY) + return gray, alpha + gray = cv2.cvtColor(raw_image[:, :, :3], cv2.COLOR_RGB2GRAY) + return gray, None + + @staticmethod + def _compute_alpha(gray: np.ndarray) -> np.ndarray: + gray_f = gray.astype(np.float64) + background = float(np.percentile(gray_f, 92)) + dark_pixels = gray_f[gray_f < background - 8] + dark_level = float(np.percentile(dark_pixels, 5)) if dark_pixels.size > 20 else max(background - 140, 0.0) + denom = max(background - dark_level, 1e-3) + + raw = (background - gray_f) / denom + raw = np.clip(raw, 0.0, 1.0) + # Discard near-background noise (sub-threshold jitter from scanning artifacts). + raw[raw < 0.06] = 0.0 + softened = raw**0.85 + return (softened * 255.0).astype(np.uint8) + + def _remove_speckles(self, alpha: np.ndarray) -> np.ndarray: + binary = (alpha > 15).astype(np.uint8) + n_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary, connectivity=8) + if n_labels <= 1: + return alpha + cleaned = alpha.copy() + for label in range(1, n_labels): + area = stats[label, cv2.CC_STAT_AREA] + if area < self.min_component_area: + cleaned[labels == label] = 0 + return cleaned + + @staticmethod + def _compose_rgba(alpha: np.ndarray, ink_color: tuple[int, int, int]) -> np.ndarray: + h, w = alpha.shape + rgb = np.empty((h, w, 3), dtype=np.uint8) + rgb[:, :] = ink_color + return np.dstack([rgb, alpha]) + + @staticmethod + def _crop_to_content(rgba: np.ndarray, pad: int) -> np.ndarray: + alpha = rgba[:, :, 3] + ys, xs = np.where(alpha > 4) + if ys.size == 0: + logger.warning("Signature preprocessing found no ink pixels; returning a 1x1 transparent stamp.") + return np.zeros((1, 1, 4), dtype=np.uint8) + y1, y2 = max(ys.min() - pad, 0), min(ys.max() + pad + 1, rgba.shape[0]) + x1, x2 = max(xs.min() - pad, 0), min(xs.max() + pad + 1, rgba.shape[1]) + return rgba[y1:y2, x1:x2].copy() diff --git a/unified_pipeline/src/signatures/__init__.py b/unified_pipeline/src/signatures/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..da4feafc4233063f780bf480488958a55cca60a1 --- /dev/null +++ b/unified_pipeline/src/signatures/__init__.py @@ -0,0 +1 @@ +"""Signature dataset discovery and sampling.""" diff --git a/unified_pipeline/src/signatures/signature_dataset.py b/unified_pipeline/src/signatures/signature_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..f8a772c1e65754d6625d05bd8aa0b34509d670a3 --- /dev/null +++ b/unified_pipeline/src/signatures/signature_dataset.py @@ -0,0 +1,323 @@ +"""Recursive discovery of the public handwritten signature dataset. + +Scans ``dataset/`` recursively for signature images (PNG/JPG/JPEG/TIFF/BMP), +building an internal database without requiring any particular naming +convention or folder layout. Two conventions are detected automatically +when present, but neither is required: + +* ``dataset/metadata.csv`` -- if present, associates files with a signer + identity via a ``filename``/``path`` column plus a ``signer``/``name`` + column, and optionally a ``source_type`` and ``script`` column (the + handwriting script the signature is written in, e.g. ``hindi``, + ``bengali``, ``latin`` -- see ``dataset/hindi_bengali/`` for the bundled + multi-script signer pool). +* Filenames matching CEDAR-style ``original__.png`` / + ``forgeries__.png`` (as found in the bundled dataset) -- + used to recover a signer group id and genuine/forged label even without + a metadata file. + +Otherwise, each signature is assigned an automatically generated id and its +immediate parent folder name is used as its signer group (matching the +``dataset/personNNN/*.png`` layout described in the project spec). +""" + +from __future__ import annotations + +import csv +import re +from dataclasses import dataclass +from pathlib import Path + +import numpy as np + +from src.utils.image_io import discover_images +from src.utils.logging_utils import get_logger + +logger = get_logger(__name__) + +_CEDAR_PATTERN = re.compile(r"^(?P[a-zA-Z]+)_(?P\d+)_(?P\d+)$") + +# SigNet layout: /{train,test}//*.png (genuine), +# /{train,test}/_forg/*.png (forged of writer ). +_SIGNET_FORGED_DIR_PATTERN = re.compile(r"^(?P\d+)_forg$") + +# BHSig260 layout: /BHSig260-X//{H|B}-S--{G|F}-.tif +_BHSIG260_PATTERN = re.compile(r"^[HB]-S-(?P\d+)-(?P