AITRIOS | Local Console の推論結果をCSV出力してみた

SSS の野田です。

Local Edition という、ブラウザを利用せずにローカルPCの環境で AITRIOS のデバイスを動かせるソフトウェアがでましたね。  
Local Edition については以下を参照してください。  

[【紹介記事】](https://www.aitrios.sony-semicon.com/news/local-edition-released/)  

# この記事の要約
Local Edition で出力される結果はシリアライズされた txt ファイルとなっているので、サンプルコードを改造してデシリアライズ結果を csv ファイルとして出力してみます。

# はじめに
:::note info 
こちらの記事は **2024/09/20** の情報をもとに執筆されています。
今後のアップデートでUIなど仕様が変わる可能性があるので注意してください。
:::

:::note warn
**(追記)2024/12/2 
現在、Local Edition は v1.4.0 が公開されています。**
**この記事で紹介している v1.3.0 以前からは大幅にUIが変わっているため、v1.4.0 以降をお使いの方はお気を付けください。**
:::

# 前提
* こちらの記事では[ Local Edition 触ってみた！！ ](https://qiita.com/SSS-NorimitsuTakahashi/private/4768a00cd54bc18f7589)  をご覧になった方を前提に説明しますのでまだの方は、先にご覧ください。

## その他
* Python を少しでも触ったことがあることが望ましいです

# 目次
* 推論結果が Windows フォルダに出力された状態になっているか確認する
* Classification のサンプルコードでデシリアライズしてみる
* サンプルコードを改造して csv 出力してみる

# 本文
## 推論結果が Windows フォルダに出力された状態になっているか確認する
まずは [ Local Edition 触ってみた！！ ](https://qiita.com/SSS-NorimitsuTakahashi/items/4768a00cd54bc18f7589)の記事を参考に推論までしてみます。

私はソニーのイヤホンを使って、イヤホンの色を判別する画像分類ができる AIモデルを作って推論結果を出力してみました。
今回の場合、6種類の Class の中から一番 Score が高い推論結果だけを使いたかったので、Configuration に設定する json 内の dnn_output_classes と max_predictions 値を 以下のように変更しました。
変更した json ファイルは以下です。

```json
{
    "header" :
    {
        "id" : "00",
        "version" : "01.01.00"
    },
    "dnn_output_classes" : 6,
    "max_predictions" : 1
}
```
:::note info 
Class 数は違っていてもこの後のコードは利用できますが、max_predictions が 1以外の場合はこの後のコードがそのままでは利用できないので注意してください
:::

### 推論
AIモデルと Edge App のデプロイ、各種設定が終わった状態で
Inference メニューから Start Streaming を実施します。

![earphone.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725029/c6024c30-d99e-b85d-77f1-7ae827e5c0e3.png)

適宜 Stop Streaming をクリックして推論を停止しましょう。

"Inference" で設定したフォルダに以下のような推論結果ファイル
が txt ファイルとしていくつか出力されていれば csv 化するための事前準備は完了です。
### 例
```json
{"DeviceID":"Aid-80050001-0000-2000-9002-00000000016a","ModelID":"0311030000000100","Image":true,"Inferences":[{"T":"20240730082614973","O":"DAAAAAAABgAKAAQABgAAAAwAAAAAAAYACAAEAAYAAAAEAAAAAQAAAAwAAAAIAAwABAAIAAgAAAAEAAAAAABvPw=="}]}
```

## Classification のサンプルコードでデシリアライズしてみる
[ Local Edition 触ってみた！！ ](https://qiita.com/SSS-NorimitsuTakahashi/private/4768a00cd54bc18f7589)の記事にも載っているのですが、今回のCSV化の記事を書くうえで肝になる部分なので改めてこちらにも載せておきます。
Local Consoleで推論結果が表示されましたが、Local Consoleでは一時的にデシリアライズした結果を表示しているだけで、時系列でデータを保存したり、他のアプリケーションで利用する場合には元の推論結果をデシリアライズする必要があります。

そのためには FlatBuffers のバイナリを手に入れる必要があります。
Windows 前提で説明しますが https://github.com/google/flatbuffers/releases
から Windows.flatc.binary.zip をダウンロードして解凍します。
その中の flatc.exe を Local Edition を解凍した時に出来た `EdgeApp` フォルダにコピーしてください。
(もしくは、`EdgeApp` フォルダ内の classification.fbs や objectdetection.fbs を flatc.exe と同じ場所にコピーする)

そのうえで、`EdgeApp` フォルダで Shift+右クリック をして、Git Bash や PowerShell などを開きます。そこで、
```
./flatc.exe -p ./classification.fbs
```
を実行すると、その場に `SmartCamera` フォルダが作成されます。

SmartCameraフォルダがあるディレクトリと同じディレクトリに、チュートリアルで紹介されているPythonのサンプルプログラムを配置します。
inference_dirだけ変えれば利用できると思います。
```Python
import os
import base64
import json
from SmartCamera import ClassificationTop

if __name__ == '__main__':
    # Set the path to the directory that stores inference result metadata files.
    inference_dir = '{ここは推論が保存されているディレクトリを記載}'
    ## 例
    ## inference_dir = 'C:\\Users\\UserName\\AppData\\Local\\Temp\\LocalConsole_xp7usx9k\\inferences'

    # Process all inference result files in the inference_dir
    for inf_file in os.listdir(inference_dir) :
        inf_path = '{0}/{1}'.format(inference_dir,inf_file)
        # Read one file in the folder.
        with open(inf_path, 'r', encoding='utf-8') as json_file:
            buf = json.load(json_file)

        # Base64 decode the string in the file.
        if 'O' in buf['Inferences'][0]:
            buf_decode = base64.b64decode(buf['Inferences'][0]['O'])
        else:
            with open('decoded_result_Classification.json', 'w', encoding='utf-8') as file:
                json.dump(buf, file, ensure_ascii=False, indent=4)

        # Deserialize the Base64-decoded string.
        ppl_out = ClassificationTop.ClassificationTop.GetRootAsClassificationTop(buf_decode, 0)
        cls_data = ppl_out.Perception()
        res_num = cls_data.ClassificationListLength()

        # Store the deserialized data in json format.
        buf['Inferences'][0].pop('O')
        for i in range(res_num):
            cls_list = cls_data.ClassificationList(i)
            buf['Inferences'][0][str(i + 1)] = {}
            buf['Inferences'][0][str(i + 1)]['class_id'] = cls_list.ClassId()
            buf['Inferences'][0][str(i + 1)]['score'] = round(cls_list.Score(), 6)

        # Output a json file.
        with open('{0}/decoded_{1}'.format(inference_dir,inf_file), 'w', encoding='utf-8') as file:
            json.dump(buf, file, ensure_ascii=False, indent=4)
```

これを実行すると、メタデータが保存されているディレクトリに`decoded_`で始まるファイルが生成されており、開くとデシリアライズされていることが確認できると思います。

```json
{
    "DeviceID": "Aid-80050001-0000-2000-9002-00000000016a",
    "ModelID": "0311030000000100",
    "Image": true,
    "Inferences": [
        {
            "T": "20240730082614973",
            "1": {
                "class_id": 4,
                "score": 0.933594
            }
        }
    ]
}
```

## サンプルコードを改造して csv 出力してみる

Classification の場合、出力される情報で csv に出力して利用できそうな情報は以下の3つですので、上記のサンプルコードから一部だけ変更を加えて csv を出力できるようにしていきます。
- 時間
- class id
- score

修正後のサンプルコードがこちらになります。
変更箇所は start code と end code で囲ってあります。
```Python
import os
import sys
import base64
import json
from pathlib import Path
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
from SmartCamera import ClassificationTop

#################### start code ######################
import csv
#columns = ["filename", "classid", "score"]
with open('data.csv', mode='w', newline='', encoding='utf-8') as f:
        pass  # clear data
#################### end code ########################

if __name__ == '__main__':
    # Set the path to the directory that stores inference result metadata files.
    inference_dir = '{ここは推論が保存されているディレクトリを記載}'
    ## 例
    ## inference_dir = 'C:\\Users\\UserName\\AppData\\Local\\Temp\\LocalConsole_xp7usx9k\\inferences'

    # Process all inference result files in the inference_dir
    for inf_file in os.listdir(inference_dir) :
        #################### start code ######################
        if 'decoded' in inf_file: continue # skip decoded file
        #################### end code ########################
        inf_path = './{0}/{1}'.format(inference_dir,inf_file)
        # Read one file in the folder.
        with open(inf_path, 'r', encoding='utf-8') as json_file:
            buf = json.load(json_file)
        # Base64 decode the string in the file.
        if 'O' in buf['Inferences'][0]:
            buf_decode = base64.b64decode(buf['Inferences'][0]['O'])
        else:
            with open('decoded_result_Classification.json', 'w', encoding='utf-8') as file:
                json.dump(buf, file, ensure_ascii=False, indent=4)
        # Deserialize the Base64-decoded string.
        ppl_out = ClassificationTop.ClassificationTop.GetRootAsClassificationTop(buf_decode, 0)
        cls_data = ppl_out.Perception()
        res_num = cls_data.ClassificationListLength()
        # Store the deserialized data in json format.
        buf['Inferences'][0].pop('O')
        for i in range(res_num):
            cls_list = cls_data.ClassificationList(i)
            buf['Inferences'][0][str(i + 1)] = {}
            buf['Inferences'][0][str(i + 1)]['class_id'] = cls_list.ClassId()
            buf['Inferences'][0][str(i + 1)]['score'] = round(cls_list.Score(), 6)
            #################### start code ######################
            time_filename = inf_file
            time = os.path.splitext(time_filename)[0] #remove extension
            classid = cls_list.ClassId()
            score = round(cls_list.Score(), 6)
            data = [time,classid,score]
            print(data)
            with open('data.csv', 'a', newline='') as f:
                writer = csv.writer(f)
                writer.writerow(data)
            #################### end code ########################
        # Output a json file.
        with open('./{0}/decoded_{1}'.format(inference_dir,inf_file), 'w', encoding='utf-8') as file:
            json.dump(buf, file, ensure_ascii=False, indent=4)
```
変更した箇所を順に説明します。

まず、csv ファイルを出力するために Python 標準ライブラリの csv モジュールを import します。
```Python
import csv
```
data.csv という名前でファイルを書き込み出来る形で新規作成します
テストで何回も実施する時に、csvにデータが入っている場合は消すようにしておきます。
```Python
#columns = ["filename", "classid", "score"]
with open('data.csv', mode='w', newline='', encoding='utf-8') as f:
        pass  # clear data
```

こちらのコードを2回実行すると `decoded_xxx` ファイル内のデータ読み取りで Error が起きてしまうのでここではスキップするように修正しておきます。 
```Python
if 'decoded' in inf_file: continue # skip decoded file
```

時間情報は json の 'T'から取得しなくてもファイル名に同じ情報が利用されているのでファイル名を利用します。
拡張子は不要なので削除しておきます。
```Python
time_filename = inf_file
time = os.path.splitext(time_filename)[0] #remove extension
```

class_id と score はデシリアライズ後の情報をそれぞれ保存します。
```Python
classid = cls_list.ClassId()
score = round(cls_list.Score(), 6)
```

配列データにして csv ファイルに書き込みます。
```Python
data = [time,classid,score]
print(data)
with open('data.csv', 'a', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(data)
```

こちらの Python を実際に動かしてみます。

![csv_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725029/7aa9a92b-2950-734a-c3b1-268977b1a9d4.png)




無事に CSV ファイルが出力されていました。

![csv_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725029/0c73e66d-fe04-fb64-e9c7-2afe4769b964.png)


![csv_folder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725029/0b5bf625-daa6-8f65-8d9d-d0b66ccdd3f7.png)

EXCELでも開けました

![csv_excel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725029/682dc0a1-13ad-4673-0a65-59ed7fe09b40.png)

## 困った時は
もし、記事の途中でうまくいかなかった場合は、気軽にこの記事にコメントいただいたり、以下のサポートのページもご覧ください。
コメントのお返事にはお時間を頂く可能性もありますがご了承ください。
- [Support(FAQ)](https://developer.aitrios.sony-semicon.com/edge-ai-sensing/support)

また、記事の内容以外で AITRIOS についてお困りごとなどあれば以下よりお問い合わせください。
- [Contact Us](https://developer.aitrios.sony-semicon.com/edge-ai-sensing/contact-us)

## 最後に
今回は Local Edition の出力テキストから csv 出力する方法について解説しました。

CSV にメタデータを出力することでデータの転送やバックアップ、データ分析や BI ツールなどでダッシュボードへのデータ入力もやりやすくなると思いますので、是非ご活用ください！

同じ流れで Object Detection でもできるのでリクエストがあれば記事化しますのでご要望あれば是非コメントください。
ご覧いただきありがとうございました！