Pythonの覚書

Pythonの覚書として記録します。
よく利用するもの、ハマったものなどの覚書として、順次、追加していきます。
   動作環境は
    OS:BookwormPup64
    開発環境:VSCode
         Python:Python 3.11.2

別ページにあるもの。


・tkinterでファイルを選択し、パスとファイル名を取得

import os
import tkinter as tk
from tkinter import filedialog
'''
tkinterについて
python3より標準でインストールされているはずだが、BookwormPup64ではインストールされていなかった。
    python3-tk をパッケージマネージャでインストール(pipコマンドではインストールできない)

インストールされているか確認
    python -m tkinter --> tkinterのウィンドウが表示されたらインストールずみ
'''

# Tkinterウィンドウの作成
root = tk.Tk()
root.withdraw() # 非表示

# ファイル選択ダイアログの表示
file_path = filedialog.askopenfilename() # フルパス付きのファイル名

# os.path.sep                    : osの区切り文字
# os.path.dirname(file_path)     : dir名(パス)
# os.path.basename(file_path)    : ファイル名
# os.path.split(file_path)       : dirとファイル名 戻り値はタプル

file_dir,file_name=os.path.split(file_path)

print(file_dir+os.path.sep)      # --> /root/       (パス)
print(file_name)                 # --> test.mp4    (ファイル名)

・外部コマンドの実行

スクリプトの中から、外部コマンドを実行させたり、その結果を利用したい時がある。subprocess.run が便利である。

import subprocess

# pwdの実行
subprocess.run(['pwd']) # 実行されるが、pythonスクリプト内で利用できない

# 実行した結果を利用したいとき
# 実行結果をファイルにテキストとして保存 戻り値は stdout stderr returncode(成功:0)
# subprocess.run に渡す引数は、 ['ls','-l'] のようなlistとして渡す(ls -l の例)
#  .split()関数
#   空白、改行(\n)、タブ(\t)で分離し、リストとして戻り値を返す
#    cmd = 'ls -a'
#    cmd.split(' ') --> ['ls', '-a']
# コマンド実行中に、入力するものがあるときは、input='y' のように指定することで渡せる

# lsの実行
cmd = 'ls'          # 'ls *.mp4' のような「*」は利用できない
result = subprocess.run(cmd.split(),capture_output=True, text=True) 

# stdoutの中から、拡張子がmp4のファイルを出力
fnames=result.stdout.split('\n') # (sep='\n' は、区切り文字の指定)
for n in fnames:
    if n[-3:]=='mp4':
        print(n)

# stderr returncode の出力
print(f'stderr : {result.stderr} \n終了コード : {result.returncode}')

・動画のスロー再生(2024/10/17)

動画をスロー再生するためのスクリプト。使用モジュールは、OpenCVです。
クラス化したものは、
    https://note.com/izumi78/n/n4aff2128bfdb を参照

'''  2024/10/17
動画のスロー再生
  各種の設定すべき項目
    file_path = 'IMG_4938.MOV'          :  再生する動画のファイル名
    delay = 100    # [ms] スロー設定値   :  約 1000/(fps*delay) 倍速
    size = 0.33                               :  動画のサイズ変更(縦横の比率)
    start_time = 50 # 再生開始時刻[s]
    end_time = 70   # 再生終了時刻[s]
    time = [62,70,41,48,19,23] # チャプター時刻[s]  一時停止させたい時刻

  使用方法
    実行すると、再生開始時刻から再生終了時刻の間をスロー再生し、下記のタイミングで一時停止する
        (スロー再生は、deley[ms]分待って、1フレームごとに表示。フレームの補完処理はしていない)

    再生が一時停止する箇所
        スペースキーを押す
        チャプターの時刻
        最終フレーム

    一時停止中に、
        qで終了
        q以外のキーで継続再生
            (再生開始と再生終了の間を繰り返す)

'''

import cv2
import sys

# 各設定
file_path = 'IMG_4938.MOV' # カレントディレクトリ以外の場合は、パスを付加すること
delay = 100   # [ms] 待つ時間をセット スロー再生の速さを設定   約 1000/(fps*delay) 倍速
size = 0.33    # 拡大縮小サイズ

start_time = 60 # 再生開始時刻[s]
end_time = 70   # 再生終了時刻[s]
c_time = [62,70,41,48,19,23] # チャプター時間[s]  <-- この時刻で一時停止
c_time.sort()

# windowの名前 フレームを表示する時に必要 タイトル欄に表示される
window_name = 'frame'
                    
