プログラミングガイド

ここでは、サンプルコードを中心に、プログラミングの方法を説明します。

Hylable Discussion

分析を開始しリアルタイム表示するサンプル

このサンプルでは、レコーダーの録音を開始し、 リアルタイムに発話量の時間変化と総発話時間をグラフで表示します。 ここでは、サンプルコードを一部ずつ説明していきます。最後にコード全体を表示します。

クライアントオブジェクトの作成

まず、 hylable パッケージを import してから、サーバーと通信するためのオブジェクト HDClient を作成します。 ここでは default で指定した認証情報を使うので、 引数 profile_name には default を与えます。

import time
import matplotlib.pyplot as plt

from hylable import HDClient, Member

if __name__ == "__main__":
    # Hylable Discussion とコミュニケーションするクライアントを作成
    client = HDClient(profile_name="default")

レコーダーの検索

次に、録音するためのレコーダーを検索します。 ここでは、 M00-00-0000 というレコーダーを探したいので、 まずは get_recorders() メソッドで使用可能な前レコーダーを列挙し、 その中から nameM00-00-0000 と一致するレコーダーを探します。

# 指定された名前と一致するレコーダーを探す
recorder_name = "M00-00-0000"
print("レコーダーを探しています...")
for recorder in client.get_recorders():
    if recorder.name == recorder_name:
        break
else:
    raise ValueError(f"レコーダー {recorder_name} は見つかりませんでした。")

コースの検索

続いて、録音データを保存するコースを検索します。 ここでは、 サンプルコース というコースを探したいので、 レコーダーと同様に get_courses() メソッドでコースを列挙し、 その名前 name が一致するコースを探します。

# 指定された名前と一致するコースを探す
course_name = "サンプルコース"
client.speak_recorder([recorder], "保存先コースを探しています")
print("保存先コースを探しています...")
for course in client.get_courses():
    if course.name == course_name:
        break
else:
    raise ValueError(f"コース {course_name} は見つかりませんでした。")

空ディスカッションの作成

探してきたレコーダーとコースのオブジェクトを引数として、 create_discussion() メソッドで空のディスカッション discussionobj を作成します。 これがレコーダーの録音・分析データがアップロードされる入れ物になります。

# 開始の準備として、空のディスカッションを作成する
print("ディスカッションを作成しています...")
discussionobj = client.create_discussion(
    course_id=course.id,
    recorder=recorder
)

空ディスカッションの設定と更新

作成したディスカッションに対して各種設定を行います。 まずは、トピックを テスト録音 とします。

print("座席やトピックを設定しています...")
discussionobj.topic = "テスト録音"

次に、 discussionobjmembers にディスカッションの参加者を設定します。 ディスカッションの参加者は、座席位置とメンバーに対応する Member オブジェクトのリストを指定します。 ここでは、座席は S-1, S-2, Unknwon の3名をそれぞれ 0度, 90度, 180度の位置に設定しました。 S-1S-2 はメンバーとして登録されているので、 get_members() メソッドで取得した オブジェクトが得られますが、 Unknwon は登録されていません。 そこで、id が Unknwon の空 Member オブジェクトを作成します。

# メンバーと座席位置を探す
members = [
    {"position": 0, "member": "S-1"},
    {"position": 90, "member": "S-2"},
    {"position": 180, "member": "Unknwon"},
]
memberlist = {m.name: m for m in client.get_members()}
for index, member in enumerate(members):
    if (found := memberlist.get(member["member"], None)):
        members[index]["member"] = found
    else:
        members[index]["member"] = Member(id=member["member"])
discussionobj.members = members

最後に、情報を更新した discussionobj オブジェクトを引数として update_discussion() を呼び出すことで サーバー側の情報を上書き更新します。

client.update_discussion(discussionobj)

録音の開始(とレコーダーの音声合成)

いよいよ録音を開始しましょう! speak_recorder() でレコーダーから録音を開始する旨を音声合成してから start_recording() で録音を開始します。 数秒から数十秒経過するとレコーダーの LED が赤くなって、リアルタイム分析が始まり。

# 録音の開始
print("録音を開始します")
client.speak_recorder([recorder], "録音を開始します")
client.start_recording(discussionobj)

リアルタイム可視化

ここではリアルタイムに得られる分析結果を matplotlib を使って表示していきます。 発話量の時間変化を表す activity と、参加者ごとの発話量を表す tlot_ms を使って、 この図のような可視化を行います。

../_images/visualization.png

ループ中で可視化されるデータサンプル

録音時間は 600秒、1ループあたりのスリープを5秒に設定しているので、全体で120回のループが実行されます。 まずは、 get_discussion() で現時点の分析データを取得します。 分析データが貯まる前は activitytlot_ms のデータは無いので、そのときは5秒待って戻ります。

データが取得できたら、まずはメンバーの情報を取得します。 get_discussion() で得られるメンバーはデータ量節約のためにメンバーIDしか含まれていないので、 sync_data() でメンバーの名前などの情報を更新します。 もし組織に登録されているメンバーリストにない場合は、メンバーIDをそのまま名前として扱います。

ここまで準備ができたら、 matplotlibimshowbarh メソッドを使って可視化を行います。

