基礎実験-TA引継ぎ

 Playgroundが潰されたため,こちらに基礎実験TAのメモをのこす.長野先生の回では,1週目に皮膚温の測定,2週目に皮膚コンダクタンスの測定を行う.

0. TAの仕事

授業時間内
・計測器の装着方法の説明.
・計測データをエクセルにまとめてアップする.
・2週目のSC回では,ジェンガが倒れないようにする.
・先生が遅れてくるときは,代わりにTAが説明する.
・先生のレポート執筆方法の説明の補足
→ 毎回聞かれるため,レジュメに直書きしたため要確認.3限の教室場所も説明.

授業時間外
・実験機材,環境の準備.詳細は後ほど説明.
・席は自由で,筆記用具を机の上に出すようにアナウンスする.
・レポート返却.

1. 皮膚温

①準備するもの
・プリント:レジュメ,記録用紙,計算問題(両群に配布)
・実験機材:計測器,スピーカー,ドミノ,メンディングテープ
・その他:先生にメモ用紙,荷物置きの椅子を3つ×3セット

②注意事項
・室内温度を外気温と合わせる(クーラーから調節する).
・ドアを閉めて3Dプリンタの電源等を落とす.
・計測器の動作確認を事前に行う.
・遅刻者は後ろから入室させ,測定が始まっている場合は2限から参加.

変化量の算出方法
・安静,課題期の最後の1分をそれぞれ算出して,「課題-安静=変化量」を算出.
・その後,群間でt検定を行うが有意でないことがちょいちょいある.

2. 皮膚コンダクタンス

①準備するもの
・プリント:レジュメ,記録用紙
・実験機材:計測器(指抜き手袋,電極,クリップ),ジェンガ(フル稼働)
・その他:先生にメモ用紙,荷物置きの椅子を3つ×3セット

②注意事項
・ジェンガはフル稼働のため,計測が終わるたびに即回収.
・電極の在庫を確認し,なくなりかけたら先生に報告する.
・(皮膚温と同様)

3. 連絡事項-テンプレ

