ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English
PDFの透明テキスト抽出における順序保持の課題と解決策

PDFの透明テキスト抽出における順序保持の課題と解決策

はじめに PDFファイルから透明テキストレイヤーを抽出する際、「テキストの順序が元のPDFと異なってしまう」という問題に直面しました。本記事では、この問題の原因と、JavaScriptとPythonそれぞれでの解決策について解説します。誤っている点もあるかもしれませんが、参考になりましたら幸いです。 PDFの透明テキストとは PDFの透明テキストレイヤーは、PDFファイル内に埋め込まれた検索可能なテキスト情報です。OCR処理されたPDFや、デジタル生成されたPDFには、この透明テキストレイヤーが含まれており、以下のような機能を実現しています: テキスト検索 コピー&ペースト スクリーンリーダーによる読み上げ 機械翻訳 問題:テキストの順序が乱れる理由 PDFの内部構造 PDFファイルは、テキストを「コンテンツストリーム」という形式で保存しています。このストリームには、テキストとその位置情報が含まれていますが、必ずしも読む順序で格納されているわけではありません。 例:PDFコンテンツストリームの概念図 [位置: x=100, y=200, テキスト="見出し"] [位置: x=300, y=400, テキスト="脚注"] [位置: x=100, y=300, テキスト="本文"] 一般的な抽出方法の問題点 多くのPDF処理ライブラリは、以下のような手順でテキストを抽出します: コンテンツストリームからテキストと位置情報を取得 座標でソート (上から下、左から右) ソート結果を出力 この「座標でソート」する処理が、テキスト順序の乱れを引き起こす主な原因です。 具体的な問題例 縦書きと横書きの混在 :日本語文書でよく見られる 複数カラムレイアウト :新聞や雑誌形式 図表の挿入 :本文の流れを分断する要素 ヘッダー・フッター :ページをまたぐ要素 解決策:言語別アプローチ JavaScript (PDF.js) での解決策 PDF.jsは、Mozillaが開発したJavaScriptベースのPDFレンダリングライブラリです。 順序を保持する実装 // PDF.jsを使用した順序保持テキスト抽出 async function extractTextWithOrder(page) { // getTextContent()はコンテンツストリームの順序を維持 const textContent = await page.getTextContent(); // itemsは元の順序を保持した配列 const orderedText = textContent.items.map(item => { return { text: item.str, x: item.transform[4], y: item.transform[5], width: item.width, height: item.height }; }); // 配列の順序をそのまま使用(座標ソートしない) return orderedText; } ポイント getTextContent()メソッドは、PDFの内部構造に忠実な順序でテキストを返す 配列のインデックスが元の順序を表現 座標による再ソートを行わない Python (PyMuPDF) での解決策 PyMuPDF(fitz)は、MuPDFライブラリのPythonバインディングです。 ...

校異源氏物語テキストDBのTEI/XMLからPDFを作成する

校異源氏物語テキストDBのTEI/XMLからPDFを作成する

