Pythonを使ってペーパークロマトグラフィーのデータから色画像を再構成〜自宅でできる簡単な研究(FR-4.4)

 ペーパークロマトグラフィーの結果をImageJで解析して得られたR,G,Bの数値データ(csvファイル)から、モニターに画像を再構成するプログラムをPythonで作成してみました。
 Pythonのプログラミングは初心者のため、生成AI(EdgeのCopilot)に質問して、得られたプログラムを参考にプログラムを作成しました。

考え方と目次

考え方
 何かの役には立たないし、人から見たらどうでも良いことで自己満足の世界でありますが、個人研究として取り組んでいます。

目次
 1. 食紅「緑」のペーパクロマトグラフィー
 2. ペーパークロマトグラフィーの結果の再構成
 3. ペーパークロマトグラフィーのバックグラウンドを除いた結果の再構成
 4. ImageJによるデーター取得法
 5. Pythonプログラムの作成
  5-1 条件の入力
  5-2 ImageJデーターファイルをまとめる
  5-3 CSVファイルのデーターから画像の作成
  5-4 画像の再構成
 6. 完成したプログラム

1. 食紅「緑」のペーパクロマトグラフィー
 食紅の緑を濾紙を使って(下の写真_A)水を展開液としてペーパクロマトグラフィーを実施すると(下の写真_B)、下の写真_Cの上の様に青と黄色に分離されます。
 無漂白の茶色いコーヒーフィルターで同様の実験をすると、黄色が青より移動距離が大きくなること(下の写真_Cの下)を「コーヒーのペーパーフィルターの違いを食紅で調べてみました」で紹介しました。


2. ペーパークロマトグラフィーの結果の再構成
 無漂白の茶色いコーヒーフィルターで食紅「緑」を水を展開液としてペーパークロマトグラフィーを実施すると下の写真_Aの様に移動する先端が平らではなく山型になることが多くみられます。
ImageJで下の写真_Bの左の赤矢印の範囲(幅全体)を選択すると、x-pointにおけるyの平均の値を得られます。
 後に紹介するPytonのプログラムで画像を再構成すると下の写真_Bの右の様になります。
 ImageJで下の写真_Cの左の赤矢印の範囲でを選択すると、右に示す青に移動距離が大きくなった画像になります。


3. ペーパークロマトグラフィーのバックグラウンドを除いた結果の再構成
 無漂白の茶色いコーヒーフィルターのバックグラウンドを除くため、下の写真左の赤四角の部分のR-,G-,B-チャンネルの値、(169, 121, 91)を用いました。
 バックグラウンドを除く考え方は、「紅大根の溶液で酸性・アルカリ性の色変化について調べてみました」に掲載しています。