①データ配布用の連絡
#皮膚温
お疲れ様です.Dグループ用,皮膚温データはこちらの学習用サイトはこちら(https://protolab.sakura.ne.jp/smarthealth/?p=28)から確認できます.よろしくお願いいたします.

#皮膚コンダクタンス
お疲れ様です.Dグループ用,皮膚コンダクタンスのデータはこちらの学習用サイトはこちら(https://protolab.sakura.ne.jp/smarthealth/?p=12)から確認できます.よろしくお願いいたします.

②PC室の場所
・3限はPC室(E-211)にて長野先生とTAが質疑応答を行います.参加は自由ですので,どうぞ気軽にお越しください

③レポートの返却
基礎実験,長野先生回のレポートを返却いたします.
*リアクションは不要です.

4. 印刷物

・皮膚温:記録用紙,レジュメ,計算用紙
・皮膚コンダクタンス:記録用紙,レジュメ
*印刷物は,E-151のラック一番上のクリアファイル内に保存している.

FitbitとPolarの比較

測定結果の概観
FitbitCharge4とPolarUniteを装着し、学内を歩いたり多少走ったりしながら測定結果を比較した。位置情報はGPSを内蔵したCharge4の方が正確だ。心拍数も似たような変化を示していたものの、変化を観察していると、他サイトでも指摘されていたとおり、Fitbitの変化がやや遅れてやってくる印象であった。

測定結果取得方法の比較
Polarのデータ取得は、デバイスをPCに接続しPolarFlowSyncで同期を行い、WebアプリのPolarFlowにログインしてセッションを選択し、エクスポートを選ぶだけだ。非常に簡単だ。出力されるCSVデータの内容も極めて常識的である。対するFitbitのデータ取得は、自由度が高い反面、その手順は煩雑だ。Pythonを使って取得する方法が一般的なようだが、実行環境を整えたり、必要に応じてスクリプトを用意したりする必要がある。下に1秒単位でHRを取得するコードを掲載した。

import sys
import fitbit
import gather_keys_oauth2 as Oauth2
from datetime import datetime, date, timedelta
  
print('Fitbit02') 
USER_ID     = "ほげほげ"
CLIENT_SECRET = "ほげほげ"
DATE = "2023-03-24" # 取得したい日付 
  
print('Hello fitbit02') 
   
def requestFitbit(DATE):
    rval=""
    global auth2_client
 
    fitbit_stats = auth2_client.intraday_time_series('activities/heart', DATE, detail_level='1sec')
    HRstats = fitbit_stats['activities-heart-intraday']['dataset']
    
    OUTPUT_FILE = "ALL2.csv"
    csv_file = open(OUTPUT_FILE, 'a')
   
    csv_file.write(DATE+",")
    for num1 in range(24):
        for num2 in range(60):
            for num3 in range(60):
                key='{:02}'.format(num1)+':{:02}'.format(num2)+':{:02}'.format(num3)
                hr=""
                for var in range(0, len(HRstats)):
                    #csv_file.write(str(HRstats[var]['time']))
                    #csv_file.write(",")            
                    if str(HRstats[var]['time']) == key:
                        hr=str(HRstats[var]['value'])
                        #print("hit")
                        break
                print(key+"/"+hr)
                csv_file.write(key+","+hr)
                csv_file.write("\n")              
                 
    csv_file.write("\n")
    csv_file.close()
    return rval
##################################################
 
def writeindex():   
    OUTPUT_FILE = "ALL.csv"
    csv_file = open(OUTPUT_FILE, 'a')
     
    csv_file.write(",")
    for num1 in range(24):
        for num2 in range(60):
            key='{:02}'.format(num1)+':{:02}'.format(num2)+':00'
            csv_file.write(key)
            csv_file.write(",")              
             
    csv_file.write("\n")
    csv_file.close()
##################################################
 
"""Get tokens"""
server = Oauth2.OAuth2Server(USER_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN = str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN = str(server.fitbit.client.session.token['refresh_token'])
  
#print(ACCESS_TOKEN)  
#print(REFRESH_TOKEN)
 
"""Authorization"""
auth2_client = fitbit.Fitbit(USER_ID, CLIENT_SECRET, oauth2=True, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)
  
"""request"""
 
#writeindex()
today = datetime.today()
for var in range(0, 1):
    #day=today + timedelta(days=1)
    #print(var+":" + datetime.strftime(yesterday, '%Y-%m-%d'))
    stamp=datetime.strftime(today - timedelta(days=var), '%Y-%m-%d')
    print("target: " + stamp)
    requestFitbit(stamp)

測定されたHRの比較
FitbitCharge4のデータをPythonで取得し、同区間のPolarUniteのデータに重ねてみた。PolatUniteは3色10点のPolar Precision Primeセンサー搭載でいかにも正確に測れそうだ。対するFitbitCharge4は単色で測定点数も少なくやや心もとない。結果を見比べると、概ね一緒なのだが、Charge4は中盤の走っている場面で値が低めというか、かなり遅れて変化している事が見て取れる。また、PolarUniteのデータが1秒間隔で全量取得できるのに対し、Fitbitは1~5秒程度の不定間隔データとなっている。走り終わって座っている後半区間に関しては、両者はほとんど一緒だ。動きが大きくなると正確な測定が難しくなるという研究もあり、その結果を裏付ける比較結果であった。動きが少ない場面ではFitbitでも良いと言えそうだが、心理学実験での利便性は、総合的にPolarが高いと言えそうだ。
追記:わー!!文献:Assessment of the Fitbit Charge 2 for monitoring heart rateでも同様の結果だ。Charge4と2は同じアルゴリズムと思われる。30BPM低く見積もることがある、と。

Polar UniteとH7の比較
Polarには旧来のチェストバンド型センサー(H7やH10など)もある。H7を保有しているため、胸部から心電図を測定するH7と、腕から脈波を測定するUniteでHRがどのように異なるか比較してみることにした。左が座位で3分ほど、右が屋外を走り休憩する12分ほどの比較。H7で測ったデータも、Uniteで測ったデータも、PolarFlow(Webアプリ)で参照可能になる。とても便利だ。

H7の値を真のHRとすると、Uniteの測定はかなり頑張っていると言える。特に走っている最中でも追従しているのは素晴らしい。しかし「やや遅れている」のも事実のようである。そもそもH7の心拍数データは正しいのだろうか?(後継機種のH10の売り文句は「Polar史上最も正確なHRセンサー」だ)正確に測れているかは、心電図を見ないと結局わからないので、H10を買う必要があるのかもしれない。

Fitbit/Python230324

この期に及んでPythonである。Fitbit/Pythonとの縁はしばらく切れぬらしい。


Anacondaのインストール
Python実行環境であるAnacondaのインストールを行う。とりあえずAnaconda3をダウンロードして道なりにインストール。終了すると、Anaconda PromptからPythonが実行できるようになる。インストールは、こちらのサイトが参考になる。インストールできたら、startメニューからAnaconda Promptを起動。Pythonコマンドで動作確認。Ctrl+Zで対話モード終了。

Fitbitの開発者向けサイトでアプリケーションを登録する
FitbitDevelopperサイトにログインして、Manage>RegisterAppの順で進み、アプリケーションを登録する。設定は以下の通り。URL関係は全て「https://kodamalab.sakura.ne.jp/wordpress/?p=46853」、RedirectURLは、「http://127.0.0.1:8080/」とする。ページ内で作成された「OAuth 2.0 Client ID」と「Client Secret」をPythonスクリプト中で使用することになる。

FitbitDevelopperサイトにログインする必要あり
Devサイトでのアプリ作成方法

Fitbit API Python Clientのインストール
GitHub – orcasgit/python-fitbit: Fitbit API Python Client Implementationからcodeボタンを押し、Zipファイルをダウンロードし、中身をCドライブ直下に配置する(こちらのサイトが参考になる)。さらに、下記コマンドで追加パッケージをインストールする。

pip install -r requirements/base.txt
pip install cherrypy 


Pythonスクリプトでクラウド上のファイルを取得
c:\python-fitbitフォルダに下記スクリプトをfitbit01.pyという名前で保存し、実行する。Anaconda Promptから下記コマンドを打ち込む

python fitbit01.py

import sys
import fitbit
import gather_keys_oauth2 as Oauth2
 
print('Hello FitbitAPP3') 
 
USER_ID     = "ほげ"
CLIENT_SECRET = "ほげ"
 
DATE = "2021-06-03" # 取得したい日付
 
 
server = Oauth2.OAuth2Server(USER_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN = str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN = str(server.fitbit.client.session.token['refresh_token'])
 
#print(ACCESS_TOKEN) print('\n') 
#print(REFRESH_TOKEN)print('\n') 
 
"""Authorization"""
auth2_client = fitbit.Fitbit(USER_ID, CLIENT_SECRET, oauth2=True, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)
 
"""Getting data"""
#print('Getting data\n') 
#fitbit_stats = auth2_client.intraday_time_series('activities/heart', DATE, detail_level='1min')
  
##################################################
fitbit_stats = auth2_client.intraday_time_series('activities/heart', DATE, detail_level='1min')
stats = fitbit_stats['activities-heart-intraday']['dataset']
 
OUTPUT_FILE = "HR_%s.csv" % DATE
csv_file = open(OUTPUT_FILE, 'w')
for var in range(0, len(stats)):
    csv_file.write(stats[var]['time'])
    csv_file.write(",")
    csv_file.write(str(stats[var]['value']))
    csv_file.write("\n")
csv_file.close()
 
##################################################
fitbit_stats = auth2_client.intraday_time_series('activities/steps', DATE, detail_level='1min')
stats = fitbit_stats['activities-steps-intraday']['dataset']
 
OUTPUT_FILE = "STEP%s.csv" % DATE
csv_file = open(OUTPUT_FILE, 'w')
for var in range(0, len(stats)):
    csv_file.write(stats[var]['time'])
    csv_file.write(",")
    csv_file.write(str(stats[var]['value']))
    csv_file.write("\n")
csv_file.close()
 
##################################################
fitbit_stats = auth2_client.intraday_time_series('activities/calories', DATE, detail_level='1min')
stats = fitbit_stats['activities-calories-intraday']['dataset']
 
OUTPUT_FILE = "CALO%s.csv" % DATE
csv_file = open(OUTPUT_FILE, 'w')
for var in range(0, len(stats)):
    csv_file.write(stats[var]['time'])
    csv_file.write(",")
    csv_file.write(str(stats[var]['value']))
    csv_file.write("\n")
csv_file.close()

トラブルシューティング

・RedirectURLが、http://127.0.0.1:8080/で正しく動作しない場合
ポート8080をなにかのプログラムが使っている可能性がある。その場合は、RedirectURLを、「http://127.0.0.1:8088/」とし、python-fitbitフォルダ中のgather_keys_oauth2.pyの該当部分を「8088」に変更してスクリプトを実施する。

・Pythonのスクリプトは何で作ればいいの?
プログラミング用のテキストエディタ、Notepad++がおすすめです。

WS230315@同志社大学

Arduino入門
 今回のワークショップでは、生理指標の測定にArduinoを使用します。Arduinoは、電子工作で何かをプロトタイプする場合に使用される開発環境です。用途に応じた様々なタイプのマイクロコンピュータを、ArduinoIDEという単一のインターフェースで使用することができ、世界中の人々が、ありとあらゆるハードウェアをArduino経由でPCに接続しています。

//プログラム名:Blink
//LEDを点滅させる
void setup()
{
  pinMode(13, OUTPUT); //13番のピンを出力モードに
}

void loop()
{
  digitalWrite(13, HIGH); //13をONにする
  delay(1000); //1000ms待つ
  digitalWrite(13, LOW); //13をOFFにする
  delay(1000); //
}

電源およびAD変換部分の組み立て
下の写真と部品リストを照らし合わせ、ブレッドボード上に電源およびAD変換部分の部品をセットしてみましょう。Arduinoに接続できたら、下のプログラム「ADC1」をArduinoに書き込んで動作させてみましょう。プログラムを動作させるには、「Adafruit ADS1x15」ライブラリが必要です。ライブラリのインストール方法、プログラムの動作のさせかたは動画をご覧ください。

//プログラム名:ADC1
#include <Wire.h> //I2C通信ライブラリ
#include <Adafruit_ADS1X15.h> //ADS1015ライブラリ
Adafruit_ADS1015 ads; //ADS1015クラスのインスタンス化
 
void setup(void)
{
  Serial.begin(115200);  //シリアル通信開始
  // ADS1015 gain 2/3  input range +/- 6.144V 
  ads.begin(); //ADS1015通信開始
}
 
void loop(void)
{
  int16_t results; //AD変換結果
  results = ads.readADC_Differential_0_1();  //差動入力
    
  float   multiplier = 3.0F;  // デフォルトゲイン(2/3倍)における係数
  Serial.print(results * multiplier); //得られたデジタル値をmvに換算しシリアルモニタに表示
  Serial.print(",3000,0"); 
  Serial.println(); //シリアルモニタ改行
  delay(0);
}

SCC測定アンプの組み立てと測定
さらに、下の写真と部品リストを照らし合わせ、ブレッドボード上にSCC測定アンプの部品をセットしてみましょう。

組み立て手順は自由ですが、下記のような順序でやってみるとやりやすいかもしれません。組み立て中は、念のためArduinoのUSBケーブルを抜いて、回路の電源を切っておきましょう。

部品の配置が終わったら、ArduinoをPCに接続し、プログラム「SCC1」をArduinoに書き込んで動作させてみましょう。
※身体に電極を装着して測定する際は、感電を防止するため、PCから電源を必ず抜くようにしましょう。

//プログラム名:SCC1
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
Adafruit_ADS1015 ads;
 
void setup(void)
{
  Serial.begin(115200);
  // ADS1015 gain 8x  input +/- 0.512V
  ads.setGain(GAIN_EIGHT);  //ゲインを8倍に設定
  ads.begin();
}
 
void loop(void)
{
  int16_t results1,results2;
  results1 = ads.readADC_Differential_0_1();  //差動入力
     
  float   multiplier = 0.25F; //ゲイン8倍時の係数
  Serial.print(results1 * multiplier); 
  Serial.print(","); 
  Serial.print("194,"); //24k Ohm = 42uS
  Serial.print("-7,");  //1M Ohm = 1uS
  Serial.println();
 
  delay(200);
}

ECG測定アンプの組み立てと測定
 下の写真と部品リストを照らし合わせ、ブレッドボード上にECG測定アンプの部品をセットしてみましょう。

組み立て手順は自由ですが、下記のような順序でやってみるとやりやすいかもしれません。組み立て中は、念のためArduinoのUSBケーブルを抜いて、回路の電源を切っておきましょう。

部品の配置が終わったら、ArduinoをPCに接続し、プログラム「ECG1」をArduinoに書き込んで動作させてみましょう。電極は、上記資料を参考に、胸部に装着します。
※身体に電極を装着して測定する際は、感電を防止するため、PCから電源を必ず抜くようにしましょう。

//プログラム名:ECG1
#include <Wire.h> //I2C通信ライブラリ
#include <Adafruit_ADS1X15.h> //ADS1015ライブラリ
Adafruit_ADS1015 ads; //ADS1015クラスのインスタンス化
 
void setup(void)
{
  Serial.begin(115200);  //シリアル通信開始
  // ADS1015 gain 2/3  input range +/- 6.144V 
  ads.begin(); //ADS1015通信開始
}
 
void loop(void)
{
  int16_t results; //AD変換結果
  results = ads.readADC_Differential_0_1();  //差動入力
    
  float   multiplier = 3.0F;  // デフォルトゲイン(2/3倍)における係数
  Serial.print(results * multiplier); //得られたデジタル値をmvに換算しシリアルモニタに表示
  Serial.print(",4000,1000"); 
  Serial.println(); //シリアルモニタ改行
  delay(0);
}

資料
Arduinoで生理指標を測る
右足回路動画

使用部品

TLE2426CLP レールスプリッタ
LM385B-1.2 電圧リファレンス

その他

追記

改善点

  • 作業進行用ブレットボードの用意
  • Ardino開発環境は1.8.19に指定
  • ライブラリinstall時に問題発生多し(MacOS,OneDrive内Desktopなど)
  • 可変抵抗の向き(銀点が見えなくなってしまっている)
  • ICの向き
  • テスト用24kΩ抵抗の用意
  • ディスポ電極を多めに用意

授業観察アプリ230223

FireHD8plusが到着したので、作業を進めてインストールしてみる。スマートフォンだと小さすぎるし10インチだと重いしで、8インチくらいがちょうど良いのかな?とりあえず1ページ目で観察者名や実験名、生徒数、インターバルなどの基本情報を入力し、2ページ目で行動を記録する、というインターフェースにしてみた。

でもこれくらい画面が広いと、
1.いっそボタンに観察対象となる行動を文字で表示する
2.座席表や簡単な作業マニュアルなどを画面上で参照可能にする
などの改善案が可能という気もする。

以下にUnity側のc#スクリプトと、サーバー側のPHPスクリプトを記載する。


1行入力やスライダーの値をPlayerPrefsに保存し、「次へ」ボタンでページ遷移するプログラム。PlayerPrefsはUnity実行環境で利用できるディスク領域のようなもの。テキストや数値などの各種パラメータを保存できる。シーンをまたぐときに使うと便利。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using UnityEngine.Networking;
using TMPro;


public class procP1 : MonoBehaviour
{
    public TMP_InputField input1;
    public TMP_InputField input2;
    public Slider slider1;
    public TextMeshProUGUI mes1;
    public Slider slider2;
    public TextMeshProUGUI mes2;

    // Start is called before the first frame update
    void Start()
    {
        //Screen.SetResolution(1200, 540, true, 60);    // RedmiNote11
        Screen.SetResolution(1280, 800, true, 60);      // Fire HD 8

        //read PlayerPrefs
        string username,expcode;
        int studentnum = 0;
        int interval = 0;
        username = PlayerPrefs.GetString("username", "-");
        if (username.Length > 1) {input1.text = username;}
        expcode = PlayerPrefs.GetString("expcode", "-");
        if (expcode.Length > 1) { input2.text = expcode; }
        studentnum = PlayerPrefs.GetInt("studentnum", 0);
        slider1.value = studentnum;
        interval = PlayerPrefs.GetInt("interval", 0);
        slider2.value = interval;

        /*
         username
        expcode
        studentnum
        interval
         */
    }

    // Update is called once per frame
    void Update()
    {
        mes1.text = slider1.value + "名";
        mes2.text = slider2.value + "秒";
    }

    public void procNextBtn() {

        // write playerPrefs
        PlayerPrefs.SetString("username", input1.text);
        PlayerPrefs.SetString("expcode", input2.text);
        PlayerPrefs.SetInt("studentnum", (int)slider1.value);
        PlayerPrefs.SetInt("interval", (int)slider2.value);
        SceneManager.LoadScene("P2");   // load scene "P2"
    }
}

ボタンのOn/Offを記憶し、定められた時間間隔でHTTP通信でサーバーへ値を送信するプログラム。ボタンのOn/Offは毎フレーム、文字などの更新処理は1秒間隔でおこなっている。HTTP通信はStartCoroutineで非同期処理。Post形式でサーバー側のPHPスクリプトに値を送っている。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using TMPro;
using UnityEngine.SceneManagement;
using UnityEngine.Networking;

public class procP2 : MonoBehaviour
{
    public Button b1;
    public Button b2;
    public Button b3;
    public Button b4;
    public Button b5;
    public TextMeshProUGUI mes1;
    public TextMeshProUGUI mes2;
    public TextMeshProUGUI mes3;
    public AudioClip clip1;

    bool sw1, sw2, sw3, sw4, sw5;
    Color pink;
    DateTime dt;
    long t1, t0;
    AudioSource audio;
    int interval =10;
    int currentst = 0;


    // Start is called before the first frame update
    void Start()
    {
        sw1 = sw2=sw3=sw4=sw5=false;
        pink= new Color(255f / 255f, 100f / 255f, 100f / 255f, 1f);

        // read PlayerPrefs
        interval= PlayerPrefs.GetInt("interval", 0);

        audio = gameObject.GetComponent<AudioSource>();
    }

    // Update is called once per frame
    void Update()
    {
        dt = DateTime.Now;  // get time info

        // process SW color
        if (sw1) { b1.image.color = pink; } else { b1.image.color = Color.white; }
        if (sw2) { b2.image.color = pink; } else { b2.image.color = Color.white; }
        if (sw3) { b3.image.color = pink; } else { b3.image.color = Color.white; }
        if (sw4) { b4.image.color = pink; } else { b4.image.color = Color.white; }
        if (sw5) { b5.image.color = pink; } else { b5.image.color = Color.white; }

        ////// 1S loop //////
        t0 = t1;
        t1=(long)Time.time;
        if (t1 != t0) {
            Debug.Log(t1);
            mes2.text = "日時:" + dt;
            mes1.text = "評価者:" + PlayerPrefs.GetString("username", "-");
            mes1.text += " 研究区分:" + PlayerPrefs.GetString("expcode", "-");
            mes1.text += " 観察生徒:" + currentst + "/" + PlayerPrefs.GetInt("studentnum", 0);

            // exec HTTP communication
            if (dt.Second % interval == 0) {
                Debug.Log("HTTP_send");
                audio.PlayOneShot(clip1);
                StartCoroutine("HTTP_Post");
                sw1 = sw2 = sw3 = sw4 = sw5= false; // reset SW
                currentst++;                        // increment student cnt
            }
        }
    }

    public void ApplicationQuit()
    {
        Application.Quit();
    }

    public void procButton(int swnum)
    {
        if (swnum == 1) { sw1 = !sw1; }
        if (swnum == 2) { sw2 = !sw2; }
        if (swnum == 3) { sw3 = !sw3; }
        if (swnum == 4) { sw4 = !sw4; }
        if (swnum == 5) { sw5 = !sw5; }
    }

    IEnumerator HTTP_Post()
    {
        string URL = "http://kodamalab.sakura.ne.jp/WGL/SCRIPT/class/regist.php";
        Debug.Log("URL:" + URL);

        WWWForm form = new WWWForm();
        form.AddField("sampletime", dt.ToString());
        form.AddField("username", PlayerPrefs.GetString("username", "---"));
        form.AddField("expcode", PlayerPrefs.GetString("expcode", "---"));
        form.AddField("studentnum", PlayerPrefs.GetInt("studentnum", 99));
        form.AddField("interval", PlayerPrefs.GetInt("interval", 99));
        form.AddField("currentst", currentst);
        form.AddField("sw1", Convert.ToInt32(sw1));
        form.AddField("sw2", Convert.ToInt32(sw2));
        form.AddField("sw3", Convert.ToInt32(sw3));
        form.AddField("sw4", Convert.ToInt32(sw4));
        form.AddField("sw5", Convert.ToInt32(sw5));

        UnityWebRequest www = UnityWebRequest.Post(URL, form);
        yield return www.SendWebRequest();

        if (www.isNetworkError || www.isHttpError)
        {
            Debug.Log(www.error);
        }
        else
        {
            string log;
            log = www.downloadHandler.text;
            Debug.Log("POST process complete!:" + log);
            mes3.text = "LOG:" + log;
        }

    }
}

PHP側のスクリプト。postで受け取った値をディスクに追加方式で書き込むだけ。

<?
$stamp=strftime("%Y%m%d_%H%M%S");
$fn=strftime("./data/classdat%Y%m%d.csv");
echo"posted ";


if($_POST['username']!=""){
		
	$fp=fopen($fn,"a");
	fprintf($fp,"%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
		$_POST['sampletime'],
		$_POST['username'],
		$_POST['expcode'],
		$_POST['studentnum'],
		$_POST['interval'],
		$_POST['currentst'],
		$_POST['sw1'],
		$_POST['sw2'],
		$_POST['sw3'],
		$_POST['sw4'],
		$_POST['sw5'],
		);
	fclose($fp);
	echo"->saved $fn/$stamp";

}

//echo"{$_POST['IF1']}/{$_POST['IF2']}/{$_POST['IF3']}/";
?>

授業観察アプリ230221

あらまし
授業観察を行うための装置は、独自HW準備の夢はあるものの、実利をとってAndroidタブレット化をメインにすすめることに。

実行環境

ハードウェアの候補はAmazonのFireHDタブレット。価格の割に質が高く、かつ入手性が良いのが特徴。ハードウェアのラインアップは、7,8,10のインチ、無印orPlusのグレードがあり、インチ数が大きくPlusがついているモデルがRAMや、GPUのコアが多いなど、リッチな構成。片手で操作でき、そこそこ性能の良いHD8が良さそうである。FireOS搭載というが、Unityで作成したAndroidアプリが動作する(従来どおりであれば)。ぱっと見、WebGLは非サポートのようである。

FireHD 8Plus(2022) パフォーマンスが30%向上したという。
HD8無印とPlusの違い RAMが2->3G、外側カメラの画素が200->500万
SoCはMT8169A GPUがMaliG52だ・・・。でも性能がいいのはやっぱHD10だね。
FireHDタブレットにUnityアプリをインストールする例 2019年なので比較的最近


アプリの作成
とりあえず手持ちのアンドロイドスマホRedmiNote11にビルドして入れてみる。5行動をボタンで記録できる。触れると赤くなり、定期的にHTTPでサーバーに情報を送る。送る情報は、1)評価者、2)評価時刻、3)評価対象者、4)行動記録・・・だろうか。評価対象者はたくさんいるだろうから、プルダウンにしたほうが良いのだろうか?詳しく聞いておけばよかった。一定時間おきに定期的に送信し、送信時に振動・・・FireHDはバイブないかもねぇ。

