Python独習!

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

Pythonでpipが対応しているWheelファイルを調べる

2021/01/13 更新

ネットワークにつながっていないパソコンにモジュールを追加したい場合、そのモジュールのWheelファイル(.whl)を使うとことでインストールすることができる。Wheelファイルを適当なディレクトリに置いて、pipで指定してあげればよい。ただし、自分のpython環境にマッチしたWheelをもってくる必要がある。
どうやってそれを調べるか?ググればたくさん出てくるが、それ通りにはできなかったので忘備録を残しておく。Python3.8 pip 20.1 環境における対応cpの調べ方の記事を見つけられなかったので苦労した

cpとはCPython versionの頭2文字のこと。Python = CPythonと思っていてよい。
What does version name 'cp27' or 'cp35' mean in Python? - Stack Overflow

要件

Pythonのバージョンを確認する

例えば、v3.8.1だったら『cp38』に対応したWheelファイルが必要になる。v3.7.* であれば『cp37』となる。

pipが対応しているcpを確認する

cp38に対応したWheelファイルを見つけても、pipがcp38に対応していないとインストールできない。
以下の.pyを実行すると対応cpがわかる。

#python 3.8.1
#pip 20.1.1
from setuptools import pep425tags
print(pep425tags.get_supported())
[('cp38', 'cp38m', 'win32'), ('cp38', 'none', 'win32'), ('py3', 'none', 'win32'), ('cp38', 'none', 'any'), ('cp3', 'none', 'any'), ('py38', 'none', 'any'), ('py3', 'none', 'any'), ('py37', 'none', 'any'), ('py36', 'none', 'any'), ('py35', 'none', 'any'), ('py34', 'none', 'any'), ('py33', 'none', 'any'), ('py32', 'none', 'any'), ('py31', 'none', 'any'), ('py30', 'none', 'any')]

Pythonでグラフ画像から数値を読み取ってエクセルに出力する

シーンとしては、例えば、製品Aと製品Bの性能を比較するときに特性図(グラフ)を参照することがある。カタログやWebにグラフは掲載されているが、画像になっているので重ね合わせて比較することができず、なんとなくAの方が優れてるかな?なんてあいまいな感じに終わってしまう。メーカーに問い合わせても数値を提供してくれることは稀。まぁそりゃそうだろう。
ということで、画像になっているグラフから数値を読み取って、それをエクセルに出力するプログラムを作った。

結果

参考データとしては十分に使えるレベルで数値化できたと思う。画像処理の膨張縮小、線の検出で位置ズレが起きているはずなのであくまで参考データ。
左が入力画像、右が出力結果。数値はエクセルに出力するがグラフ化は手作業で。
f:id:greenhornprofessional:20200603214010p:plain

注意事項としては、

  • 第一象限のグラフしか対応していない。(マイナスを含むデータは非対応)
  • 対数グラフは対応していない。
  • 入力するグラフ画像は補助線よりもデータの線が太い必要がある。画像処理で消せなくなる。
  • 入力するグラフ画像の淵はペイントなどで消す必要あり。上記の左画像の元データは以下。

 f:id:greenhornprofessional:20200603214749j:plain

プログラム

相変わらず、センスのなさに歯がゆい思いがする…まぁ動くのでよしとするが。

# 29_ExtractChart_001.py
# python 3.8.1
# opencv-python 4.1.2.30
# coding: utf-8
#
import cv2
import numpy as np
import datetime
import openpyxl

#===================#
# Define parameters #
#===================#
image = "f.png"
k = 2
th = 100
y_max = 1
x_min = 350
x_max = 750

#=================#
# Define function #
#=================#
# Function for extracting XY data from an imaged chart.
def get_profile():
    ## Add closing and threshold to erase auxiliary lines on the chart.
    img = cv2.imread(image, 0)
    kernel = np.ones((k, k), np.uint8)
    ret,img = cv2.threshold(img, th, 255, cv2.THRESH_BINARY)
    img = cv2.dilate(img, kernel,iterations = 1)
    img = cv2.erode(img, kernel,iterations = 1)
#    img = cv2.dilate(img, kernel,iterations = 10)
#    img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
#    img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
    cv2.imshow("output", img)

    ## Get num of pixels of img. h:height  w:width
    h, w = img.shape
    
    ## Array for XY data. [X, Y].    
    profile = []
    ## print(type(profie)) -> <class 'list'> Not nparray!

    ## Get each XY-coordinate of each points on the line of the chart. 
    for i in range(w):
        line = img[:,i]
        edge = [j for j in range(h-1) if line[j] != line[j+1]]      ##Seach two inflection points (W to B and B to W) on a vertical line.
        if len(edge) < 1:
            val = 0
        elif len(edge) == 1:
            if edge[0] > h/2:
                val = 0
            else:
                val = h
        else:
            val = h - sum(edge)/2       ## Reverse Y-coordinate(top to bottom -> bottom to top). 
        point = [i+1, val]              ## XY data [X, Y]
        profile.append(point)

    ## Do unit conversion on X and Y coordinate.
    y = max(profile, key= lambda p:p[1])[1]     ##Get maximum value of Y data.
    for q in range(w):
        profile[q][0] = profile[q][0] * (x_max - x_min) / w + x_min
        profile[q][1] = profile[q][1] * y_max / y

    return profile

