えいあーるれいの技術日記

ROS2やM5 Stack、Ubuntuについて書いています

Image-Processing-Node-EditorにPyPI用設定を追加しました。

Image-Processing-Node-EditorにDockerfileを追加しました - えいあーるれいの技術日記の続きです。(結構空いてしまいました…)


かずひとさんが画像処理を行う面白いプログラム「Image-Processing-Node-Editor」を公開されたようです。

Image-Processing-Node-Editorはdearpyguiをベースとしたエディタで、フィルタや物体検出などの各画像処理を入出力が可視化されたブロックとして扱うことができます。

画像処理ノードを線で繋いでいくのでもちろんコードを書く必要はありません。

作者は「処理の検証や比較検討での用途を想定」と説明していますが、カーネルエディタも作れば、画像処理の勉強になるのではないかと考えています。


このプログラムはGitHubで公開されており、すでに120以上のスターがついています(!?)。これからの開発に期待です。

github.com

PyPIパッケージ化?

このアプリを公開されたときに、「youtube-dl」のようにコマンドラインで一発で実行できれば便利なのでは…?と思い、PyPIパッケージ化の話を提案してみたのがきっかけでした。

考えられていなかったみたいなので、勝手にプルリクを出しました。

(1回目のプルリクは、フォルダの差分が大きすぎたので、閉じて再度プルリクを送ることにしました。)


rclpyのsetup.pyの記述やPyamlQtを弄っていたので、分かっていた部分は多かったものの、初めて知ったところも結構あったので、そこらへんについて解説できたらいいなと思います。

Linuxで対話型アプリを作りたいなら、PySide2とPyPIパッケージの作り方を知っておけばなんとかなる印象です。

どちらもググれば結構でてくるし、Linuxじゃなくても動くのでオススメです。


pipでのインストールについて

python3のライブラリをpipでインストールできることは誰もが知っている常識?だとは思いますが、設定を書くことでどのディレクトリからでも呼び出し可能なパッケージ化ができます。

必要なのは、setup.pyと各Pythonファイルが格納されているディクトリに対する__init__.pyのみ。

__init__.pyを各ディレクトリに配置するのが多少面倒ではありますが、__init__.pyの中身は空でも問題ないので、実質setup.pyが書ければそれでOKです。


以下にsetup.pyの書き方を示します。

(ほとんどがテンプレートそのまま引っ張って一部書き換えを繰り返しているだけなので、ところどころ省きます)

from setuptools import setup, find_packages
import re
import os
from os import path
from os.path import splitext
from os.path import basename
from glob import glob

package_keyword = "ipn_editor"

def get_version():
    with open( "__init__.py", "r") as f:
        version = re.search(
            r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
            f.read(), re.MULTILINE
        ).group(1)
    return version

def package_files(directory):
    paths = []
    for (path, directories, filenames) in os.walk(directory):
        for filename in filenames:
            paths.append(os.path.join('..', path, filename))
    return paths

extra_py_files = package_files("node") + package_files("node_editor")
extra_py_files = ','.join(extra_py_files)

readme_path = path.abspath(path.dirname(__file__))
with open(path.join(readme_path, 'README.md'), encoding='utf-8') as f:
    long_description = f.read()

setup(
    name = "ipn-editor",
    packages=find_packages(),
    py_modules=[splitext(basename(path))[0] for path in glob("./*")],
    package_data={  "node_editor": ["font/YasashisaAntiqueFont/*",
                                      "font/YasashisaAntiqueFont/IPAexfont00201/*",
                                      "setting/*",
                                      extra_py_files
    ]},
    include_package_data=True,

    version = get_version(),

    # 中略
    entry_points = {
        "console_scripts": [
            "ipn-editor=main:main",
        ],
    }
)


へぇーーとなったところをいくつか挙げます。


get_version 関数

実はYOLOXのリポジトリから拝借しています。(元がどこからかは分かっていません)

バージョン指定はsetup.pyに書くものですが、__init__.pyに変数として定義を記述することができるので、他のファイルとの同期が取りやすいメリットがあります。

正規表現ちゃんと使いこなせるようにならないとなーー。


# __version__ = "0.1.0"から"0.1.0"を取得
def get_version():
    with open( "__init__.py", "r") as f:
        version = re.search(
            r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
            f.read(), re.MULTILINE
        ).group(1)
    return version


package_data

package_dataとは、Python以外のファイルをモジュールとして読み込むことで、pip内で相対パス呼び出しを行っても問題なく呼び出せるようにするためのシステムです。

ただし、これが結構厄介。


Pythonのファイル追加は、__init__.pyを追加すれば、py_modules=[splitext(basename(path))[0] for path in glob("探索開始ディレクトリ")],再帰的に読み込まれます。

しかし、モジュールはフォルダよりも下の階層を再帰的に読み込んでくれません。

そのため、自作スクリプトを組んで再帰的にフォルダを追加するか、手動で入力しないといけません。

以下のスクリプトで追加していきました。

extra_py_files = package_files("node") + package_files("node_editor")
extra_py_files = ','.join(extra_py_files)
#(省略)
    package_data={  "node_editor": ["font/YasashisaAntiqueFont/*",
                                      "font/YasashisaAntiqueFont/IPAexfont00201/*",
                                      "setting/*",
                                      extra_py_files


entry_points

rclpyではおなじみentry_points

呼び出し名=ファイルの.py抜き:開始時に呼び出される関数の順に記述してきます。

これも、同じディレクトリ内に__init__.pyが必要だったはず

    entry_points = {
        "console_scripts": [
            "ipn-editor=main:main",
        ],


インストール方法

現在は、PyPIへの登録は行われていないため、URLを使う必要があります。

# 不足パッケージは追加でインストール
pip install numpy
pip install Cython
pip install -e git+https://github.com/samson-wang/cython_bbox.git#egg=cython-bbox


その他

  • 「Image-Processing-Node-Editor」という名前が自分には長すぎたため、「IPN-Editor」という名付けをしました。半ば強制させたような感じになっていますが、他のアプリ起動でも似たような感じになっているので、OK(?)

  • pipでインストール時はipn-editorという名前ですが、インポート時は、nodenode_editorという名前になってしまっているそうです。

    • py_modulesにプロジェクトのホームディレクトリを選択したからこその現象です。
  • 私が、事前にPyPIテストのためにテストリポジトリを作っていたところ、作成者本人がアップできないという事態になりました。自分のパッケージでなければ、testPyPIでも作らないが吉ですね。

  • PyPIパッケージとしてアップする場合はこのURLの記事を見ながらいつもアップしています。


Windows用の実行ファイルも整備されているようで、かなりサポートが手厚くなっているようです。

個人的には、画像をつなげて遊ぶ的な使い方で教育用途でも使えそうだと感じたので、サーバー経由でサービス化しないかなぁなんて思ったりもしました。