PythonでPDFからテキストを抽出する

$ pip3 install pdfminer3

#!/usr/bin/env python3  
# -*- coding: utf-8 -*-

from pdfminer3.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer3.converter import PDFPageAggregator
from pdfminer3.pdfpage import PDFPage
from pdfminer3.layout import LAParams, LTTextContainer

resourceManager = PDFResourceManager()
device = PDFPageAggregator(resourceManager, laparams=LAParams())

pdf_file_name = './sample.pdf'
with open(pdf_file_name, 'rb') as fp:
	interpreter = PDFPageInterpreter(resourceManager, device)
	for page in PDFPage.get_pages(fp):
		interpreter.process_page(page)
		layout = device.get_result()
		for lt in layout:
			if isinstance(lt, LTTextContainer):
				print(lt.get_text())
device.close()

読み込み対象のPDFはこちら

これで実行してみる
$ python3 pdf_text1.py
1. フリーランス新法概要 1

1. フリーランス新法(フリーランス保護新法) 概要

2022年秋の臨時国会でフリーランス保護新法が成立すると報道されています。では、フリーランス保護新法とは一体どんなものなのでしょうか。
業務委託を行う事業者の遵守事項が規定されます。

ア) 業務委託の開始・終了に関する義務
①事業委託の際の書面の交付等
事業者がフリーランスに対して仕事を依頼するときは、「業務委託の内容(仕事内容)、報酬額等」を記載した書面を交付するか、メールなどの電磁的記
録を提供しなければならない
また一定期間以上、継続してフリーランスと仕事をする場合、契約期間や契約の終了事由、中途解約時の費用などを合わせて記載する必要がある
②契約の中途解約・不更新の際の事前予告
「中途解除日または契約期間満了日の30日前まで」にその旨を予告をしなければならない。またフリーランスから求められれば、事業者は契約の終了
理由を明らかにする必要がある。

イ) 業務委託の募集に関する義務
①募集の際の的確表示
不特定多数のフリーランスを対象に仕事を募集する場合は、正確かつ最新の情報を伝えなければならない。虚偽の表示や誤解を生むような表示は禁

②募集に応じた者への条件明示、募集内容と契約内容が異なる場合の説明義務
事業者は応募してきたフリーランスに対し、仕事内容や報酬等を明示しなければんらない。募集時点で明示した情報と異なる内容で契約した場合は、
その旨を説明する必要がある。

ウ) 報酬の支払いに関する義務
事業者はフリーランスから納品物やサービスの提供を受けた日から60日以内に報酬を支払わなければならない。

エ) フリーランスと取引を行う事業者の禁止行為
フリーランスに責任のある理由なしに受領を拒否する
フリーランスに責任のある理由なしに報酬を減額する
フリーランスに責任のある理由なしに返品を行う
相場に比べ、著しく低い報酬額を不当に定める
正当な理由なく、物の購入やサービスの利用を強制する
金銭やサービス、その他の経済上の利益を提供させる
フリーランスに責任のある理由なしに給与の内容を変更させたり、やり直させたりする

関係者外秘

1

2. フリーランス新法概要

2. フリーランス新法(フリーランス保護新法) 概要

オ) 就業環境の整備として事業者が取り組むべき事項
①ハラスメント対策
②出産・育児・介護との両立への配慮

2. 違反した場合の対応等
行政から必要な履歴確保措置が執行される

3.フリーランスの申告及び国が行う相談対応の実現
事業者に違反行為があった場合、フリーランスはその旨を国の行政機関に申告することができる。

関係者外秘

2

うおおおおおおお、これは凄い

【python3】タイムアウト設定

#!/usr/bin/env python3  
# -*- coding: utf-8 -*-

import sys
import time
from time_decorator import timeout, TimeoutError

class TimeoutDecoratorTest(object):
	def test(self, sleep_sec, timeout_sec):
		@timeout(timeout_sec)
		def inner_test():
			print("Start sleep " + str(sleep_sec) + " seconds.")
			print("Timeout in " + str(timeout_sec) + " seconds.")
			time.sleep(sleep_sec)
		try:
			inner_test()
			print("End sleep.")
		except TimeoutError:
			print("Timeout!")

if __name__ == '__main__':
	sleep_sec = int(sys.argv[1])
	timeout_sec = int(sys.argv[2])
	t = TimeoutDecoratorTest()
	t.test(sleep_sec, timout_sec)

