Queryable walkthrough over the local aharley/rvl_cdip SQLite index (.venv/rvl_cdip/rvl_cdip.db), then a seeded stratified draw of 60–70 documents from each of the 16 classes (label ids 0–15) for the recreation experiment.
This notebook completes the sampling task: it writes a JSONL sample set, a machine-readable manifest, and a per-class count table under data/notebook_demo/rvl_cdip_recreation/.
from __future__ import annotationsimport jsonimport loggingimport randomimport sysfrom collections import Counterfrom pathlib import Pathimport matplotlib.pyplot as pltimport pandas as pdfrom IPython.display import Markdown, displayCWD = Path.cwd().resolve()# Walk up from docs/notebooks/ (or notebooks/) until pyproject.toml is found.REPO_ROOT =next( (p for p in (CWD, *CWD.parents) if (p /"pyproject.toml").exists()),None,)assert REPO_ROOT isnotNone, f"Could not find repo root from {CWD}"ifstr(REPO_ROOT) notin sys.path: sys.path.insert(0, str(REPO_ROOT))from src.rvl_cdip import LABEL_NAMES, RvlCdipStore, default_db_pathfrom src.utils.io import write_json, write_jsonllogging.basicConfig(level=logging.INFO, format="%(levelname)s%(name)s: %(message)s")DEMO = REPO_ROOT /"data"/"notebook_demo"/"rvl_cdip_recreation"DEMO.mkdir(parents=True, exist_ok=True)# --- Recreation knobs ---SEED =42N_PER_CLASS =65# choose any integer in [60, 70]assert60<= N_PER_CLASS <=70, "N_PER_CLASS must be between 60 and 70 inclusive"# None = sample across train/test/validation; or e.g. "train"SPLIT_FILTER: str|None=Noneprint(f"repo: {REPO_ROOT}")print(f"db: {default_db_path()}")print(f"demo out: {DEMO}")print(f"SEED={SEED} N_PER_CLASS={N_PER_CLASS} SPLIT_FILTER={SPLIT_FILTER!r}")print(f"classes: {len(LABEL_NAMES)} → {list(enumerate(LABEL_NAMES))}")
RvlCdipStore() opens .venv/rvl_cdip/rvl_cdip.db. If the documents table is empty, build_from_labels() downloads the split label files (~17 MB) and ingests all ~400k rows.
store = RvlCdipStore()summary = store.summary()print(f"documents indexed: {summary['documents']:,}")print(f"with image paths: {summary['with_image_abspath']:,}")print(f"built_at: {summary.get('built_at')}")print(f"dataset_id: {summary.get('dataset_id')}")if summary["documents"] ==0:print("Index empty — building from Hub label files (safe; no 38 GB archive)…") stats = store.build_from_labels() summary = store.summary()print("build stats:", stats)print(f"documents indexed: {summary['documents']:,}")assert summary["documents"] >0, ("RVL-CDIP index is empty. Run: python -m src.rvl_cdip build")display(pd.DataFrame(summary["by_split"]))display(pd.DataFrame(summary["by_label"]))
store.labels() and ad-hoc store.query(...) (SELECT-only).
labels_df = pd.DataFrame(store.labels())display(labels_df)by_label = store.query(""" SELECT d.label_id, l.name AS label, COUNT(*) AS n FROM documents d JOIN labels l ON l.label_id = d.label_id GROUP BY d.label_id ORDER BY d.label_id """)display(pd.DataFrame(by_label))fig, ax = plt.subplots(figsize=(10, 4))plot_df = pd.DataFrame(by_label)ax.bar(plot_df["label_id"].astype(str) +"\n"+ plot_df["label"], plot_df["n"], color="#2a6f97")ax.set_title("RVL-CDIP documents per class")ax.set_xlabel("label_id / name")ax.set_ylabel("count")plt.xticks(rotation=0, fontsize=8)plt.tight_layout()plt.show()
label_id
name
0
0
letter
1
1
form
2
2
email
3
3
handwritten
4
4
advertisement
5
5
scientific report
6
6
scientific publication
7
7
specification
8
8
file folder
9
9
news article
10
10
budget
11
11
invoice
12
12
presentation
13
13
questionnaire
14
14
resume
15
15
memo
label_id
label
n
0
0
letter
25000
1
1
form
25000
2
2
email
25000
3
3
handwritten
25000
4
4
advertisement
25000
5
5
scientific report
25000
6
6
scientific publication
25000
7
7
specification
25000
8
8
file folder
25000
9
9
news article
25000
10
10
budget
25000
11
11
invoice
25000
12
12
presentation
25000
13
13
questionnaire
25000
14
14
resume
25000
15
15
memo
25000
3b. Split × label heatmap-style table
Useful to see whether a class is balanced across train / test / validation.
split_label = store.query(""" SELECT d.split, d.label_id, l.name AS label, COUNT(*) AS n FROM documents d JOIN labels l ON l.label_id = d.label_id GROUP BY d.split, d.label_id ORDER BY d.split, d.label_id """, max_rows=200,)pivot = ( pd.DataFrame(split_label) .pivot(index=["label_id", "label"], columns="split", values="n") .fillna(0) .astype(int))display(pivot)# Example: five invoice rows from train via the list helperinvoice_train = store.list_documents(split="train", label="invoice", limit=5)display(pd.DataFrame(invoice_train))
split
test
train
validation
label_id
label
0
letter
2464
20106
2430
1
form
2506
19957
2537
2
email
2516
19954
2530
3
handwritten
2532
20034
2434
4
advertisement
2515
19963
2522
5
scientific report
2498
19994
2508
6
scientific publication
2572
19902
2526
7
specification
2472
19997
2531
8
file folder
2527
20022
2451
9
news article
2463
20011
2526
10
budget
2505
20010
2485
11
invoice
2477
19947
2576
12
presentation
2489
20043
2468
13
questionnaire
2435
20048
2517
14
resume
2537
20037
2426
15
memo
2492
19975
2533
document_id
split
label_id
label
image_relpath
image_abspath
source_line
0
train:imagesr/r/l/z/rlz20d00/521107137+-7140.tif
train
11
invoice
imagesr/r/l/z/rlz20d00/521107137+-7140.tif
None
7
1
train:imagesk/k/w/s/kws90e00/91514628.tif
train
11
invoice
imagesk/k/w/s/kws90e00/91514628.tif
None
91
2
train:imagesk/k/f/z/kfz64c00/91507726_7727.tif
train
11
invoice
imagesk/k/f/z/kfz64c00/91507726_7727.tif
None
93
3
train:imagesq/q/i/z/qiz36c00/2070435354.tif
train
11
invoice
imagesq/q/i/z/qiz36c00/2070435354.tif
None
101
4
train:imagesk/k/s/v/ksv13c00/92270027_0029.tif
train
11
invoice
imagesk/k/s/v/ksv13c00/92270027_0029.tif
None
105
3c. Example filtered SELECTs
Same patterns as the CLI:
python-m src.rvl_cdip query "SELECT split, COUNT(*) AS n FROM documents GROUP BY split"
examples = {"rows_per_split": """ SELECT split, COUNT(*) AS n FROM documents GROUP BY split ORDER BY split ""","handwritten_train_sample": """ SELECT d.document_id, d.split, d.label_id, l.name AS label, d.image_relpath FROM documents d JOIN labels l ON l.label_id = d.label_id WHERE d.label_id = 3 AND d.split = 'train' ORDER BY d.source_line LIMIT 5 ""","memo_vs_letter": """ SELECT l.name AS label, d.split, COUNT(*) AS n FROM documents d JOIN labels l ON l.label_id = d.label_id WHERE d.label_id IN (0, 15) GROUP BY l.name, d.split ORDER BY l.name, d.split """,}for name, sql in examples.items():print(f"\n=== {name} ===") display(pd.DataFrame(store.query(sql)))
=== rows_per_split ===
split
n
0
test
40000
1
train
320000
2
validation
40000
=== handwritten_train_sample ===
document_id
split
label_id
label
image_relpath
0
train:imageso/o/e/x/oex80d00/522787731+-7732.tif
train
3
handwritten
imageso/o/e/x/oex80d00/522787731+-7732.tif
1
train:imagesn/n/t/y/nty14d00/507705676.tif
train
3
handwritten
imagesn/n/t/y/nty14d00/507705676.tif
2
train:imageso/o/v/q/ovq40d00/517303299+-3300.tif
train
3
handwritten
imageso/o/v/q/ovq40d00/517303299+-3300.tif
3
train:imagese/e/j/k/ejk41d00/515625855+-5858.tif
train
3
handwritten
imagese/e/j/k/ejk41d00/515625855+-5858.tif
4
train:imagesj/j/q/a/jqa60d00/517515626+-5627.tif
train
3
handwritten
imagesj/j/q/a/jqa60d00/517515626+-5627.tif
=== memo_vs_letter ===
label
split
n
0
letter
test
2464
1
letter
train
20106
2
letter
validation
2430
3
memo
test
2492
4
memo
train
19975
5
memo
validation
2533
4. Recreation sample — 60–70 random docs per class (0–15)
Writes under data/notebook_demo/rvl_cdip_recreation/:
File
Contents
recreation_samples.jsonl
One sampled document per line
recreation_manifest.json
Seed, n_per_class, counts, db path
recreation_counts.csv
Per-class counts
samples_path = DEMO /"recreation_samples.jsonl"manifest_path = DEMO /"recreation_manifest.json"counts_path = DEMO /"recreation_counts.csv"write_jsonl(samples_path, samples)counts.to_csv(counts_path, index=False)manifest = {"experiment": "rvl_cdip_recreation","dataset_id": summary.get("dataset_id"),"db_path": summary.get("db_path"),"seed": SEED,"n_per_class": N_PER_CLASS,"n_classes": 16,"total_samples": len(samples),"split_filter": SPLIT_FILTER,"label_names": list(LABEL_NAMES),"counts_by_label_id": {int(r["label_id"]): int(r["n"]) for _, r in counts.iterrows() },"samples_path": str(samples_path.relative_to(REPO_ROOT)),"counts_path": str(counts_path.relative_to(REPO_ROOT)),}write_json(manifest_path, manifest)print("wrote:", samples_path)print("wrote:", manifest_path)print("wrote:", counts_path)display(Markdown(f"```json\n{json.dumps(manifest, indent=2)}\n```"))# Reload checkreloaded = [json.loads(line) for line in samples_path.read_text().splitlines() if line.strip()]assertlen(reloaded) ==len(samples)assert Counter(r["label_id"] for r in reloaded) == Counter( {i: N_PER_CLASS for i inrange(16)})print("✓ export verified on disk")
Reload helpers for downstream recreation / OCR / classifier work.
# Peek one document per classpeek = ( sample_df.sort_values(["label_id", "document_id"]) .groupby("label_id", as_index=False) .first()[["label_id", "label", "split", "document_id", "image_relpath"]])display(peek)# Optional: resolve a single row through the store APIexample_id = samples[0]["document_id"]print("store.get_document:", store.get_document(example_id))display( Markdown(f"""**Done.** Recreation set ready:- `{samples_path.relative_to(REPO_ROOT)}` — {len(samples)} rows- `{manifest_path.relative_to(REPO_ROOT)}` — seed={SEED}, n_per_class={N_PER_CLASS}CLI equivalents:```bashpython -m src.rvl_cdip summarypython -m src.rvl_cdip query "SELECT label_id, COUNT(*) AS n FROM documents GROUP BY label_id"```""" ))