Loads the recreation experiment set (recreation_samples.jsonl, typically 1040 rows = 65 × 16 classes), materializes page images (selective tar extract when the archive is present), optionally OCRs them, then queries OpenRouter models to:
Classify into the 16 RVL-CDIP labels (gold labels available)
python scripts/setup_env.py # OPENROUTER_API_KEY in .envpython-m src.rvl_cdip build # labels + SQLite (if not already)pip install -e".[ocr]"# only if using the text/OCR path# Image archive (~38 GB) — preview then dual opt-in (writes ONLY under .venv/rvl_cdip):python-m src.rvl_cdip download-images --preflightpython-m src.rvl_cdip download-images \--i-understand-large-download--confirm-writes-under-venv
Defaults below keep DRY_RUN=True so this notebook is safe for docs/CI.
0b. Optional: download image archive into .venv/rvl_cdip only
Hub cache + rvl-cdip.tar.gz write only under .venv/rvl_cdip/ (never data/ or ~/.cache). Set both flags below to True after reviewing the preflight block, then run the cell. Prefer CLI for long downloads:
python-m src.rvl_cdip download-images --preflightpython-m src.rvl_cdip download-images \--i-understand-large-download--confirm-writes-under-venv# interactive phrase prompt instead of the second flag:# python -m src.rvl_cdip download-images \# --i-understand-large-download --interactive-confirm
I_UNDERSTAND_LARGE_DOWNLOAD =False# ~38 GB archiveCONFIRM_WRITES_UNDER_VENV =False# must acknowledge .venv/rvl_cdip-only writesFORCE_REDOWNLOAD =Falsepreflight = image_download_preflight(force=FORCE_REDOWNLOAD)print(format_image_download_preflight(preflight))display(preflight)if I_UNDERSTAND_LARGE_DOWNLOAD and CONFIRM_WRITES_UNDER_VENV:ifnot preflight["enough_free_space"] andnot preflight["archive_already_present"]:raiseRuntimeError(f"Need ≥ {preflight['min_free_gb']} GB free; have {preflight['free_gb']} GB." ) result = download_images( force=FORCE_REDOWNLOAD, i_understand_large_download=True, confirm_writes_under_venv=True, ) updated = RvlCdipStore().refresh_image_paths() display( {"local_path": str(result.local_path),"bytes": result.bytes,"skipped": result.skipped,"image_abspath_updated": updated,"writes_only_under": result.detail.get("writes_only_under"), } )else: display( Markdown("> **Download not started.** Set `I_UNDERSTAND_LARGE_DOWNLOAD = True` and ""`CONFIRM_WRITES_UNDER_VENV = True` after reviewing the preflight ""(writes only under `.venv/rvl_cdip`)." ) )
RVL-CDIP image archive download — preflight
CONFIRMATION: all Hub cache + archive writes stay under:
/Users/morningstar/Desktop/Cold_Storage/smol-doc-analyzer/.venv/rvl_cdip
(not data/, not ~/.cache/huggingface)
Remote: aharley/rvl_cdip/data/rvl-cdip.tar.gz
Archive → /Users/morningstar/Desktop/Cold_Storage/smol-doc-analyzer/.venv/rvl_cdip/source/rvl-cdip.tar.gz
HF home → /Users/morningstar/Desktop/Cold_Storage/smol-doc-analyzer/.venv/rvl_cdip/hf_home
Images dir: /Users/morningstar/Desktop/Cold_Storage/smol-doc-analyzer/.venv/rvl_cdip/source/images
Archive size (est.): 36.1 GB
Free space now: 56.73 GB (need ≥ 45.0 GB)
Enough free space: True
Already present: False
Ack phrase: "writes only under .venv/rvl_cdip"
Download not started. Set I_UNDERSTAND_LARGE_DOWNLOAD = True and CONFIRM_WRITES_UNDER_VENV = True after reviewing the preflight (writes only under .venv/rvl_cdip).
Loaded 1040 samples from data/notebook_demo/rvl_cdip_recreation/recreation_samples.jsonl
label_id
label
size
0
0
letter
65
1
1
form
65
2
2
email
65
3
3
handwritten
65
4
4
advertisement
65
5
5
scientific report
65
6
6
scientific publication
65
7
7
specification
65
8
8
file folder
65
9
9
news article
65
10
10
budget
65
11
11
invoice
65
12
12
presentation
65
13
13
questionnaire
65
14
14
resume
65
15
15
memo
65
document_id
split
label_id
label
image_relpath
image_abspath
source_line
recreation_seed
n_per_class
split_filter
0
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
test
0
letter
imagesz/z/p/v/zpv93d00/508330972_508330974.tif
None
13284
42
65
None
1
train:imagese/e/u/v/euv68e00/2001213893.tif
train
0
letter
imagese/e/u/v/euv68e00/2001213893.tif
None
57555
42
65
None
2
train:imagesp/p/s/e/pse16c00/2071710123.tif
train
0
letter
imagesp/p/s/e/pse16c00/2071710123.tif
None
13386
42
65
None
2. Materialize images (+ optional OCR)
Resolution order: existing image_abspath → .venv/rvl_cdip/source/images/{relpath} → extract that member from rvl-cdip.tar.gz when the archive is present.
mats = materialize_samples(samples, run_ocr=RUN_OCR and ("text"in MODALITIES))mat_summary = summarize_materialize(mats)display(Markdown("### Materialize summary"))display(mat_summary)mat_df = pd.DataFrame([m.to_dict() for m in mats])display(mat_df.head(5))if mat_summary["n_with_image"] ==0and"vision"in MODALITIES andnot DRY_RUN: display( Markdown("> **Vision path blocked:** no images on disk and archive missing. ""Use section **0b** or\n""`python -m src.rvl_cdip download-images --i-understand-large-download ""--confirm-writes-under-venv`, then re-run materialize ""(selective extract of the sample paths into `.venv/rvl_cdip/source/images/`)." ) )
Classification: accuracy + macro recall vs gold label
Extraction: valid-JSON rate, field fill-rates, pairwise agreement (no gold fields)
cls_path = OUT_DIR /"summary_classification.json"ext_path = OUT_DIR /"summary_extraction.json"cls = json.loads(cls_path.read_text()) if cls_path.exists() else {}ext = json.loads(ext_path.read_text()) if ext_path.exists() else {}cls_df = pd.DataFrame(cls.get("per_model") or [])ext_df = pd.DataFrame(ext.get("per_model") or [])display(Markdown("### Classification"))display(cls_df)display(Markdown("### Extraction proxies"))display(ext_df)if ext.get("pairwise_agreement"): display(Markdown("### Pairwise agreement")) display(pd.DataFrame(ext["pairwise_agreement"].get("pairs") or []))
Classification
model_id
modality
n
accuracy
macro_recall
per_class_recall
error_rate
avg_latency_seconds
total_input_tokens
total_output_tokens
0
google/gemini-2.0-flash-001
vision
1040
1.0
1.0
{'letter': 1.0, 'form': 1.0, 'email': 1.0, 'ha...
0.0
0.0
0
0
1
meta-llama/llama-3.2-3b-instruct:free
text
1040
1.0
1.0
{'letter': 1.0, 'form': 1.0, 'email': 1.0, 'ha...
0.0
0.0
0
0
2
openai/gpt-4o-mini
text
1040
1.0
1.0
{'letter': 1.0, 'form': 1.0, 'email': 1.0, 'ha...
0.0
0.0
0
0
3
openai/gpt-4o-mini
vision
1040
1.0
1.0
{'letter': 1.0, 'form': 1.0, 'email': 1.0, 'ha...
0.0
0.0
0
0
Extraction proxies
model_id
modality
n
valid_json_rate
mean_field_fill_rate
field_fill_rates
error_rate
0
google/gemini-2.0-flash-001
vision
1040
1.0
0.4
{'document_type_guess': 1.0, 'title_or_subject...
0.0
1
meta-llama/llama-3.2-3b-instruct:free
text
1040
1.0
0.4
{'document_type_guess': 1.0, 'title_or_subject...
0.0
2
openai/gpt-4o-mini
text
1040
1.0
0.4
{'document_type_guess': 1.0, 'title_or_subject...
0.0
3
openai/gpt-4o-mini
vision
1040
1.0
0.4
{'document_type_guess': 1.0, 'title_or_subject...
0.0
Pairwise agreement
model_a
model_b
n_compared_fields
agreement_rate
0
google/gemini-2.0-flash-001|vision
meta-llama/llama-3.2-3b-instruct:free|text
3120
1.0
1
google/gemini-2.0-flash-001|vision
openai/gpt-4o-mini|text
3120
1.0
2
google/gemini-2.0-flash-001|vision
openai/gpt-4o-mini|vision
3120
1.0
3
meta-llama/llama-3.2-3b-instruct:free|text
openai/gpt-4o-mini|text
3120
1.0
4
meta-llama/llama-3.2-3b-instruct:free|text
openai/gpt-4o-mini|vision
3120
1.0
5
openai/gpt-4o-mini|text
openai/gpt-4o-mini|vision
3120
1.0
5. Peek at predictions
pred_path = OUT_DIR /"predictions.jsonl"preds = [ json.loads(line)for line in pred_path.read_text(encoding="utf-8").splitlines()if line.strip()]pred_df = pd.DataFrame(preds)display(Markdown(f"**{len(pred_df)}** prediction rows"))cols = [ cfor c in ["document_id","label","task","modality","model_id","prediction","error","dry_run","cache_hit", ]if c in pred_df.columns]display(pred_df[cols].head(12))
8320 prediction rows
document_id
label
task
modality
model_id
prediction
error
dry_run
cache_hit
0
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
letter
classify
vision
openai/gpt-4o-mini
letter
None
True
False
1
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
letter
extract_annotate
vision
openai/gpt-4o-mini
{'document_type_guess': 'letter', 'title_or_su...
None
True
False
2
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
letter
classify
vision
google/gemini-2.0-flash-001
letter
None
True
False
3
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
letter
extract_annotate
vision
google/gemini-2.0-flash-001
{'document_type_guess': 'letter', 'title_or_su...
None
True
False
4
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
letter
classify
text
openai/gpt-4o-mini
letter
None
True
False
5
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
letter
extract_annotate
text
openai/gpt-4o-mini
{'document_type_guess': 'letter', 'title_or_su...
None
True
False
6
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
letter
classify
text
meta-llama/llama-3.2-3b-instruct:free
letter
None
True
False
7
test:imagesz/z/p/v/zpv93d00/508330972_50833097...
letter
extract_annotate
text
meta-llama/llama-3.2-3b-instruct:free
{'document_type_guess': 'letter', 'title_or_su...
None
True
False
8
train:imagese/e/u/v/euv68e00/2001213893.tif
letter
classify
vision
openai/gpt-4o-mini
letter
None
True
False
9
train:imagese/e/u/v/euv68e00/2001213893.tif
letter
extract_annotate
vision
openai/gpt-4o-mini
{'document_type_guess': 'letter', 'title_or_su...
None
True
False
10
train:imagese/e/u/v/euv68e00/2001213893.tif
letter
classify
vision
google/gemini-2.0-flash-001
letter
None
True
False
11
train:imagese/e/u/v/euv68e00/2001213893.tif
letter
extract_annotate
vision
google/gemini-2.0-flash-001
{'document_type_guess': 'letter', 'title_or_su...
None
True
False
Next steps
Download archive via section 0b / CLI (--confirm-writes-under-venv), then materialize
Set DRY_RUN=False for a live vision run
Lower MAX_PER_CLASS (e.g. 2) for a cheap smoke before the full 1040
Compare summary_classification.csv across model slugs; review extraction JSONL manually