TextureMeshで日本語使いたい TextureMeshになってから結構面倒なのね。
UnityでHTTP POST形式でHTTP通信する場合

OpenCVで顔検出

Processing+OpenCVで顔を検出して位置を保存するアプリを作成。dataフォルダにファイル名「日付_日時.csv」でデータが保存される。サンプル間隔は100msに設定。保存データにはタイムスタンプがつくので、複数マシンで計測する場合は、時間を手掛かりに後から同期すればよい。csv形式なので、エクセルで開いてXYグラフにするとこんな感じ。Webカメラは、ミニ三脚でPCの背後にセットしたほうが自由度があってよい感じと思われる。
OpenCVの顔認識は、あまり横方向を向くと検出されなくなるので、ちょっと注意が必要だ。このライブラリでは顔の向きまでは判定できない。WidthとHeight、できれば顔の検出数も記録しておけば、横をむいているかを判断できるかもしれない。


さらに1台のマシンに2カメラ接続して動作を検証。CPU負荷は100%になっちゃうけど、サンプル数も足りているし、軌跡もほとんど一緒だ。どうやら、二つのカメラを1台のマシンで測定できるようだ。これどうにかして一本のアプリにならんものだろうか・・・
あ、一個になりますね。これ一個にした方が絶対速いし、タイムスケジュールの管理も楽だ。しかし改造にはもう少し時間が必要かも。