タイムアウト処理を行うためのライブラリ
pip install wrap-timeout-decorator

import wrapt_timeout_decorator

@wrapt_timeout_decorator.timeout(dec_timeout=30)
def func():
while True:
pass

if __name__ == ‘__main__’:
func()

タイムアウトの設定をするのね

pythonのdebug

pdbを使用できる

import pdb

def factorial(n, sum=0):
	if n == 0:
		return sum

	pdb.set_trace()
	sum += n
	print(sum)
	return factorial(n-1, sum)

if __name__ == "__main__":
	factorial(5)
$ python3 test.py
> /home/vagrant/dev/test/test.py(8)factorial()
-> sum += n
(Pdb) bt
  /home/vagrant/dev/test/test.py(13)<module>()
-> factorial(5)
> /home/vagrant/dev/test/test.py(8)factorial()
-> sum += n
(Pdb) list
  3  	def factorial(n, sum=0):
  4  		if n == 0:
  5  			return sum
  6  	
  7  		pdb.set_trace()
  8  ->		sum += n
  9  		print(sum)
 10  		return factorial(n-1, sum)
 11  	
 12  	if __name__ == "__main__":
 13  		factorial(5)
(Pdb) 

Pythonのテストコード

# coding:utf-8

import unittest
import fizzbuzz as fb

class FizzBuzzTest(unittest.TestCase):
	def setUp(self):
		pass

	def tearDown(self):
		pass

	def test_normal(self):
		self.assertEqual(1, fb.fizzbuzz(1))

	def test_fizz(self):
		self.assertEqual("Fizz", fb.fizzbuzz(3))

	def test_buzz(self):
		self.assertEqual("Buzz", fb.fizzbuzz(5))

	def test_fizzbuzz(self):
		self.assertEqual("FizzBuzz", fb.fizzbuzz(3))

if __name__ == "__main__":
	unittest.main()

メインコード

# coding:utf-8

def fizzbuzz(number):
	if number % 15 == 0:
		return "FizzBuzz"
	if number % 5 == 0:
		return "Buzz"
	if number % 3 == 0:
		return "Fizz"

	return number

if __name__ == "__main__":
	for i in range(1, 101):
		print(fizzbuzz(i)) 

t$ python3 test_fizzbuzz.py
..F.
======================================================================
FAIL: test_fizzbuzz (__main__.FizzBuzzTest)
———————————————————————-
Traceback (most recent call last):
File “test_fizzbuzz.py”, line 23, in test_fizzbuzz
self.assertEqual(“FizzBuzz”, fb.fizzbuzz(3))
AssertionError: ‘FizzBuzz’ != ‘Fizz’
– FizzBuzz
+ Fizz

———————————————————————-
Ran 4 tests in 0.002s

FAILED (failures=1)

Pythonのpycファイルのマジックナンバー

Pythonファイル(平文ソースコード)をコンパイルするとpycファイルになる。
pythonユーザがpycファイルを作成する必要なし。モジュールとしてインポートされる場合に自動的に作成
元のモジュールが更新されると、次回インポート時にpycファイルも自動的に再作成

マジックナンバーとは、ファイルの種類を識別するため、ファイル先頭に付与する特別なバイト列。Pythonのバージョンごとにpycファイルのマジックナンバーが決まっている。

$ python3
Python 3.8.10 (default, Mar 15 2022, 12:22:08)
[GCC 9.4.0] on linux
Type “help”, “copyright”, “credits” or “license” for more information.
>>> import importlib.util
>>> importlib.util.MAGIC_NUMBER.hex()
‘550d0d0a’

Pythonでfor文を使って{辞書: [{辞書},{辞書},{辞書}…]}のjsonを作りたい

ありたいてに言うと、下記のような構造のjsonをfor文で作りたい

{
	"item": [
		{
			"area": "東京都","population": 1300, "capital": "東京"
		},
		{
			"area": "北海道","population": 538, "capital": "札幌市"
		},
		{
			"area": "沖縄","population": 143, "capital": "那覇市"
		}	
	]
}

for文で辞書を配列に入れて、それを辞書の中に入れてjsonにすればOK

