Python独習!

習得したPython知識をペイフォワード

Pythonで48bitカラー画像を読み込んでRGBの単色16bit画像に分解する

Pythonで48bitカラー画像を諧調を維持したまま取り扱うにはどうしたらよいか?
imageioモジュールを使う。PillowOpencvは対応していないらしい。

※画像フォーマットはTiffです。PNGでもできると説明している記事がありましたが、今回は試していません。
python - Resizing a 48bit PNG retaining its 48bits, without dropping it to a 24bit file - Stack Overflow

サンプルプログラム

概要

48bitカラー画像をRGBに分解して、16bitの単色画像(計3枚)として保存するプログラムです。GIMPやImageJでも同じことができます。
そもそも48bitカラーはモニターが対応していないと恩恵がないのですが、このサンプルプログラムのように単色化もしくはグレースケール化して画素値(数値)を取り扱う分には有用だと思っています。例えば、24bitカラーよりも照明の微小な変化を拾うことができます。

テスト画像として、左から右に向かって赤→緑→青というように変化する画像をGIMPで作成しました。48bitカラーのTiff形式です。
python_48bit_color

これをRGBに分解して保存すると、ちゃんと赤緑青の領域で分かれてるのがわかります。深さも16bitで狙い通りです。
python_48bit_color
python_48bit_color
※解像度が何故か1dpiになっているのかわかりません…

ソースコード

#python 3.8.1
#imageio 2.9.0

import imageio

img = imageio.imread('48bit.tif')

imageio.imwrite('16bit_Red.tif', img[:,:,0])
imageio.imwrite('16bit_Green.tif', img[:,:,1])
imageio.imwrite('16bit_Blue.tif', img[:,:,2])

Python IDLE を Raspbian 10 (Buster) にインストールする

手元にあったラズパイ(Raspberry Pi 2 Model B)のPythonを3.5.3 ⇒ 3.8.1にバージョンアップしたのですが、IDLEやThonnyに紐づいているバージョンが3.5.3のまま変わらず。しかし、Linuxはど素人のため自身で打開策を考えることができず…ネットでそれっぽい情報を見つけては試す、見つけては試すを繰り返すうちにOSが起動しなくなりました。

私の中での結論は「次のRaspbianを待つ」になりました。
Raspberry Pi Forumにも"system default python3 version" を変えるのは良くないというコメントがありました。
Whats the best way to install 3.8? - Raspberry Pi Forums
How do I install IDLE for Python 3.7.2? - Raspberry Pi Forums


結論を出したとはいえ、ラズパイが動かないままなのは困るのでOSをゼロからインストールしました。
手順を忘備録として残しておきます。

Raspberry Pi Imager 公式のイメージライター

www.itmedia.co.jp

バージョン確認

バイスのバージョン
$ cat /proc/device-tree/model

Raspberry Pi 2 Model B Rev 1.1

Raspbianのバージョン
$ cat /etc/debian_version

⇒ 10.7

Python3のバージョン
$ python3 -V

Python 3.7.3

Python IDLEインストール

Python IDLEが入っていないので別途インストールする必要があります。
インストールすると、IDLEにはPython 3.7.3が紐づきます。

$ sudo apt update
$ sudo apt install python3 idle3

Pythonで設定ファイル(.xml)の読み込みと書き換えをする

ソフトウェアの設定(好みの画面設定、出力ファイルの保存場所とか)を保存するには、ini、xmljsonどれがいいの?
ソフトウェアの目的に合わせて選択するしかない。ネットに沢山の情報が転がっているが、それらは異なる背景を持つプログラマーたちの意見である。そこからは何がベストなのか決めることはできない、という結論に至った。流行り廃りだけで言えば、iniファイルは時代遅れ、xmlが普及している、jsonが伸びてきている、といったところ。

サンプルプログラム

今回はxmlを取り上げることにして、気が向いたらjsonを試してみる。
作ったプログラムはありふれた内容だが、[Read]ボタンで設定ファイルから値を読み込んでテキストボックスに表示、[Write]ボタンでテキストボックス上で編集した内容を設定ファイルに書き込む、というもの。

〇[Read]ボタンを押すとconfig.xmlの値をテキストボックスに表示する
python_xml_read

〇[Write]ボタンを押すとconfig.xmlにテキストボックスの値を書き込む
python_xml_write

ソースコード