M2設定方法

計測器の使用方法自体はこちらで解説されています。ここでは、Wifiの設定方法を解説します。

計測器の電源を入れ、一番下のボタンを押すと、LEDが、緑→青→赤、の順で変化します。

・緑はアクセスポイント1に繋がるモード
・青はアクセスポイント2に繋がるモード
・赤はサーバー(設定)モード

サーバーモードにすると、PCのWifi設定に計測器が現れます。ESPから始まり、計測器に書かれている英数字(上記では8f64)が含まれるSSIDが計測器です。接続し、ブラウザのアドレスバーからhttp://192.168.4.1をひらくと、計測器の設定画面にアクセスできます。ここで、接続先のアクセスポイントのSSIDとパスワードを設定します。

設定した二つのアクセスポイントは、計測器の起動時に一番下のボタンを押すことで切り替えることができます。緑がひとつめのアクセスポイント、青はふたつめのアクセスポイントにつながる事を意味します。

ETVREC221211S

動画バージョンのアイカメラ

動画バージョンのアイカメラ。「保存せねば」と持っているうちに3年経ってしまった。散逸しないうちにブログに登録しておきます。EyeTribeのサイトは、なんとまだあるのだ。開発用ソフトウェアもいちおう登録しておこう。

使用方法
オンデマンド授業受講時の視線運動:学習スタイル、音声有無

