RDKit:分子が描画されないエラー
●事始め
久しぶりにRDKit(1)を使ったプログラムをJupyter Notebookで書いたら、
分子が描画されないエラー(2)が発生し、
色々と奔走してしまったので、その対処方法を備忘録として残しておきます。
結論から先に書いておくと、画像処理ライブラリPillowのバージョンアップが
原因でした。
(1) version 2019.09.1 : Ubuntu 20.04
(2) 以下ようなエラーです。
●対処方法1[Pillowのバージョン下げ]
この問題は、Pillow 8.0.0以上で発生するので、
素直にそれ未満のバージョンに下げます。
8.0.0未満の最新バージョンは7.2.0です。
Pillow バージョンの確認
pip show pillow
結果例
Name: Pillow
Version: 8.2.0
Summary: Python Imaging Library (Fork)
Home-page: https://python-pillow.org
Author: Alex Clark (PIL Fork Author)
Author-email: aclark@python-pillow.org
License: HPND
Location: /home/ubuntu/.local/lib/python3.8/site-packages
Requires:
Required-by: pytesseract, pyocr, matplotlib, ipymol
Pillow ダウングレード
pip install pillow==7.2.0
結果例
Defaulting to user installation because normal site-packages is not writeable
Collecting pillow==7.2.0
Downloading Pillow-7.2.0-cp38-cp38-manylinux1_x86_64.whl (2.2 MB)
|████████████████████████████████| 2.2 MB 13.7 MB/s
Installing collected packages: pillow
Attempting uninstall: pillow
Found existing installation: Pillow 8.2.0
Uninstalling Pillow-8.2.0:
Successfully uninstalled Pillow-8.2.0
Successfully installed pillow-7.2.0
Pillow 8.0.0からim.tostring()メソッドが削除されたため(3)、
これを使用しているcairoCanvas.pyから呼び出せないことによるエラーです。
一応、version 2.0.0から既にim.tobytes()メソッドへの置き換えが
推奨されていたようです。
(3) https://pillow.readthedocs.io/en/stable/deprecations.html#removed-features
●対処方法2[cairoCanvas.pyの書き換え]
PythonとLinuxの知識をお持ちの方限定。
初心者の方は環境を破壊してしまう恐れがありますので、お勧めできません。
エラーの原因となっているファイルはcairoCanvas.pyのみで、該当箇所は4つ。
これを書き換えることで対処します。
該当のディレクトリに移動(環境に合わせ、読み替えてください)
cd /usr/lib/python3/dist-pakages/rdkit/Chem/Draw
念のためバックアップを作成
sudo cp cairoCanvas.py cairoCanvas.py.old
テキストエディタで開く(管理者権限でオープンすることを忘れずに!)
sudo gedit carioCanvas.py
該当箇所(Line 147, 151, 194, 197)の修正
(以下は該当部分を含むソースコードの一部)
class Canvas(CanvasBase): def __init__(self, image=None, # PIL image size=None, ctx=None, imageType=None, # determines file type fileName=None, # if set determines output file name ): """ Canvas can be used in four modes: 1) using the supplied PIL image 2) using the supplied cairo context ctx 3) writing to a file fileName with image type imageType 4) creating a cairo surface and context within the constructor """ self.image = None self.imageType = imageType if image is not None: try: imgd = getattr(image, 'tobytes', image.tostring)("raw", "BGRA") except SystemError: r, g, b, a = image.split() mrg = Image.merge("RGBA", (b, g, r, a)) imgd = getattr(mrg, 'tobytes', mrg.tostring)("raw", "RGBA") a = array.array('B', imgd) stride = image.size[0] * 4 surface = cairo.ImageSurface.create_for_data(a, cairo.FORMAT_ARGB32, image.size[0], image.size[1], stride) ctx = cairo.Context(surface) size = image.size[0], image.size[1] self.image = image elif ctx is None and size is not None: if hasattr(cairo, "PDFSurface") and imageType == "pdf": surface = cairo.PDFSurface(fileName, size[0], size[1]) elif hasattr(cairo, "SVGSurface") and imageType == "svg": surface = cairo.SVGSurface(fileName, size[0], size[1]) elif hasattr(cairo, "PSSurface") and imageType == "ps": surface = cairo.PSSurface(fileName, size[0], size[1]) elif imageType == "png": surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size[0], size[1]) else: raise ValueError("Unrecognized file type. Valid choices are pdf, svg, ps, and png") ctx = cairo.Context(surface) ctx.set_source_rgb(1, 1, 1) ctx.paint() else: surface = ctx.get_target() if size is None: try: size = surface.get_width(), surface.get_height() except AttributeError: size = None self.ctx = ctx self.size = size self.surface = surface self.fileName = fileName def flush(self): """temporary interface, must be splitted to different methods, """ if self.fileName and self.imageType == 'png': self.surface.write_to_png(self.fileName) elif self.image is not None: # on linux at least it seems like the PIL images are BGRA, not RGBA: if hasattr(self.surface, 'get_data'): getattr(self.image, 'frombytes', self.image.fromstring)(bytes(self.surface.get_data()), "raw", "BGRA", 0, 1) else: getattr(self.image, 'frombytes', self.image.fromstring)( bytes(self.surface.get_data_as_rgba()), "raw", "RGBA", 0, 1) self.surface.finish() elif self.imageType == "png": if hasattr(self.surface, 'get_data'): buffer = self.surface.get_data() else: buffer = self.surface.get_data_as_rgba() return buffer
Pillow 8.0.0未満の環境で実行する予定がないのであれば、
下のような感じに修正するだけでOK 。
147 imgd = image.tobytes("raw","BGRA") 151 imgd = mrg.tobytes("raw","RGBA") 194 self.image.frombytes(bytes(self.surface.get_data()), "raw", "BGRA", 0, 1) 197 self.image.frombytes(bytes(self.surface.get_data_as_rgba()), "raw", "RGBA", 0, 1)
getattrメソッドでtostring、fromstringをそれぞれtobytes、frombytesに
置き換えるようコードされているため、今回のようにアトリビュートから
削除されてしまうとエラーになってしまいます。
汎用性を求めるなら、try~except~で対処すべきと思うのですが・・・
最新版のソースコード(4)では、上記コードに置き換えられていますので、
RDKitの方を2021.03.1へバージョンアップするのが確実です。
2021/5/5時点だと、Ubuntu 20.04上のaptコマンドでインストールできる
バージョンは 2019.09.1のため、condaでインストールまたは
ソースコードからの ビルドになります。
(4)https://github.com/rdkit/rdkit/blob/master/rdkit/Chem/Draw/cairoCanvas.py