【試験研究】RigakuのSmartLabデータ rasファイルを普通のデータファイルにするpythonコード

 Rigaku社製の薄膜用X線回折装置にSmartLabという機種があって、これを使う機会が最近多くなってきた。他の機関の機器利用制度を使って使用しているので、データ解析やらは自分のinstituteに戻ってから行っている。できる限り測定に時間を割きたいからだ。SmartLabの制御ソフトが出力するファイルには、拡張子名でraw, asc, rasという3種類がある。設定によるのだろうが、同じデータを3つの形式でいつも持って帰っている。

japan.rigaku.com

 rawというのは、Rigaku社が30年以上前から使っている形式で、バイナリファイルだ。ちょっと解析したくない。ascというのはいかにもな名前から分かるとおりテキストファイルだ。エディタで中身を見ると、わりと分かりやすい形で測定パラメータが並んだヘッダ部分があるのだが、肝心のデータ部分は、数字が4つずつカンマで区切られた形式となっている。しかもそれはx, yの組ではなく、xのデータ列は測定条件から計算し、yのデータ列が1行に4つずつ並んでいるというものだ。データ部はユーザーフレンドリでない。最後のras形式もテキストファイルである。こちらは、測定条件を示すヘッダ部分が、パラメータ名を示すブロック、パラメータ値が入っているブロック、という具合にユーザーフレンドリでないヘッダ構造なのだが、データ部はx, y, アッテネーションの3つの数字がカンマで区切られており、なかなかナイスである。まあ、100人いたら98人はrasファイルが好きになるだろう。

 rasファイルの問題点は、1回の測定で複数の測定を行う場合に、それを1つのファイルに入れてしまうことだ。つまり「汎用測定」(というメニューがある)などでω-2θとω scanの測定を一度に行ったとする。すると、1つのrasファイルの中に、これらの測定データが2ブロック含まれてしまうのである。

 データファイルというと、空白区切りかカンマ区切りの(x, y)データが基本であり、測定パラメータは行頭に#を入れてコメントアウトして残しておきたい。そこで、こういう事情に対応すべく、RigakuのSmartLabが出力するrasファイルをdatファイルに変換するPythonスクリプトを書いたので公開します。ω-2θとかロッキングカーブとかφ-scanぐらいなら大丈夫だと思います。大したコードではないし、私が必要とする範囲で処理するような内容となっているところはご容赦いただきたい。もしこれを使うという方がおられたら、ご自分でコードを改変してください。また、極点図測定はしたことがない(時間かかるでしょ)ので、このコードを通すとどうなるのかは分かりません。

 使い方は実行するだけ。Python3で書いているが、3でございますというほどのものではない。tkinterpythonをインストールすると入っているはずなので、pipの必要はないはず。最初にデータフォルダを聞いてくるので、rasファイルが入ったフォルダを指定すると、そのフォルダ内のrasファイルをすべて変換する。拡張子がdatのファイルが生成され、オリジナルは残る。複数のブロックがあったら、ファイル名に1, 2, とナンバリングされたファイル名がつく、というもの。プログラム中の使っていないファイルダイアログ関数中に、rasファイルが "Rigaku Ascii" の略のように書いているが、これは私のでっち上げです。

 最後にプログラムについて。このプログラムは、ダイアログはWindowで出てくるが、その他はコマンドプロンプトで表示される。つまり、ファイル名指定はキーボードから入力したくないのでGUIを使いたいが、動作中のメッセージはCUIで十分だ、という発想。過去に測定プログラムを、見た目を重視してC#+WPFで作ったりしていた時期もあったが、あれは時間の無駄だった。やりすぎでした。フルGUIなんていうのは、GUIを担当する専門の方がおられるような販売用ソフトのプロジェクトで使うもので、研究開発で時間短縮をするためにプログラムを用いるときには全く必要ありませんね!以上!

import os
import sys
import tkinter
import tkinter.filedialog

#===== path dialog =====
def getPath(initdir):
    root = tkinter.Tk()
    root.withdraw()
    dirname = tkinter.filedialog.askdirectory(initialdir=initdir)
    return dirname

#===== file dialog =====
def getFilename():
    root = tkinter.Tk()
    root.withdraw()
    fTyp = [("Rigaku Ascii", "ras")]
    iDir = os.path.abspath(os.path.dirname(__file__))
    filename = tkinter.filedialog.askopenfilename(filetypes=fTyp, initialdir=iDir)
    return filename

#===== get item from RAS file =====
def getItem(str, begin, end, data):
    item = ""
    str += " "
    for i in range(begin + 1, end):
        if (data[i])[0:len(str)] == str:
            item = (data[i])[len(str)+1:].replace('"', '')
    return item

#***** Program Body *****
dirname = getPath(os.environ['HOME'])
rasfiles = []
for file in os.listdir(dirname):
    base, ext = os.path.splitext(file)
    if ext == '.ras':
        rasfiles.append(dirname + "/" + file)