ys = cl.OrderedDict()
result = []
for item in data:
	print(item["at"][:16] +" " + item["anm"] + " " + item["mag"] + " " + item["maxi"] + " " + item["cod"][:5] + " " + item["cod"][5:11]+ " " + item["cod"][12:-1])
	data = cl.OrderedDict()
	data["at"] = item["at"][:16]
	data["anm"] = item["anm"]
	data["mag"] = item["mag"]
	data["maxi"] = item["maxi"]
	data["tokei"] = item["cod"][:5]
	data["hokui"] = item["cod"][5:11]
	data["depth"] = item["cod"][12:-1]

	result.append(data)

ys["item"] = result
print(ys)

with open("test.json", "w") as f:
    json.dump(ys, f, ensure_ascii=False)

pythonによるjson操作と辞書の理解が浅かったので、これ作るのに丸一日かかった orz…
なんてこったい

Pythonでjson作成

import json

str = {
	"東京":{
		"population": 1300,
		"capital": "東京"
	},
	"北海道": {
		"population": 538,
		"capital": "札幌市"
	},
	"沖縄":{
		"population": 143,
		"capital": "那覇市"
	}
}

with open("population.json", "w") as f:
	json.dump(str, f, ensure_ascii=False)

{“東京”: {“population”: 1300, “capital”: “東京”}, “北海道”: {“population”: 538, “capital”: “札幌市”}, “沖縄”: {“population”: 143, “capital”: “那覇市”}}

なるほどー

[自然言語処理] チャットボット基礎

ChatterBot:
The program selects the closest matching response by searching for the closest matching known statement that matches the input, it then returns the most likely response to that statement based on how frequently each response is issued by the people the bot communicates with.

chat.txt

こんにちは
こちらこそ こんにちは
今日もよろしくお願いします

chat2.txt

こんばんは
こちらこそ こんばんは
もう眠いですね
おやすみなさい

main.py

from chatterbot import ChatBot 
from chatterbot.trainers import ListTrainer

bot = ChatBot("チャットボット試作品")

training_ = open('chat.text','r').readlines()
training2_ = open('chat2.text','r').readlines()

trainer = ListTrainer(bot)

trainer.train(training_)
trainer.train(training2_)

while True:
	try:
		bot_input = bot.get_response(input())
		print(bot_input)
	except(KeyboardInterrupt, EOFError, SystemExit):
		break