計算方法

 (1) 透過光(反射光)の値(R, G, B-チャンネル)を255から引き算して吸収光の値を得ます。
 (2) バックグラウンドの吸収光の値をを引きます。
 (3) 255(白色光)から(2)の値を引き算します。マイナスの値は、"0″とします(吸収極大)。

 ペーパークロマトグラフィーの透過光をAとし、バックグラウンドの透過光をBとすると
 (1)で255-A、255–Bとなり、(2)でバックグラウンドを引くと(255-A)-(255-B)となります。
 (3)で255-{(255-A)-(255-B)}で「A+(255-B)」の式を計算することで、バックグラウンドを除いたペーパークロマトグラフィーの透過光のR, G, Bの値が得られます(255以上の値は、"255″とします)。


4. ImageJによるデーター取得法
 ImageJによるデーター取得法の詳細は、「ImageJを用いた呈色反応モデル(食紅)のデジタル画像解析法」で説明しています。

画像の数値化方法

 (1) Edit >> Copyメニューで解析領域をコピー
 (2) File >> New >> Internal Clipboardメニューで解析領域をペースト
 (3) Image >> Split Channelsを実行
   "red","green","blue"のチャンネルに色分解される
 (4) Edit >> Selection >> Select Allで選択
 (5) Analyze >> plot Profileを実行
 (6) 解析データーをCSVファイルに名前をつけて保存
   "red","green","blue"のチャンネルについて行う

 マクロで自動で行う方法は、「呈色反応モデル画像の数値化をImageJのマクロで自動」で紹介しています。

5. Pythonプログラムの作成
 「生成AIで作ったPythonプログラムで、数値化した画像の解析を自動化」で紹介した方法と同様に、生成AI(EdgeのCopilot)に質問して、得られたプログラムを参考にプログラムを作成しました。
 マクロを使って得られた結果のファイルは、"Red_m_.csv", “Breen_m_.csv", “Blue_m_.csv"となっています。

 5-1 条件の入力
 行番号1-4は、ここで使うモジュールやライブラリの読み込みです。
 行番号7は、結果のファイル名と作業に使うフォルダー名を入力します。でここでは’Melita_NB’にしています。
 行番号8は、ImageJの結果ファイル名の"xxx_m_"の後に文字を追加してある場合に入力します。
 行番号9は、データー数を減らす割合(1/N)を入力します。ここでは、1/2にするため '2’ を入力しています(データー数を全ての '1’ にすると結果が出るのに時間がかかります)。
 行番号10は、バックグラウンドを除くときにバックグラウンドの値を入力します。左からR,G,B-チャンネルの順番で、何もしないときは、’255, 255, 255’の値を入力します。
 行番号13が、バックグラウンドを除く「A+(255-B)」の式です。
 バックグラウンド値は、R:169, G:121, B:91を使用しました。

 5-2 ImageJデーターファイルをまとめる
 行番号19-21は、’Melita_NB_.csv’ファイルの1行目に書き込むデーターです(下の写真、青枠の右)。
 行番号22-26は、ImageJのデーターを読み込みます(下の写真、青枠の左は’Red_m_.csv’)。
 行番号28で’Melita_NB_.csv’ファイルの1行目を書き込みます。
 行番号31-41で、ImageJの"Red_m_.csv","Breen_m_.csv","Blue_m_.csv"ファイルの値を読み込み 'Melita_NB_.csv’ ファイルにまとめます。
 行番号19-21と28-33は、csvファイルの体裁を整える為で必要ないかも知れません。
 「生成AIで作ったPythonプログラムで、数値化した画像の解析を自動化」で紹介したプログラムを改変しています。

 5-3 CSVファイルのデーターから画像の作成
 行番号43は、作業用ファイルを入れる 'Melita_NB_’ フォルダーを作ります。
 行番号47で、csvファイルの1から(0ではない)データーを読み込みます。
 行番号50-50は、データーの数を減らすための処理です。
 行番号48-57は、1×100ピクセルの画像をcloro1から最後の行まで1つずつ画像をPNG形式のファイルに作成します。
 行番号58-29で、’Melita_NB_’フォルダーへファイルを保存します。
 「Pythonを使ってRGB値のデータから色画像を自動作成」で紹介したプログラムを改変しています。

 5-4 画像の再構成
 行番号61-64は、背景の画像(ここでは黒色)を作成し 'color_0.png’ というファイル名で保存します。
 行番号66-68で背景の画像を作り、行番号70-86で 'Melita_NB_’ フォルダーに保存したPNG形式のファイルを並べて再構成します。
 行番号85の’#’を除くと、’Melita_NB_’ フォルダーを削除します。
 これも「Pythonを使ってRGB値のデータから色画像を自動作成」で紹介したプログラムを改変しています。


6. 完成したプログラム
 Pythonのプログラムは、以下となります。Macintoshで動作確認しています。Windowsでは修正が必要かも知れません。

import csv
from PIL import Image
import shutil
import os

#-----データー入力-----
ymd = 'Melita_NB' #ファイルを保存するフォルダー名
f_no = ''  # ImageJ file No, 番号なしの時は、''とする
rat = 2 #ピクセル数を減らす割合
r_bk, g_bk, b_bk = 255, 255, 255 #バックグラウンド値 小数点可
#--------------------