『仮想事故』

【事故】

衝突事故

webGLにもアップロードしました。

サイトはこちら

轢き逃げ

【ステージ】

Racing Track VOL. 01:街灯版レーシングコース
・時間帯:埼玉県警によると夜間に事故が起こりやすい
・Track Colloider:消さないと見えない壁に阻まれる

【計測風景】

・計測機:ST, SC
・機材:VR, ハンコン(T150)

【実験結果】
・生理指標:SCは衝突前後で有意。STはn.s.
・心理指標:NA,CAは衝突前後で有意(性差はあったが, 運転免許有無はn.s.

フルカラーLEDを使う

フルカラーLED(NeoPixel)の使い方を学びます。

パーツの組立
 フルカラーLEDのGND端子に黒線を、VDD端子に赤線を、DI端子に白線をハンダ付けします。裏面にあらかじめハンダをのせておくとやりやすい。端子の反対側にピンヘッダーをハンダ付けして完成。収縮チューブで補強しておくと折れにくい。
マイコン内蔵RGBLEDモジュール

フルカラーLEDの接続
 下記のようにLEDを接続します。
フルカラーLED R→Arduino 3.3V
フルカラーLED B→Arduino GND
フルカラーLED W→Arduino 13

プログラム:ColorLED
 カラーLEDの制御には、Adafruit_NeoPixelライブラリを用います(ライブラリのインストール方法は動画をご覧ください)。プログラム冒頭で、デジタルポートの13番を制御用に使うこと、LEDの数が1個であることを定義しています。プログラムは、setup関数で初期化を行い、loop関数でLEDの点灯を処理しています。色のセットは、pixels.clear→pixels.setPixelColor→pixels.showの順で行います。pixels.Color(R, G, B)の形式で、色の強さは0~255で指定します。

 

//カラーLEDを用い、赤・緑・青色を点灯
#include <Adafruit_NeoPixel.h>
#define PIN 13        //制御用ピン
#define NUMPIXELS 1   //LEDの数
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
 
void setup() {
  pixels.begin();     //LEDを初期化
}
 
void loop() {
  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(20, 0, 0));  //赤
  pixels.show();
  delay(1000);    //1000ms停止
 
  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(0, 20, 0));  //緑
  pixels.show();
  delay(1000);    //1000ms停止
 
  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(0, 0, 20));  //青
  pixels.show();
  delay(1000);    //1000ms停止
}

