pythonプログラムをモジュールベースで開発する

### はじめに
[サービスの集まりとして全体システムを作る](https://qiita.com/nonbiri15/items/f6f34873f9e905110630)
で、実現したい全体システムをLinuxでのサービスとして分割することを述べた。
この文章では、そのサービスプログラムを、テストされたモジュールの集まりとして実装することを述べる。

Pythonプログラムでモジュールベースの開発をする例を述べる。

## 想定読者
- Linux ユーザー
- Pythonプログラマ
- 画像認識・機械学習は熟知しているが、システム全体の構築経験は少ないプログラマ

#### 前提
- あるタスクに対して利用するアルゴリズムが既に候補が上がっていること。
- そのアルゴリズムを使ってサービスプログラムを作ればよいという見通しがたっていること。


## サービスをモジュールへと分解する。
サービスとして自動起動される１つのプログラムも、さらにモジュールという構造を分解していく。
サービス-> モジュールの集まりへの呼び出しに分解

サービスプログラムの完成度を高めることを
各モジュールの完成度を高めることにブレークダウンしたい。

- 巨大すぎるプログラムでは、プログラムのテストが、プログラム開発完了後にスタートされることが多い。
- しかし、モジュールをベースとしたプログラムでは、開発中の時点で既にテストコードが存在して、テストをパスする状態を保ちながらコードが開発されていく。（テスト駆動開発）

## まず、べたなコードでもいいから動くコードを書こう
- 最初からきれいなコードをtopdownで書けるということは少ない。
- だからまずは、関数やモジュールの抽出ができていないコードでもいいから動くコードを書こう。
- 何が必要なのかは、動かしてみるまでわからない場合も多い。

## モジュール（やパッケージ）の役割を明確にすること
- モジュールに機能を加えすぎない。
- モジュールの仕様を確定させて変更を加える理由をなくしていく
# モジュールを適度に分割すること
## モジュールを分割する視点
#### 目的が違う関数は、同一のモジュールに含めない。
- 例：自作ステレオカメラに、IMUをつけた場合
そのIMUに対するモジュールは、ステレオ計算のモジュールとは別にしよう。
- 例：物体検出プログラムとの連携
目的が違いので、このモジュールの中には含めない。
含めてしまうと、改変が必要になる理由が増えてしまって、
実装が安定しないことにつながる。

#### 寿命の長い実装と短い実装とを同一のパッケージにしない。
- 視差計算の部分は、ステレオ計算のアルゴリズムの進展によって急速に入れ替わる可能性が高い。
#### 外部の依存ライブラリが極端に違うものは別ファイルにする。
依存ライブラリが増えることに、テストがしにくくなっていく。
・特定のデバイスがないと動作できない。例：ステレオカメラ
・特定のライブラリがないと動作できない。例：特定のステレオカメラのSDK
・特定のマシンのライブラリ。例：TensorRT
テストしにくくなっていくと、モジュールの劣化を生じていても気づきにくくなっていく。
その点、ライブラリの依存を分割していくと、単体テストの可能な領域を確保できる。

#### 最終運用用目的の部分と開発目的の部分は別ファイルにする。
- 開発目的ではビューワーが必要になるが、完成後にはビューワーが必要にならない。そのような部分は、運用時のwhlファイルには含めなくて済む。運用時には依存ライブラリの数を減らしておくことが大事だ。
###### 減らす利点
- ディスクスペースを削除できる。
- import の結果消費するメモリを節約できる。
- import する依存ライブラリが少なくなればpip install が失敗する可能性が減る。

## モジュール分割の例
ステレオ計測のプログラムをモジュールに分解するとしよう。
その例だと、以下の４つは分離されるべきである。少なくとも、別ファイルにした方が良さそうである。
- 視差disparityの算出
    - （カメラ非依存）
- 視差から深度、点群への変換
    - (カメラ非依存。基線長やカメラパラメータを、実行時に受け取れば、カメラの種類によらない）
- 画像の取得・カメラパラメータの取得
    - （カメラ依存。カメラが異なれば、差し替えが必要）
- データの表示
    - (カメラ非依存)
- サンプルスクリプト
  サンプルスクリプトは、これらのモジュールを全て必要とするだろう。
  例：所定のステレオカメラを接続した時に、同期した左カメラ・右カメラの画像から視差（もしくは深度）、点群を表示するプログラム。
例：保存済みの同期した左カメラ・右カメラの画像から視差（もしくは深度）、点群を表示するプログラム。

### 依存性を減らすことで、単体テスト可能な範囲が増える。
カメラ非依存の部分
- カメラ非依存の部分は、カメラなしでテストできる。
- カメラ非依存の部分はCircleCIでもテストできる。
カメラ依存の部分
- カメラ接続が必須の部分だけをテストすればいい。

### 外部モジュールだけを使う部分はテストを簡略化できる。
- 視差から深度、点群への変換
これはOpen3Dのモジュールの呼び出しに、置き換えれば、自作のコードの必要性が大幅に減る。
テスト内容は、Open3Dのモジュールの使い方が間違っていない程度に簡略化できる。


#### つまり、視差の算出ライブラリは、利用するアルゴリズムを変更した時に差し替えられる。

- することとしないことが明らかになっていること。
- 例：ステレオ計算のアルゴリズムのモジュール
- 左カメラ、右カメラの画像が入ってくれば視差を計算すること
#### ステレオカメラでの視差の計算アルゴリズムは変わっても、APIの仕様は同じである（べき）
```.py:
def calc_disparity(leftimg: np.ndarray, rightimg: np.ndarray) -> np.ndarray
    何かの計算方法
    return disparity
```

```.py:
class DisparityCalculator:
    何かデータメンバー
def __post_init__(self):
    何か初期化処理
def calc_disparity(self, leftimg: np.ndarray, rightimg: np.ndarray) -> np.ndarray
    何かの計算方法
    return disparity
```

- 含めないもの：
    - 視差disparityを深度depthに変換すること
    - `depth = baseline * focal_length / disparity`
    - 深度depthを元に点群に変換すること
    - [open3d.geometry.create_point_cloud_from_depth_image](https://www.open3d.org/docs/0.7.0/python_api/open3d.geometry.create_point_cloud_from_depth_image.html)
- 利用しなくていいもの：
    -  視差、深度データをカラー画像として表示する

- 汎用性のために含めてはいけないもの
    - 使用しているカメラでのカメラパラメータを取得する関数
    - 使用している3Dカメラ特有のデータ構造
    - 余分な依存性を含めてしまうとimport しようとした時点で失敗してしまう。

## 以下の内容は、別課題として解決しよう。
- 物体検出の結果をステレオ計測結果と連動させる。
- セグメンテーションの結果をステレオ計測結果と連動させる。

## pyproject.toml を書く
モジュールのインストール手順を pyproject.toml に書く
```
python3 -m pip install .[dev] 
```
whlを作成する
packagecloud に登録する。