print("***** {} files found *****\n".format(len(rasfiles)))

for filename in rasfiles:
    print("* Processing file : {}".format(filename), end="")
    filename_wo_ext = (os.path.splitext(filename))[0]
    fh = open(filename, 'r')
    datalines = fh.readlines()
    for i in range(len(datalines)):
        datalines[i] = datalines[i].rstrip('\n')
    if datalines[0] != '*RAS_DATA_START':
        print("Not a RAS file.")
        continue
    ras_header_start = []
    ras_header_end = []
    ras_int_start = []
    ras_int_end = []
    for i in range(len(datalines)):
        if datalines[i] == '*RAS_HEADER_START':
            ras_header_start.append(i)
        if datalines[i] == '*RAS_HEADER_END':
            ras_header_end.append(i)
        if datalines[i] == '*RAS_INT_START':
            ras_int_start.append(i)
        if datalines[i] == '*RAS_INT_END':
            ras_int_end.append(i)
    print(" : Found {} data blocks.".format(len(ras_header_start)))
    for num_block in range(len(ras_header_start)):
        with open(filename_wo_ext + "-{0:02d}.dat".format(num_block + 1), "w") as fh:
            tmp1 = getItem("*MEAS_SCAN_START_TIME", ras_header_start[num_block], ras_header_end[num_block], datalines)
            tmp2 = getItem("*MEAS_SCAN_END_TIME", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Scan Date   : {0} - {1}\n".format(tmp1, tmp2))
            tmp1 = getItem("*FILE_SAMPLE", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# File Sample : {0}\n".format(tmp1))
            tmp1 = getItem("*FILE_MEMO", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# File Memo   : {0}\n".format(tmp1))
            fh.write("# User Comment : \n")
            tmp1 = getItem("*MEAS_COND_OPT_NAME", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Optical     : {0}\n".format(tmp1))
            tmp1 = getItem("*HW_COUNTER_SELECT_NAME", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Counter     : {0}\n".format(tmp1))
            for ii in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 19, 27, 28, 35]:
                tmp1 = getItem("*MEAS_COND_AXIS_NAME-{}".format(ii), ras_header_start[num_block], ras_header_end[num_block], datalines)
                tmp2 = getItem("*MEAS_COND_AXIS_POSITION-{}".format(ii), ras_header_start[num_block], ras_header_end[num_block], datalines)
                tmp3 = getItem("*MEAS_COND_AXIS_UNIT-{}".format(ii), ras_header_start[num_block], ras_header_end[num_block], datalines)
                fh.write("# Condition {0} : {1} {2}\n".format(tmp1, tmp2, tmp3))
            tmp1 = getItem("*MEAS_SCAN_AXIS_X", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Data X axis : {0}\n".format(tmp1))
            tmp1 = getItem("*MEAS_SCAN_MODE", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Scan mode   : {0}\n".format(tmp1))
            tmp1 = getItem("*MEAS_SCAN_START", ras_header_start[num_block], ras_header_end[num_block], datalines)
            tmp2 = getItem("*MEAS_SCAN_UNIT_X", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Scan start  : {0} {1}\n".format(tmp1, tmp2))
            tmp1 = getItem("*MEAS_SCAN_STOP", ras_header_start[num_block], ras_header_end[num_block], datalines)
            tmp2 = getItem("*MEAS_SCAN_UNIT_X", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Scan stop   : {0} {1}\n".format(tmp1, tmp2))
            tmp1 = getItem("*MEAS_SCAN_STEP", ras_header_start[num_block], ras_header_end[num_block], datalines)
            scan_step = float(tmp1)   # deg/step
            tmp2 = getItem("*MEAS_SCAN_UNIT_X", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Scan step   : {0} {1}\n".format(tmp1, tmp2))
            tmp1 = getItem("*MEAS_SCAN_SPEED", ras_header_start[num_block], ras_header_end[num_block], datalines)
            tmp2 = getItem("*MEAS_SCAN_SPEED_UNIT", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Scan speed  : {0} {1}\n".format(tmp1, tmp2))
            scan_speed = float(tmp1) / 60.0    # deg/min -> deg/sec
            count2cps = scan_step / scan_speed   # sec/step
            fh.write("# Scan time per step : {0:.3f}\n".format(count2cps))
            tmp1 = getItem("*MEAS_SCAN_UNIT_Y", ras_header_start[num_block], ras_header_end[num_block], datalines)
            fh.write("# Scan Y unit : {0}\n".format(tmp1))
            for ii in range(ras_int_start[num_block] + 1, ras_int_end[num_block]):
                tmp1, tmp2, tmp3 = datalines[ii].split(' ')
                fh.write("{0} {1:.3f} {2} {3}\n".format(tmp1, float(tmp2) * float(tmp3) / count2cps, tmp2, tmp3))
input("Hit anykey to exit.")