# ReadWriteXML_001.py
# Python 3.8.1

import tkinter as tk
import xml.etree.ElementTree as ET

class App(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.pack()
        self.create_widgets()

    def create_widgets(self):
        ## Make instances
        self.ent1 = tk.Entry(self, width=50)
        self.ent2 = tk.Entry(self, width=50)
        self.ent3 = tk.Entry(self, width=50)

        self.lb1 = tk.Label(self, text="ent1:")
        self.lb2 = tk.Label(self, text="ent2:")
        self.lb3 = tk.Label(self, text="ent3:")

        self.bt1 = tk.Button(self, text="Read",  width=6, command=self.read_xml)          
        self.bt2 = tk.Button(self, text="Write", width=6, command=self.write_xml)

        ## Place them
        self.lb1.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.lb2.grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        self.lb3.grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)

        self.ent1.grid(row=0, column=1, columnspan=2, padx=5, pady=5)
        self.ent2.grid(row=1, column=1, columnspan=2, padx=5, pady=5)
        self.ent3.grid(row=2, column=1, columnspan=2, padx=5, pady=5)
        
        self.bt1.grid(row=3, column=1, padx=5, pady=5)
        self.bt2.grid(row=3, column=2, padx=5, pady=5)

    def read_xml(self):
        try:
            tree = ET.parse('config.xml')
            root = tree.getroot()

            text_ent1 = root.find("./setting/ent1").text
            text_ent2 = root.find("./setting/ent2").text
            text_ent3 = root.find("./setting/ent3").text

            self.ent1.delete(0, tk.END)
            self.ent2.delete(0, tk.END)
            self.ent3.delete(0, tk.END)        
        
            self.ent1.insert(0, text_ent1)
            self.ent2.insert(0, text_ent2)
            self.ent3.insert(0, text_ent3)
        except Exception:
            print("例外エラー")
        else:
            print("XML読み込み完了")

    def write_xml(self):
        try:
            tree = ET.parse('config.xml')
            root = tree.getroot()

            text_ent1 = self.ent1.get()
            text_ent2 = self.ent2.get()
            text_ent3 = self.ent3.get()

            root.find("./setting/ent1").text = text_ent1
            root.find("./setting/ent2").text = text_ent2
            root.find("./setting/ent3").text = text_ent3

            tree.write('config.xml')
        except Exception:
            print("例外エラー")
        else:
            print("XML書き込み完了")

if __name__ == "__main__":
    root = tk.Tk()
    app = App(master=root)
    app.mainloop()

PythonでBASLERのカメラを制御する ~インストールの忘備録~

マシンビジョン業界で有名なBASLER社。産業用カメラの豊富なラインナップを展開していて、仕事で何かとお世話になることが多い。そんなBASLERカメラをPythonで制御するための環境を構築したので忘備録を残しておく。
インストール手順は色んなサイトで紹介されているので割愛する。



環境

パソコン

windows10 64bit

pylon

インストーラー : Basler_pylon_6.1.1.19832.exe
pylonバージョン詳細 : 下図参照
f:id:greenhornprofessional:20210106222054p:plain

python

Python3.8.1 32bit

pypylon

whlファイル : pypylon-1.5.4-cp38-cp38-win32.whl

※ whlファイルのインストール方法は他でも使えるので記載しておく。
 1. whlファイルを適当なフォルダに保存する。
 2. コマンドプロンプトでwhlファイルを保存したディレクトリに移動する。

C:\***\***>cd C:\***\***\***\Python\Python38-32

 3. pipでwhlファイルを指定してインストールする。

C:\***\***\***\Python\Python38-32>pip install pypylon-1.5.4-cp38-cp38-win32.whl

動作確認

  • aceシリーズでUSB3インターフェースのカメラを使う。
  • GitHubからサンプルソース grab.py をダウンロードする。
  • grab.pyを実行して、以下の画面が表示されればOK。

f:id:greenhornprofessional:20210106225305p:plain

Pythonでsecretsモジュールを使ってパスワードを自動生成する

Pythonでパスワード作るにはsecrets.choice()を使った方が良いと言われているけど、random.choice()と何が違うのか?
random.choice()で作成されたパスワードは予測される可能性がある。パスワードを構成するすべての文字がランダムに選ばれたように見えて実はどんな文字が選ばれているのかを第三者が計算することができる。 対して、secrets.choice()はより予測が難しくなったもの。