概要 校異源氏物語テキストDBは、『校異源氏物語』のテキストデータを公開するデータベースです。 https://kouigenjimonogatari.github.io/ 今回、本DBに以下のようなPDFファイルを追加しました。 https://kouigenjimonogatari.github.io/output/01/main.pdf 本記事は、上記のようなPDFファイルを、XSLTとTeXを使って作成します。 リポジトリのクローン 以下のように、リポジトリをクローンします。 git clone --depth 1 https://github.com/kouigenjimonogatari/kouigenjimonogatari.github.io そして以下のコマンドにより、xslt3をインストールします。 npm i xslt3 https://www.npmjs.com/package/xslt3 XSLファイルの作成 今回は、まずTEI/XMLファイルをTeXファイルに変換します。 以下のようなXSLファイルを作成しました。 https://github.com/kouigenjimonogatari/kouigenjimonogatari.github.io/blob/master/xsl/tex.xsl <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tei="http://www.tei-c.org/ns/1.0"> <xsl:output method="text" encoding="UTF-8"/> <xsl:template match="/"> \documentclass[a4paper,11pt,landscape]{ltjtarticle} \usepackage{xcolor} \usepackage{luatexja-fontspec} % fontspec を LuaTeX-ja と共に利用 \usepackage[top=2cm,bottom=2cm,left=2cm,right=2cm,textwidth=25cm]{geometry} % スタイル定義 \newcommand{\person}[1]{\textbf{\color{blue}#1}} \newcommand{\place}[1]{\textit{\color{green}#1}} % 日本語フォント設定 \setmainjfont{Noto Serif CJK JP} % 日本語フォントを指定 \title{<xsl:value-of select="//tei:title"/>} \date{} \begin{document} \maketitle % 本文 <xsl:for-each select="//tei:seg"> <xsl:apply-templates/> \par\medskip </xsl:for-each> \end{document} </xsl:template> <!-- 人名の処理 --> <xsl:template match="tei:persName"> \person{<xsl:value-of select="."/>}</xsl:template> <!-- 地名の処理 --> <xsl:template match="tei:placeName"> \place{<xsl:value-of select="."/>}</xsl:template> </xsl:stylesheet> 先にインストールしたxslt3を使って、以下のようにTEI/XMLファイルをTeXファイルに変換できます。 ...

Google Cloud Vision APIを用いて、単一ページから構成される透明テキスト付きPDFを作成する

Google Cloud Vision APIを用いて、単一ページから構成される透明テキスト付きPDFを作成する

概要 PDFを対象に、Google Cloud Vision APIを使って、透明テキスト付きPDFを作成する機会がありましたので、備忘録です。 以下、simpleで検索した例です。 背景 今回は単一ページから構成されるPDFを対象とします。 手順 画像の作成 OCRの対象とする画像を作成します。 デフォルトの設定だとボヤけた画像ができてしまったので、解像度を2倍に設定し、また後述するプロセスで、解像度を考慮した位置合わせを実施しています。 以下をインストールします。 PyMuPDF Pillow import fitz # PyMuPDF from PIL import Image import json from tqdm import tqdm import io # 入力PDFファイルと出力PDFファイル input_pdf_path = "./input.pdf" # 単一ページのPDFファイル output_pdf_path = "./output.pdf" # 入力PDFファイルを開き、単一ページを読み込み pdf_document = fitz.open(input_pdf_path) page = pdf_document[0] # 最初のページを選択 # ページを画像としてレンダリングし、OCRでテキストを抽出 # pix = page.get_pixmap() # 解像度300 DPIでレンダリング zoom = 2.0 # 解像度を上げるためにズーム設定 mat = fitz.Matrix(zoom, zoom) pix = page.get_pixmap(matrix=mat) img = Image.open(io.BytesIO(pix.tobytes("png"))) img.save("./image.png") Google Cloud Vision API 出力された画像を対象に、Google Cloud Vision APIを適用します。 { "textAnnotations": [ { "boundingPoly": { "vertices": [ { "x": 141, "y": 152 }, { "x": 1082, "y": 152 }, { "x": 1082, "y": 1410 }, { "x": 141, "y": 1410 } ] }, "description": "Sample PDF...", "locale": "la" }, { "boundingPoly": { "vertices": [ { "x": 141, "y": 159 }, { "x": 363, "y": 156 }, { "x": 364, "y": 216 }, { "x": 142, "y": 219 } ] }, "description": "Sample" }, { "boundingPoly": { "vertices": [ { "x": 382, "y": 156 }, { "x": 506, "y": 154 }, { "x": 507, "y": 213 }, { "x": 383, "y": 215 } ] }, "description": "PDF" }, ... 出力結果として得られるJSONファイルを./google_ocr.jsonといった名前で保存します。 ...

File Information Tool Set (FITS)を試す

File Information Tool Set (FITS)を試す