# Export 2Darray to xlsx format.           
def export_xlsx(arry):
    wb = openpyxl.Workbook()
    ws = wb.create_sheet(index= 0, title = "Line profile")
    ws = wb["Line profile"]             ## I don't know how this works.
    wb.active = wb.sheetnames.index("Line profile")

    row = 1
    for i in arry:
        celA = "A" + str(row)
        celB = "B" + str(row)
        ws[celA] = i[0]
        ws[celB] = i[1]
        row += 1
    try:
        now = datetime.datetime.now()
        wb.save('LineProfile_{0:%Y%m%d%H%M%S}.xlsx'.format(now))
        print("Save completed")
    except:
        print("Save failed!")
  
#======#
# Main #
#======#
if __name__ == "__main__":
    export_xlsx(get_profile())

Pythonで円と四角を描画する

顕微鏡の視野とTVアダプタレンズの関係を描画するプログラムを作った。

結果

f:id:greenhornprofessional:20200516162955p:plain

プログラム

# 27_FNwithTVlens_001.py
# python 3.8.1
# coding: utf-8

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np

#============#
# Parameters #
#============#
W = 6.9
H = 5.2

#=============#
# Draw images #
#=============#

# Field of View
circ22 = patches.Circle(xy=(0, 0), radius=22/2,   ec='black', fill=False)
circ26 = patches.Circle(xy=(0, 0), radius=26.5/2, ec='black', fill=False)

# TV lens 1X
rect = patches.Rectangle((-W/2, -H/2), width=W, height=H, ec='r', fill=False)

# TV lens 0.35X
W035 = W / 0.35
H035 = H / 0.35
rect035 = patches.Rectangle((-W035/2, -H035/2), width=W035, height=H035, ec='b', fill=False)

# TV lens 0.5X
W05 = W / 0.5
H05 = H / 0.5
rect05 = patches.Rectangle((-W05/2, -H05/2), width=W05, height=H05, ec='g', fill=False)

# TV lens 0.65X
W065 = W / 0.65
H065 = H / 0.65
rect065 = patches.Rectangle((-W065/2, -H065/2), width=W065, height=H065, ec='orange', fill=False)

ax = plt.axes() 
ax.add_patch(circ22)
ax.add_patch(circ26)
ax.add_patch(rect)
ax.add_patch(rect035)
ax.add_patch(rect05)
ax.add_patch(rect065)

ax.axis('scaled')
ax.set_xlim(-14, 14)
ax.set_ylim(-14, 14)
ax.set_xticks(np.arange(-14, 14), minor=True)
ax.set_yticks(np.arange(-14, 14), minor=True)
plt.grid(which='both', axis='both', color='gray', alpha=0.3)
plt.show()

Pythonで2つのリスト間の重複要素をとる方法

2つのリストで重複している要素を得る場合、for文とif文で処理していたが、setで簡単にできると知った。

入出力は以下のとおり。

入力:dataset1 = 'abcd' , dataset2 = 'cdefg'
出力:'c', 'd'

プログラムはCase1がfor + if 処理。Case2がsetを利用する場合。
行数は変わらないが、例えば、比較したいリストが3つになった場合、Case2だと & を付け足すだけでリストを追加できる利点がある。

def case1(dataset1, dataset2):
    comp = [letter for letter in dataset1 if letter in dataset2]
    return sorted(comp)

def case2(dataset1, dataset2):
    comp = list(set(dataset1) & set(dataset2))
    return sorted(comp)

if __name__ == "__main__":

    dataset1 = 'abcd'
    dataset2 = 'cdefg'

    print("case1_result ->", case1(dataset1, dataset2))
    print("case2_result ->", case2(dataset1, dataset2))


実行結果は以下のとおり。

case1_result -> ['c', 'd']
case2_result -> ['c', 'd']

Pythonでsorted関数で第2キー(Key)をセットする方法

リスト要素の並び替えでsorted()をよく使うが、並び替えの条件(キー)が1個だけなのは不便だなと思っていた。
しかし、他人さまのコードで並び替え条件を2個使っているものがあった。
使い方を理解したいので、メモ書き程度に残しておく。
解説しているサイトを見つけられなかったので、正しいかは分からないが挙動からすると自分の理解でたぶんあってる。

題材は以下の記事を流用する。
greenhornprofessional.hatenablog.com


