最強の解析ソフトを作っていきたい

### はじめに

どんな解析もできる、そして将来どんな新しい解析技術が登場しても導入できる、そんな「最強な解析ソフトが欲しいと思い、頑張って作っています。

https://github.com/hanjinliu/himena

ドキュメントは[こちら](https://hanjinliu.github.io/himena/)。
インストールは `pip install himena[recommended]` でできます。

`himena` という名前は、なんとなく[テトラヒメナ](https://ja.wikipedia.org/wiki/%E3%83%86%E3%83%88%E3%83%A9%E3%83%92%E3%83%A1%E3%83%8A)って姿も名前も神秘的・近未来的でいいよねっていう浅い理由から来ています[^1]。
昔好きだった子の名前とかではない
。

[^1]: テトラヒメナの綴りならhymenaでは？となるが、キーボードでの打ちやすさと英語での処女膜hymenを連想しないようhymenaではなくhimenaにした。

ここに至った経緯や動機、現存するソフトウェアの問題点、そしてどのような設計で「万能」を実現していけそうかを書いていこうと思います。

# §1. 良い感じのデータ解析ソフトがないんですよね

プログラミング言語というのはチューリング完全なので、いわば「万能」なわけです。しかも拡張可能性が非常に高くデザインされているおかげで、新しい手法・技術を取り入れたり、既存のものと組み合わせたりが簡単にできます。

にもかかわらず、そのプログラミング言語で書かれたデータ解析ソフトはどうかというと、「簡単に拡張できますよ」と言っていて、確かにプラグインのインストールなどで簡単に拡張できるのですが、結局いまだに「このソフトすごい！いろいろできて便利！」というものが見つからないのが現状です。たとえば、僕は生物物理の研究をしているので、解析で扱うのは画像や時系列データ、遺伝子配列などになりますが、これを皆がどうやるかというと

|やりたいこと|使用ソフト|
|:-:|:-:|
|画像処理・画像解析|[ImageJ](https://imagej.net/software/fiji/), [napari](https://napari.org/stable/)|
|時系列データの可視化|Excel|
|遺伝子配列の設計・解析|[ApE](https://jorgensen.biology.utah.edu/wayned/ape/)|
|統計解析|Prism|
|タンパク質構造関係|[ChimeraX](https://www.cgl.ucsf.edu/chimerax/), [Pymol](https://pymol.org)|

というふうにソフトウェアを使い分けて、組み合わせて解析をする際には中間の結果をコピーしたりファイルに一度吐き出してほかのソフトウェアで読み込むといった煩雑な手順を踏んでいます[^7]。

[^7]: ここで挙げたものは、どれも他とかなり独立しているので、別々のソフトでもまあよいのだが、実は電子顕微鏡関係が現在非常にカオスで、ソフトウェアの統制が全く取れていない上にほとんど全部使いづらいのである。

ソフトウェアごとにショートカットが違かったり、古いソフトウェアはUIが悪かったりと、かなりフラストレーションがたまります。

# §2. 何故これまでのソフトウェアでは「万能」が実現できなかったのか

これまでソフトウェアの拡張がどのようになされ、何が不完全だったかを考えてみます。

## §2.1. プラグインという発想

1つのソフトウェアで標準であらゆるものをカバーというのは不可能です。そのため、多くのソフトでは「プラグイン」というシステムを採用することで、**必要に応じてサードパーティが機能を追加できる仕組み**にしています。上で登場した中ではImageJやnapari、またプログラミング用のものではVSCodeやJupyter Notebookでプラグインは活発に開発されており、日々更新されています。

実装としては、

- ソフトウェアの状態を取得または更新するインターフェイスを用意し、外部プログラムからそれを利用できる。
- 外部プログラムをソフトウェアから呼び出せる。

の2点に過ぎません。シンプルですが、このおかげで標準実装ではできないことが数多くできるようになります。

例えば、現在開いている画像を取得する関数`get_image`、その画像を更新する関数`set_image`、そして関数をメニューに追加するような関数`register_function`が用意されていれば、以下のように様々な画像解析プラグインが作れます。

#### ファイルの読み書きするプラグイン

標準でサポートしていない画像ファイルの拡張子がある場合、GUIと無関係なパッケージ`my_package`があれば、以下のプラグイン関数を介してファイルを読み込めるようになります。

```python
from my_package import read_image, write_image  # 画像を読み書きする外部関数

def my_reader_plugin(path):
    img = read_image(path)
    set_image(img)

def my_writer_plugin(path):
    img = get_image()
    write_image(img, path)

# 登録
register_function(my_reader_plugin)
register_function(my_writer_plugin)
```

#### 画像処理するプラグイン

以下の関数を定義し、これをソフトウェアから呼び出せば、どんな画像処理もできるようになります。

```python
def my_plugin():
    img = get_image()
    img_output = nanika_sugoi_keisan(img)
    set_image(img_output)

# 登録
register_function(my_plugin)
```

#### ウィジェットプラグイン

ウィジェットを追加するインターフェイス`add_widget`が用意されていれば、GUIそのものを拡張することもできます。

```python
def my_widget_plugin():
    my_widget = VeryGoodWidget()  # ウィジェット作成
    add_widget(my_widget)  # メインウィンドウに追加

# 登録
register_function(my_widget_plugin)
```

当然、`VeryGoodWidget`にボタンを追加し、その中で`get_image` / `set_image`を呼び出せば、解析のツールボックスのようなものも作れます。

## §2.2. プラグインの限界

以上挙げたように、プラグインを使えばかなり自由に拡張できるので、十分なのではないか？と思うかもしれませんが、一つ重大な問題があります。それは、**プラグインではプラグインの拡張ができない**という点です。

`napari`のプラグインの一つである[napari-skimage-regionprops](https://www.napari-hub.org/plugins/napari-skimage-regionprops)を例にとりましょう。このプラグインは、画像の領域ごとの特徴量 (面積、平均強度、長短径など) を計算してテーブルとして表示することができ、使いやすい優れたプラグインです。こんな感じ &darr; で左の画像で手で塗った領域の特徴量が計測され、右のテーブルに出ています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/f3ff4b2b-9272-44ec-9d38-0e6a8eb73782.png)

テーブルがあるので、ここからプロットしたいですよね。例えば、明るさの分布や、その時間依存的な変化などがよく行われる解析です。ところが、それをやるには**CSV出力してほかで読み出すしかない**です。何故なら**このプラグインにプラグインシステムがない**からです。このテーブルの機能を拡張するには、プラグイン開発者に連絡するしかありません。
また逆に、右上のボタンにあるように、せっかく実装した「Copy to clipboard」と「Save as csv...」も、ほかのプラグインからは使えません。とてももったいないですよね。

# §3. `himena`のソフトウェアデザイン

`himena`はこの欠点の克服を第一に考え、**開発者がお互いのプラグインをより良いものにして行ける**設計を心がけています。どのように従来のプラグインシステムの欠点を克服しているのかを述べていきます。

## §3.1. 強力なアプリケーションフレームワークの提供

`himena`は**何か特定の解析に特化しません**。データ解析ソフト一般を考えたときに必要な、またあると便利な機能を備えているだけで、それ以外の具体的なウィジェットや関数はすべてプラグインに委ねています。言い換えると、ソフトウェアの実装よりも一歩抽象化した階層にある**フレームワークを実装**しています。

見た目は以下のような感じです。

![window.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/2e111e3f-cc9e-4c38-9813-e1423f9cd47f.png)

ここに、

- メニューバーとツールバーでコマンドを整理
- コマンドパレットで素早くコマンドを検索・実行
- コピー&ペースト、ドラッグ&ドロップによるウィジェット間のやり取り
- セッションの保存

といった、実装されたウィジェットをサポートする機能を多く備えています。

## §3.2. `WidgetDataModel`によるスタンダード化

では`himena`ではどのように機能を拡張するのかという点です。GUI操作で解析が行われる以上、ウィジェットの実装が不可欠です。

`himena`では、**ウィジェットからのデータの入出力は必ず`WidgetDataModel`を介して行われます**。なお、`WidgetDataModel`は以下の通り、1つのPythonオブジェクトにGUI固有の追加情報を付加したようなものです。

```python
WidgetDataModel(
    value: Any = ...  # 何かしらのPythonオブジェクト
    type: str = ...  # "text", "table"のような、「ウィジェット型」のようなもの
    title : str = ...  # サブウィンドウのタイトル
    metadata: Any = ...  # 各ウィジェット特有の、データには関係ない情報（テーブルの選択範囲など）
    ...
)
```

これにより、まず、プラグインの実装は非常にスッキリします[^2]。

[^2]: フロントエンドとバックエンドを分けているだけとも言えるが、従来の意味でのフロントエンド・バックエンドの分業かというとそうとも言えない。

#### ファイルの読み書き

ファイルを読むプラグインは、ファイルを開いて`WidgetDataModel`を返すだけの関数です。**返ってくる`WidgetDataModel`を表示するウィジェットが実装されている必要はありません**。

同様に、ファイルに書き込むプラグインは、`WidgetDataModel`のデータを指定されたパスに書き込むだけの関数です。ウィジェットからどのように`WidgetDataModel`を読み出すかは次のウィジェットプラグインに委ねているので、ここで考える必要はありません。

#### ウィジェットプラグイン

ウィジェットは、与えられた`WidgetDataModel`から状態を更新する`update_model()`と、今の状態から適切な`WidgetDataModel`を返す`to_model()`が実装されているのが基本です。あとは`register_widget_class`で、そのウィジェットがどのようなデータの表示を想定しているかを記述すれば、対応する`WidgetDataModel`が出てきたときにこのウィジェットを選んでくれます。

```python
from himena.plugins import register_widget_class

# "text"型が来たらMyWidget使ってねと教える
register_widget_class("text", MyWidget)
```

これだけあればほかのプラグインとの連携は問題なくできます（他にもいくつか使い勝手を良くするプロトコルが用意されていますが割愛します）。

#### データ処理プラグイン

データ処理は`WidgetDataModel`から新しい`WidgetDataModel`を計算するだけの関数です。メニューのどこに配置するか、いつアクティブになるかといった情報を`register_function`で記述することで使えるようになります。

```python
from himena import WidgetDataModel
from himena.plugins import register_function

@register_function(
    menus="tools/my-plugins",  # ここに配置してねと教える
    type="table",  # どのデータ型に使えるか
    title="すごい計算する",  # メニューでの表示名
)
def my_function(model: WidgetDataModel) -> WidgetDataModel:
    model_out = ...  # 実装部分
    return model_out

```

入力元、出力先のウィジェットがどうかは関係ありません。すなわち、解析手法の開発者はGUIを意識する必要はありません。

以上をまとめた図が次のようになります。

![02_chart_io.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/3c7fe01b-2640-4d61-86c7-805e7b4cb50a.png)

- ユーザーはメモリの層は見えないので、今まで通りファイルを読み込み (1, 2)、テキストからテーブルに変換し (3, 4, 5)、CSVに書き出します (6, 7)。
- 開発者はファイルの読み込み (1)、ウィジェットの実装 (2, 3 および 5, 6)、処理関数の実装 (4)、ファイルへの書き出し (7)を分業します。この設計により、各ステップはプラグイン間で互換性があるので、**あらゆるプラグインはほかのプラグインを拡張する**ことになります。

ユーザーと開発者でやっていることが半拍ずつずれているのが何だか面白いですね。

## §3.3. ワークフローの記録とセッションの保存

全てのコマンドは`register_function`によって登録されます。このおかげで、実行コマンドやパラメータなど、どのようなワークフローでそのウィジェットがが作られたかがほぼ[^3]記録され、見返して参考にしたり、保存したり、さらには再度実行したりできます。以下のGIFは例として、「CSVの読み込み &rarr; "dataframe"型への変換 &rarr; プロット」という作業を記録したワークフローを右のウィンドウで表示[^4]しており、ウィンドウをすべて消してもワークフローを実行するコマンドで結果を再取得できていることを実演しています。また、ドラッグ＆ドロップで途中までのワークフローを取り出しています。

![workflow.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/d8b9f2b5-247d-4ec1-8724-7313d9a74fd1.gif)

[^3]: 「ほぼ」と言っているのは、例えばデータが手動で編集された場合にその追跡方法が定義されていない場合、クリップボードからの画像の貼り付けのように大きなデータをそのまま記録せざるを得ない場合、関数内部で乱数の取得やウィジェットの状態の取得を「暗に」行っている場合といった、記録が困難な場合があるため。しかし、そのような場合でも、解析の流れの記録は見返す際に非常に有用である。
[^4]: 鋭い読者は気づいたかもしれないが、このワークフローを表示しているウィンドウ自体も、ただの"workflow"型の`WidgetDataModel`から来ていて、ワークフローの実行もただのコマンドである（ともに標準実装のプラグイン）。

このワークフローはJSON出力が可能なため、データの再現に非常に便利です。また、`himena`には全ウィンドウをまとめてセッションとして保存する機能[^5]があり、その際にもワークフローは便利です。たとえば、ファイルへの書き出しが困難だったり、ファイルが重くなるようなデータは、ワークフローだけ記録しておけばセッションで復元できます。

[^5]: ちょっとまだexperimental。

## §3.4. データ型によるフィルタリング

機能の多いソフトウェアは、仕方ないことですが、その機能の多さゆえに重大な欠点があります。

- コマンドが覚えきれない
- メニューが多いし、ネストが深くて探すのに時間がかかる
- ショートカットが足りない

Officeの各製品やAffinity Designerなどが最たる例ですね（悪く言っているわけではないぞ）。`himena`はコマンドパレットで全コマンドを管理できるので、この問題は部分的には解決していますが、もう一つ工夫をしています。

![00_model_menu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/e212342a-09e0-4bff-b0f8-60a74053fb9d.png)

サブウィンドウの左上にあるこちらのボタンには、そのサブウィンドウに対応するコマンドのみが格納されます。例えば、標準の状態でテーブルデータでは「Plot」メニューが出てきますが、テキストデータでは出てきません。
`himena`の各ウィジェットは1つのデータを有しており、`WidgetDataModel`で簡潔に記述されるということを見てきました。`WidgetDataModel`にはデータ型の情報が入っているので、わざわざ実装しなくとも、すべてのウィジェットでこのボタンが使えます。

## §3.4. Pythonなので「つぶしがきく」

PythonでGUIを実装する利点、それはPythonが使えることです。そのソフトウェアでしか動かないマクロ言語とか覚える必要がないし、やりたいことがなんでもできて非常に体験がいい。

`Ctrl+Shift+C`で下部に[Jupyter QtConsole](https://qtconsole.readthedocs.io/en/stable/)が出てきます。

![00_qtconsole.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/a25c2419-3086-492b-ae5e-b01eeb1c416b.png)

ここの名前空間にはメインウィンドウのハンドラが`ui`という変数名で入っており、開いているデータすべてにアクセス可能です。例えば、

```python
ui.tabs  # タブのリスト（みたいなオブジェクト）
ui.tabs[0]  # 最初のタブ＝サブウィンドウのリスト（みたいなオブジェクト）
ui.tabs[0][i]  # 最初のタブのi番目のサブウィンドウ
ui.tabs[0][i].to_model()  # そのサブウィンドウの`WidgetDataModel`
ui.tabs[0][i].to_model().value  # その内部データ（`str`, `ndarray`など身近なもの）
```

かゆい所にとりあえず手が届くので安心。

# §4. プラグインの例

## §4.1. 今あるプラグイン

僕自身が日ごろ必要になる、少しニッチな機能はほかのパッケージでプラグインとして公開しています。

### [`himena-image`](https://github.com/hanjinliu/himena-image)

多くの拡張子の画像の読み書きのサポート、`scikit-image`のサンプルデータのロード、フィルタ・FFTなどベーシックな画像解析関数と、期待の3次元ビューアー[`ndv`](https://github.com/pyapp-kit/ndv)を使ったヌルヌル動くウィジェットを提供しています。作った中では一番完成度が高い。目指せ打倒ImageJ。

![window.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/7b1e62f8-8c22-4e6b-9632-b7a4eb4173fa.png)

### [`himena-bio`](https://github.com/hanjinliu/himena-bio)

生物系の解析をカバーしていきたい。遺伝子配列の基本的操作、アラインメント、PCRのシミュレーションなどができます。目指せ打倒ApE。

![window.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/a8b4e76f-34e4-4678-b84a-277c30fbe745.png)

### [`himena-seaborn`](https://github.com/hanjinliu/himena-seaborn)

テーブルデータから`seaborn`の関数を呼び出します。`seaborn`は引数が多くてコードを書くときは不便だったんですが、GUIにしておくと楽ですね。

![window.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/39cd2ccb-70ee-4631-a3fa-fe995f83856d.png)

### [`himena-stats`](https://github.com/hanjinliu/himena-stats)

検定、確率分布からのサンプリングなど、統計関係の機能が入っています。

![window.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/d1f1430d-5efb-4ce2-bd5b-a5b2247ca0e0.png)

## §4.2. 未完成・今後作りたいプラグインやアイデアなど

- `himena-terminal`: VSCodeみたいにターミナル動かしたい。やり方全くわからん。
- `himena-lmfit`: [`lmfit`](https://github.com/lmfit/lmfit-py)を使ったフィッティングや最適化...やりたい！テーブルをプロットしてそのままフィッティングとかやりたい！
- `himena-sklearn`: [scikit-learn](https://scikit-learn.org/stable/index.html)でのテーブルデータの解析。PCAとかすぐできたら嬉しい。
- `himena-napari`: [`napari`](https://napari.org/stable/)のプラグイン全部吸収してやる...(napariはメインウィンドウで動かす前提の設計が多いので、一筋縄では行かない)
- `himena-micro-manager`: 光学顕微鏡の操作をするMicroManagerをPythonから動かす[pymmcore-plus](https://github.com/pymmcore-plus/pymmcore-plus)を導入してイメージング技術を発展させたい。[`napari`のプラグインではもうある](https://github.com/pymmcore-plus/napari-micromanager)が、開発者本人も「別にnapariじゃなくてよくね」となっているようだ。
- `geohimena`: [`geopandas`](https://geopandas.org/en/stable/getting_started/introduction.html)で衛星データとかの解析（素人並感）
- `astrohimena`: [`astropy`](https://github.com/astropy/astropy)で宇宙とかの解析（素人並感）

# 最後に

GUI開発は長くやってきましたが、なかなか良いアイデアだと思っていて、これまで作ってきたウィジェットや関数もどんどんプラグインとして`himena`に移行していこうと考えています。
データサイエンスに取り組んでいる皆さんにも、ぜひユーザーとしてもプラグイン開発者としても使っていただければ幸いです！