# 動画ファイルを開く
cap = cv2.VideoCapture(file_path) # <class 'cv2.VideoCapture'>
# オープンできないときは終了
if not cap.isOpened():
    print('ファイルをオープンできません。')
    sys.exit()
    
fps = cap.get(cv2.CAP_PROP_FPS)                           # fpsを取得
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)  # フレーム数を取得 float

print(f'総フレーム数 : {frame_count:.0f}     {frame_count/fps:.3f} [s]')

# チャプター時刻からフレーム番号を計算 --> 使用時は、popを使って捨てていく
frame_no = [] # チャプターをかけるフレーム番号
if len(c_time)>0:
    for t in c_time:
        frame_no.append(int(fps*t))

print(frame_no,' [frame]     ',c_time,' [s]')

# 再生制御のためのフレーム番号を設定
start_frame = int(fps*start_time) # 再生開始フレーム
end_frame = int(fps*end_time)     # 再生終了フレーム

# next_frame の設定(再生中、一時停止させるフレーム)
next_frame = frame_no.pop(0) # 取り出し捨てる
while True:
    if start_frame <= next_frame:    # チャプターをかけるフレームが、開始と終了フレームの間にある
        break
    elif len(frame_no) > 0:              # チャプター用の配列が空でない
        next_frame = frame_no.pop(0)    # 次の時刻を取り出し捨てる
    else:                                    # チャプター用の配列が空
        next_frame=end_frame
        break

# 繰り返し再生するためにbackupを取っておく    
next_frame0=next_frame
frame_no0 = frame_no.copy()

print(f'strat : {start_frame}\t end : {end_frame}\t next : {next_frame}')

# スローで再生
# 再生のための処理
cap.set(cv2.CAP_PROP_POS_FRAMES,start_frame) # 再生開始のフレームをセット
while True:
    recent_frame = cap.get(cv2.CAP_PROP_POS_FRAMES) # 現在のフレーム
    if recent_frame < frame_count: # 最終フレームを越えていない
        print(f'\r再生中 : {recent_frame:.0f} / {next_frame} [frame]\
              \t{recent_frame/fps:.3f} [s]',end='') # 有効桁:g
        
        # 1フレームを読み込む
        ret, frame0 = cap.read()
        # 読み込み成功の時、表示
        if ret:
            frame = cv2.resize(frame0, dsize=None, fx=size, fy=size)  # 等比拡大縮小
            #frame=frame0[50:150, 30:170]                               # フレームの部分抽出
            cv2.imshow(window_name, frame) # フレームの表示

    # 最終フレーム、チャプターをかけるフレーム、スペースキー入力で一時停止させるための処理
    #   スペースキー入力の入力待ち時間の調整で、スロー再生の速さ設定(delay はスロー設定値)
    #   停止中 qで終了 q以外で再生継続

    # 一時停止させる条件
    if recent_frame == end_frame or \
            recent_frame == next_frame or \
            cv2.waitKey(delay) & 0xFF == ord(' '): # スロー再生の設定を兼ねている(delay)
        
        print(f'\r一時停止(終了:q 継続:q以外) : {recent_frame:.0f} [frame]\
              \t{recent_frame/fps:.3f} [s]') # 有効桁:g
        
        # 終了の処理
        if cv2.waitKey(0) & 0xFF == ord('q'): # q で終了
            print(f'\n終了 : {recent_frame:.0f} [frame] \t{recent_frame/fps:.3f} [s]') # 有効桁:g
            break

        # 終了フレーム --> 繰り返し再生するための初期設定
        elif recent_frame == end_frame:
            cap.set(cv2.CAP_PROP_POS_FRAMES,start_frame) # 最初に戻す
            frame_no = frame_no0.copy()
            if len(frame_no) > 1:
                next_frame = frame_no.pop(0)
            else:
                next_frame = next_frame0

            print(f'strat : {start_frame}\t end : {end_frame}\t next : {next_frame}')

        # チャプターのフレーム --> 次のチャプターを設定        
        elif recent_frame == next_frame:
            if len(frame_no) > 0:
                next_frame = frame_no.pop(0) # 次のフレームをセット 0番目を取り出し、捨てる
            else:
                next_frame = frame_count-1 # 動画の最終フレーム

# ウィンドを閉じる
cv2.destroyWindow(window_name)

・エスケープシーケンスをVSCoede Jupyter で利用するときの注意点(2024/11/14)

ターミナルに出力されたものを書き直したくて、エスケープシーケンスを利用したスクリプトを作成していたときに、VSCoede Jupyterのセル出力では動作しないものがあったので、メモしておきます。(ターミナルでは動作します。)

