SDFを構造式の画像も含めてサクッとエクセルに変換する

#はじめに
以前、[SDFをサクッとCSVに変換する](https://qiita.com/kimisyo/items/bc391d451b90c819996b)という記事を(全く反響はなかったものの)書いてみたが、やはりエクセルで構造式もあわせて見たい！という(自分だけの)要望に応えるためにやってみたメモ

ちなみにRDKitのツールでも似たようなことはできることは知っているが、カスタマイズ性も考慮して今回トライした。

# 環境
こんな感じ。

- python
- openpyxl
- RDKit 

# ソース
はい、いきなりのどん。

```python
import argparse
from rdkit import Chem
from rdkit.Chem import AllChem
from rdkit.Chem.Draw import rdMolDraw2D
import openpyxl
from openpyxl.styles import numbers
import openpyxl.drawing.image
from io import BytesIO
from cairosvg import svg2png
import PIL

def is_float(value):
    try:
        float(value)
        return True
    except ValueError:
        return False

def generate_image(mol, size):

    image_data = BytesIO()
    view = rdMolDraw2D.MolDraw2DSVG(size[0], size[1])
    tm = rdMolDraw2D.PrepareMolForDrawing(mol)

    view.SetLineWidth(1)
    view.SetFontSize(1.0)
    option = view.drawOptions()    
    #option.useBWAtomPalette()

    view.DrawMolecule(tm)
    view.FinishDrawing()
    svg = view.GetDrawingText()
    svg2png(bytestring=svg, write_to=image_data)
    #image_data.seek(0)
    return PIL.Image.open(image_data)        

def main():

    # 引数の解釈
    parser = argparse.ArgumentParser()
    parser.add_argument('-i', type=str, required=True)   
    parser.add_argument('-o', type=str, required=True)
    args = parser.parse_args()

    # SDFの読み込み(1回目はパラメータ名を全て読み取る)
    sdf_sup = Chem.SDMolSupplier(args.i)
    props = []
    props.append("_Name")
    for mol in sdf_sup:
        for name in mol.GetPropNames():
            if name not in props:
                props.append(name)

            
    # SDFの読み込み(2回目は化合物のパラメータを取得。なければエラー)
    datas = []
    images = []
    sdf_sup = Chem.SDMolSupplier(args.i)
    for mol in sdf_sup:
        # 名前の取得
        data = []
        for name in props:
            if mol.HasProp(name):
                value = mol.GetProp(name)
                data.append(value)
            else:
                data.append(None)
        datas.append(data)
        if mol is not None:
            image = generate_image(mol, (120, 120))
            images.append(image)
            

    wb = openpyxl.Workbook()
    wb.remove(wb.worksheets[-1])
    ws = wb.create_sheet(title="Result")
    wb["Result"].cell(1,1).value = "Image"
    for i, prop in enumerate(props):
        wb["Result"].cell(1,(i+2)).value = prop

    for i, data in enumerate(datas):
        # 画像を最初の列に入れる
        cell_address = ws.cell((i+2), 1).coordinate 

        wb["Result"].row_dimensions[(i+2)].height = 90
        wb["Result"].column_dimensions['A'].width = 15
        
        image = openpyxl.drawing.image.Image(images[i])
        wb["Result"].add_image(image, cell_address)
        
        # 2列目以降はプロパティを格納する。
        for j, prop in enumerate(props):
            wb["Result"].cell((i+2), (j+2)).alignment = \
                    openpyxl.styles.Alignment(wrapText=True, vertical="center")
            if data[j].isdecimal():
                wb["Result"].cell((i+2), (j+2)).number_format = numbers.FORMAT_NUMBER
                wb["Result"].cell((i+2), (j+2)).value = int(data[j])
            elif is_float(data[j]):
                wb["Result"].cell((i+2), (j+2)).number_format = '0.000'
                wb["Result"].cell((i+2), (j+2)).value = float(data[j])
            else:
                wb["Result"].cell((i+2), (j+2)).value = data[j]

    wb.save(args.o)

if __name__ == "__main__":
    main()        
```

## 解説

- 全化合物のプロパティを知るためにSDFを最初に読み込んでいる。そして2度目の読み込みで各化合物のプロパティの値を読みとっている。
- 画像は1列目に表示することとし、2列目以降に全プロパティを出力するようにした。
- ```generate_image``` 関数でRDKitの機能を用いて構造式画像を生成している。細かい点であるが、多少見栄えが良くなるよう、```view.SetLineWidth(1)```で結合の太さ、```view.SetFontSize(1.0)```でフォントサイズを調整している。
- 細かい工夫だが、int、floatに応じて、数値データが **いい感じ** に表示されるよう調整している。
- エクセルの好みの問題だが、縦方向のアラインメントを中央寄せ、「折り返して全体を表示する」になるようにしている。

# 使い方

こんな感じで、-i オプションに入力SDFを、-oオプションに出力xlsxファイルを指定してほしい。

```
python sdf2xlsx.py -i solubility.test.sdf -o solubility.test.xlsx    
```


以下が、https://raw.githubusercontent.com/rdkit/rdkit/master/Docs/Book/data/solubility.test.sdf  のデータを変換した一部である。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/f2e56c3b-e3a7-82fb-406f-aaf36d405704.png)

csvに比べて、はるかに **ケモケモ感** が伝わってきて最高である。

#課題
これだけでも **相当便利で既に業務で使いまくっている** が、以下の点に不満を感じている。

- 構造式画像が見ずらい。今回画像のサイズは固定としているが大きな構造だと特に見にくい。エクセルの「セルに合わせて移動やサイズを変更する」を指定した時のように、セルのサイズを変更すると画像も追従してて拡大するようにできればよかったが、openpyxlでその方法を探しきれなかった。