プログラム:SmoothR
 プログラムを使い、赤色のみをスムースに点滅させます。

//カラーLEDを用い赤色をスムースに点滅
#include <Adafruit_NeoPixel.h>
#define PIN 13        //制御用ピン
#define NUMPIXELS 1   //LEDの数
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
  
void setup() {
  pixels.begin();     //LEDを初期化
}
  
void loop() {
  int t=5;

  for(int i=0;i<256;i++){ 
    pixels.setPixelColor(0, pixels.Color(i,0,0)); //Red 
    pixels.show(); 
    delay(t); 
  } 

  for(int i=0;i<256;i++){ 
    pixels.setPixelColor(0, pixels.Color(255-i,0,0)); //Red 
    pixels.show(); 
    delay(t); 
  } 
}

プログラム:VRLED
可変抵抗を使ってLEDの色を変えます。その際、接続は下記の通り。

可変抵抗R→Arduino 5V
可変抵抗B→Arduino GND
可変抵抗W→Arduino A0
フルカラーLED R→Arduino 3.3V
フルカラーLED B→Arduino GND
フルカラーLED W→Arduino 13

 

//可変抵抗でカラーLEDの色を変える
#include <Adafruit_NeoPixel.h>
#define PIN 13        //制御用ピン
#define NUMPIXELS 1   //LEDの数
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
int vr1;