入力は以下の通り。並び替えの条件は、①頻度順、②同じ頻度の場合は入力リストのindex順とする。
※ちなみに上記の過去記事でも条件②index順を考慮している。most_common()がそれ。

入力:['c', 'c', 'b', 'd', 'c', 'b', 'a', 'b', 'e']
出力:['c', 'c', 'c', 'b', 'b', 'b', 'd', 'a', 'e']

プログラムは以下。
Keyにlambdaを使って、式の中身をタプルで記述することで、並び替え条件を2つセットしている。
比較のためにCase4から並び替え条件②を消した場合をCase5として書いてある。

def case4(dataset):
    return sorted(dataset, key=lambda i:(dataset.count(i),-dataset.index(i)), reverse=True)

def case5(dataset):
    return sorted(dataset, key=lambda i: dataset.count(i)                   , reverse=True)

if __name__ == "__main__":

    dataset = ['c', 'c', 'b', 'd', 'c', 'b', 'a', 'b', 'e']

    print("case4_result ->", case4(dataset))
    print("case5_result ->", case5(dataset))


実行結果が以下。頻度順なので、cとbが分裂できていない。

case4_result -> ['c', 'c', 'c', 'b', 'b', 'b', 'd', 'a', 'e']
case5_result -> ['c', 'c', 'b', 'c', 'b', 'b', 'd', 'a', 'e']

Pythonで二重ループ(for文の入れ子)の内包表記

他人のコードを見ていると2重ループが内包表記で記述されていることがある。
リストの要素を頻度順に並び変えるプログラムを考えていて、また見かけたのでこれを機に使い方を勉強した。

題材となるプログラムは、入力に対して以下のように出力がほしい。

入力:['c', 'c', 'b', 'd', 'c', 'b', 'a', 'b', 'e']
出力:['c', 'c', 'c', 'b', 'b', 'b', 'd', 'a', 'e']


プログラムはこんな感じになるかと。
Case1は自分で考えたもの。難産だった。
Case2は他人さまのコードを参考に、for文の入れ子で記述したもの。
Case3はCase2を内包表記で書いたもの。

from collections import Counter as cont

def case1(dataset):
    s_dataset = cont(dataset).most_common()
    result = []
    for i in range(len(s_dataset)):
        for j in range(s_dataset[i][1]):
            result.append(s_dataset[i][0])
    return result

def case2(dataset):
    result = []
    for k, v in cont(dataset).most_common():
        for i in [k] * v:                       # ['a'] * 3 = ['a', 'a', 'a']
            result.append(i)
    return result

def case3(dataset): # showing case2 in a way of list comprehensitions
    return [i for k, v in cont(dataset).most_common() for i in [k] * v]

if __name__ == "__main__":

    dataset = ['c', 'c', 'b', 'd', 'c', 'b', 'a', 'b', 'e']

    print("case1_result ->", case1(dataset))
    print("case2_result ->", case2(dataset))
    print("case3_result ->", case3(dataset))


実行結果は以下の通り。

case1_result -> ['c', 'c', 'c', 'b', 'b', 'b', 'd', 'a', 'e']
case2_result -> ['c', 'c', 'c', 'b', 'b', 'b', 'd', 'a', 'e']
case3_result -> ['c', 'c', 'c', 'b', 'b', 'b', 'd', 'a', 'e']

Pythonで重複しない要素をリストから削除する

測定値から異常値や低頻度の値を除去する際に使える(かも)。
例えば、以下のようにリスト内で重複していない要素(100と1)を削除したいケース。

入力:[100, 2, 2, 3, 3, 3, 2, 1, 2, 4, 4, 3]
出力:[2, 2, 3, 3, 3, 2, 2, 4, 4, 3]


プログラムはこんな感じで書ける。3種類。

def case1(dataset):
    temp = dataset.copy()
    for i in dataset:
        if not dataset.count(i)>1:
            temp.remove(i)
    return temp

def case2(dataset):
    temp = []
    for i in dataset:
        if dataset.count(i)>1:
            temp.append(i)
    return temp
            
def case3(dataset):
    #case2の内包表記#
    return [i for i in dataset if dataset.count(i)>1]

if __name__ == "__main__":

    dataset = [100, 2, 2, 3, 3, 3, 2, 1, 2, 4, 4, 3]

    print("case1_result ->", case1(dataset))
    print("case2_result ->", case2(dataset))
    print("case3_result ->", case3(dataset))


実行結果はどれも同じ。

case1_result -> [2, 2, 3, 3, 3, 2, 2, 4, 4, 3]
case2_result -> [2, 2, 3, 3, 3, 2, 2, 4, 4, 3]
case3_result -> [2, 2, 3, 3, 3, 2, 2, 4, 4, 3]
/* -----codeの行番号----- */