LoginSignup
10
10

More than 5 years have passed since last update.

scikit-imageを読み解く(Cythonの実例)

Last updated at Posted at 2015-08-19

scikit-imageというライブラリがどのようにCythonを使っているのかを読み解いて、
Cythonについての理解を深めてみようと思う。scikit-imageに実装されているアルゴリズムをC++で書き直すことができるようscikit-imageについて読んでみる。

scikit-imageのソースコードをダウンロードする。

http://scikit-image.org/
https://github.com/scikit-image/scikit-image

__init__.pyファイルを読む

scikit-image-master/skimage/draw/__init__.py
__init__.pyファイルは
skimage.draw モジュールの中身を定義するものです。
以下のファイルは、
draw
draw3d
_draw
という3つから、それぞれの関数を取り込んでいることを示しています。

__init__.py
from .draw import circle, ellipse, set_color
from .draw3d import ellipsoid, ellipsoid_stats
from ._draw import (line, line_aa, polygon, ellipse_perimeter,
                    circle_perimeter, circle_perimeter_aa,
                    _bezier_segment, bezier_curve)

__all__ = ['line',
           'line_aa',
           'bezier_curve',
           'polygon',
           'ellipse',
           'ellipse_perimeter',
           'ellipsoid',
           'ellipsoid_stats',
           'circle',
           'circle_perimeter',
           'circle_perimeter_aa',
           'set_color']

ディレクトリ内部のファイルを見る

_draw.pyx
draw.py
draw3d.py
の3つのファイルがこのディレクトリにあり、
それらがskimage.drawの関数を実現していることがわかります。
.pyファイルは通常のpythonで実装されていて、
_draw.pyxはCythonのコードで書かれていることがわかります。

pyxで書かれたコードを読む

pyxベースのコード(型付けされたpythonコードによる実装)は、python類似の言語で、
変数が明示的に型を付与されている点でpythonとは異なります。

(前略)
cimport numpy as cnp
from libc.math cimport sqrt, sin, cos, floor, ceil, fabs
from .._shared.geometry cimport point_in_polygon
(中略)
cdef char steep = 0
cdef Py_ssize_t x = x0
cdef Py_ssize_t y = y0
cdef Py_ssize_t dx = abs(x1 - x0)
cdef Py_ssize_t dy = abs(y1 - y0)
cdef Py_ssize_t sx, sy, d, i
(後略)

などの記述(cimport, cdefなど)が、Cython固有の表現です。

_draw.pyxの抜粋
    cdef char steep = 0
    cdef Py_ssize_t x = x0
    cdef Py_ssize_t y = y0
    cdef Py_ssize_t dx = abs(x1 - x0)
    cdef Py_ssize_t dy = abs(y1 - y0)
    cdef Py_ssize_t sx, sy, d, i

    with nogil:
        if (x1 - x) > 0:
            sx = 1
        else:
            sx = -1
        if (y1 - y) > 0:
            sy = 1
        else:
            sy = -1
        if dy > dx:
            steep = 1
            x, y = y, x
            dx, dy = dy, dx
            sx, sy = sy, sx
        d = (2 * dy) - dx

while ループの中で使われている変数x, y, dx, dy, sx, sy, d, iが
Py_ssize_t 型であることが明示的に指定されるため、python言語での動的な性質がなくなるので実行時間の短縮がはかられる。

cimport 文は
「cimport は、定義ファイルや実装ファイルの中で、他の定義ファイル中で宣言した名前にアクセスするときに使います。」
他にもfrom モジュール名 cimport 識別子 の構文が存在しています。
from libc.math cimport sqrt, sin, cos, floor, ceil, fabs

cimport numpy as cnp
の直前には、通常のimport文でのnumpyのimportがある。
import numpy as np
cimport文の結果読み込まれたcnpの名前空間は、次のようにpyxファイルの中で
型の定義がpyxファイルの中で参照できるようになっているようです。

どうやらcimport numpy as cnp の行が書かれているモジュールでは、
numpyのC/C++のソースコードで定義されている型をCythonを使って明示的に型を与えることで
通常のpythonでの動的な性格を減らして高速化させるのに使っているようです。

_draw.pyxの抜粋
    # make contigous arrays for r, c coordinates
    cdef cnp.ndarray contiguous_rdata, contiguous_cdata
    contiguous_rdata = np.ascontiguousarray(y, dtype=np.double)
    contiguous_cdata = np.ascontiguousarray(x, dtype=np.double)
    cdef cnp.double_t* rptr = <cnp.double_t*>contiguous_rdata.data
    cdef cnp.double_t* cptr = <cnp.double_t*>contiguous_cdata.data

skimage.draw モジュールの範囲の中では、C/C++言語で書かれたライブラリをwrapしている部分がないことがわかります。(.pxdファイルやCやC++のコードがこのディレクトリにはありませんでした。)

Cythonを使ってモジュールをビルドする手順は、setup.pyに記述されている。

skimage/draw/setup.py
#!/usr/bin/env python

import os
from skimage._build import cython

base_path = os.path.abspath(os.path.dirname(__file__))


def configuration(parent_package='', top_path=None):
    from numpy.distutils.misc_util import Configuration, get_numpy_include_dirs

    config = Configuration('draw', parent_package, top_path)
    config.add_data_dir('tests')

    cython(['_draw.pyx'], working_path=base_path)

    config.add_extension('_draw', sources=['_draw.c'],
                         include_dirs=[get_numpy_include_dirs(), '../_shared'])

    return config

if __name__ == '__main__':
    from numpy.distutils.core import setup
    setup(maintainer='scikit-image developers',
          author='scikit-image developers',
          maintainer_email='scikit-image@googlegroups.com',
          description='Drawing',
          url='https://github.com/scikit-image/scikit-image',
          license='SciPy License (BSD Style)',
          **(configuration(top_path='').todict())
          )

scikit-imageを手作業でビルドしてみることで、理解が進みそうです。

このようにすることで、scikit-imageをブラックボックスではなしに利用していくことが可能になります。

次回は、C/C++で書かれたライブラリをcythonでwrapするコードを見てみましょう。
scikit-imageを読み解く 2(Cythonの実例)

参考URL:「Cython モジュール間で宣言を共有する」

10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10