研究活動などを行っていると、光学顕微鏡写真など複数の図を並べて表示したい場面が多々あります。図を並べる作業はMicrosoft Power point (PP)などを使って視覚的に実行することができます。2つのグラフを並べる程度であれば、PPで作業を行ってもほとんど手間はかかりませんが、3×3(9枚)、4×3(12枚)など図の数が多くなると手作業で図を並べるのは手間がかかります。
本稿では、著者がpythonで作製した、複数枚の図をTable状に配列して一枚の図とするためのソースコードを公開します。
なお、画像の操作に関してはpythonライブラリのPILを用いています。
【この記事を読むのに必要な知識】python
目次
このコードでできる事
このコードを用いることで、以下のように元画像をTable状に配列して新しい図を作成することができます。また、画像の配列に併せて、図の上に図ラベル、表の上と左横に行ラベルおよび列ラベルを付与することができます。
コード
処理は以下の”Picture_Table.py”の”Pict_Table”クラスを用いて行います。
Picture_Table.py
from PIL import Image, ImageDraw, ImageFont
import numpy as np
class Pict_Table():
# Table内に配置する1つの画像に対応するクラス
class Image_Data():
def __init__(self,table_pict,path,position=0,label=""):
self.image=Image.open(path)
self.path=path
self.label=label
self.im_no=len(table_pict.images)
print(self.im_no)
if position==0:
self.position=((self.im_no % table_pict.table_size[0])+1,int(self.im_no/ table_pict.table_size[0])+1)
else:
self.position=position
print(self.position)
# Labelの設定に関するクラス
class Labels():
def __init__(self):
self.label=[]
self._width=0
self._height=0
self.fontdata="arial.ttf"
self.fontsize=50
self.font=ImageFont.truetype(self.fontdata,self.fontsize)
self.space_h=0
self.space_w=0
self._space_h_size=0
self._space_w_size=0
self.loc="Left"
def __init__(self,table_size=[1,1]):
self.images=[]
self.table_size=table_size
self.save_name="Figure.png"
self.row_space=0
self.col_space=0
self._row_space_size=0
self._col_space_size=0
self.pict_width=0
self.pict_height=0
self.pict_label=self.Labels()
self.row_label=self.Labels()
self.col_label=self.Labels()
# Tableに画像を追加
def add_image(self,path,position=0,label=""):
self.images.append(self.Image_Data(self,path,position,label))
# Tableに画像を配置
def joint_images(self):
im:self.Image_Data
# 行、列数を取得
self.table_size[0]=np.array([im.position[0] for im in self.images]).max()
self.table_size[1]=np.array([im.position[1] for im in self.images]).max()
# 各種ピクセル数を取得
self._set_sizes()
# プロットする背景画像を作成
self.table=Image.new("RGB",(self.width,self.height),(255,255,255))
draw=ImageDraw.Draw(self.table)
# 画像と画像ラベルを配置
for im in self.images:
x,y=self._get_figure_position("pict",im.position,(im.image.width,im.image.height))
self.table.paste(im.image,(x,y))
# 画像ラベルを配置
if im.label != "":
x,y=self._get_figure_position("pict_label",im.position,draw.textsize(im.label,font=self.pict_label.font))
draw.text((x,y),im.label,font=self.pict_label.font,fill=(0,0,0),spacing=4)
# 行ラベルを配置
for i,txt in enumerate(self.row_label.label):
if txt!="":
x,y=self._get_figure_position("row_label",(1,i+1),draw.textsize(txt,font=self.row_label.font))
draw.text((x,y), txt, font=self.row_label.font, fill=(0, 0, 0), spacing=4)
# 列ラベルを配置
for i,txt in enumerate(self.col_label.label):
if txt!="":
x,y=self._get_figure_position("col_label",(i+1,1),draw.textsize(txt,font=self.col_label.font))
draw.text((x,y), txt, font=self.col_label.font, fill=(0, 0, 0), spacing=4)
# ラベルのフォントサイズを一括設定
def set_all_label_condition(self,fontsize="",loc=""):
if fontsize!="":
self.pict_label.fontsize=fontsize
self.row_label.fontsize=fontsize
self.col_label.fontsize=fontsize
if loc!="":
self.pict_label.loc=loc
self.row_label.loc=loc
self.col_label.loc=loc
# Tableを表示(事前にjoint_imageの実行が必要)
def show_table(self):
self.table.show()
# Tableを保存(事前にjoint_imageの実行が必要)
def save_table(self):
self.table.save(self.save_name)
# 配置ピクセル位置を計算
def _get_figure_position(self,type,position,area_size):
x=0
y=0
# 画像の位置
if type=="pict":
x = self.row_label._width
x += self.pict_width * (position[0] - 1)
x += self._col_space_size * (position[0] - 1)
y=self.col_label._height
y+=self.pict_height*(position[1]-1)
y+=self._row_space_size*(position[1]-1)
y+=np.array(self.pict_label._height[:position[1]]).sum()
# 画像ラベルの位置
elif type=="pict_label":
x = self.row_label._width
x += self.pict_width * (position[0] - 1)
x += self._col_space_size * (position[0] - 1)
y=self.col_label._height
y+=self.pict_height*(position[1]-1)
y+=self._row_space_size*(position[1]-1)
y+=np.array(self.pict_label._height[:position[1]-1]).sum()
loc = self.pict_label.loc
if loc == "Middle":
x += self.pict_width / 2
x += -area_size[0] / 2
# 行ラベルの位置
elif type=="row_label":
x = 0
y = self.col_label._height
y+=self.pict_height * (position[1]-1)
y+=self._row_space_size * (position[1]-1)
y+=np.array(self.pict_label._height)[:position[1]].sum()
loc = self.row_label.loc
if loc == "Middle":
y += self.pict_height / 2
y += -area_size[1] / 2
# 列ラベルの位置
elif type=="col_label":
x=self.row_label._width
x+=self.pict_width * (position[0] - 1)
x+=self._col_space_size * (position[0] - 1)
y = 0
loc = self.col_label.loc
if loc == "Middle":
x += self.pict_width / 2
x += -area_size[0] / 2
return x,y
# 各種ピクセルサイズを取得
def _set_sizes(self):
im:self.Image_Data
# 最大の配置画像サイズを取得
ws = [im.image.width for im in self.images]
hs = [im.image.height for im in self.images]
self.pict_width = np.array(ws).max()
self.pict_height = np.array(hs).max()
# 行、列の空間を設定
self._col_space_size=self._set_size_value(self.col_space,self.pict_width)
self._row_space_size=self._set_size_value(self.row_space,self.pict_height)
# 図上のラベルサイズを取得
draw=ImageDraw.Draw(Image.new("RGB",(1000,1000)))
fontsize=self._set_size_value(self.pict_label.fontsize,self.pict_height)
font=ImageFont.truetype(self.pict_label.fontdata,fontsize)
self.pict_label.font=font
heights=[[0] for i in range(self.table_size[1])]
for im in self.images:
val=draw.textsize(im.label,font,spacing=4)[1]
heights[im.position[1]-1].append(val)
height=[np.array(lst).max() for lst in heights]
self.pict_label._height=height
# 行ラベルの最大幅を取得
fontsize = self._set_size_value(self.row_label.fontsize, self.pict_height)
font = ImageFont.truetype(self.row_label.fontdata, fontsize)
self.row_label.font = font
val = 0
for txt in self.row_label.label:
val1 = draw.textsize(txt, font, spacing=4)[0]
if val < val1:
val = val1
self.row_label._width = int(val)
# 列ラベルの最大高さを取得
fontsize = self._set_size_value(self.col_label.fontsize, self.pict_height)
font = ImageFont.truetype(self.col_label.fontdata, fontsize)
self.col_label.font=font
val = 0
for txt in self.col_label.label:
val1 = draw.textsize(txt, font=font, spacing=4)[1]
if val < val1:
val = val1
self.col_label._height = int(val)
# Tableに必要なサイズを取得
width=self.row_label._width
width+=self.pict_width*self.table_size[0]
width+=self._col_space_size*(self.table_size[0]-1)
height=self.col_label._height
height+=self.pict_height*self.table_size[1]
height+=self._row_space_size*(self.table_size[1]-1)
height+=np.array(self.pict_label._height).sum()
self.width=width
self.height=height
# %指定した場合にピクセル数を計算
def _set_size_value(self,value,ref_value):
val=0
if type(value) is str:
if "%" in value:
rate=float(value.replace("%",""))/100
val=ref_value*rate
else:
val=value
return int(val)
if __name__=="__main__":
# インスタンスの生成
PT=Pict_Table()
# ラベルのフォントサイズ、配置を設定
PT.set_all_label_condition(fontsize="20%",loc="Middle")
PT.pict_label.fontsize="15%"
PT.pict_label.loc="Left"
# 画像のスペースを設定 ピクセル数 or %
PT.row_space="5%"
PT.col_space="5%"
# 行ラベルと列ラベルを設定
PT.row_label.label=["Row1","Row2"]
PT.col_label.label=["Col1","Col2","Col3"]
# Tableの行列数を設定
PT.table_size=[3,2]
# Tableに画像を追加
# positionを指定する場合は(列番号,行番号)で指定(数学の行列と順序が異なるので注意)
PT.add_image("Fig1.png",label="Fig1")
PT.add_image("Fig2.png",label="Fig2")
PT.add_image("Fig1.png",label="Fig3")
PT.add_image("Fig2.png",label="Fig4")
PT.add_image("Fig1.png",position=(3,2),label="Fig5")
# Tableを作成
PT.joint_images()
# Tableの表示と保存
PT.show_table()
PT.save_name="Figure.png"
PT.save_table()
コードの使い方
”Pict_Table”クラスの基本的な使い方は以下の通りです。
# インスタンスの生成
PT=Pict_Table()
# ラベルのフォントサイズ、配置を設定
PT.set_all_label_condition(fontsize="20%",loc="Middle")
PT.pict_label.fontsize="15%"
PT.pict_label.loc="Left"
# 画像のスペースを設定 ピクセル数 or %
PT.row_space="5%"
PT.col_space="5%"
# 行ラベルと列ラベルを設定
PT.row_label.label=["Row1","Row2"]
PT.col_label.label=["Col1","Col2","Col3"]
# Tableの行列数を設定
PT.table_size=[3,2]
# Tableに画像を追加
# positionを指定する場合は(列番号,行番号)で指定(数学の行列と順序が異なるので注意)
PT.add_image("Fig1.png",label="Fig1")
PT.add_image("Fig2.png",label="Fig2")
PT.add_image("Fig1.png",label="Fig3")
PT.add_image("Fig2.png",label="Fig4")
PT.add_image("Fig1.png",position=(3,2),label="Fig5")
# Tableを作成
PT.joint_images()
# Tableの表示と保存
PT.show_table()
PT.save_name="Figure.png"
PT.save_table()
主要なメンバー
操作する主要なメンバーは下図のように対応します。
"Pict_Table"クラスの主要なメンバーは以下の通りです。
メンバー | 型 | 説明 |
---|---|---|
images | [class Image_Data] | Table上に表示する画像の情報をまとめたリスト |
pict_label | class Labels | 画像ラベルに関する設定情報 |
-.fontsize | int, str | 画像ラベルのフォントサイズ int: 文字サイズ "○○%": images内の最大画像高さを基準としてその○○%の大きさに文字サイズを設定 |
-.loc | str | 画像ラベルの配置 "Left": 各画像に対して左揃え ”Middle": 各画像に対して水平方向に中央揃え |
row_label | class Labels | 行ラベルに関する設定情報 |
-.fontsize | int, str | 行ラベルのフォントサイズ int: 文字サイズ "○○%": images内の最大画像高さを基準としてその○○%の大きさに文字サイズを設定 |
-.loc | str | 行ラベルの配置 "Left": 各画像に対して上揃え ”Middle": 各画像に対して垂直方向に中央揃え |
-.label | [str] | 行ラベルとして表示する文字列のリスト |
col_label | class Labels | 列ラベルに関する設定情報 |
-.fontsize | 列ラベルのフォントサイズ int: 文字サイズ "○○%": images内の最大画像高さを基準としてその○○%の大きさに文字サイズを設定 | |
-.loc | str | 列ラベルの配置 "Left": 各画像に対して上揃え ”Middle": 各画像に対して水平方向に中央揃え |
-.label | [str] | 列ラベルとして表示する文字列のリスト |
row_space | int, str | 画像間の行スペースサイズ int: ピクセル数 "○○%": images内の最大画像高さを基準としてその○○%のピクセル数 |
col_space | int, str | 画像間の列スペースサイズ int: ピクセル数 "○○%": images内の最大画像幅を基準としてその○○%のピクセル数 |
table_size | [int, int] | Tableの行数と列数 e.g. 3行2列のTableを作る場合は、[2,3]とする。 add_image()でpositionを指定する場合には、値を与えなくても問題ない |
table | PIL.Image.Image | 配置後の画像 |
save_name | str | tableを保存する際のファイル名 |
主要なメソッド
set_all_label_condition(fontsize="",loc="")
- 説明
"pict_label", "row_label", "col_label"について、"fontsize", "loc"を一括設定
""が与えられたパラメータについては設定を行わない - 引数
fontsize: Pict_Table.Labels.fontsizeとして設定する値
loc: Pict_Table.Labels.locとして設定する値
add_image(path,position=0,label="")
- 説明
Tabelに"path"で指定した画像を追加する。"position=0"の場合は、追加された順に下図のように配置する - 引数
path: 画像のファイルパス
position: Table内での画像の配置位置, e.g. 2行1列目に配置する画像の場合は、(1,2)とする。
label: 画像ラベルとして表示する文字列
joint_images()
- 説明
設定した情報をもとに図を配置し一枚の図("table")にする。
"show_table()", "save_table()"を実行する前に実行しておく必要がある。
show_table()
- 説明
"table"を表示する。
save_table()
- 説明
"save_name"の名称で"table"を保存