# 録音時間だけ待つ
duration = 600
sleep_per_loop = 5
for d in range(duration // sleep_per_loop):
    print(f"\r{duration} 秒待機中。あと {duration-d*sleep_per_loop}{'.' * (d % 3)}", end="")

    data = client.get_discussion(discussionobj.id)
    if "activity" not in data.frames:
        time.sleep(sleep_per_loop)
        continue
    if "tlot_ms" not in data.stats:
        time.sleep(sleep_per_loop)
        continue

    # メンバー情報の取得
    for m in data.members:
        try:
            m['member'].sync_data(client)
        except:
            m['member'].name = m['member'].id

    # 発話量の時間変化と総発話時間をグラフで表示
    plt.clf()
    plt.subplot(2, 1, 1)
    plt.imshow(data.frames['activity'].T, aspect="auto", interpolation="nearest", origin="lower")
    plt.yticks(range(len(data.members)), [m['member'].name for m in data.members])
    plt.title("発話量の時間変化")

    plt.subplot(2, 1, 2)
    plt.barh([m['member'].name for m in data.members], data.stats["tlot_ms"] / 1000)
    plt.title("総発話時間[秒]")
    plt.draw()
    plt.pause(sleep_per_loop)

録音停止

10分経過したら、stop_recording() で録音を停止します。

# 録音の停止
print("録音を停止します")
client.stop_recording([recorder])

ダッシュボードURLの表示

最後に、 Course オブジェクトと Discussion オブジェクトを 使ってダッシュボードへのURLを生成して表示します。

# ダッシュボード URL の表示
url = f"https://discussion.hylable.com/#/course/%s/discussion/%s" % (
    course.id, discussionobj.id
)
print("ディスカッションが作成されました。 URL はこちらです。")
print(url)

完全なサンプル

これらをまとめたものがこのサンプルです。レコーダーのIDやコース名は組織の状態によって異なりますので 適宜変更してください。

import time
import matplotlib.pyplot as plt

from hylable import HDClient, Member

if __name__ == "__main__":
    # Hylable Discussion とコミュニケーションするクライアントを作成
    client = HDClient(profile_name="default")

    # 指定された名前と一致するレコーダーを探す
    recorder_name = "M00-00-0000"
    print("レコーダーを探しています...")
    for recorder in client.get_recorders():
        if recorder.name == recorder_name:
            break
    else:
        raise ValueError(f"レコーダー {recorder_name} は見つかりませんでした。")

    # 指定された名前と一致するコースを探す
    course_name = "サンプルコース"
    client.speak_recorder([recorder], "保存先コースを探しています")
    print("保存先コースを探しています...")
    for course in client.get_courses():
        if course.name == course_name:
            break
    else:
        raise ValueError(f"コース {course_name} は見つかりませんでした。")

    # 開始の準備として、空のディスカッションを作成する
    print("ディスカッションを作成しています...")
    discussionobj = client.create_discussion(
        course_id=course.id,
        recorder=recorder
    )

    print("座席やトピックを設定しています...")
    discussionobj.topic = "テスト録音"

    # メンバーと座席位置を探す
    members = [
        {"position": 0, "member": "S-1"},
        {"position": 90, "member": "S-2"},
        {"position": 180, "member": "Unknwon"},
    ]
    memberlist = {m.name: m for m in client.get_members()}
    for index, member in enumerate(members):
        if (found := memberlist.get(member["member"], None)):
            members[index]["member"] = found
        else:
            members[index]["member"] = Member(id=member["member"])
    discussionobj.members = members

    client.update_discussion(discussionobj)

    # 録音の開始
    print("録音を開始します")
    client.speak_recorder([recorder], "録音を開始します")
    client.start_recording(discussionobj)

    # 録音時間だけ待つ
    duration = 600
    sleep_per_loop = 5
    for d in range(duration // sleep_per_loop):
        print(f"\r{duration} 秒待機中。あと {duration-d*sleep_per_loop}{'.' * (d % 3)}", end="")

        data = client.get_discussion(discussionobj.id)
        if "activity" not in data.frames:
            time.sleep(sleep_per_loop)
            continue
        if "tlot_ms" not in data.stats:
            time.sleep(sleep_per_loop)
            continue

        # メンバー情報の取得
        for m in data.members:
            try:
                m['member'].sync_data(client)
            except:
                m['member'].name = m['member'].id

        # 発話量の時間変化と総発話時間をグラフで表示
        plt.clf()
        plt.subplot(2, 1, 1)
        plt.imshow(data.frames['activity'].T, aspect="auto", interpolation="nearest", origin="lower")
        plt.yticks(range(len(data.members)), [m['member'].name for m in data.members])
        plt.title("発話量の時間変化")

        plt.subplot(2, 1, 2)
        plt.barh([m['member'].name for m in data.members], data.stats["tlot_ms"] / 1000)
        plt.title("総発話時間[秒]")
        plt.draw()
        plt.pause(sleep_per_loop)

    # 録音の停止
    print("録音を停止します")
    client.stop_recording([recorder])

    # ダッシュボード URL の表示
    url = f"https://discussion.hylable.com/#/course/%s/discussion/%s" % (
        course.id, discussionobj.id
    )
    print("ディスカッションが作成されました。 URL はこちらです。")
    print(url)