複数の画像を並べて表示するPythonコード

python

研究活動などを行っていると、光学顕微鏡写真など複数の図を並べて表示したい場面が多々あります。図を並べる作業は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_labelclass Labels画像ラベルに関する設定情報
-.fontsizeint, str画像ラベルのフォントサイズ
int: 文字サイズ
"○○%": images内の最大画像高さを基準としてその○○%の大きさに文字サイズを設定
-.locstr画像ラベルの配置
"Left": 各画像に対して左揃え
”Middle": 各画像に対して水平方向に中央揃え
row_labelclass Labels行ラベルに関する設定情報
-.fontsize int, str行ラベルのフォントサイズ
int: 文字サイズ
"○○%": images内の最大画像高さを基準としてその○○%の大きさに文字サイズを設定
-.loc str行ラベルの配置
"Left": 各画像に対して上揃え
”Middle": 各画像に対して垂直方向に中央揃え
-.label[str]行ラベルとして表示する文字列のリスト
col_labelclass Labels列ラベルに関する設定情報
-.fontsize列ラベルのフォントサイズ
int: 文字サイズ
"○○%": images内の最大画像高さを基準としてその○○%の大きさに文字サイズを設定
-.locstr列ラベルの配置
"Left": 各画像に対して上揃え
”Middle": 各画像に対して水平方向に中央揃え
-.label[str]列ラベルとして表示する文字列のリスト
row_space
int, str画像間の行スペースサイズ
int: ピクセル数
"○○%": images内の最大画像高さを基準としてその○○%のピクセル数
col_spaceint, str画像間の列スペースサイズ
int: ピクセル数
"○○%": images内の最大画像幅を基準としてその○○%のピクセル数
table_size[int, int]Tableの行数と列数
e.g. 3行2列のTableを作る場合は、[2,3]とする。
add_image()でpositionを指定する場合には、値を与えなくても問題ない
tablePIL.Image.Image配置後の画像
save_namestrtableを保存する際のファイル名

主要なメソッド

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"を保存
タイトルとURLをコピーしました