注意事項

secretsモジュールはより安全であると紹介しているサイトを見かけますが、PEP506のQ/Aに「専用の暗号化ソフトウェアの代用にはならない」との記述があります。過度な信頼をしてはいけないと私は解釈しました。

この記事はrandom.choice()よりはsecrets.choice()のほうがベターだよね、ということをお伝えするのが目的であり、安全であるとお伝えする意図は一切ありません。ご使用は自己責任でお願いします。

解説

random.choice()

random.Randomクラスの関数であり、Mersenne Twisterという乱数発生器(アルゴリズム)を使用しているが、暗号化の目的には向いていない。
実は、randomモジュールは別に random.SystemRandomクラス も提供していて、このクラスはOSがもつ乱数発生器を利用して乱数を作成する。

secrets.choice()

secrets.SystemRandomクラスの関数であり、このクラスもOSがもつ乱数発生器を利用して乱数を作成する。

結局は…

random.SystemRandomのインスタンスを作ってchoice()メソッドを使えば、OSがもつ乱数発生器を利用することができるのに、このことを知らないユーザーが多いので新たにsecretsモジュールを設けて間違いを防ぐことにしたらしい。というようなことがPEP506に書いてあった。

サンプルプログラム

アルファベットの大文字と小文字、数字0~9の中から10文字をランダムに選びパスワードを作成する。そして、パスワードをクリップボードにコピーし、念のためテキストファイル(.txt)にしてデスクトップにも保存する。というプログラム。
python_create_password_secrets

ソースコード

import os
import datetime
import string
import secrets
import ctypes
import pyperclip

#Determine password length
len_pw = 10

#Create password
def create_pw(length):
    chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
    pw = ''.join([secrets.choice(chars) for i in range(length)])

    return pw

#Save as txt
def save_txt(password):
    now = datetime.datetime.now()
    filename = "PassWord_{:%Y%m%d%H%M%S}.txt".format(now)
    desktop = os.getenv("HOMEDRIVE") + os.getenv("HOMEPATH") + "\\Desktop"

    with open(os.path.join(desktop, filename), mode='a') as f:
        f.write(password)

def main():
    x = create_pw(len_pw)
    save_txt(x)
    pyperclip.copy(x)
    ctypes.windll.user32.MessageBoxW(
        0,
        "パスワードをクリップボードにコピーしました",
        "Complete!!",
        0x00000040)

if __name__ == '__main__':
    main()

Pythonでグラフのプロットとプロットの間の値を読み取る

実験で〇〇を測定したけど測定間隔が粗かった。データとデータの間にある値が欲しんだけど…
interpolateのスプライン補間でデータ数を増やす。※あくまでもデータの“補間”ですのでその意味をちゃんと理解してご使用ください。

解説

スプライン補間の詳細はググってもらうとして、interpolateの使い方は以下のサイトが非常にわかりやすかった。応用例は↓↓のサンプルプログラムを参照してほしい。
org-technology.com

サンプルプログラム

エクセルからデータを読み込んで、任意の「横軸の値」を与えるとそれに対応するデータを返すというプログラム。
元データは以下のようにエクセルで表形式で保存してあるとする。A列が横軸データ、B列が縦軸データとなっている。見てのとおりデータ数は26個。

元データ(エクセルシート)

これをデータ数1000個でスプライン補間すると以下の青線グラフになる。赤線は元データのグラフ。

python-spline-スプライン補間
スプライン補間結果

ここで、例えば課題のレポートに横軸が10.5における測定値が必要だったことが後からわかったとする。元データには存在しないので再測定するか…もしくは、スプライン補間したデータを求めるか。サンプルプログラムでは44行目、48行目のようにすることでスプライン補間結果から値を得ている。

x=10.5に対応するyは205.713です


ソースコード

# 37_SplineCurve_001.py
# python 3.8.1
# openpyxl 3.0.3
# coding: utf-8

import numpy as np
from scipy import interpolate
from matplotlib import pyplot as plt
import openpyxl


def read_excel(wb_path):
    col_A = []
    col_B = []
    
    wb = openpyxl.load_workbook(wb_path)
    sheet_list = wb.sheetnames
    sheet = wb[sheet_list[0]]
    
    num_data = sheet.max_row

    for i in range(1, num_data+1):
        cell = sheet.cell(row=i, column=1)
        col_A.append(cell.value)

        cell = sheet.cell(row=i, column=2)
        col_B.append(cell.value)

    return col_A, col_B


