handwriting-ocr / dataprep.py
prrao87's picture
Upload folder using huggingface_hub
89cc2cb verified
Raw
History Blame Contribute Delete
6.36 kB
#!/usr/bin/env python3
"""Convert Doctor's Handwritten Prescription BD images to Lance.
The source provides cropped handwritten medication words and their labels. This
conversion preserves the original PNG bytes and creates the text/search fields
used by the companion OCR retrieval project, without generating embeddings.
Indices per split:
- FTS on ``searchable_summary``
- BTREE on ``id``
- BITMAP on ``category`` and ``needs_human_review``
"""
from __future__ import annotations
import argparse
import csv
import shutil
import sys
from pathlib import Path
from typing import Iterator
import lance
import pyarrow as pa
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT))
from _common.indexing import build_default_indices
from _common.upload import push_to_hub
HF_REPO_ID = "lance-format/handwriting-ocr"
SOURCE_DATASET = "mamun1113/doctors-handwritten-prescription-bd-dataset"
MAX_BYTES_PER_FILE = 8 * 1024 * 1024 * 1024
SPLITS = {
"train": ("Training", "training_labels.csv", "training_words"),
"validation": ("Validation", "validation_labels.csv", "validation_words"),
"test": ("Testing", "testing_labels.csv", "testing_words"),
}
def _schema() -> pa.Schema:
return pa.schema(
[
pa.field("id", pa.string(), nullable=False),
pa.field("image", pa.large_binary(), nullable=False),
pa.field("medicine_name", pa.string(), nullable=False),
pa.field("generic_name", pa.string(), nullable=False),
pa.field("normalized_text", pa.string(), nullable=False),
pa.field("category", pa.string(), nullable=False),
pa.field("is_medical", pa.bool_(), nullable=False),
pa.field("needs_human_review", pa.bool_(), nullable=False),
pa.field("searchable_summary", pa.string(), nullable=False),
pa.field("source_dataset", pa.string(), nullable=False),
pa.field("split", pa.string(), nullable=False),
pa.field("image_filename", pa.string(), nullable=False),
]
)
def _rows(source_root: Path, lance_split: str) -> Iterator[dict]:
source_split, labels_name, words_name = SPLITS[lance_split]
split_root = source_root / source_split
labels_path = split_root / labels_name
words_dir = split_root / words_name
with labels_path.open(newline="", encoding="utf-8") as labels_file:
for row_number, row in enumerate(csv.DictReader(labels_file)):
image_filename = row["IMAGE"]
medicine_name = row["MEDICINE_NAME"].strip()
generic_name = row["GENERIC_NAME"].strip()
with (words_dir / image_filename).open("rb") as image_file:
image = image_file.read()
yield {
"id": f"{lance_split}_{row_number:05d}",
"image": image,
"medicine_name": medicine_name,
"generic_name": generic_name,
"normalized_text": medicine_name,
"category": "medication",
"is_medical": True,
"needs_human_review": False,
"searchable_summary": f"Medication mention: {medicine_name} ({generic_name})",
"source_dataset": SOURCE_DATASET,
"split": source_split,
"image_filename": image_filename,
}
def _batches(source_root: Path, lance_split: str, batch_size: int) -> Iterator[pa.RecordBatch]:
schema = _schema()
batch: list[dict] = []
for row in _rows(source_root, lance_split):
batch.append(row)
if len(batch) >= batch_size:
yield pa.RecordBatch.from_pylist(batch, schema=schema)
batch = []
if batch:
yield pa.RecordBatch.from_pylist(batch, schema=schema)
def write_split(source_root: Path, lance_split: str, out_path: Path, *, batch_size: int, overwrite: bool) -> None:
if out_path.exists():
if not overwrite:
raise FileExistsError(f"{out_path} already exists; pass --overwrite to replace it")
shutil.rmtree(out_path)
out_path.parent.mkdir(parents=True, exist_ok=True)
print(f"Writing {lance_split} -> {out_path}")
lance.write_dataset(
_batches(source_root, lance_split, batch_size),
str(out_path),
schema=_schema(),
mode="create",
max_bytes_per_file=MAX_BYTES_PER_FILE,
)
def index_split(out_path: Path) -> None:
dataset = lance.dataset(str(out_path))
build_default_indices(
dataset,
fts_columns=("searchable_summary",),
btree_columns=("id",),
bitmap_columns=("category", "needs_human_review"),
)
def main() -> None:
parser = argparse.ArgumentParser(description="Doctor's Handwritten Prescription BD -> Lance")
parser.add_argument(
"--source-data",
default=str(REPO_ROOT.parent / "ocr-handwriting-retrieval" / "data"),
help="Path containing the Training, Validation, and Testing directories.",
)
parser.add_argument(
"--out",
default=str(REPO_ROOT.parent / "lance_cache" / "handwriting-ocr"),
)
parser.add_argument("--batch-size", type=int, default=256)
parser.add_argument("--overwrite", action="store_true")
parser.add_argument("--no-index", action="store_true")
parser.add_argument("--push", action="store_true")
parser.add_argument("--repo-id", default=HF_REPO_ID)
args = parser.parse_args()
source_root = Path(args.source_data)
out_root = Path(args.out)
for source_split, labels_name, words_name in SPLITS.values():
for path in (source_root / source_split / labels_name, source_root / source_split / words_name):
if not path.exists():
raise FileNotFoundError(path)
for lance_split in SPLITS:
out_split = out_root / "data" / f"{lance_split}.lance"
write_split(source_root, lance_split, out_split, batch_size=args.batch_size, overwrite=args.overwrite)
if not args.no_index:
index_split(out_split)
card = Path(__file__).parent / "HF_DATASET_CARD.md"
(out_root / "README.md").write_text(card.read_text(encoding="utf-8"), encoding="utf-8")
if args.push:
url = push_to_hub(repo_id=args.repo_id, folder_path=out_root)
print(f"Done: {url}")
if __name__ == "__main__":
main()