画像認識サンプルをモジュールとして改造する

画像認識関係のサンプルを元に改造するときのよくあることをメモしました。

## はじめに
- 画像認識のサンプルコードは、モジュールとして実装することを目的としていません。
- 基本となる考えかたを示すことを目的とするからです。
- モジュールとして活用する際の不具合は、オリジナルの作者を責めないでください。

## 画像認識・機械学習系のサンプルをモジュールとして再利用しようとするときに問題になること
### オリジナルの作者の目的は、論文の根拠となる結果を出すことです
特定のデータセットに対して、目的の計算をことにする主力をおいています。
ファイル入力に対して、一連の処理結果を得られることのインタフェースを重視しています。
含まれないことがあること：
ライブラリとして利用するために必要になるAPI
画像データインタフェースのAPI

## まず単純にgit clone した状況で動作させよう
- それが利用するに値するものなのかどうか、実際に動作させてみないとわからない。
- 既存の環境を壊さないようにDockerを使って動作させるのがいいだろう
#### 最初の課題
- Dockerを使って、他のプロジェクトへの環境の悪影響を防ぐ
    - 付記：Docker環境を利用する際には .dockerignoreを書くこと。
    - 機械学習の分野では、大量のデータをあつかうことになるので、それらをdockerの管理下から外しておくことが大事となる。
- pip でインストールしたときにバージョンの組合せがうまくいかない。
- `python3 -m pip install 'numpy<2'` などとしないと意図しないバージョンのnumpyが入って、オリジナルの動作環境が作れない。
- opencv-python が循環import 問題を生じる版が入ってしまったりするので、問題のない版のopencv-python をインストールし直す。
- 必要なpretrained modelのダウンロード
- 必要なデータファイルのダウンロード
- それらを展開後、適切な場所に配置する。
- 実行用のスクリプトの中にある編集必須箇所を編集する。
- そして期待した動作をすることを確認する。
#### 実行結果のoutput のディレクトリを確認する。
- プロジェクトによっては、ドキュメントが少ないこともある。
- どのディレクトリに書かれるのを確かめよう。
- docker環境の場合には、それがdocker環境からexit　しても残るディレクトリになるように、mountしたディレクトリになるようにしておこう。



#### 想定している処理のデータ量が多すぎる
- オリジナルのリポジトリでは、論文で評価するための大量のデータセットを使っています。
- しかし、それを動かし切るには、死ぬほど時間がかかります。（マシンによっては数日かかるかもしれない）
- 実行中にディスクスペースを使い切って、マシンが異常終了するかもしれません。
##### 動作検証用の小規模なデータセットを作る
オリジナルのデータセットを理解して、それを元に小規模のデータ・セットを作ります。
それが切り替えて動作するようにします。
実際に動作を検証します。


### メインのスクリプトからモジュールが切り出されていない。
簡単です。モジュールを切り出せばいいのです。

## python3.7 以降の想定で書き直す。
- type hint を使う
- pathlib.Pathを使う

### global変数がありすぎる。
まずは、モジュール内のglobal 変数にします。
`import モジュール名`
として、どのモジュールに属するglobal 変数なのかを明らかにします。

### 意味のまとまりがclassにまとめられていない
アルゴリズムの実装者は、モジュールとしての設計に対しては控えめの場合があります。
よいインターフェースになっていないclass実装の場合だと、逆に使い勝手が悪くなる場合があることを知っています。
だから、画像認識のモジュールが関数ベースで実装されていることもあります。
#### classを自分の流儀で実装すればいいようにしてあるってこと
画像認識系の実装はたいがい、次のようなインタフェースを持つことになる。
```.py:
estimator = Estimator()
results = estimator.infer(image)
```

`@dataclass` を使って実装することになるだろう。

#### model ファイルのダウンロード
モジュールとしての実装は、他のユーザーがすんなり使えるようにする作業だ。
だからmodel ファイルのダウンロードの処理も自動化できるようにスクリプトを書く

#### 改変版の公開範囲を明確にする
大元の作者に迷惑がかからないようにすること。
forkした版が互換性を失うようだったら、リポジトリ名を変更すること


### import するモジュールが多すぎる
気の利いた統合環境を使うことです。
使われていないimport があると教えてくれます。
そのようなimport を減らします。

### import する対象が多すぎる
```
from hogehoge import *
```
のようなコードは、確実に問題の原因となります。