概要 Archivematicaの調査にあたり、File Information Tool Set (FITS)の挙動について確認したい点があり、Dockerを使って試してみましたので、その備忘録です。 https://github.com/harvard-lts/fits インストール 以下にDockerを用いたインストール方法が記載されています。 https://github.com/harvard-lts/fits?tab=readme-ov-file#docker-installation ただ、マニュアルに記載のある以下のページにアクセスしても、Dockerfileが含まれる最新のリリース(1.6.0)はダウンロードできませんでした。 https://projects.iq.harvard.edu/fits/downloads 代わりに、GitHubの以下のリリースページで、最新版のzipファイルをダウンロードできました。 https://github.com/harvard-lts/fits/releases/tag/1.6.0 あとは、READMEの記載の通りに展開、およびビルドを行いました。 ただし、M1 Macの場合、手順通りに実行したところ、以下のエラーが発生しました。 % docker run --rm -v `pwd`:/work fits -i fits.sh 2024-01-26 11:41:10 - ERROR - MediaInfo:95 - Error loading native library for this operating system for tool: MediaInfo. ostype=[Linux] -- jvmModel=[64] -- nativeLibPath=[/opt/fits/tools/mediainfo/linux] -- No native MediaInfo library for this OS java.lang.UnsatisfiedLinkError: Unable to load library 'mediainfo': libmediainfo.so: cannot open shared object file: No such file or directory libmediainfo.so: cannot open shared object file: No such file or directory /opt/fits/tools/mediainfo/linux/libmediainfo.so.0: cannot open shared object file: No such file or directory ... これに対して、ChatGPT 4に確認した結果、Dockerfileに以下の追記を行う指示がありました。 ...

IIIFマニフェストファイルからPDFファイルを作成する

IIIFマニフェストファイルからPDFファイルを作成する

概要 IIIFマニフェストファイルからPDFファイルを作成する機会がありました。このソリューションとして、以下のリポジトリが見つかりましたが、うまく動かすことができませんでした。 https://github.com/jbaiter/pdiiif そこで、上記リポジトリはJavaScriptを使用していますが、今回はPythonを用いた変換ツールを作成しました。 使い方 以下のノートブックからお試しいただけます。 https://colab.research.google.com/github/nakamura196/ndl_ocr/blob/main/iiif2pdf.ipynb 初回インストール時に、img2pdfをインストールしますが、PILのバージョンの関係で、「RESTART RUNTIME」ボタンが表示されますので、クリックの上、再度同じセルを実行してください。 オプションとして、「IIIFマニフェストURLの指定」「画像が格納されているフォルダのパスの指定」「ローカルに存在するIIIFマニフェストファイルのパスの指定」の3種類を用意しています。 注意点として、2023年5月末時点では、IIIF Presentation API v2にのみ対応しています。v3へは今後の対応を検討しています。 まとめ IIIFからのPDFファイルの作成にあたり、参考になりましたら幸いです。

TEI Critical Apparatus Toolboxを用いてTEI XMLをLaTeXに変換してみる

TEI Critical Apparatus Toolboxを用いてTEI XMLをLaTeXに変換してみる

概要 TEI Critical Apparatus Toolboxは、a tool for people preparing a natively digital TEI critical editionです。日本語訳の一例は以下です。 ネイティブ デジタル TEI クリティカル エディションを準備する人々のためのツール http://teicat.huma-num.fr/index.php 校異情報を可視化する機能を提供する他、複数の便利な機能を提供しています。 その中で、「TEI から LaTeX および PDF への変換」機能があることを教えていただいたので試してみます。 Print an edition 以下にアクセスします。 http://teicat.huma-num.fr/print.php this dummy edition fileという文字列のリンクをクリックして、以下のサンプルデータをダウンロードします。 http://teicat.huma-num.fr/print-feature-test-file.xml 上記のファイルを本ツールにアップロードします。変換に関する様々なオプションが用意されていますが、デフォルト設定のまま、画面下部の「送信」ボタンをクリックします。 すると、zipファイルがダウンロードされます。myEditionというフォルダが展開され、.log、.pdf、.texの3つのファイルが格納されていることが確認できます。 PDFの例は以下です。 TEXの例は以下です。 注意 2023年4月時点において、上記の機能は日本語を含むxmlファイルではうまく出力できませんでした。この点の改善についても今後検討してみたいと思います。 まとめ TEI/XMLファイルの変換ツール、フローを検討する上で、参考になりましたら幸いです。

