教育用GM管開発を振り返って(11)

www.radi-edu.jp
apgmman.hatenablog.com

放射線教育支援サイト「らでぃ」の実験集に掲載した「Pythonで計数経時変化の実験」のPythonプログラムについて解説する。Pythonやライブラリーのインストール方法や注意点、Pythonの文法の特徴などは「教育用GM管開発を振り返って(9)」に記載した。


Pythonで計数経時変化の実験」のプログラム例


1秒間の計数を繰り返し、経過秒数と1秒率を画面に表示するとともに、csvファイルに出力するプログラムで、ラドン220の半減期の測定や、統計的変動の実験に使用できる。


まず、必要なライブラリーをインポートする。このうち、numpyはPythonのインストールで同時にインストールされるので、ライブラリーとして別途インストールする必要はない。#以下はコメント文である。

import pyaudio #マイク入出力処理
import numpy as np
import matplotlib.pyplot as plt #グラフ処理
import cv2 #カメラ画像処理

次に、グローバル変数を宣言し、初期値を決める。閾値の変数thresholdは、パルス波高を予めモニターして数値を決め、キーボードから入力する。

global m, threshold #グローバル変数
m = 1 #経過時間
threshold = input('Threshold?') #閾値を設定

出力ファイルcps_data.csvを開き、ヘッダーを書き込んでおく。出力ファイルはExcelなどの処理ソフトを利用することを念頭にcsvファイルとした。

with open('cps_data.csv', mode='w') as fc: #計数値を書き込むファイルを新規モードで開く
    fc.write('Time(sec)' + ',' + 'CPS' + '\n') #タイトル

cps_data = [] #計数値の空リストを用意

次に一連の音声入力をストリーミングとしてデータ化し、計数からグラフ描画に至るまでを関数化する。CHUNKは入力単位でここでは、1024個ごとにサンプリングする。サンプリング周波数は変えられるが、デフォルトのままにした。録音時間は1秒である。

def streamandcount(): #入力からグラフ描画まで処理
    CHUNK = 1024 #入出力の長さ
    FORMAT = pyaudio.paInt16 #2Byte整数
    CHANNELS = 2 #ステレオ
    RATE = 44100 #サンプリング周波数(Hz)
    RECORD_SECONDS = 1 #録音時間(秒)

ストリーミングをオープンし、CHUNKごとのデータdata-rawをframesというlistに追記していく。

    p = pyaudio.PyAudio()
    stream = p.open(format = FORMAT,
                    channels = CHANNELS,
                    rate = RATE,
                    input = True, #入力のみ
                    frames_per_buffer = CHUNK)
    frames = [] #空のlistを用意

1秒分のデータを加算して、framesに追記していく。

    for i in range(int(RATE * RECORD_SECONDS // CHUNK)): #CHUNK単位で切り捨て
        data_raw = stream.read(CHUNK) 
        frames.append(data_raw) #43回分を加算

    frames_int16 = np.array(frames)

ここでストリームを閉じる。

    stream.stop_stream() #ストリームを閉じる
    stream.close()
    p.terminate()

以下は、計数と計数率の画面とファイルへの出力のための演算で、まず、2進数を10進数に変換し、データは片チャンネルだけでよいので、ステレオのLチャンネルのデータだけを抽出する。

    pulse = 0 #計数を初期化
    data_l = data[::2] #ステレオのLチャンネルを取り出す
    onflag = False #閾値超過フラッグを初期化
||< 

データが閾値を超えるとフラッグを立て、閾値を下回るとカウント1を加算し、フラッグを降ろす。閾値を超えたところから下回ったところまでを1カウントとするので、複数パルスでも重複カウントすることはない。

>|python|
for k in range(len(data_l)): #data_l全体が範囲
        dat = data_l[int(k)] #data_lのk番目の値
        if int(dat) > int(threshold) and not onflag: #閾値を初超過
            pulse = pulse + 1 #1カウントを加算
            onflag = True #閾値超過フラッグを立てる
        if int(dat) <= int(threshold): #閾値以下になったら
            onflag = False #閾値超過フラッグを降ろす 

計数率をCPSで画面に表示する。

    cps = pulse #CPSに換算
    print('CPS = ' + str(int(cps))) #計数値を画面に出力

同時に計数率をファイルに出力する。フォーマットは、経過秒数、1秒率となっている。 cps_data.append(cps) #計数値のlistに追加

    with open('cps_data.csv', mode='a') as fc: #ファイルを開く
        fc.write(str(m) + ',' + str(cps) + '\n') #経過と計数値を追記

さらに、入力信号の一部の44msec分をパルス波形のモニターとして上段のグラフに表示し、下段には経過時間と1秒率をグラフに表示して計数のトレンドが分かるようにした。

    fig = plt.figure(num = 1, figsize = (6, 7)) #グラフを定義
    plt.subplot(211) #グラフの上半分を指定
    plt.plot(data_l[200:1200]) #入力信号の一部44msec分を表示
    plt.ylim(-250, 1000) #y軸の範囲
    plt.xlabel('t (msec/44)') #x軸のラベル
    plt.ylabel('PulseHeight') #y軸のラベル
    plt.subplot(212) #グラフの下半分を指定
    plt.plot(cps_data) #計数値を表示
    plt.xlim(0, 600) #x軸の範囲
    plt.ylim(0, 1000) #y軸の範囲
    plt.xlabel('t (sec)') #x軸のラベル
    plt.ylabel('CPS') #y軸のラベル
    plt.pause(0.3) #0.3秒間だけ描画
    plt.clf() #グラフを閉じる

キーボードからqを入力するまで、計数を反復する。プログラムの停止には、キーボードからqを入力する。

while True: #キー入力を待つ
    streamandcount() #処理を反復
    m = m + 1 #経過時間1秒を加算
    if cv2.waitKey(0) & 0xFF == ord('q'): #qを入力
        break #処理を終了

画面に出力したグラフの例を図に示す。

f:id:apgmman:20211217234759p:plain

上のグラフの上段はモニター用のパルス波形で、下段は計数率の経時変化、すなわち経過秒数に対する1秒率の関係を描画している。


(※id:TJOid:apgmmanから受領したWord原稿を元に再構成、代理投稿したものです)