```
import hogehoge
```
などと書き換えます。

```
from foo import bar
```
は、モジュールがわかりにくくなるので、推奨しません。


### ユーザーが意識しなくてよいもの識別子を"_"で始めていない。
python では オブジェクト名の先頭に `'_'` を 1つ 付けると、import されなくなります。
そのため、ユーザーが意識しなくてよいもの識別子を`"_"`で始めることが推奨されます。
しかし、アルゴリズムの実装者の多くは、大規模なプログラミングの一部として使うことを意識していません。
そのため、ユーザーが意識しなくてよいもの識別子を`"_"`で始めていない。
ですから、大規模なプログラミングの一部として使う人は、
モジュールとして安定に使えるように「ユーザーが意識しなくてよいもの識別子を`"_"`で始める」ように書き換えることです。


### ライブラリのメソッドが制御構文(例 while True:)を含んでしまっている。
機械学習の推論のライブラリは、ユーザーに使い方を強制しすぎてはいけません。
```.py:
def infer(self, cap: cv2.VideoCapture):
    while True:
        _, image = cap.read()
        if image is None:
            continue
        results = self.infer_image(image)
            
```
のようなメソッドは、ライブラリの中にあるべき実装というよりは、サンプル実装というべきものでしょう。

理由：
- 一つの画像に対して、このライブラリでの推論以上のことをwhile ループの中で実行したくなるはずです。
- ですから、ライブラリとして提供するメソッドの中では制御を含むべきではないと考えます。
類例：
- ディレクトリの中にある一連のファイルの対する処理もそのようなものだと思います。

### infer後の処理が特定のアプリケーションを前提としすぎている。
そのため、infer(image)メソッドの戻り値が、検出結果を描画した画像だったりする場合がある。
そのような場合は、モジュールとしての実装としてふさわしく、推論結果のデータを返すようにする。
そして、描画用のメソッドを抽出する。


### 破壊的代入を行いすぎている。
以下のような例
```
from camera import Camera
camera = Camera()
```

モジュール名のcamera をインスタンス名のcamera が上書きしている。
インスタンス名をcameraから別の名前に変更する。

### 使わないオプションがありすぎる。
- 使わないオプションは削除します。
- モデルファイル名など、固定値に置換えていいものは、オプションに記載する必要をなくします。
- 
### ファイル入力しかない画像の推論のAPIには、画像データを引数に追加しよう
画像認識の推論処理の場合には
```
estimator.infer(image)
```

`def infer(self, image: np.ndarray):`

`def infer(self, p: Path):` は推奨しない。
なぜなら、
```
_, image = cap.read()
```
で読み込んだ値をinfer()しなければならないのだから。

同様な理由で
`def infer(self, cap: cv2.VideoCapture):` も推奨しない。

#### モジュールにするファイルには`__init__.py` ファイルをおいてあるよね。
このファイルがないと、`python3 -m pip install .[dev]` で該当のモジュールのファイルがインストールされません。
まずは、空のファイルをおいてください。


#### model ファイルに対するpath の設定を書き換える
- たいがいのプロジェクトでは、シェルスクリプトやpythonスクリプトの実行時のディレクトリを想定している。
そのため
scripts/foo.sh
scripts/bar.py
などがあったときに
```
cd scripts
bash foo.sh
python3 bar.py
```

として動くものになっている。

このため
```
bash scripts/foo.sh
python3 scripts/bar.py
```
ではmodel ファイルを取得できないようなっていることが多い。

モジュールとして使う場合には、どちらでも動作するようにスクリプトを修正する。


### 自動化testを実装する

`make test` でpytestでのテストを実行できるようにする。

### サンプルスクリプトを作る
- python のargparseを使って、サンプルスクリプトを作ります。
- ファイル入力の処理
- カメラ入力の処理
などを実現できるようにします。

### README.md を自分用に書きます。

### deploy 先に最適化する
- NVIDIAのGPUが利用できる場合には TensorRTにモデルを変換しよう。


### wheel化
~~python3 setup.py bdist_wheel
でwhlファイルを作れるように設定します。~~
setup.py は推奨されない時代になっています。
setup.cfg ではなく
pyproject.toml を書きます。

[pyproject.toml をベースにwhl ファイルを作る手順](https://qiita.com/nonbiri15/items/5b517cf1b52e5ed6e1f9)



### packagecloud にupload する
https://packagecloud.io/