初期バージョン制作:Blue Basket(Windows向け業務自動化デスクトップアプリ開発者) 以降の開発・保守:space ocr(ビョルビ合同会社)
space ocr 活用デモプログラムの公募により、有償で制作を依頼した作品です。著作権は Blue Basket に帰属します。 space ocr は、著作権者から本ソースコードを改変・複製し、自社製品として無償/有償を問わず配布・販売する許諾 (非独占・期間の定めなし)を得ています。一般的なオープンソースライセンスではありません → LICENSE
不具合の報告や使い方のご質問など、お問い合わせの窓口は space ocr です(→ 8. クレジット・ライセンス・お問い合わせ)。 初期バージョン制作者は保守・サポートを行っていません。
請求書・納品書の写真やPDFが入ったフォルダを選び、「まとめて読み取り」を1回押すと、 必要な項目が表に並び、CSVで書き出せるWindowsアプリです。
space ocr API(https://api.space-ocr.com)を使っています。
開発者向けのAPIを、経理・総務の人がそのまま使える形(exe 1個)にするとどうなるか、
というのがこのデモの狙いです。
- 抽出する項目:日付/取引先/件名/金額/税額(
core/fields.pyで定義) - 出力:CSV(UTF-8 BOM付き)。Excel でも会計ソフトでもそのまま開けます
- 動作環境:Windows 10 / 11。Python のインストールは不要(exe に同梱)
動作するアプリ本体(exe 一式)は Releases からダウンロードできます。
書類まるごとCSV.exe を起動してフォルダを選ぶと、キーの入力欄が出ます
(残量を表示するために、読み取りより先にキーが必要になります)。
入力したキーは この PC の %APPDATA%\SpaceOcrDemo\spaceocr.env にだけ保存されます。
(環境変数の設定は不要です。次回からは聞かれません)
- ① フォルダを選ぶ — 書類の写真(PNG/JPEG/TIFF等)やPDFが入ったフォルダを選びます
- 選んだ直後に「画像3枚+PDF1本=5ページ/このあと5回消費します(無料枠の残り○回・残高○回)」 と表示されます。1ページ=1回なので、PDFはページ数で数えます
- 子フォルダは見に行きません(気づかないうちに去年の書類まで課金されるのを防ぐため)
- ② まとめて読み取り — 6ページずつ並列で読み取ります(50ページで約2分)
- ③ CSVで書き出す — 保存先を選ぶだけです
| 場所 | 何が出るか |
|---|---|
| 左(全件の表) | 全ページ・全項目。確認が必要なセルだけ色が付きます。セルはその場で直せます |
| 左(順に確認) | 確認が必要な項目を1件ずつ順番に出します |
| 右(原本) | 選んだセルの該当箇所を、原本全体を出したまま枠で囲みます。下に拡大を添えます |
セルの色は「人が何をすればいいか」で分かれています。
| 色 | 意味 | すること |
|---|---|---|
| 赤 | 打つ | 値が取れていません。原本を見て入力してください |
| 橙 | 選ぶ | 同じ値がページ内に複数あるなど、場所が定まりません。どれが正しいか決めてください |
| 黄 | 見比べる | 値も場所もありますが、文字が疑わしいです。原本と1文字ずつ照合してください |
| 緑 | 直した | あなたが入力し直した値です(CSVにはこの値が出ます) |
| 青 | 確認済み | 直さずに「このままでよい」と確認した項目です |
色は「見たほうがよい場所」を示しますが、「見なくていい場所」を保証しません。 そのため、このアプリは
- 「完了しました」と言い切りません(「4ページを読み取りました。要確認 打つ2件…」と出します)
- 全件の表をいつでも見られる場所に置いています(色付きだけを見る画面にしていません)
同じ画像を送り直すと、違う結果が返ることがあります(実測で確認しました)。 読み取った時点の結果を固定して、確認作業の途中で値が変わらないようにしてあります。 値を変えたいときは、人が直してください。それが確認作業そのものです。
| 通信先 | https://api.space-ocr.com のみ。ほかのサーバーへは何も送りません |
| 送るもの | 書類の画像と、抽出したい項目の定義だけ |
| 保存されるか | Idempotency-Key を使っていません。このヘッダーを付けると応答が最大24時間サーバーに保持されるため、あえて使わず、重複送信の防止はアプリ側で持っています |
| APIキー | %APPDATA%\SpaceOcrDemo\spaceocr.env にのみ保存。exe にもソースにも埋め込んでいません |
| 読み取り結果 | %APPDATA%\SpaceOcrDemo\sent_pages.json(ハッシュと応答だけ。書類そのものは保存しません) |
| クラウド | 使いません。アプリを消せば残りません |
# Python 3.12 系で確認しています
pip install -r requirements.txt
# 開発中はここにキーを置きます(リポジトリには入れません)
# C:\Users\<user>\secrets\spaceocr.env
# SPACEOCR_API_KEY=...
# SPACEOCR_API_BASE=https://api.space-ocr.com
python main.pyキーの探索順は 環境変数 SPACEOCR_API_KEY → %APPDATA%\SpaceOcrDemo\spaceocr.env
→ ~/secrets/spaceocr.env です(core/config.py)。
pytest # 73本。APIは呼びません(偽クライアントと記録済みの応答を使います)並列処理・台帳・振り分け表・CSV・異常系を固定しています。 実機で試すと1ページごとに課金されるため、壊れ方の検証はすべてテスト側に寄せています。
pyinstaller --noconfirm 書類まるごとCSV.spec書類まるごとCSV.spec は手書きです。使っていない Qt モジュールを1件ずつ理由付きで除外
しており(opengl32sw.dll 20.6MB、Qt Quick/Qml/Pdf、AVIF、lxml など)、
85.8MB → 57.8MB になっています。
ビルドした exe は、起動するかどうかを画面なしで確認できます。
dist\書類まるごとCSV.exe --selftest結果は標準出力と %TEMP%\spaceocr_demo_selftest.txt の両方に出ます
(--windowed の exe は標準出力を持たないため)。Qt の読み込みに加えて、
HTTPS が通るかも GET /amount で確認します(このエンドポイントは無料で、スキャンを消費しません)。
app/
├─ main.py 入口(--selftest で自己診断)
├─ core/ business logic(画面なしで動く/テスト対象)
│ ├─ config.py APIキーの読み書き
│ ├─ spaceocr.py API クライアント(薄い)
│ ├─ fields.py 抽出する5項目の定義
│ ├─ pages.py フォルダ → ページ列(PDF分割・ページ番号)
│ ├─ images.py 送る前の縮小(長辺4000px)
│ ├─ batch.py 並列実行・順序保証・失敗の隔離
│ ├─ ledger.py 送信済みページの台帳(二重課金の防止+結果の固定)
│ ├─ triage.py flag理由12種 →「打つ/選ぶ/見比べる」の割り付け
│ ├─ sheet.py 応答 → 人が直せる表(画面は印字のまま/CSVは normalized)
│ ├─ csvout.py CSV書き出し(UTF-8 BOM/CRLF)
│ └─ overlay.py 座標を画像に描く(検証用)
├─ ui/ 画面(PySide6)
│ ├─ main_window.py 全件の表+順に確認+原本
│ └─ image_view.py 原本の表示(拡大・パン・quadの枠)
├─ tools/ 検証・資料作成(納品物ではないが再現用に同梱)
├─ tests/ pytest 73本
├─ samples/ 自作のダミー請求書(実在の情報は含みません)
├─ probe_out/ APIの生の応答(フィードバックの根拠)
└─ 書類まるごとCSV.spec PyInstaller の設定
core/ は UI を一切 import しません。画面が無い環境(CI・テスト)でも全部動きます。
理由の詳細は docs/何を作ったか・なぜ選んだか.md に書いています。ここは索引です。
| 決めたこと | 理由(短く) |
|---|---|
枠は quad(4隅)で描く。box は使わない |
傾いた行を軸平行の矩形で囲むと隣の文字を飲み込む。実物で確認済み |
| 原本は全体表示のまま囲む。拡大は別枠 | 拡大だけ見せると「隣の行の似た数字を拾った」間違いに気づけない |
| flag理由ではなく行動で3つに分ける | 画面を使うのは経理・総務。overwide_box と ambiguous_occurrence の違いに意味はない |
| 優先順位は 打つ > 選ぶ > 見比べる | 場所が決まっていないのに文字を照合させると、人は違う場所と見比べて「合っている」と判断する |
verified=false は flag が無くても「見比べる」へ |
2エンジンが食い違った値は、拾わないと誰の目にも触れずCSVへ出る |
| 値が空の項目は、APIが何も言ってこなくても「打つ」へ | 実測で、見つからない項目は cells にも flagged にも現れなかった。放置すると空欄が黙って流れる |
画面は印字のまま/CSVは normalized |
印字のままは回ごとに揺れる(¥165,000-/165,000)。normalized は一定(165000) |
| 縮小はドキュメントの1.5MBでなく長辺4000px | サーバー側が長辺4000pxに縮小している。1.5MBに合わせると通る画質を自分で捨てる |
Idempotency-Key を使わない |
応答が24時間保持される。重複防止は台帳(core/ledger.py)で持つ |
| 「もう一度読み取る」を付けない | 同じ画像で結果が変わるため。台帳が読み取り時点の結果を固定する |
| CSVは UTF-8 BOM | Shift_JIS では API が返す半角 ¥(U+00A5)を書けない。BOM無しUTF-8は Excel が化けさせる |
python tools/preview_ui.py # 記録済みの応答で画面を開く(API未使用)
python tools/make_demo.py # 動作説明の動画とGIFを作り直す(API未使用・要 ffmpeg)
python tools/make_sample.py # ダミー請求書を作り直す
python tools/make_demo_data.py # デモ用の読み取り結果を取り直す(★ここだけAPIを消費)デモの映像は demo_data/ に固定したある1回の読み取り結果から作っています。
実測で結果は回ごと・時刻ごとに変わるため、資料側を固定しないと数字が動いてしまうためです。
- PDFはAPIが受け付けないので、アプリ側でページ画像に分けて1枚ずつ送っています(PyMuPDF・300dpi)
- HEIC(iPhoneの標準形式)は未対応です。PNG/JPEG/TIFF/BMP/WebP に対応しています
- 子フォルダは読みません(意図しない課金を避けるため)
- exe の起動に約4秒かかります(1ファイルにまとめた展開の時間)
- 抽出項目は5つ固定です。画面から項目を足す機能はデモの範囲外にしています
(
core/fields.pyを編集すれば増やせます) - 元ファイルが小さい大きな画像は、縮小後に PNG で送られて送信量がかえって増えることがあります。
core/images.pyが保存形式を「送信するデータの大きさ」ではなく「元ファイルの大きさ」で決めているためです。 実測では 8000×8000・9〜12MB の JPEG が base64 で約28〜29MB になり、API の JSON ボディ上限(28MB)に 達する/超えることがあります(14MB の JPEG は JPEG のまま 2.5MB で送られます)。正式公開前に修正予定です
| 初期バージョン制作 | Blue Basket — v1.0(2026年8月22日公開)までの設計・実装・検証・資料 |
| 以降の開発・保守 | space ocr(ビョルビ合同会社) — v1.0 より後の修正・機能追加・配布 |
v1.0 より後に加えられた変更は space ocr によるもので、Blue Basket の制作物ではありません。
確認が必要な項目を「打つ/選ぶ/見比べる」の3つに分けて優先順位を付ける考え方(core/triage.py)は、
初期バージョンで Blue Basket が設計したものです。
- 著作権は Blue Basket に帰属します
- space ocr は、本ソースコードを改変・複製し、自社製品として無償/有償を問わず配布・販売する許諾を、 非独占・期間の定めなしで得ています(2026年8月25日 合意)
- MIT や Apache などのオープンソースライセンスではありません。 space ocr 以外への利用許諾は行っていません。 リポジトリの公開は、制作内容を閲覧できるようにするためのものです
- 全文は LICENSE をご覧ください
不具合の報告・使い方のご質問・その他のお問い合わせは、すべて space ocr が窓口です。
- 本リポジトリの Issues
- メール:oisidonut@gmail.com
初期バージョン制作者(Blue Basket)は保守・サポートを行っていません。直接のお問い合わせはご遠慮ください。