void setup() {
  pixels.begin();     //LEDを初期化
}
  
void loop() {
  vr1= analogRead(0);
  
  pixels.setPixelColor(0, pixels.Color(vr1/4,0,255-vr1/4));
  pixels.show();
  delay(10); 

}

バイオフィードバックトピック

心拍変動バイオフィードバック
 心理学系の貢献が大きいのは、心拍変動バイオフィードバックの解説と普及であると思われる。筋電図や発汗、血流などの指標に関するバイオフィードバックは、リハビリ(非心理系)を除き、ここ数年ほとんど議論されておらず、心拍変動BF一色となっている。特にこの数年は(コロナの影響もあり)、ウェアラブル機器を利用して、各自が心拍変動バイオフィードバックを行う事例が紹介されている。

各種新技術の応用事例
 ここ数年、スマートグラス、VR、IoT、ウェアラブル、非接触計測、スマートフォンアプリなど、新しい技術をBFに取り込んだ事例が紹介されているが、コストが高かったり、不安定であったり(非接触計測)、実際はあまり普及していないと思われる。現代のバイオフィードバックとして最も有望なのは、アクティブトラッカーを使った活動量と心拍数の自己制御であると思われる。

2021年度
・スマートフォンアプリを使った共鳴周波数の評価
・少年院における心拍変動バイオフィードバックを用いた呼吸セルフコントロールプログラム
→心拍変動バイオフィードバックが近年のトピックになっている。さらにそれを、スマートフォンなどの携帯機器で行う事例が紹介されている。

・AI, ICT, VRを活用する未来に向けて 
・遠隔による心拍変動バイオフィードバック
→コロナの影響を受けて、BF機器を遠隔診療へ応用する可能性が論議されている。心拍変動バイオフィードバック(emWaveなどの機器使用が推奨される)を遠隔で行った事例が紹介される。

2020年度
・呼吸法とリラクセーション‒心拍変動バイオフィードバックによる呼吸法の評価‒
→”同定された共鳴周波数の呼吸法を実践することで,副交感神経系の活動を活発にし,喘息などの疾患の治療に役立つとして,詳しい治療プログラムを発表している”の部分に、心拍変動バイオフィードバックの元論文が紹介されている。

2019年
・長時間心拍変動解析 : その適用と誤用
→ウェアラブルセンサーによって測定された心拍変動データの読み方について解説。ウェアラブル計測は急速に普及しつつある、と紹介されている。

・臨床に活かす心拍変動バイオフィードバック
→心拍変動バイオフィードバックの臨床応用が講座で紹介されている。

・入眠困難者の入眠期における生理心理学的指標の動態
→入眠困難者の生理指標が検討されているが、脳波と心拍変動が解析対象となっている。

2018年
・医療からみた次世代バイオフィードバック
・バーチャルリハビリテーションにおける次世代バイオフィードバックの可能性
・ICTやAIの時代にバイオフィードバックはどう活用できるか : 産・学・官の連携
・AI・IoTの基礎とバイオフィードバック装置への応用検討
→スマートグラス利用のBF、VRを用いたリハビリ、認知行動療法アプリ、AIやIoT利用などの新しいアプローチが紹介される。

・カメラを用いた非接触バイタルセンシング技術とその応用
・ウエアラブル バイオフィードバックについて
→非接触計測、ウェアラブル機器によるBFの可能性などが紹介される。

EasyRoad3D Tips

・ロードネットワークを調整して、VegetationMaskLineがズレてしまった場合は、VegetationStudioStatusのActiveを一度はずして入れなおせば、MaskLineが再描画されて、道路エリアに木が生えなくなる。

・TreePerimeterなどの設定で、道路わきから何メートルまで木を生やさなくするかなどの設定ができる。が、この設定は、エリア下方のResetAllRoadTypes and Objectsのボタンを押さないと反映されない。

ふー2:21になった。

・・・と思ったら、Activeを押してもResetAllRoadTypes and Objectsを押しても反映されなくなった。なんでやー涙。これバグなんじゃないの!?と思い、藁にもすがる思いでUnity再起動して設定してみると、ちゃんと動くのだった。・・・バグじゃん!怒EasyRoadには悩まされてばかりだ。

ふー2:43になった。

なんか日本語のWikiがあるっぽい?ただのリンクにも思えるが。

3Dモデリングを始めたって話

ここに八角形があるじゃろ?

( ^ω^)

⊃八角形⊂

これをこうして

( ^ω^)
≡⊃⊂≡

こうじゃ
( ^ω^)
⊃手⊂

\テ↑ーテレレ テレレ レー♪/

3Dモデリングもいろいろ

そもそもモデリングの方法っていろいろあって、上の手はボックスモデリングといってコンピューターで扱いやすいポリゴンの作りになってます。

ソフトで扱うには4角形か3角形がベストらしいです。ちなみにUnityは3角形のポリゴンを使うらしい。よう知らんけど。他にもスカルプティングという方法で、多分想像する液タブを使って粘土こねくり回して作ってるような感じでモデルを作る方法もあるのですが、あれは異様にポリゴンが多くなるのでコンピューターで扱うにはやや処理が重すぎるので画像みたいな感じでレンダリングするのに使われます。リトポといってスカルプティングで作ってからボックスモデリングでポリゴンを減らしたりすればソフトでも使えるようになるようですが。