TEI/XMLファイルを縦書きPDFに変換する方法の1例

TEI/XMLファイルを縦書きPDFに変換する方法の1例

概要 TEI/XMLファイルを縦書きPDFに変換する方法について、その1例を備忘録として残します。 以下のノートブックで、「校異源氏物語」を対象としたプログラムをお試しいただけます。 https://colab.research.google.com/github/nakamura196/ndl_ocr/blob/main/TEI_XMLファイルを縦書きPDFに変換する.ipynb 変換の流れ 今回は、Quartoを使用しています。 https://quarto.org/ インストールの方法は以下を参考にしてください。 https://quarto.org/docs/get-started/ TEI/XML -> qmd まずTEI/XMLファイルの内容をqmdファイルに変換します。以下は、変換スクリプトのサンプルです。 from bs4 import BeautifulSoup soup = BeautifulSoup(open(file,'r'), "xml") elements = soup.findChildren(text=True, recursive=True) import os id = os.path.splitext(os.path.basename(file))[0] title = soup.find("title").text author = soup.find("author").text elements = soup.find("body").find("p").findChildren() text = "" for e in elements: if e.name == "pb": text += "\n" if e.name == "seg": text += e.text + " \n" opath = f"data/{id}.qmd" os.makedirs(os.path.dirname(opath), exist_ok=True) text = f"""--- title: "{title}" author: "{author}" format: docx: reference-doc: /content/kouigenjimonogatari/tools/genji-doc-style.docx --- {text.strip()} """ with open(opath, "w") as f: f.write(text) 以下がqmdファイルの例です。 --- title: "校異源氏物語・きりつぼ" author: "池田亀鑑" format: docx: reference-doc: /content/kouigenjimonogatari/tools/genji-doc-style.docx --- いつれの御時にか女御更衣あまたさふらひ給けるなかにいとやむことなきゝは にはあらぬかすくれて時めき給ありけりはしめより我はと思あかり給へる御方 〱めさましきものにおとしめそねみ給おなしほとそれより下らうの更衣たち はましてやすからすあさゆふの宮つかへにつけても人の心をのみうこかしうら みをおふつもりにやありけむいとあつしくなりゆきもの心ほそけにさとかちな るをいよ〱あかすあはれなる物におもほして人のそしりをもえはゝからせ給 はす世のためしにもなりぬへき御もてなし也かんたちめうへ人なともあいなく めをそはめつゝいとまはゆき人の御おほえなりもろこしにもかゝることのおこ りにこそ世もみたれあしかりけれとやう〱あめのしたにもあちきなう人のも てなやみくさになりて楊貴妃のためしもひきいてつへくなりゆくにいとはした なきことおほかれとかたしけなき御心はへのたくひなきをたのみにてましらひ 給ちゝの大納言はなくなりてはゝ北の方なんいにしへの人のよしあるにておや うちくしさしあたりて世のおほえはなやかなる御方〱にもいたうおとらすな にことのきしきをももてなしたまひけれととりたてゝはか〱しきうしろみし なけれは事ある時はなをより所なく心ほそけ也さきの世にも御ちきりやふかか qmd -> Word(docx) 次に、qmdファイルをwordファイルに変換します。この時、事前に用意した縦書きのwordテンプレートを参照することで、マークダウン形式のテキストを縦書きのwordファイルに変換します。 ...