$ pip3 install chatterbot
$ pip3 install chatterbot-corpus
$ python3 main.py
[nltk_data] Downloading package stopwords to
[nltk_data] /home/vagrant/nltk_data…
[nltk_data] Package stopwords is already up-to-date!
[nltk_data] Downloading package averaged_perceptron_tagger to
[nltk_data] /home/vagrant/nltk_data…
[nltk_data] Package averaged_perceptron_tagger is already up-to-
[nltk_data] date!
List Trainer: [####################] 100%
List Trainer: [####################] 100%
こんにちは
こちらこそ こんにちは
もう眠いですね
おやすみなさい
もう眠いですね
おやすみなさい
こちらこそ こんばんは
もう眠いですね
aaa
もう眠いですね
そうですか
こちらこそ こんばんは
おはよう
もう眠いですね
こんにちは
こちらこそ こんにちは

なんだこれ…

[Python] labelImageを使って運転免許証を読み込み

lavelImageをやり直します

import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "data/vision.json"

import io
import cv2
import matplotlib.pyplot as plt
import matplotlib
from google.cloud import vision
import xml.etree.ElementTree as ET

img = cv2.imread("img/license.jpeg")

client = vision.ImageAnnotatorClient()
with io.open("img/license.jpeg", 'rb') as image_file:
	content = image_file.read()
image = vision.Image(content=content)
response = client.document_text_detection(image=image)

text_infos = []
document = response.full_text_annotation
for page in document.pages:
  for block in page.blocks:
    for paragraph in block.paragraphs:
      for word in paragraph.words:
        for symbol in word.symbols:
          bounding_box = symbol.bounding_box
          xmin = bounding_box.vertices[0].x
          ymin = bounding_box.vertices[0].y
          xmax = bounding_box.vertices[2].x
          ymax = bounding_box.vertices[2].y
          xcenter = (xmin+xmax)/2
          ycenter = (ymin+ymax)/2
          text = symbol.text
          text_infos.append([text, xcenter, ycenter])

tree = ET.parse("data/license.xml")
root = tree.getroot()
result_dict = {}
for obj in root.findall("./object"):
  name = obj.find('name').text
  xmin = obj.find('bndbox').find('xmin').text
  ymin = obj.find('bndbox').find('ymin').text
  xmax = obj.find('bndbox').find('xmax').text
  ymax = obj.find('bndbox').find('ymax').text
  xmin, ymin, xmax, ymax = int(xmin), int(ymin), int(xmax), int(ymax)
  texts = ''
  for text_info in text_infos:
    text = text_info[0]
    xcenter = text_info[1]
    ycenter = text_info[2]
    if xmin <= xcenter <= xmax and ymin <= ycenter <= ymax:
      texts += text
  result_dict[name] = texts
  
for k, v in result_dict.items():
    print('{} : {}'.format(k, v))

$ python3 app.py
name : 日本花子
birthday : 昭和61年5月1日生)
address : 東京都千代田区霞加喂2-1-2
data of issue : 令和01年05月07日12345
expiration date : 12024年(今和06年)06月01日未有动
driver license : 運転免許証
number : 第012345678900号
Public Safety Commission : 00000公安委員会

Finally
やっと来たね
というか、サイズが一定なら免許証以外でもOCRできるね

[Ubuntu] tesseractをinstallして使いたい

$ sudo apt -y install tesseract-ocr tesseract-ocr-jpn libtesseract-dev libleptonica-dev tesseract-ocr-script-jpan tesseract-ocr-script-jpan-vert
$ tesseract -v
tesseract 4.1.3
leptonica-1.79.0
libgif 5.1.4 : libjpeg 8d (libjpeg-turbo 2.0.3) : libpng 1.6.37 : libtiff 4.1.0 : zlib 1.2.11 : libwebp 0.6.1 : libopenjp2 2.3.1
Found AVX2
Found AVX
Found SSE
Found libarchive 3.4.0 zlib/1.2.11 liblzma/5.2.4 bz2lib/1.0.8 liblz4/1.9.2 libzstd/1.4.4
$ tesseract –list-langs
List of available languages (5):
Japanese
Japanese_vert
eng
jpn
osd
※osdとは、言語の判定、文字角度の識別を行う

$ tesseract test1.png output
->
$ tesseract test1.png stdout
Hello world
$ tesseract test2.png stdout -l jpn
こんにちは

### Pythonで使えるようにする
$ pip3 install pyocr

from PIL import Image
import sys
import pyocr

tools = pyocr.get_available_tools()
langs = tools[0].get_available_languages()

img = Image.open('test1.png')
txt = tools[0].image_to_string(
	img,
	lang=langs[0],
	builder=pyocr.builders.TextBuilder(tesseract_layout=6)
)
print(txt)

※tesseract_layout=6 が精度に重要

$ python3 main.py
Hello world

lang=’jpn’に変更すると、日本語も出力できる
$ python3 main.py
とこんにちは
|
tesseract_layout=3で実行すると…
$ python3 main.py
こんにちは

#### tesseract_layoutの意味
0 = Orientation and script detection (OSD) only.
1 = Automatic page segmentation with OSD.
2 = Automatic page segmentation, but no OSD, or OCR
3 = Fully automatic page segmentation, but no OSD. (Default)
4 = Assume a single column of text of variable sizes.
5 = Assume a single uniform block of vertically aligned text.
6 = Assume a single uniform block of text.
7 = Treat the image as a single text line.
8 = Treat the image as a single word.
9 = Treat the image as a single word in a circle.
10 = Treat the image as a single character.

0 =方向とスクリプトの検出(OSD)のみ。
1 = OSDによる自動ページセグメンテーション。
2 =自動ページセグメンテーション、ただしOSDまたはOCRなし
3 =完全自動のページセグメンテーション。ただし、OSDはありません。 (ディフォルト)
4 =可変サイズのテキストの単一列を想定します。
5 =垂直に配置されたテキストの単一の均一なブロックを想定します。
6 =単一の均一なテキストブロックを想定します。
7 =画像を単一のテキスト行として扱います。
8 =画像を1つの単語として扱います。
9 =画像を円の中の1つの単語として扱います。
10 =画像を1文字として扱います。

6で設定されているのが多いですね
文字角度を考慮しなければ3で、文字角度を考慮する場合は6でしょうか?
読み取りたい文字に併せて設定を変えながら試していくイメージか…