r_bkc, g_bkc, b_bkc = 255-r_bk, 255-g_bk, 255-b_bk
print('バックグラウンド値_', r_bkc, g_bkc, b_bkc)
rnm = 0 #新しい番号用
ymd2 = ymd + '_' + str(f_no) #png fileにNo.をつける

#R,G,B fileのデーターをcsv fileにまとめる
data1 = [
    ['file No.', 'R', 'G', 'B']
] # 1行目に書き込むデータを定義
with open('Red_m_' + str(f_no) + '.csv') as red_file, open('Green_m_' + str(f_no) + '.csv') as green_file, open('Blue_m_' + str(f_no) + '.csv') as blue_file, open(ymd2 + '.csv', mode='w', newline='') as rgb_file:
    red_reader = csv.reader(red_file)
    green_reader = csv.reader(green_file)
    blue_reader = csv.reader(blue_file)
    rgb_writer = csv.writer(rgb_file)

    rgb_writer.writerows(data1) # 1行目にデータを書き込む

    # Skip the first row of each file
    next(red_reader)
    next(green_reader)
    next(blue_reader)

    for red_row, green_row, blue_row in zip(red_reader, green_reader, blue_reader):
        R0 = red_row[0]
        R1 = red_row[2]
        G1 = green_row[2]
        B1 = blue_row[2]
        R2, G2, B2 = min(float(R1)+r_bkc, 255), min(float(G1)+g_bkc, 255), min(float(B1)+b_bkc, 255)
        rgb_writer.writerow([R0, R2, G2, B2]) #RGB.csv fileを作る

os.makedirs(ymd2, exist_ok=True) #フォルダーを作る

with open(ymd2 + '.csv', 'r') as file:
    reader = csv.reader(file)
    rows = [row for i, row in enumerate(reader) if i >= 1]
    for row in rows:
        N, R, G, B = row[0], row[1], row[2], row[3]
        n = int(N)              
        if n % rat == 0:
            rnm += 1 #新しい番号を作る
            # RGBの数値から色を作成
            color = (round(float(R)), round(float(G)), round(float(B))) 
            img = Image.new('RGB', (1, 100), color) #1x100ピクセル        
            x = 1
            y = 80
            img.save('color_' + str(rnm) + '.png', 'png') # png形式のファイルに保存       
            shutil.move('color_' + str(rnm) + '.png', ymd2 + '/' + 'color_' + str(rnm) + '.png') #ファイルを移動する

#背景の画像を作成する
color = (0, 0, 0) # RGBの数値から色を作成
img0 = Image.new('RGB', (1, 100), color)
img0.save(ymd2 + '/color_0.png') # png形式のファイルに保存

# 画像の数 rnm
im1 = Image.open(ymd2 + '/color_0.png')
width, height = im1.width * rnm + 10, im1.height # 背景画像のサイズ width 5+5ピクセル

# 画像を左から順番にペーストして保存
for i in range(rnm + 1):
    # 画像を開く
    img = Image.open(f'{ymd2}/color_{i}.png') # file No.1から
    # 画像をリサイズ
    img = img.resize((width, height))
    if i == 0:
        result = img # べースの黒を作成        
    else:
        img = img.resize((int(im1.width * 1), int(height * 0.9))) # xは1ピクセル、Yはサイズを0.9倍(90ピクセル)
        result.paste(img, (im1.width * (i -1) + 5, 5)) # 左と上から5ピクセル開けてペースト
    if i%100 == 0:
        print('計算中_' + str(i)) #100回ずつ計算中を表示   
    result.save(f'{ymd2}.png')# 画像を保存
result.show('result.png') # ファイルを見せる
#shutil.rmtree(ymd2) #フォルダーを中身ごと削除
print('>>>>> 終了 <<<<<')