def interpolate_spline(data_x, data_y, xoi):
    data_x_min = min(data_x)
    data_x_max = max(data_x)

    interpolated_func = interpolate.interp1d(data_x, data_y, kind="quadratic")
    corr_data_x = np.linspace(data_x_min, data_x_max, 1000)
    corr_data_y = interpolated_func(corr_data_x)

#    plt.plot(data_x, data_y,"r")
#    plt.plot(corr_data_x, corr_data_y)
#    plt.show()

    return interpolated_func(xoi)

if __name__ == '__main__':

    xoi = 10.5  #欲しいy値に対応するx値 (X of Interest) 
        
    wb_path = 'OriginalData.xlsx'
    x, y = read_excel(wb_path)
    yoi = interpolate_spline(x, y, xoi)  #欲しいy値を求める (Y of Interest)
    print("x={}に対応するyは{:.3f}です".format(xoi, yoi))


Pythonで自動化!Outlookメールを自動送信する

STMPサーバー?よく分からない…それを使わずにメールを自動送信する方法あるか?
win32comモジュールを使ってOutlookを操作する

解説

Pythonでメール操作する方法をググるstmplibを使用する方法が多く出てくる。この方法はSTMPサーバーやパスワードを指定する必要があり、詳しくない人だとちょっと忌避感があるかもしれない。そこで、手軽に実現する方法としてwin32comを使用する。サーバーとかパスワードの指定不要!
コードも簡単これだけ↓↓で、勝手にOutlookが立ち上がってメールが送信される。

from win32com import client
outlook = client.Dispatch('Outlook.Application')
new_email = outlook.CreateItem(0)
new_email.BodyFormat = 1
new_email.To = '送信メールアドレス' 
new_email.Subject = 'タイトル'
new_email.Body = '本文'
new_email.Send()

Line 4

メールの書式設定を選択する。
1:テキスト 2:HTML 3:リッチテキスト
python-win32com-outlook

Line 5,6,7

必要事項をここにべた書きするか、変数でもよい。

Line 8

このタイミングでメールが送信される。動作確認したい場合は代わりにnew_email.Display(True)を入れておく。送信前のメール画面が確認できる。


サンプルプログラム

毎日、朝9時と夕方18時に上司にメール連絡を入れるプログラムを作成した。リモートワークの勤怠管理のための定時連絡を自動化する、という想定。

  • 朝9時に送信されるメール画面。キャプチャーするためにnew_email.Display(True)で送信しないように止めている。
Outlook 自動作成されたメール画面①
  • 夕方18時に送信されるメール画面。
Outlook 自動作成されたメール画面②


ソースコード

# 36_EmailAutomatically_001.py
# python 3.8.1
# schedule 0.6.0
# coding: utf-8

import time
from win32com import client
import schedule

def makeEmail(set_subject, set_body):
    outlook = client.Dispatch('Outlook.Application')
    new_email = outlook.CreateItem(0)

    #フォーマットを指定する。1:テキスト 2:HTML 3:リッチテキスト
    new_email.BodyFormat = 1

    #送信先メールアドレス
    new_email.To = 'test1@test.com; test2@test.com'
    new_email.CC = 'test3@test.com'

    #メールタイトル
    new_email.Subject = set_subject

    #メール本文
    new_email.Body = set_body

    #メール送信実行
    new_email.Display(True)   #Outlookの新規メール画面が開くとこまで
#   new_email.Send()          #問答無用で送信するモード

def main():
    #朝用の定型文
    subj_morning = "[連絡]業務開始"
    body_morning = "〇〇さん\n業務開始します。\n□□"

    #夕方用の定型文
    subj_afternoon = "[連絡]業務終了"
    body_afternoon = "〇〇さん\n業務終了します。\nお疲れさまでした。\n□□"

    #朝9時と、夕方18時にタイマーをセットする
    schedule.every().day.at("9:00").do(makeEmail, subj_morning, body_morning)
    schedule.every().day.at("18:00").do(makeEmail, subj_afternoon, body_afternoon)

    while True:
        schedule.run_pending()
        time.sleep(1)

if __name__ == '__main__':
    main()

参考サイト

schedule · PyPI