#9 PowerApps アプリ で撮影した画像を FaceAPI で感情分析してみました（累積分析）


## 概要
PowerApps のカメラで撮影した画像を API Management 経由で FaceAPI に転送し、その画像の分析結果「感情、性別、年齢」を PoweApps に表示するアプリの実装手順を数回に分け記載しています。また、並行して、その分析結果を CosmosDB に保存しておき、PowerApps からの累積情報取得リクエストにより累積クエリ結果「感情分布、性別分布」を PowerApps に返し、円グラフ表示する機能の実装手順も複数回に分け記載します。なお、PowerApp の画面作成については省略し、APIコール部分とその戻り値の部分に焦点をあてて記載しています。
本アプリの全体構成は下図となります。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/db59c4a4-3030-7de7-6432-2399f7cc5841.png)

今回（第９回目）の構成箇所は下図の部分となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/ce5df2a9-cde6-70c5-baf0-729ec668f64a.png)


第９回目は、[第７回](https://qiita.com/turupon/items/a7c5459028b142ee6839) で作成した CosmosDBからのデータ取得ローカルプログラムを、新たに作成する Azure Functions に実装させ、感情毎の件数と性別毎の件数を CosmosDB からArray型のJSON形式で取得できることを確認します。

---

## 実行環境
macOS Big Sur 11.3
Python 3.8.3

---

## Azure Portal上での FunctionsApp の作成
[この記事](https://qiita.com/gsy0911/items/61198607476ac686ce6f) 等を参考にFunctions環境を準備しておきます。
上記記事の「３．FunctionAppの作成」を実施します。今回は以下の値で設定しています。

|項目|値|
|-|-|
|Functions App name|iturufuncfacecosmos|
|Publish|Code|
|Runtime stack|Python|
|Version|3.8|
|Region|Japan East|
|Storage Account|storageituru|
|Operating System|Linux|
|Plan type|Consumption|

---

## Azure Portal 上での CosmosDB のキーとエンドポイント情報（クレデンシャル情報）の定義
前々回（第７回）取得した CosmosDB のキーとエンドポイント情報（「COSDB_KEY」「COSDB_ENDPOINT」）とCosmosDB Container情報（「COSDB_DATABASE」「COSDB_CONTAINER」）を 上記で作成した Azure Functions:「iturufuncfacecosmos」の「設定」 ー 「構成」 ー 「＋新しいアプリケーション設定」 で追加定義しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/fd3ec8c4-4a6d-6229-3976-1f159c01c5c2.png)

---

## ローカルでのAzure Functionsの実装
次に、以下の順で Azure Functions を実装していきます。
なお、Azure Functions の作成手順については [この記事](https://qiita.com/turupon/items/8aaf3571f00ebffd3052)を参考にしてください。
１．作業実施のための任意のディレクトリを作成
２．Python仮想環境の定義
３．Functionプロジェクトの作成
４．Functionの作成　⇒　「HTTP trigger」を選択します。(Function name: HttpTriggerGet)

---

## Functions で実行されるコード
第７回で作成したCosmosDB データ取得プログラムをベースに以下のように、該当コードプログラムを以下のように変更します。

```__init__.py
import logging
import os
import json
import azure.functions as func
from azure.cosmos import exceptions, CosmosClient, PartitionKey
from collections import OrderedDict

# emotion定義
emobase = ['anger', 'contempt', 'disgust', 'fear', 'happiness', 'neutral', 'sadness', 'surprise']
emolist = ['怒', '侮', '嫌', '恐', '幸', '無', '悲', '驚']
genlist = ['male', 'female']

# Azure CosmosDB Info
COSDB_KEY = os.environ['COSDB_KEY']
COSDB_ENDPOINT = os.environ['COSDB_ENDPOINT']
DatabaseName = os.environ['COSDB_DATABASE']
ContainerName = os.environ['COSDB_CONTAINER']

# CosmosDBから情報の取得
def cosmosdb_query_item(name) :

    # CosmosDBへの接続
    client = CosmosClient(COSDB_ENDPOINT, COSDB_KEY)
    db = client.get_database_client(DatabaseName)
    container = db.get_container_client(ContainerName)

    # クエリ条件： emotion 毎の件数をカウント
    # 取得プロパティ： emotion, カウント件数
    emo_list = []
    for el in emolist:
        query = ("SELECT VALUE COUNT(1) FROM items i WHERE i.emotion = '{}'").format(el)
        items = container.query_items(query, enable_cross_partition_query=True)
        emo_list.append(OrderedDict(感=el, 値=list(items)[0]))

    # クエリ条件： gender 毎の件数をカウント
    # 取得プロパティ： gender, カウント件数
    gen_list = []
    for gl in genlist:
        query = ("SELECT VALUE COUNT(1) FROM items i WHERE i.gender = '{}'").format(gl)
        items = container.query_items(query, enable_cross_partition_query=True)
        gen_list.append(OrderedDict(性=gl, 値=list(items)[0]))

    # 戻り値の編集
    return_dict = {'emo_graph': emo_list, 'gen_graph': gen_list}    # 辞書データの作成
    return_json = json.dumps(return_dict, ensure_ascii=False)       # Jsonエンコード
    logging.info(return_json)

    return return_json


def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    name = req.params.get('name')
    # req_body = req.get_body()
    result_json = cosmosdb_query_item(name)

    return func.HttpResponse(
            result_json,
            mimetype = "application/json",
            status_code = 200
    )
```

---

## ローカルでコードを実行してみます
コードを実行する前に、ローカルで環境情報を取得します。以下のコマンドを実行します。

```
$ func azure functionapp fetch-app-settings iturufuncfacecosmos
App Settings:
Loading APPINSIGHTS_INSTRUMENTATIONKEY = *****
Loading APPLICATIONINSIGHTS_CONNECTION_STRING = *****
Loading AzureWebJobsStorage = *****
Loading COSDB_CONTAINER = *****
Loading COSDB_DATABASE = *****
Loading COSDB_ENDPOINT = *****
Loading COSDB_KEY = *****
Loading FUNCTIONS_EXTENSION_VERSION = *****
Loading FUNCTIONS_WORKER_RUNTIME = *****
```

その後、コードを実行します。

```
$ func host start                                   
Found Python version 3.8.3 (python3).

Azure Functions Core Tools
Core Tools Version:       3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d  (64-bit)
Function Runtime Version: 3.0.15584.0

Functions:
	HttpTriggerGet: [GET,POST] http://localhost:7071/api/HttpTriggerGet

For detailed output, run func with --verbose flag.
[2021-05-19T13:59:55.984Z] Worker process started and initialized.
[2021-05-19T14:00:01.165Z] Host lock lease acquired by instance ID '0000000000000000000000002CF30641'.
```

---

## Functions へのアクセスプログラムの実行
結果確認のためのFunctionsへのアクセスプログラムは以下となります。

```GetEmotionData.py
import requests
import pprint

url = "http://localhost:7071/api/HttpTriggerGet"
headers = {
    'Content-Type': 'application/octet-stream',
}

try:
    response = requests.get(url, headers=headers)
    pprint.pprint(response.text)
except Exception as e:
    print(e)
```

実行結果は以下となります。

```
$ python GetEmotionData.py
('{"emo_graph": [{"感": "怒", "値": 1}, {"感": "侮", "値": 1}, {"感": "嫌", "値": 0}, '
 '{"感": "恐", "値": 0}, {"感": "幸", "値": 21}, {"感": "無", "値": 13}, {"感": "悲", '
 '"値": 5}, {"感": "驚", "値": 3}], "gen_graph": [{"性": "male", "値": 25}, {"性": '
 '"female", "値": 19}]}')
```

ローカルFunctions側の実行結果は以下となります。

```
[2021-05-19T14:01:49.649Z]     'Date': 'Wed, 19 May 2021 14:01:49 GMT'
[2021-05-19T14:01:49.649Z] {"emo_graph": [{"感": "怒", "値": 1}, {"感": "侮", "値": 1}, {"感": "嫌", "値": 0}, {"感": "恐", "値": 0}, {"感": "幸", "値": 21}, {"感": "無", "値": 13}, {"感": "悲", "値": 5}, {"感": "驚", "値": 3}], "gen_graph": [{"性": "male", "値": 25}, {"性": "female", "値": 19}]}
[2021-05-19T14:01:49.650Z] Executed 'Functions.HttpTriggerGet' (Succeeded, Id=fee1f1ed-4f42-4142-9ecc-1b97fcc64d78, Duration=1041ms)
```

CosmosDBからのデータ取得ローカルプログラムで、感情毎の件数と性別毎の件数を CosmosDB から取得できることを確認できました。

---

## Azureへのデプロイ
ローカルで動作確認したコードをAzureに再デプロイします。

```
$ func azure functionapp publish iturufuncfacecosmos           
Getting site publishing info...
Creating archive for current directory...
Performing remote build for functions project.
	:
	中略
	:
Deployment successful.
Remote build succeeded!
Syncing triggers...
Functions in iturufuncfacecosmos:
    HttpTriggerGet - [httpTrigger]
    	:
```

デプロイした結果、該当Functionsの関数として登録されていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/92313315-cb9b-37c3-240c-60eee8e77e27.png)

---

## Azure Portal からのデータ取得確認
上記画面から「HttpTriggerGet」関数を選択します。新たに表示される画面の左側から「コードとテスト」を選択し、上部タブから「テストと実行」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/58c73efb-fd8e-ad49-5dc7-42483a153105.png)

