以前 jsPsych で作ったローカルPC完結の実験プログラムを完全オンライン版に修正してみる(作業ボリューム 半日くらい)

## はじめに

 jsPsych でオンライン実験できると言っても実験結果はローカルPCに保存だし、実験は実験室でやれば良いじゃんということで、今まではOSを選ばず実験できる便利なフレームワークという位置づけで使っていました。
　これが、2023年早々に jsPsych で実験結果をクラウドストレージに保存できるサービス datapipe がリリースされて、実験プログラムもデータも完全にオンラインで完結できるようになりました。 でも、やっぱり実験は実験室でやれば良いよねー思っていたので無視していたところ、授業でやるときにいちいちブラウザの設定をしたりデータの回収したりが手間だからオンライン完結でやってよと言われ、やってみました。
　日本語の情報から探していくのでいつものように [黒木先生のドキュメント](https://qiita.com/kurokida/items/ad92fb0c56d5176e0a2f) にお世話になりながら進めていきました。

### 結論(全部読むのが面倒な人はここだけで)

- プログラムも実験データも、全てオンライン上だけで完結することができるようになりました。　手間としては、最初に必要な OSF / DataPipe それぞれの登録と設定が一番面倒で、プログラムの修正は大した作業ではありませんでした。 とは言え、PCに不慣れな人が独力でやるのは難しいと思います。

- 完全オンライン化にはメリットもデメリットもあります。　手間と用途のバランスを考えてどうするかを決めましょう。


#### 今回の作業で大変だったところ
　面倒なのはファイル名の重複を自分でコントロールしなければならない点でした。
DataPipe の設定画面で「ファイル名生成はこれをコピペしな」というコードが出てくるのですが、ファイル名+乱数文字列n桁という作りで、「そのファイル名だと開いてみるまでわからないのよ」と言われてしまいました。　しかし入力値を動的にファイル名にしようとしたところ、 jsPsych + DataPipe の機能では無理っぽかったので、仕方がないのでお行儀良くない方法で入力値をファイル名にすることにしました。
　作業中不安だったのは、DataPipe と OSF の疎通確認でした。　最初はこれがわからず、どこまでできているのかわからないままプログラムを書いて不安でしたが、最終的には解決できました。

#### 詳細を読み進めるにあたって
　OSF , DataPipe , git の基礎的なところは、ここでは説明しませんのであらかじめ予習しておいてください。
jsPsychは、ver.7でプログラムを書いた事がある前提で話を進めます。

## 以下、詳細
　それでは実際にやった作業を見ていきましょう。

### 今までの使い方
　実験用プログラムと jsPsych 本体を含めて丸っと zip にして受け渡していました。
一番手っ取り早くみんなに説明できるのと、バージョンアップの影響を受けないのでこの方法に落ち着いていました。

![スライド1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1252322/38f0a07c-1590-d375-cd11-7cf2ba216814.png)

#### ディレクトリ構成
　jsPsych を展開したディレクトリ直下に実験プログラム用のディレクトリを置いています。
 ```
-  📂 jsPsych
--  📂 dist
--  📂 examples
--  📄 code-of-conduct.md
--  📄 contributors.md
--  📄 license.txt
--  📄 README.md
(ローカルPCに展開したjsPsychの直下に自分の実験用ディレクトリを作成)
--  📂 Myexp
---  📄 index.html
---  📄 script.js
---  📄 1.jpg
---  📄 2.jpg
---  📄 3.jpg
---  📄 4.jpg
```

### 今回手を入れるプログラム
ローカルPC上で動作している、このプログラムをクラウド対応します。

<iframe width="560" height="315" src="https://www.youtube.com/embed/4N0fczS_sEI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

#### プログラム構成
実験用のプログラムは以下の2つのファイルで構成されています。
(1) プラグインの読み込みと、プログラム本体を指定する、index.html

<details><summary> index.html </summary><div>

```html
<!DOCTYPE html>
<meta charset="UTF-8">
<html>
    <head>
      <title>My experiment</title>
      <!-- jspsych 本体の読み込み -->
      <script src="../dist/jspsych.js"></script>
      <link href="../dist/jspsych.css" rel="stylesheet" type="text/css">

      <!-- jspsych プラグインの読み込み -->
      <script src="../dist/plugin-html-keyboard-response.js"></script>
      <script src="../dist/plugin-survey-text.js"></script>
      <script src="../dist/plugin-fullscreen.js"></script>
      <script src="../dist/plugin-preload.js"></script>      

      <!-- jspsych 実験用プログラムの読み込み -->
      <script src="./script.js"></script>
  </head>
  <body>
  </body>
</html>
```
</div></details>

(2) プログラム本体 script.js
<details><summary> script.js </summary><div>

```javascript
// 保存用のファイル名を生成
function yyyymmddhhmise() {
  // 日付時間秒を文字列で返す	
    const dt = new Date();
    var yyyy = dt.getFullYear();
    var mm = ('00' + (dt.getMonth()+1)).slice(-2);
    var dd = ('00' + dt.getDate()).slice(-2);
    var hh = ('00' + dt.getHours()).slice(-2);
    var mi = ('00' + dt.getMinutes()).slice(-2);
    var se = ('00' + dt.getSeconds()).slice(-2);
  
    var answer = yyyy + mm + dd + "-" + hh + mm + se ;
    return (answer);
  }
var filename = "temochiz-" + yyyymmddhhmise() + ".csv" ;

var jsPsych = initJsPsych({
  on_finish: function() {
    jsPsych.data.get().localSave('csv', filename);
//    jsPsych.data.displayData();
  }
});

// ------------------------------------------------------------------------
// 共通の実験パーツ
// ------------------------------------------------------------------------

// 最初の説明と被検者情報の入力
var par_id = {
  type: jsPsychSurveyText,
  questions: [
    {prompt: '<strong>これから大学生における魚の認知度についての実験を始めます。</strong><br><br><br>学籍番号を入力してください', columns: 10, required: true, name: 'id'},
    {prompt: 'あなたの性別を男性であれば 1、女性であれば 2、答えたくない場合は 3 を入力してください。', columns: 5, required: true, name: 'sex'},
    {prompt: 'あなたの年齢を入力してください。', columns: 5, required: true, name: 'age'},
  ],
  button_label: '次へ',
};

// 実験の説明
var hello = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '実験を始めます。 写真の前には1秒凝視点が出ます。<br><br>何かキーを押すと始まります。',
};

// 凝視点
var eyepoint = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '<p style="font-size: 48px;">+</p>',
  choices: "NO_KEYS",
  trial_duration: 1000,
};

// 実験の終了
var bye = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'これで実験は終了です。 PCには触れずに実験者の指示に従ってください。',
};

// フルスクリーン開始
var enter_fullscreen = {
  type: jsPsychFullscreen,
  message: '<p>実験名: temochiz test local</p><p>開始ボタンを押すと全画面表示で実験が始まります。</p>',
  button_label: "開始",
  fullscreen_mode: true
}

// フルスクリーン終了
var exit_fullscreen = {
  type: jsPsychFullscreen,
  fullscreen_mode: false,
  delay_after: 0
}

// プリロード
var preload = {
  type: jsPsychPreload,
  auto_preload: true
}

// ------------------------------------------------------------------------
// 画像問題の作成
// ------------------------------------------------------------------------

// 画像ファイルの用意
var baseURL = './' ;
var examPictures = [
  { label: 'No.01 egi'           , filename: '1.jpg' },
  { label: 'No.02 akoudai'       , filename: '2.jpg' },
  { label: 'No.03 chikamekintoki', filename: '3.jpg' },
  { label: 'No.04 madako'        , filename: '4.jpg' },
];

// 選択肢
var myScale = '\
<font size=2>\
<center>\
これはどのくらい魚だと思いますか。\
<TABLE style=”border:none;border-collapse:collapse;">\
<TR>\
  <TD align="center">┣━━━━━━ 1 ━━━━━━<BR>絶対に魚</TD>\
  <TD align="center">┣━━━━━━ 2 ━━━━━━<BR>たぶん魚</TD>\
  <TD align="center">┣━━━━━━ 3 ━━━━━━<BR>わからない</TD>\
  <TD align="center">┣━━━━━━ 4 ━━━━━━<BR>たぶん魚以外</TD>\
  <TD align="center">┣━━━━━━ 5 ━━━━━━┫<BR>絶対に魚以外</TD>\
</TR>\
</TABLE>\
</center>\
</font>\
' ;

// 順番をランダマイズしたいので指定しておく
var trials = {
  timeline: [],
  timeline_variables: examPictures,
  randomize_order: true,
};

// 画像問題の本体
var exam = {
    type: jsPsychHtmlKeyboardResponse,
    stimulus: function () {return  '<img src="'+ baseURL + jsPsych.timelineVariable('filename') + '" height=350><br><br>'; },
    prompt: function () {return myScale; },
    choices: ["1","2","3","4","5"],
    data: {
      label: jsPsych.timelineVariable('label'),
    },
};

trials.timeline.push(eyepoint) ;
trials.timeline.push(exam) ;

// ------------------------------------------------------------------------
// 実験の開始
// ------------------------------------------------------------------------

//jsPsych.run([preload,enter_fullscreen,par_id,hello,trials,bye,exit_fullscreen]);
jsPsych.run([preload,par_id,hello,trials,bye]);


```
</div></details>

## オンライン対応
それでは実際にオンライン対応を進めて行きます。
ざっくりで言うと、
・実験用プログラムだけをオンライン化
・保管場所(OSF)の準備
・実験用プログラムと OFS を結びつけるための準備(DataPipeの設定)
・プログラムに DataPipe 使用を記述
と、なります。

### オンライン対応 Step1/2 実験プログラム本体のオンライン化

#### 準備.gitの設定
　オンライン実験のプログラムを使用する際に、git を Webサーバとして利用します。
ですので、git の アカウントと、プログラムを公開するためのレポジトリを作成します。

jsPsych では、質問のタイプや画面制御など諸々の機能をプラグインで提供しています。
今までは jsPsych 本体とプラグインを丸ごと自分のPCにコピーして使っていましたが、これを変更します。

もともと使っていたプログラム、index.html の、
```html
<!DOCTYPE html>
<meta charset="UTF-8">
<html>
    <head>
      <title>My experiment</title>
      <!-- jspsych 本体の読み込み -->
      <script src="../dist/jspsych.js"></script>
      <link href="../dist/jspsych.css" rel="stylesheet" type="text/css">

      <!-- jspsych プラグインの読み込み -->
      <script src="../dist/plugin-html-keyboard-response.js"></script>
      <script src="../dist/plugin-survey-text.js"></script>
      <script src="../dist/plugin-fullscreen.js"></script>
      <script src="../dist/plugin-preload.js"></script>      

      <!-- jspsych 実験用プログラムの読み込み -->
      <script src="./script.js"></script>
  </head>
  <body>
  </body>
</html>
```

```html
      <!-- jspsych 本体の読み込み -->
      <!-- jspsych プラグインの読み込み -->
```
の辺りが該当するので、これを公式ページのマニュアル通りに修正します。
具体的には、各行でローカル保存のパスを示している ../dist の部分を cdn 経由に書き換えます。
これが修正後です。
```html
<!DOCTYPE html>
<meta charset="UTF-8">
<html>
    <head>
      <title>My experiment</title>
      <!-- jspsych 本体の読み込み -->
      <script src="https://unpkg.com/jspsych@7.3.4"></script>
      <link href="https://unpkg.com/jspsych@7.3.4/css/jspsych.css" rel="stylesheet" type="text/css" />

      <!-- jspsych プラグインの読み込み -->
      <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.1.3"></script>
      <script src="https://unpkg.com/@jspsych/plugin-survey-text@1.1.3"></script>
      <script src="https://unpkg.com/@jspsych/plugin-fullscreen@1.2.1"></script>
      <script src="https://unpkg.com/@jspsych/plugin-preload@1.1.3"></script>
      
      <!-- jspsych 実験用プログラムの読み込み -->
      <script src="./script.js"></script>
  </head>
  <body>
  </body>
</html>
```
修正は jsPsych 本体とプラグインの読み込み部分だけなので、 プログラム本体を呼び出す部分のパス指定はそのまま使えますし、
```html
      <script src="./script.js"></script>
```
プログラム本体に記述してある画像ファイルへのパスも修正は不要です。
```javascript
// 画像ファイルの用意
var baseURL = './' ;
var examPictures = [
  { label: 'No.01 egi'           , filename: '1.jpg' },
  { label: 'No.02 akoudai'       , filename: '2.jpg' },
  { label: 'No.03 chikamekintoki', filename: '3.jpg' },
  { label: 'No.04 madako'        , filename: '4.jpg' },
];
```

ここまで修正できたら、index.html , script.js , [1-4].jpg を git に push して、Github Pages を有効にすると動作するので確認しておきましょう。

![スライド2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1252322/82e807cc-00de-4fbe-36c5-25ab30268520.png)

しかし、この段階では、実験結果が相変わらずローカル保存されるので、完全オンライン化するためにはさらに修正する必要があります。

### オンライン対応 Step2/2 クラウドストレージ(OSF)へ保存するためのプログラム修正

#### 準備1. / 保管場所(OSF)の用意
https://osf.io/
最初に、実際のクラウドストレージであるOSFを準備しておきます。

ここでは、
各実験で共通で使えるアクセスキー Personal Access Token(OSF Token) の生成と、
保存したい実験単位の Project , Data Component を作成しておきます。

#### 準備2.DataPipeの設定
https://pipe.jspsych.org/
次に、jsPsych と OSF を結びつける DataPipe の設定を行います。
New Experiment で 新規の実験を作成、準備1で作成した3個セット(OSF Token , Project , Data Component )を設定します。

すると、jsPsychに貼り付けるコードが生成されます。(その中の experiment_id で自分の実験プログラムとDataPipeが結びつきます。)

#### 準備3. DataPipe と OSF の疎通確認
　この確認をしておかないと、プログラムを DataPipe 対応したつもりでもデータの保存ができていない時に、どの個所が悪いのか特定する事ができません。
DataPipe で OSF で発行したアクセストークンが正しく設定されて、OSF プロジェクトの URL が正しければ、Create a New Experiment 画面で指定する New OSF Data Component Name が、OSF 側で自動で作ってもらえるので、これを利用して疎通確認していきましょう。

##### 手順1 OSFでアクセストークンの発行とプロジェクト作成をしておく。
このとき、プロジェクトの作成だけで、データコンポーネントの作成はしません。
![1-osfプロジェクト作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1252322/a1f4e376-de1c-4dc6-0146-a42b731bfb7f.png)

##### 手順2 DataPipe で OSFが発行したアクセストークンを指定する。
![2-dpでofsトークン設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1252322/97e15b90-d87d-4cfa-fef1-52f0bdec730e.png)

##### 手順3 DataPipe で 新規実験を作成。
Create a New Experiment 画面で、
**Title** → お好み
**Existing OSF Project** → OSF で作成したプロジェクトのURL(手順1画面のアドレスバーに表示されている)
**New OSF Data Component Name** → OSF側に作成したいデータコンポーネント(保管場所)の名前
今回は temochizpractice-data としてから Create ボタンを押します。
![3-dpでプロジェクト作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1252322/d944e362-2c4e-9e6f-b198-c6c75b09f2bf.png)

(4) OSF 側で確認
OSF で ブラウザをリロードして、左下に DataPipe で指定した temochizpractice-data ができていれば疎通OKです。
![4-osfにdataコンポーネント作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1252322/5c3aa3cc-e7fd-0315-2743-bef05aed26f1.png)

ここまで用意してからプログラムの修正に入ります。

### プログラムの修正
DataPipe の使用には、以下の3個所のプログラム修正が必要になります。
これらの追加コードは、DataPipeの設定画面に出てくるので、しかるべき場所にコピペするだけです。
![5-dp設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1252322/8d21ef81-bd62-c4b0-5383-ea98318d3e7d.png)

#### 手順1 DataPipe プラグイン読み込みの追加
DataPipe プラグインを読み込むので、先ほどのindex.htmlに、
```html
<script src="https://unpkg.com/@jspsych-contrib/plugin-pipe"></script>
```
を追加します。

#### 手順2 保存ファイル名生成コードの追加
ローカルPCに保存していたときは、保存用のファイル名が被るときはブラウザが自動で myfile(2).txt のような別のファイル名を付けてくれたのですが、DataPipe + OSF では上書きされます。　ですのでファイル名が重複しないようにしなければなりません。　DataPipe 設定時に「これをコピペして使って」と出てくるコードをとりあえず使ってみます。
```javascript
const subject_id = jsPsych.randomization.randomID(10);
const filename = `${subject_id}.csv`;
```
これは、自分で設定したファイル名に10桁の乱数を付与して重複を避けています。(100億分の1で重複しますが)

#### 手順3 DataPipe使用の宣言
DataPipe は、他の試行と同じく変数として宣言しておきます。 ここでは save_data としました。
```javascript
const save_data = {
    type: jsPsychPipe,
    action: "save",
    experiment_id: "hogehoge" ← ここが保存先ごとに内容が変わります",
    filename: filename,
    data_string: ()=>jsPsych.data.get().csv()
};
```
#### 手順4 jsPsych.run に追加
宣言した変数を jsPsych.run に入れます。　場所は全ての試行が終わった後になります。
```javascript
jsPsych.run([(..試行いろいろ..),save_data,exit_fullscreen]);
```

以上でプログラムの修正は完了です。

#### 完全オンライン化

修正したコードを git に push して完全オンライン化 完了となります。　しかし、、、

![スライド3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1252322/fdd0f650-b8b0-1c21-438f-75211c8078df.png)

#### 追加手順 入力した学生番号をファイル名として使用する
さて、ここで困った問題が。
ファイル名が重複しないように乱数10桁を追加しましたが、OSF で確認してみると誰の実験結果なのかわかりません。

```
--  📄 myexp-7392481560.csv
--  📄 myexp-5029648371.csv
--  📄 myexp-6187503924.csv
--  📄 myexp-9250361847.csv
--  📄 myexp-3748296150.csv
```

これを、こんな感じ↓に入力した学籍番号がファイルになるように変更したいです。
```
--  📄 myexp-202300001.csv
--  📄 myexp-202300002.csv
--  📄 myexp-202300003.csv
--  📄 myexp-202300004.csv
--  📄 myexp-202300005.csv
```

　しかし、jsPsychSurveyText の、入力値を使う事ができなかったので(jsPsych.run()に入った時点で固定されてしまう模様、あまり頑張って調べてませんが)、学籍番号の入力のみを index.html 上に作ったフォームで入力し、「実験開始」ボタンを押すと、プログラム本体に学籍番号を引数として渡すという作りにしてみました。

**html側**
```html
<center>
<b>これらから実験を始めます。</b><br><br>
学籍番号を入力してください。<br><br>
<input type="text" id="myInput" required><br><br><br>
<input type="submit" value="ボタンを押すと実験が始まります。" onclick="pushNext();">
</center>
```

プログラム本体では、html側で入力した学籍番号を引数として取得してから 自前のファイル名生成関数 createfilename () を呼び出し、その後に今までのプログラムを関数化した startExperiment() を呼び出すという構造にしました。
ファイル名生成関数では学籍番号を入れた上で、念のため後ろにランダム文字列10桁を入れてみました。(学籍番号を例示通りに入力した人達でファイル名が被るかもしれないので..)

**プログラム本体**
```javascript
function pushNext() {
    // myInputが空でないか確認する
    inputVal = document.getElementById("myInput").value;
    if (inputVal) {
        // ファイル名を生成する
        filename = createfilename(expname + '-ID.' + inputVal+ '-') ;
        startExperiment() ;
    }
}

function startExperiment() {
    元々のプログラムがこの中に入る
    jsPsych.run([hoge])
}

// クラウド(DataPipe)保存用のファイル名を生成
function createfilename(argseed) {
  // 日付時間秒を文字列で返す	
  const dt = new Date();
  var yyyy = dt.getFullYear();
  var mm = ('00' + (dt.getMonth()+1)).slice(-2);
  var dd = ('00' + dt.getDate()).slice(-2);
  var hh = ('00' + dt.getHours()).slice(-2);
  var mi = ('00' + dt.getMinutes()).slice(-2);
  var se = ('00' + dt.getSeconds()).slice(-2);
  var answer = yyyy + mm + dd + "-" + hh + mm + se ;
  const subject_id = jsPsych.randomization.randomID(10);
  answer =  argseed + answer + "-" + subject_id +".csv" ;
  return (answer);
  } ;

```

これで保存時のファイル名が以下のようになります。
```
--  📄 temochizpractice-ID.202300001-20240126-220131-7392481560.csv
--  📄 temochizpractice-ID.202300001-20240126-220130-5029648371.csv
--  📄 temochizpractice-ID.202300001-20240126-220129-6187503924.csv
--  📄 temochizpractice-ID.202300001-20240126-220128-9250361847.csv
--  📄 temochizpractice-ID.202300001-20240126-220127-3748296150.csv
```

#### プログラム最終版
最終的にこうなりました。

<details><summary> index.html </summary><div>

```html
<!DOCTYPE html>
<meta charset="UTF-8">
<html>
    <head>
      <title>My experiment</title>
      <!-- jspsych 本体の読み込み -->
      <script src="https://unpkg.com/jspsych@7.3.4"></script>
      <link href="https://unpkg.com/jspsych@7.3.4/css/jspsych.css" rel="stylesheet" type="text/css" />

      <!-- jspsych プラグインの読み込み -->
      <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.1.3"></script>
      <script src="https://unpkg.com/@jspsych/plugin-survey-text@1.1.3"></script>
      <script src="https://unpkg.com/@jspsych/plugin-fullscreen@1.2.1"></script>
      <script src="https://unpkg.com/@jspsych/plugin-preload@1.1.3"></script>
      <script src="https://unpkg.com/@jspsych-contrib/plugin-pipe"></script>
      
      <!-- jspsych 実験用プログラムの読み込み -->
      <script src="./script.js"></script>
      <!-- css -->
        <style>
        .container {
          display: grid;
          place-items: center;
          height: 70vh;
        }
      </style>
  </head>
  <body>
    <div class="container">
      <center>
      <b>これから大学生における魚の認知度についての実験を始めます。</b><br><br>
      学籍番号を入力してください。<br><br>
      <input type="text" id="myInput" required><br><br><br>
      <input type="submit" value="ボタンを押すと実験が始まります。" onclick="pushNext();">
      </center>
      </div>
  </body>
</html>
```
</div></details>

<details><summary> script.js </summary><div>

```javascript
// ------------------------------------------------------------------------
// 共通の実験パーツ
// ------------------------------------------------------------------------
// 実験固有で設定するのはこの2個所
const expname = "temochizpractice";         // 【要設定変更1/2】 ファイル名で使用
const datapipe_experiment_id = "hogehoge";  // 【要設定変更2/2】 DataPipeで表示されるID

var filename ; // OSFのファイル名
var inputVal ; // 入力ボックスの要素を取得

var jsPsych = initJsPsych({
  on_finish: function() {
    jsPsych.data.get().localSave('csv', filename);
//    jsPsych.data.displayData();
  }
});

// クラウド(DataPipe)保存用のファイル名を生成
function createfilename(argseed) {
  // 日付時間秒を文字列で返す
  const dt = new Date();
  var yyyy = dt.getFullYear();
  var mm = ('00' + (dt.getMonth()+1)).slice(-2);
  var dd = ('00' + dt.getDate()).slice(-2);
  var hh = ('00' + dt.getHours()).slice(-2);
  var mi = ('00' + dt.getMinutes()).slice(-2);
  var se = ('00' + dt.getSeconds()).slice(-2);
  var answer = yyyy + mm + dd + "-" + hh + mm + se ;
  const subject_id = jsPsych.randomization.randomID(10);
  answer =  argseed + answer + "-" + subject_id +".csv" ;
  return (answer);
  } ;
var filename = createfilename(expname) ;

// htmlからボタンを押された時の呼び出し
function pushNext() {
  // myInputが空でないか確認する
  inputVal = document.getElementById("myInput").value;
  if (inputVal) {
      // ファイル名を生成する
      filename = createfilename(expname + '-ID.' + inputVal+ '-') ;
      startExperiment() ;
  }
}

function startExperiment() {

// DataPipe保存設定
const save_data = {
  type: jsPsychPipe,
  action: "save",
  experiment_id: datapipe_experiment_id, 
  filename: filename,
  data_string: ()=>jsPsych.data.get().csv()
};

// 最初の説明と被検者情報の入力
var par_id = {
  type: jsPsychSurveyText,
  questions: [
    {prompt: 'あなたの性別を男性であれば 1、女性であれば 2、答えたくない場合は 3 を入力してください。', columns: 5, required: true, name: 'sex'},
    {prompt: 'あなたの年齢を入力してください。', columns: 5, required: true, name: 'age'},
  ],
  button_label: '次へ',
};

// 実験の説明
var hello = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '実験を始めます。 写真の前には1秒凝視点が出ます。<br><br>何かキーを押すと始まります。',
};

// 凝視点
var eyepoint = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '<p style="font-size: 48px;">+</p>',
  choices: "NO_KEYS",
  trial_duration: 1000,
};

// 実験の終了
var bye = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'これで実験は終了です。 PCには触れずに実験者の指示に従ってください。',
};

// フルスクリーン開始
var enter_fullscreen = {
  type: jsPsychFullscreen,
  message: '<p>実験名: temochiz test local</p><p>開始ボタンを押すと全画面表示で実験が始まります。</p>',
  button_label: "開始",
  fullscreen_mode: true
}

// フルスクリーン終了
var exit_fullscreen = {
  type: jsPsychFullscreen,
  fullscreen_mode: false,
  delay_after: 0
}

// プリロード
var preload = {
  type: jsPsychPreload,
  auto_preload: true
}

// ------------------------------------------------------------------------
// 画像問題の作成
// ------------------------------------------------------------------------

// 画像ファイルの用意
var baseURL = './' ;
var examPictures = [
  { label: 'No.01 egi'           , filename: '1.jpg' },
  { label: 'No.02 akoudai'       , filename: '2.jpg' },
  { label: 'No.03 chikamekintoki', filename: '3.jpg' },
  { label: 'No.04 madako'        , filename: '4.jpg' },
];

// 選択肢
var myScale = '\
<font size=2>\
<center>\
これはどのくらい魚だと思いますか。\
<TABLE style=”border:none;border-collapse:collapse;">\
<TR>\
  <TD align="center">┣━━━━━━ 1 ━━━━━━<BR>絶対に魚</TD>\
  <TD align="center">┣━━━━━━ 2 ━━━━━━<BR>たぶん魚</TD>\
  <TD align="center">┣━━━━━━ 3 ━━━━━━<BR>わからない</TD>\
  <TD align="center">┣━━━━━━ 4 ━━━━━━<BR>たぶん魚以外</TD>\
  <TD align="center">┣━━━━━━ 5 ━━━━━━┫<BR>絶対に魚以外</TD>\
</TR>\
</TABLE>\
</center>\
</font>\
' ;

// 順番をランダマイズしたいので指定しておく
var trials = {
  timeline: [],
  timeline_variables: examPictures,
  randomize_order: true,
};

// 画像問題の本体
var exam = {
    type: jsPsychHtmlKeyboardResponse,
    stimulus: function () {return  '<img src="'+ baseURL + jsPsych.timelineVariable('filename') + '" height=350><br><br>'; },
    prompt: function () {return myScale; },
    choices: ["1","2","3","4","5"],
    data: {
      label: jsPsych.timelineVariable('label'),
    },
};

trials.timeline.push(eyepoint) ;
trials.timeline.push(exam) ;

// ------------------------------------------------------------------------
// 実験の開始
// ------------------------------------------------------------------------

jsPsych.run([preload,enter_fullscreen,par_id,hello,trials,bye,save_data,exit_fullscreen]);

}
```
</div></details>

## まとめ

#### それなりにめんどくさい
　あまりコンピュータに強くない学生に自分で何とかしろと言ってドキュメントを渡しても、乗り越えることができない可能性が高いと感じました。　プログラムそのものでは難しい事はしないものの、それなりにめんどくさい手間が増えるので必要がなければオンライン化しなくても良いと思います。

#### 大人数で一斉に実験をするのであれば完全オンライン化一択
　やはり授業などで一斉に実験参加してもらう時は便利で、セットアップやデータ回収の手間も考えると完全オンライン化しておきたいところです。
他にも場所を特定せずに多数参加してもらうシチュエーションがあれば使った方が良いと思いますが、にわかに思い浮かびません..

#### プログラムを書いてくれる人が他にいて、少数のPCで実験するときはプログラムのみオンライン化
　DataPipe とか OSF とか設定がめんどくさいのですが、プログラムだけオンライン対応にしておくと、オーダー通りにプログラムができているかどうか Zoom で打合せしながら使ってもらったり、リモートでコミュニケーションしやすいので便利です。

#### 安全性について
　具体的には記述しませんがセキュリティー的に堅牢とは言えない作りなので、、、誰でも触れる共用PCを使って実験するくらいの感覚で考えるのが良いかと思います。　必要な時に公開して、使わなくなったらすぐに非公開にする運用をおすすめします。

#### 他のリスクも考えてみる
　ありそうなのは、Git / DataPipe / OSF のサービス停止です。
Gitは体感で3年に1度くらい数時間の障害が出ているような気がしますし、OSF も障害やメンテナンスがあるかもしれません。 DataPipe が資金難でサービス終了する可能性もゼロではありません。　有名なクラウドサービスが何かしらの障害でサービス停止したニュースも時々耳にしますよね。
　私の本業で稼働させているサービスでは24時間365日体制で専門のスタッフが障害対応を行いますが、ボランティアベースと思われる DataPipe と OSF にどこまでの稼働率を期待して良いのかわからなかったりします。
　最近ハードディスクの整理をしていたら Windows / C++ で作った昔の実験プログラムを発掘しましたが、こちらはそのまま動きました。 今回のオンライン完結版は5年後に動作するのでしょうか。
やっぱり、丸っと一式手元にあると安心感がありますね。

### そんなわけで
　どちらが良いとかいう結論ではなく、実験用プログラムにかける手間と用途のバランスを考えて、オンライン対応にするのかローカルPC内で完結させるのかを自分で選ぶという結論です。
　私のおすすめとしては、
- 実験用プログラムの仕様確認 → プログラムのみのオンライン化
- 実験本番 → ローカルPC内で完結
- 授業で一斉に実験 → 完全オンライン版
となりました。

以上です。