動作しないか、変な動きをするエスケープシーケンスコード(CSI制御)
  カーソルの位置を制御したり、出力を消去するもの
    \033[1D \033[G \033[A \033[2K など
  
出力文字の着色などは問題なく動作する( \033[m )

改行をさせていなければ、\r を利用することでカーソル位置を行頭に戻すことが出きる。また、出力を消去するには、下記のように空白を出力することで消去したように見せかけることができる。

  s='\r'+' '*80+'\r' # 半角80個分を消すための文字列(上書き)

  print('123456',end='')       
  print(s,end='')  #  <<--- 現在の行出力をクリアし、行頭に戻す
       print('abc',end='')

または、IPython.displayの clear_output() を利用する。これは、セル出力を全て消去するメソッドであるので、実行後のprint()での出力は1行1列目からとなる。

from IPython.display import clear_output
import time

for i in range(10,0,-1):
    clear_output() # clsと同じ
    print(i)
    time.sleep(0.5)

・pandas データの四捨五入(2024/12/9)

roundメソッドは、5の扱いが偶数への丸め処理である。今回、偶数への丸めがない四捨五入をさせたかった。
'pandas.core.frame.DataFrame'のデータを、日常よく利用される四捨五入するための関数を作成したので、メモとして記録しておく。

roundメソッドは、データフレーム内に欠損値や文字列が含まれていると処理されないばかりか、エラーともならない。これは、私にとって大変なこと。エラーでなくてもせめて警告がでたら嬉しいのだけど・・・・
データの前処理の重要性をあらためて認識した。

自作した関数は、要素を1つづつ読み込み処理をしている。データのサイズがとても大きい場合で、処理時間が長く問題になる時は、再考すること。現状、問題ない。

# 四捨五入の処理
import pandas as pd

def round_45(d,column_name):
    '''
    小数点以下を四捨五入のための処理(pandas のroundは、偶数丸め)
        第1引数:<class 'pandas.core.series.Series'> または <class 'pandas.core.frame.DataFrame'>
        第2引数:四捨五入する列名        
        返り値:<class 'pandas.core.frame.DataFrame'>
        必要なモジュール: pandas

    DataFrame内の特定の1列のデータを、小数第一位を四捨五入する処理
    (列内に、NaN型、文字列のデータが含まれていても可)
    (関数は、値渡しで処理しているので、返り値は変数に代入することで受け取ること)
    '''
    
    data=pd.DataFrame(d).copy() # SeriesでもDataFrameに変換し、値渡しにするためコピー
    row_name=data.index         # index名が付いていても処理できるようにする

    row=0
    for i in data[column_name]:
        if isinstance(i, int) or isinstance(i, float): # 数値以外を除く(NaN、文字列、
            if i>=0:   # 0以上の時
                data.loc[row_name[row],column_name]=int(i+0.5)
            elif i<0:  # 負の数の時
                data.loc[row_name[row],column_name]=int(i-0.5)     
        row+=1

    return data
# 動作確認
import pandas as pd
data1 = {
    'name': ['Alice', 'Bob', 'Charlie','Daisy'], # https://name.ichiran.info/?dd77b8183f # 外国人の名前
    'age': [25, 32, 37,28],
    'data':[50.5,-85.6,float('nan'),'test'],

}
df0 = pd.DataFrame(data1)
df0.index=['a','b','c','d']
display('元データ',df0)

# 列内にNaNや文字列が含まれていると処理されない
display('round関数(列のデータタイプがobjectなので処理されない)',df0.data.round()) 

display('四捨五入',round_45(df0,'data')) # 自作の四捨五入関数の呼び出し

d=df0.data # <class 'pandas.core.series.Series'>
display('Series',round_45(d,'data'))

dd=df0.copy()
dd.loc[:,'data']=round_45(df0,'data') # 返り値の受け取り 処理結果でddを変更
display(dd,df0)
実行結果

'元データ'
  name	  age	data
a	Alice	25	50.5
b	Bob	32	-85.6
c	Charlie	37	NaN
d	Daisy	28	test

'round関数(列のデータタイプがobjectなので処理されない)'
a    50.5
b   -85.6
c     NaN
d    test
Name: data, dtype: object

'四捨五入'
  name	  age	data
a	Alice	25	51
b	Bob	32	-86
c	Charlie	37	NaN
d	Daisy	28	test

'Series'
data
a	51
b	-86
c	NaN
d	test

  name	  age	data
a	Alice	25	51
b	Bob	32	-86
c	Charlie	37	NaN
d	Daisy	28	test

  name	  age	data
a	Alice	25	50.5
b	Bob	32	-85.6
c	Charlie	37	NaN
d	Daisy	28	test

・lambda関数とmapメソッドを使って、四捨五入(2024/12/15)

前回作成した四捨五入をする処理を、次のように変更。これが一番簡素に記述できたかなと思う。

# DataFrameの特定列の四捨五入 2
import pandas as pd
 
# DataFrameを作成
df0 = pd.DataFrame({'index': ['A', 'B', 'C', float('nan'),'E','F'],
                    'float': [1.5, 2.5, 3.7,float('nan') ,-1.6,-5.2],
                    'int':[7,55,88,25,35,18]})

display('最初',df0)

# 四捨五入する列名のセット
col_name='float'

# 四捨五入
df0[col_name]=df0[[col_name]].dropna(subset=[col_name]).map(lambda x: x-0.5 if x < 0 else x+0.5).astype(int)


df0
実行結果
 
'最初'

 index	float	int
0	A	 1.5	 7
1	B	 2.5     55
2	C	 3.7	 88
3	NaN	 NaN	 25
4	E	-1.6	 35
5	F	-5.2	 18

  index	 float	int
0	A	 2.0	  7
1	B	 3.0	 55
2	C	 4.0	 88
3	NaN	 NaN	 25
4	E	-2.0	 35
5	F	-5.0	 18

・四捨五入の最終バージョン(2025/01/07)

四捨五入する関数の最終バージョンとして作成。丸める桁指定を追加してある。この四捨五入は、日常利用される方式で、偶数丸めでない。偶数丸めで良い場合は、round()関数を利用。

# 関数

import pandas as pd
 
def round45(s,n=0):
    '''
    引数
      s : pandasのSeries
            NaN、文字列が含まれていてもOK
            数値に扱える文字列も、文字列として扱う。(このデータの返り値は、NaN)

      n : 小数点以下の桁数(整数)
            正数     --> 小数n位
            初期値 0 --> 整数
            負数     -->  -1 は 10の位、-2は 100の位、・・・

    返り値 : Series  dtype(float64)
        データが数値であるもののみを四捨五入する(偶数丸めでない。偶数丸めは round() 関数を利用すること)
        NaN、文字列であったデータは、NaNとなる

        -->  文字列、None であった元データを残すときは、呼出側で処理をすることが必要
    '''
    
    # 前処理
    s45=s.dropna() # NaNを除く(NaNはfloat型なので、isinstanceで True として判定される。よって、この行が必要)
    s45=s45[s45.map(lambda b:True if isinstance(b,(int, float)) else False)] # 文字列を除く(数値のみにする処理)

    # 四捨五入
    if n<0:
        nn=10.0*n
        s45/=nn
        s45=s45.map(lambda x: x-0.5 if x < 0 else x+0.5).astype(int) # 四捨五入
        s45*=nn        
    elif n>0:
        nn=10.0*n
        s45*=nn
        s45=s45.map(lambda x: x-0.5 if x < 0 else x+0.5).astype(int) # 四捨五入
        s45/=nn
    else:
        s45=s45.map(lambda x: x-0.5 if x < 0 else x+0.5).astype(int) # 四捨五入

    return s45
# 動作確認

# DataFrameを作成
df = pd.DataFrame({'index': ['A', 'B', 'C', float('nan'),'E','F','G','H'],
                    'float': [1.55, 2.53, None,float('nan') ,-1.65,-5.2,'A','5.25'],
                    'int':[7,55,88,25,35,18,25,60]})

display('最初(最後の行の 5.25 は文字列)',df)


# 四捨五入する列名のセット
col_name='float'
df[col_name]=round45(df[col_name],1)
col_name='int'
df[col_name]=round45(df[col_name],-1)

display('処理後',df)

df.dtypes
 
'''
'最初(最後の行の 5.25 は文字列)'
  index	float	int
0	A	1.55	7
1	B	2.53	55
2	C	None	88
3	NaN	NaN	    25
4	E	-1.65	35
5	F	-5.2	18
6	G	 A	    25
7	H	5.25	60

'処理後'
  index	float	int
0	A	1.6	    10.0
1	B	2.5	    60.0
2	C	NaN	    90.0
3	NaN	NaN	    30.0
4	E	-1.7	40.0
5	F	-5.2	20.0
6	G	NaN	    30.0
7	H	NaN	    60.0

index     object
float    float64
int      float64
dtype: object
'''

いいなと思ったら応援しよう!