ベース図面(pdf)から変更情報(エクセル)を読み込んで赤図(pdf)を出力する方法

事前準備(必須)

このコードを動かすには、以下の3つを準備する必要があります。

  1. Tesseract OCRのインストール
    • Googleが開発したOCRエンジンです。
    • Windowsの場合: インストーラーからダウンロードしてインストールしてください。インストール時に「Additional language data」で**Japanese (日本語)**にチェックを入れてください。
    • インストールパス(例: C:\Program Files\Tesseract-OCR\tesseract.exe)をコード内の設定箇所に記述します。
  2. Popplerのインストール
    • PDFを画像に変換するために必要です。
    • Windowsの場合: Poppler for Windowsなどからダウンロードし、解凍したフォルダのbinフォルダへのパスを通すか、コード内で指定します。
  3. Pythonライブラリのインストール
    • コマンドプロンプトやターミナルで以下を実行してください。
      pip install pandas openpyxl pdf2image pytesseract Pillow
import tkinter as tk
from tkinter import filedialog, messagebox
import pandas as pd
from pdf2image import convert_from_path
import pytesseract
from PIL import Image, ImageDraw, ImageFont
import os
import sys

# ==========================================
# 設定エリア (環境に合わせて変更してください)
# ==========================================

# Tesseract OCRの実行ファイルパス
# ※インストール場所に合わせて変更してください
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

# Popplerのbinフォルダへのパス
# ※Popplerを環境変数PATHに通していない場合はここで指定してください。通している場合はNoneでOK。
POPPLER_PATH = r'C:\Program Files\poppler-25.12.0\Library\bin'
#POPPLER_PATH = None 

# 日本語フォントのパス (書き込み用)
# Windows標準のMSゴシックを指定しています
FONT_PATH = r'C:\Windows\Fonts\msgothic.ttc'

# ==========================================

def select_file(title, filetypes):
    root = tk.Tk()
    root.withdraw()  # メインウィンドウを隠す
    file_path = filedialog.askopenfilename(title=title, filetypes=filetypes)
    return file_path

def create_red_lined_pdf():
    # 1. ファイル選択
    print("ベース図面(PDF)を選択してください...")
    pdf_path = select_file("ベース図面(PDF)を選択", [("PDF files", "*.pdf")])
    if not pdf_path:
        print("キャンセルされました。")
        return

    print("エビデンスExcelを選択してください...")
    excel_path = select_file("エビデンスExcelを選択", [("Excel files", "*.xlsx")])
    if not excel_path:
        print("キャンセルされました。")
        return

    try:
        # 2. Excel読み込み
        # シート名: エビデンス2, B列: 変更前, C列: 変更後
        # header=0は1行目が見出しと仮定。データが1行目からある場合はheader=Noneにする
        df = pd.read_excel(excel_path, sheet_name='エビデンス2', usecols="B,C")
        
        # NaNを除去し、文字列に変換
        changes = []
        for index, row in df.iterrows():
            old_txt = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else ""
            new_txt = str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else ""
            if old_txt:
                changes.append({"old": old_txt, "new": new_txt})

        print(f"{len(changes)}件の変更指示を読み込みました。処理を開始します...")

        # 3. PDFを画像に変換 (OCRと描画のため)
        # dpi=300程度がOCR精度と速度のバランスが良い
        images = convert_from_path(pdf_path, dpi=300, poppler_path=POPPLER_PATH)
        
        processed_images = []

        for page_num, img in enumerate(images):
            print(f"ページ {page_num + 1} を処理中...")
            
            # OCR実行 (日本語と英語)
            # dataには文字とその座標(left, top, width, height)が含まれる
            ocr_data = pytesseract.image_to_data(img, lang='jpn+eng', output_type=pytesseract.Output.DICT)
            
            draw = ImageDraw.Draw(img)
            
            # フォント設定 (サイズは適宜調整が必要ですが、ここでは仮で30ptとします)
            try:
                font = ImageFont.truetype(FONT_PATH, size=30)
            except IOError:
                font = ImageFont.load_default()
                print("指定されたフォントが見つかりません。デフォルトフォントを使用します。")

            n_boxes = len(ocr_data['text'])
            
            # 見つかったテキストを結合して検索しやすくする工夫が必要ですが、
            # ここではシンプルに「OCRで検出された単語単位」でマッチングを試みます。
            # ※長い文章の場合、OCRは単語ごとに区切られるため、完全一致が難しい場合があります。
            
            for change in changes:
                target_text = change["old"]
                replace_text = change["new"]
                
                # OCR結果の中からターゲット文字列を探す
                # 注意: OCRは誤認識やスペースの入り方で完全一致しないことが多いです。
                # ここでは簡易的に「検出されたテキストにターゲットが含まれているか」を見ます。
                
                for i in range(n_boxes):
                    detected_text = ocr_data['text'][i].strip()
                    
                    # 空白や確信度が低いものはスキップ
                    if not detected_text:
                        continue
                        
                    # 簡易マッチング: 検出された単語がターゲットと一致、あるいはターゲットを含んでいる場合
                    # 厳密な図面修正には、座標指定や正規表現など更なる工夫が必要です。
                    if target_text in detected_text or detected_text in target_text:
                        # 誤検知を防ぐため、あまりに短い文字(1文字など)は、完全一致のみにする等の調整が推奨されます
                        if len(target_text) < 2 and target_text != detected_text:
                            continue

                        (x, y, w, h) = (ocr_data['left'][i], ocr_data['top'][i], ocr_data['width'][i], ocr_data['height'][i])
                        
                        # 1. 元の文字を消す (白塗り)
                        draw.rectangle([x, y, x + w, y + h], fill="white", outline=None)
                        
                        # 2. 新しい文字を書く (黒文字)
                        # 位置合わせは簡易的に左上合わせ
                        draw.text((x, y), replace_text, font=font, fill="black")
                        
                        # 3. 赤枠で囲む (赤図化)
                        # 少し余白を持たせて囲む
                        padding = 5
                        draw.rectangle([x - padding, y - padding, x + w + padding, y + h + padding], outline="red", width=3)
                        
                        print(f"  変更: '{detected_text}' -> '{replace_text}' (座標: {x},{y})")

            processed_images.append(img)

        # 4. 結果をPDFとして保存
        output_filename = os.path.splitext(os.path.basename(pdf_path))[0] + "_赤図.pdf"
        output_path = os.path.join(os.path.dirname(pdf_path), output_filename)
        
        if processed_images:
            processed_images[0].save(
                output_path, "PDF", resolution=100.0, save_all=True, append_images=processed_images[1:]
            )
            print(f"完了しました!ファイルは以下に保存されました:\n{output_path}")
            messagebox.showinfo("完了", f"赤図PDFを作成しました。\n{output_path}")
        else:
            print("画像の処理に失敗しました。")

    except Exception as e:
        import traceback
        traceback.print_exc()
        messagebox.showerror("エラー", f"エラーが発生しました:\n{e}")

if __name__ == "__main__":
    create_red_lined_pdf()

コメント

タイトルとURLをコピーしました