3D CAD

CADは3D CADと2D CADがあって、3D CADは車とか精密なものを作ったりするのが得意です。あとはパーツ事に編集できるものが多いので保守・改造が楽です。業界ではAuto CADが有名ですが、自分はFree CAD使ってます。

 覚えれば割と複雑なものが作れますが、残念ながら日本人金持ちが多いのかFree CADの記事が少ない。学習するならYoutubeで海外のチュートリアルを見るといいです。この辺とか。Free CADは大して使ってないので画像はないです。

疲れたんでこの辺で書くのやめときます。

Blender(手を作ったモデリングソフト)は無料だし、決して業界でメジャーではないにしても最近でっけぇ企業も使い始めた激熱な3Dモデリングソフトなのでお勧め。何より2.8以降は2.7系と違ってすっきりしたので使いやすくなってます。とてもいい。ただ業界ではMayaがメジャーなようです(金持ちどもめ。。。)Blenderの使い方はBlender Guruって人の動画が分かりやすいし、この人教え方がうまいです。それとBlenderのバージョンが上がるたびにドーナツ作ってますが、ドーナツとマグカップが作れるころにはBlenderで大半のものはモデリングできるようになってるので、やるなら見て損はないかと。全部英語だけど。

以上。

WordPressでLMS

LMSとはなんぞや?LearningManagementSystemの略である。Wordpressはプラグインが豊富なので、低コストで作れるのでは・・・?実際、WordPressにはLMSプラグインがいくつもあるらしい。探すと日本人でやってるひともいるようだ。


オンラインコースをノーコード(NoCode)で作れるプラグインLearnDash
ナオミさんの紹介サイトがある。テスト、質問、課題、証明書なども、機能として統合されている。WooCommerceと組み合わせてオンライン講座を売ることもできる(!)。個人学校ができるってワケね。単純に面白そうである。10サイトで$189は高いのか安いのか・・・機能による。

根本さんの動画のほうが詳しいね。ライセンスキーとプラグインのダウンロード・・・。有効化などの方法が。見てるとやってみたくなるね。

2時間の英語講座。かなり詳しくわかりそう。学習サイトを作成するために学習しなければならないという、ポジティブな本末転倒が生じるような。。。

あ・・・他にもいろいろたくさんあるね。

・Teachableのようなeラーニングシステムが構築できるLMS有料プラグインLearnDash使い方解説目次
なるほど日本語化しやすいのも魅力か。そうかオンラインサロンというジャンルなのか。はへー。

LearnDashの使い方:WordPressでオンラインスクールを作成する方法(根本さん
ふむ・・・。LearnDashはシェアが高いLMSらしい。ふーむ、具体的な情報は少ないね。

・LearnDash LMS 世界シェアナンバーワン(恐らく)のLMSシステム
「ほとんど全てのLMSツールを知っているがLearnDash LMSが一番の大本命だろう。」だそうで。Udemyレベルのものが作れると。Udemy使ったことないからわからないけど。

LifterLMSというものもあるらしい。無料で使えるが課金システムがない。社内教育などはこれで十分とも。小テストのバリエーションは少ない。なるほど。

Unityの基本

今回はUnityを使ってオンライン実験を行うことを想定したワークショップです。Unityのインストールから基本的な操作方法の学習、FPS(1人称視点)コントローラの導入、景観の作成、Web上での公開方法までを扱います。これらの動画では、以下のアセット(ソフトウェア資産)を使います。
Standard Assets (for Unity 2018.4)
AllSky Free – 10 Sky / Skybox Set
Rock package

1.Unityのインストール
開発元は、UnityHubを使ったインストールを推奨しているようなので、ここでもまずはUnityHubをインストールし、つぎにダウンロードアーカイブからUnityをインストールします。Unity にはたくさんのバージョンがありますが、ここでは2019.4.12をインストールしておくようにしてください。

2.Unityエディターを使う
視点の変更やオブジェクトの配置など、エディタ操作の基本を学びます。Unityエディターを使うにはUnityでアカウントを登録する必要があります。googleアカウントを持っている方は、それを使うこともできます。また、Visual Studio(プログラムエディタ)を使うために、Microsoftのアカウントが必要になりますので、必要に応じて用意してください。

「マウス中央ボタンドラッグ」で視点移動
「Alt+マウス左ボタンドラッグ」で視点回転
「F」キーで選択中のオブジェクトを視点の中心にする

3.FPSコントローラを設定する
FPSコントローラーを配置し、一人称視点で世界の中を動き回る方法を学びます。FPSコントローラで飛び石を渡る簡単なゲームを作成してみます。

「Ctrl+D 」でオブジェクトを複製する(Duplicate)
「Ctrl+P」でゲームモードに移行するorエディターに復帰する

4.Terrain(地形)を作成する
Unityを使った地形作成は、箱庭を作っているような楽しさがあります。動画では大急ぎで作っていますが、少し時間をかけてディテールにこだわって作っていただくと、楽しく学べると思います。青空、夕空、曇り空の3種類を作り、スクリプトで切り替えられるようにします。

5.WebGL形式でビルドして公開する
公開しないと閲覧してもらえないので、WebGL形式でのビルドは非常に重要なプロセスです。しかし、WebGL自体が比較的新しい技術ということもあり、難易度は高めです。パソコンに登録されたユーザー名や作業フォルダ名に日本語が含まれていると、ビルドに失敗する事があるようです。

自分のWebサイトをもっていない場合は、UnityRoomのようなWebGL形式のコンテンツを無料で公開させてくれるサイトを利用する事も可能です(ただし広告が出ます)。レンタルサーバーはさくらインターネットロリポップなどで年間5000円程度で借りる事ができるので、実験用にはこちらが良いと思います。