「テストと実行」の右側表示画面で、HTTPメソッドから「GET」を選択し、画面下部の「実行」ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/a7780ba3-d794-542c-bc44-4a0932766525.png)

実行結果が表示され、CosmosDBからのデータ取得ローカルプログラムで、感情毎の件数と性別毎の件数を CosmosDB からArray型のJSON形式で取得できることを確認できました。


---

## 次回について
次回[（第10回）](https://qiita.com/turupon/items/7b317cc99fb9de797137)は、[第３回](https://qiita.com/turupon/items/b48f56637e0056a46a00) で作成した API Management に 本Functions を登録し、今回と同様の結果が得られることを確認してみます。

---

## 参考情報
以下の情報を参考にさせていただきました。感謝申し上げます。
[Azure Cosmos DB - 基本操作（Python）](https://qiita.com/takmot/items/f8cfe499b9b98bec4031)
[PythonからAzure Comos DBのデータ操作](https://qiita.com/tamo_breaker/items/38eae9e5a24fade35a36)
[Azure Functions App × Python で Azure CosmosDB にアクセスしてみる](https://qiita.com/annie/items/2245dd548b9d8c427f77)
[Azure cosmosDB のアラート設定について](https://qiita.com/kewpie134134/items/2fd28f9e31d1888fdc96)