.. _chapter-gui: グラフィカルインターフェースを活用しよう ================================================================ この章の実験の概要 ------------------------------- :numref:`第%s章 ` ではキーボードを用いてプローブ刺激の長さを調節しました。しかし、実際に実行してみた方は「カーソルキーを何度もカチカチ押すのは面倒くさい」と思われたのではないでしょうか。 BuilderのKeyboardコンポーネントでは、基本的に「キーを押した」か「(既に押してある)キーを放した」のいずれかのイベントしか検出しません。ですから、「押しっぱなしにしていたらその間伸び縮みする」という動作を実現することが難しいのです。まあCodeコンポーネントを使うと可能ですので、Codeコンポーネントの使い方を極めるという中級から上級者向けの練習問題としては面白そうですが、そんな難しいことをしなくてもぱぱっと実現できてしまった方がいいに決まっています。 残念ながら現在のBuilder(執筆時点で2024.2.5)ではキーボードを使う限りどうしても難しくなってしまうのですが、マウスを使うとキーボードよりはずっと簡単に実現可能です。 本章では、まず :numref:`第%s章 ` の実験をマウスを使って操作するように改造します。 その後、マウスの使い方をもう少し覚えようということで :numref:`第%s章 ` 章の実験にも少し改造を加えます。 それぞれの章の実験を完成させたところから解説を始めますのでご注意ください。 Buttonコンポーネント ------------------------------- マウスを使うと言ってもいろいろな使い方がありますが、基本はマウスを操作してポインターを移動させてクリックして選択といった操作でしょう。 Builder上でこれらの操作を実現するためのコンポーネントがButtonコンポーネントです。Buttonコンポーネントはコンポーネントペインの「反応」カテゴリにあります(:numref:`fig-button-component`)。 .. _fig-button-component: .. figure:: fig08/button-component.png :width: 80% Buttonコンポーネント .. tabularcolumns:: |p{8zw}|p{34zw}| :numref:`tbl-button-properties` にButtonコンポーネントの主なプロパティを示します。このコンポーネントで作成されるボタンの外見(色や大きさ、テキストの文字サイズなど)を調整するプロパティははPolygonコンポーネントとTextコンポーネントと共通するものが多いため、それらは省略してあります。まず、ButtonコンポーネントはKeyboardコンポーネント同様、ルーチンを終わらせるために使いたい場合がありますので、それに対応するように **[Routineを終了]** というプロパティを持っています。使い方はKeyboardルーチンと同じです。実験記録ファイルには button.numClicks (buttonはButtonコンポーネントの **[名前]**)という項目にクリックされた回数が出力されるので、スクリーン上に複数のボタンがあってもどのボタンがクリックされたか判別できます。 .. _tbl-button-properties: .. csv-table:: Buttonコンポーネントの主なプロパティ :header: データ属性, 概要 :delim: ; :widths: 30, 70 **[Routineを終了]** ; Keyboardコンポーネントと同様、この項目がチェックされているとボタンがクリックされたときに現在のルーチンを終了します。 **[ボタンのテキスト]** ; ボタンに表示するテキストを指定します。 **[コールバック関数]** ; ボタンがクリックされたときに実行するコードを記入します(Pythonの文法に詳しい方向け:通常の関数ではなく、「ボタン上でクリックされた」を条件式とするif文において条件式が真だったときに実行されるブロックを書きます)。 **[クリック毎に1回実行]** ; **[コールバック関数]** に書かれたコードをクリックしたときに1回だけ実行するか、クリックした後ボタンが放されるまでの間繰り返し実行するかを選択します。 **[Routineを終了]** がチェックされている場合は(直ちにルーチンが終了してしまうので)意味をなさないため無効になります。 thisTrialN, Trial-by-trial記録ファイルのthisTrialNと同じです。 **[フォント]**; ボタンに表示されるテキストのフォントを指定します。Textコンポーネントと異な **初期値のままでは日本語などの非ASCII文字化けします** ので、非ASCII文字を表示したい場合は適切なフォントを指定する必要があります。 **[文字の高さ]**; ボタンに表示されるテキストのフォントを指定します。 **[サイズ [w, h] $]** で指定した **ボタンサイズに対して大きすぎる場合、テキストがはみ出したり表示されなかったりする** ので注意してください。 .. _fig-callback-func: .. figure:: fig08/callback-func.png :width: 80% コールバック関数の仕組み Buttonコンポーネントを活用するうえで非常に重要なのが **[コールバック関数]** です。 コールバック関数とは「関数から呼び出すために引数として渡しておく関数」のことで、ここではButtonコンポーネントに「もしクリックされたらその時はこれを実行してほしい」と渡しておくコードを意味しています(:numref:`fig-callback-func`)。 プログラミングに詳しい人は却って混乱してしまうかも知れませんが「関数」という名前にもかかわらず、関数の定義(Pythonならdef文)を書く必要はありません。 **[コールバック関数]** に入力したコードは、Codeコンポーネントを使って **[フレーム毎]** のタブに :: if (ボタンがクリックされた): [コールバック関数]の欄に 記入したコード というコードを記入したのと同じように解釈されます。「(ボタンがクリックされた)」の部分はButtonコンポーネントのプロパティ設定によって実際の式が変化しますし、クリック時刻を記録するためのコードも出力されるのですが、そういった細かいことは抜きにして「そのような判定をするコードが書かれている」とだけ理解しておいてください。 具体例を挙げると、 **[コールバック関数]** に .. code-block:: python if probeLen < 0.35 probeLen += 0.005 と書いておけば :: if (ボタンがクリックされた): if probeLen < 0.35 probeLen += 0.005 というコードをCodeコンポーネントで **[フレーム毎]** のタブに挿入したのと同じように動作します。 :numref:`第%s章 ` の実験をどのように変更すればマウスで操作できるようになるか、イメージできたでしょうか。 続いて **[クリック毎に1回実行]** ですが、これがKeyboardコンポーネントと大きく異なるポイントです。 この項目がチェックされていると、Keyboardコンポーネントによるキー押し検出のように、Buttonコンポーネント上でマウスのボタンが押されたその最初の瞬間に1度だけ **[コールバック関数]** の処理が行われます。 チェックされていなければ、Buttonコンポーネント上でマウスのボタンが押されている間、フレーム毎に **[コールバック関数]** の処理が行われ続けます。 :numref:`第%s章 ` の実験で「キーを押し続けている間プローブが伸縮してほしい」という要望が出てきましたが、この項目をチェックしていない時の動作がまさにこの要望に応えるものです。 逆に **[クリック毎に1回実行]** をチェックしておきたいのはどういう状況かというと、Buttonコンポーネントをクリックする度に視覚刺激のON/OFFが切り替わるといった動作が求められるときです。 普通の人がマウスのボタンをクリックしたとき、実際にボタンが押されている時間は1フレームの時間(モニターが60Hzなら約16.7ミリ秒)よりはるかに長くなります。ですから、 **[クリック毎に1回実行]** をチェックしていない場合、ボタンを押している最中は1フレーム毎にON/OFFが切り替わり、ボタンを放した瞬間切り替わりが止まることになります。狙った状態で止めるのは困難でしょう。 Keyboardコンポーネントの動作はこちらと同じと言えます。 なお、 :numref:`tbl-button-properties` に書かれているとおり、 **[Routineを終了]** がチェックされている場合はButtonコンポーネント上でボタン押しが検出されたフレームでルーチンが終了してしまうので **[クリック毎に1回実行]** は意味を持ちません。そのため **[Routineを終了]** がチェックされている時には **[クリック毎に1回実行]** は無効になります。 その他のプロパティはおおむねPolygonコンポーネントとTextコンポーネントと同じですが、 :numref:`tbl-button-properties` で挙げている **[フォント]** と **[文字の高さ]** は注意が必要です。Textコンポーネントと異なり、Buttonコンポーネントは **[フォント]** に欧文フォントが指定されているときに自動的に日本語のフォントを割り当ててくれません。したがって、日本語の文字を表示したい場合は **[フォント]** にYu Gothic (Windowsの場合)やHiragino Kaku Gothic Pro W3 (MacOSの場合)などの日本語対応フォントを指定する必要があります。 また、 **[文字の高さ]** がボタンのサイズに対して大きすぎる場合、テキストがまったく表示されない場合があります。原因が直感的にわかりにくいので注意してください。 それでは、:numref:`第%s章 ` の実験をマウスで操作できるようにしてみましょう。 チェックリスト - Buttonコンポーネントで作成したボタンをクリックすることでルーチンを終了することができる。 - Buttonコンポーネントで作成したボタン上でマウスのボタンを押し続けることでフレーム毎に処理を実行させることができる。 - Buttonコンポーネントで作成したボタン上でマウスのボタンを1回押すごとに1回処理を実行させることができる。 - Buttonコンポーネントで作成したボタンに日本語の文字を表示させることができる。 .. _section-muller-lyer-mouse: ミューラー・リヤー錯視の実験をマウスで操作できるようにしよう ------------------------------------------------------------------------------ :numref:`第%s章 ` の作業を最後までおこなった状態(練習問題は除く)から作業する前提で手順を解説します。 - 準備作業 - :numref:`第%s章 ` の実験をフォルダごとコピーして(つまり中のpsyexpファイルやxlsxファイルもすべて含めて)、わかりやすい名前で貼り付ける。例えば :numref:`第%s章 ` の実験をexp07というフォルダにまとめている場合、まるごとコピーしてexp07_mouseなどの名前で貼り付ければよい。 - 実験設定ダイアログ - 「スクリーン」タブの **[マウスカーソルを表示]** をチェックする。 - trialルーチン - 配置済みのKeyboardコンポーネントを削除する。 - Buttonコンポーネントを1つ配置し、以下のように設定する。 - 「基本」タブの **[名前]** を ``buttonOK`` にして、 **[開始]** を「時刻(秒)」の ``0.5`` にする。 **[Routineを終了]** がチェックされていることを確認し、 **[ボタンのテキスト]** を ``OK`` に変更する。 - 「レイアウト」タブの **[サイズ [w, h] $]** を ``(0.1, 0.04)`` に、 **[位置 [x, y] $]** を ``(0, -0.3)`` にする。 - 「書式」タブの **[フォント]** を ``Yu Gothic`` (Windowsの場合)、 ``Hiragino Sans`` (MacOSの場合)など、各自のPCで使用できる日本語フォントにする。 **[文字の高さ]** を ``0.03`` にする。 - **ルーチンの一番上になるように並び替える。buttonOKが一番上にあり、そのすぐ下がCodeコンポーネントになっていることを確認する。** - buttonOKをコピーし、buttonOKのアイコンを右クリックしてメニューの「下に貼り付け(buttonOK)」を選択して ``buttonLonger`` の名前で貼り付ける。 **ルーチンペイン上で一番上にbuttonOKがあり、そのすぐ下にbuttonLonger、さらにその下にcodeになっていることを確認** してから以下のように設定する。 - 「基本」タブの **[Routineを終了]** のチェックを外す。 **[ボタンのテキスト]** に ``長く`` と入力し、 **[クリック毎に1回実行]** がチェックされていることを確認する。 - 基本」タブの **[コールバック関数]** に ``probeLen = min(probeLen+0.005, 0.35)`` と入力する。 - 「レイアウト」タブの **[位置 [x, y] $]** を ``(0.15, -0.3)`` にする。 - buttonLongerをコピーし、 buttonLongerのアイコンを右クリックしてメニューの「下に貼り付け(buttonLonger)」を選択して ``buttonLonger_2`` の名前で貼り付けてから以下のように設定する。 - 「基本」タブの **[ボタンのテキスト]** に ``長く(長押し)`` と入力し、 **[クリック毎に1回実行]** のチェックを外す。 - 「レイアウト」タブの **[サイズ [w, h] $]** を ``(0.2, 0.04)`` に、 **[位置 [x, y] $]** を ``(0.35, -0.3)`` にする。 - buttonLongerをコピーし、 buttonLonger_2のアイコンを右クリックしてメニューの「下に貼り付け(buttonLonger)」を選択して ``buttonShorter`` の名前で貼り付けてから以下のように設定する。 - 「基本」タブの **[ボタンのテキスト]** に ``短く`` と入力する。 - 基本」タブの **[コールバック関数]** に ``probeLen = max(probeLen-0.005, 0.05)`` と入力する。 - 「レイアウト」タブの **[位置 [x, y] $]** を ``(-0.15, -0.3)`` にする。 - buttonLonger_2をコピーし、 buttonShorterのアイコンを右クリックしてメニューの「下に貼り付け(buttonLonger_2)」を選択して ``buttonShorter_2`` の名前で貼り付けてから以下のように設定する。 - 「基本」タブの **[ボタンのテキスト]** に ``短く(長押し)`` と入力する。 - 基本」タブの **[コールバック関数]** に ``probeLen = max(probeLen-0.005, 0.05)`` と入力する。 - 「レイアウト」タブの **[位置 [x, y] $]** を ``(-0.35, -0.3)`` にする。 - 配置済みのCodeコンポーネントの **[フレーム毎]** に入力してあるコードをすべて削除する。 ここまで作業したら、trialルーチンには上からButtonコンポーネントが5つ並び、続いてCodeコンポーネントがひとつ、さらに続いてPolygonコンポーネントが6つ並んでいるはずです。作業手順上、各Buttonコンポーネントをどの位置に貼り付けるか指定しましたが、5つのButtonコンポーネント間の順序は異なっていても動作には影響ありません。 実行すると、 :numref:`fig-ml-buttons` のように刺激とプローブの下に5つのボタンが表示されます。「長く(長押し)」と「短く(長押し)」にマウスカーソルを合わせてマウスの左ボタンを押しっぱなしにすると、長さ0.05から0.35の範囲内でプローブの長さが変化し続けることを確認してください。また、「長く」と「短く」のボタンはKeyboardコンポーネントの時のように、マウスのボタンを押したときに1回だけ長さが変化することも確認してください。 **[クリック毎に1回実行]** をチェックする、しないの違いがおわかりいただけると思います。 「OK」と書かれたボタンをクリックすると次の試行へ進みます。 .. _fig-ml-buttons: .. figure:: fig08/ml-buttons.png :width: 80% マウスでプローブの長さを調節する。 **[コールバック関数]** に入力した probeLen = min(probeLen+0.005, 0.35) と言う式は少し補足が必要でしょうか。 min( )は :numref:`tbl-basic-math-functions` で紹介した関数で、引数の中から最小の値を返します。ここではprobeLen+0.005と0.35を引数として与えていますので、probeLen+0.005が0.35より小さければその値が、0.35より大きければ0.35を返します。これによって、「長さを0.005増加させるが0.35を越えないようにする」という処理が1行で実現できています。 同様に、probeLen = max(probeLen-0.005, 0.05)は「長さを0.005減少させるが0.05未満にならないようにする」処理を実現しています。 これは「 :numref:`{number}:{name} ` 」のヒントにもなっていますので、わからなかった方はこのテクニックを念頭において改めて「 :numref:`{number}:{name} ` 」を考えてみてください。 以上のコードにより、Codeコンポーネントの **[フレーム毎]** で行っていた処理はすべて実現できてしまっているので、 **[フレーム毎]** のコードはすべて削除しました。しかし、 **[Routine開始時]** と **[Routine終了時]** の処理がまだ必要なのでCodeコンポーネント自体は残さないといけません。 Variableコンポーネントを使用すれば完全にCodeコンポーネントを削除してしまうことができますが、それは練習問題としておきましょう。Codeコンポーネントでは probeLenに加えてtheseKeysも初期化しなければいけないこと、theseKeysの値は実験記録ファイルに出力する必要がないことの2点がポイントとなるでしょう。 SliderコンポーネントとFormコンポーネント ------------------------------------------------------------------------------ 本章をButtonコンポーネントの話だけで終えてしまうのも物足りないので、マウスと相性がよいSliderコンポーネントとFormコンポーネントを紹介しておきましょう。いずれもコンポーネントペインの「反応」カテゴリにあります。 .. _fig-slider-form-component: .. figure:: fig08/slider-form-component.png :width: 80% SliderコンポーネントとFormコンポーネント Sliderコンポーネントは、定規のような「尺度」をスクリーン上に提示して、尺度上の位置を参加者に選択させることによって反応を記録するためのコンポーネントです。心理学実験ではこの種の反応を求める課題は少なくないので、覚えておくときっと役に立つはずです。 :numref:`tbl-slider-properties` に主なプロパティを示します。 **[Routineを終了]** やフォントの設定に関することなどはButtonコンポーネントと同様なので省略してあります。 .. tabularcolumns:: |p{6zw}|p{30zw}| .. _tbl-slider-properties: .. csv-table:: Sliderコンポーネントの主なプロパティ :header: データ属性, 概要 :delim: ; :widths: 30, 70 **[スタイル]** ; 尺度の見た目を決定します。slider, rating, radio, scrollbar, choiceのいずれかを選択します。slider, rating, scrollbarは離散値と連続値の両方の記録に使用できますが、radio, choiceは整数個の選択肢のなかからの選択にのみ使用できます。 **[目盛 $]** ; 尺度の目盛位置をカンマ区切りの数値で指定します。 **[スタイル]** がradioの場合は設定できません。 **[ラベル $]** ; 目盛につけるラベルをカンマ区切り指定します。 **[スタイル]** がradioの場合は必ず指定しないといけませんが、その他の場合は省略できます(ラベルは表示されません)。 **[目盛 $]** の要素数と **[ラベル $]** の要素数が異なる場合、 **[ラベル $]** が2個なら尺度の両端のラベルとなります。 **[ラベル $]** が3個以上で **[目盛 $]** の個数と一致しない場合、エラーになりませんがそのような設定は避けるべきでしょう。 **[精度 $]**; **[スタイル]** がslider, rating, scrollbarのとき、マーカー位置の最小間隔を指定します。例えば **[目盛 $]** が1, 2, 3, 4, 5で **[精度 $]** が1の場合、マーカー位置は1刻みでしか置くことができないので五段階の尺度となります。0を指定すると実験の実験環境における最小間隔となり、実質的に連続値の尺度となります。radioではこの項目は無効になるほか、choiceでは値の設定はできますが無視されます(2024.2.5で確認)。 **[初期値 $]**; Sliderコンポーネントが開始された時のマーカーの位置を指定します。空欄の場合、マーカーがどこにも置かれていない状態で開始されます。 **[サイズ [w, h] $]**; 他のコンポーネントと同様ですが、高さよりも幅が大きければ横向き、幅よりも高さが大きければ縦向きの尺度が描かれます。 **[回転角度 $]**; 他のコンポーネントと同様ですが、表示が乱れることが多いため0度以外での使用はお勧めしません。縦にする場合は **[サイズ [w, h] $]** で指定してください。 **[反転]**; 通常、ラベルは尺度の下または左に表示されますが、この項目がチェックされていると下または右に表示されます。 **[履歴を記録]**; 最終的なマーカー位置だけでなく、それまでのマーカー位置の変更履歴(位置と時刻)も記録します。 **[Routineを終了]** がチェックされている場合は意味を持ちません。 **[スタイル]** は尺度の見た目を決めるもので、 :numref:`tbl-slider-properties` の中から選びます。さらに **[目盛 $]** と **[ラベル $]** で細かな設定をしていきますが、これらのパラメータと **[スタイル]** の関係が複雑なうえ、PsychoPyのバージョンアップの過程でいくつか仕様変更もおこなわれており、すべてを解説するのは大変です。多くの方にとってはそんな踏み込んだ話よりもお勧めの使用パターンを紹介した方が有益だと思いますので、以下にいくつか示します。 .. _fig-slider-styles: .. figure:: fig08/slider-styles.png :width: 80% Sliderコンポーネントのスタイル - カテゴリカルな尺度ではradioを使うのが良いと思います。radioでは **[ラベル $]** を省略すると実行時にエラーになります(2024.2.5で確認)。 - 離散的な順序尺度ではratingを使うのが良いと思います。ratingでは **[目盛 $]** を1, 2, 3, 4, 5のように間隔1の整数にして、 **[精度 $]** を1にすれば目盛の位置しかマーカーを置けない尺度になります。 **[ラベル $]** は両端の2個を指定するか、 **[目盛 $]** の要素数と一致する個数を指定するかをお勧めします。ラベルの位置が気に入らない場合は **[ラベル $]** を空欄にして、Textコンポーネントを使って自分でラベルを配置するとよいでしょう。 - 連続的な値の尺度ではrating, slider, scrollbarのいずれかが良いと思います。 **[精度 $]** を0にすれば実験の実行環境で最も細かな位置調整ができますが、 **[目盛 $]** を0, 100として **[精度 $]** を1としたり、 **[目盛 $]** を0, 1として **[精度 $]** を0.02とするなど、実験の目的に応じた精度を積極的に指定するのもよいでしょう。 「レイアウト」タブでは他のコンポーネントと同様に尺度のサイズや位置、回転角度などを指定できますが、 **ラベルの表示などがおかしくなることが多いのでSlider、Sliderコンポーネントを回転させるのはお勧めしません** 。 初期値ではSliderコンポーネントは横向きの尺度を描画しますが、:numref:`tbl-slider-properties` のように、 **[サイズ [w, h] $]** で高さの方が幅よりも長くなるように値を設定すると縦向きの尺度を描画します。ほとんどの用途に置いて、横向きまたは縦向きの尺度を描画できれば十分でしょう。 .. _fig-slider-size: .. figure:: fig08/slider-size.png :width: 80% **[サイズ [w, h] $]** で高さの方が幅より大きな値にすると縦向きの尺度になる 実験の実行中にSliderコンポーネントでマーカーが置かれている位置の値を得る必要がある時には、ratingいうデータ属性にアクセスします。例えばresp_sliderという名前のSliderコンポーネントがあるとき、 ``resp_slider.rating`` と書けば現在のマーカー位置の値が得られます。 ratingやslider、scrollbarのように連続量を扱えるスタイルでは、 **[精度 $]** で整数値しかとらないように制限していても浮動小数点数値(float型)となります。 radioでは、選択されている項目が **[ラベル $]** で数値として与えられているなら数値、文字列として与えられているなら文字列となります。 あまり現実的な実験ではない状況でしょうが、 **[ラベル $]** の要素がそれぞれ違うデータ型の場合、そのラベルのデータ型がそのまま反映されます。 具体的に言うと、 **[ラベル $]** に1, 2.0, '3'と定義してあるなら、1を選択すればint型、2.0を選択すればfloat型、'3'を選択すればstr型の値が得られます。 注意が必要なのは、 **参加者がSlider上をクリックする前はratingの値はNoneである** という点です。 **[初期値 $]** でマーカーの位置を指定していて既にマーカーが表示されていても、参加者がまだクリックしていなければNoneですので間違えないようにしてください。 現在どの位置にマーカーが表示されているかを知るにはmarkerPosという属性を使います。例えばresp_sliderという名前のSliderコンポーネントがあって、3.2の位置にマーカーがあるなら ``resp_slider.markerPos`` の値は3.2となります。 markerPosに値を代入すれば、コードでマーカー位置を変更することも可能です。以下のコードをCodeコンポーネントを使って実行すると、マーカーの位置を0.0へ移動させることができます。 .. code-block:: python resp_slider.markerPos = 0.0 注意が必要なのはSliderコンポーネントの **[スタイル]** がradioのときで、markerPosでは最初のラベルを0.0、2番目のラベルを1.0、…という具合に浮動小数点数で「マーカーの位置」を返してきます。 **[目盛 $]** が0からN-1(Nはラベルの個数)の整数を並べたもの、 **[精度 $]** が1に設定されていると考えれば他のスタイルと統一的に扱えます。 **[スタイル]** がradioの時に **[初期値 $]** を設定する場合も同様に考えて、最初のラベルなら0、2番目のラベルなら1という具合に指定してください。 なぜ最初のラベルを1ではなく0として数えるのかは、おそらくPythonにおけるシーケンスの位置の数え方と密接な関係があります。 :numref:`第%s章 ` で説明する予定です。 この「参加者がまだクリックしていなければデータ属性ratingの値がNoneである」ということをうまく利用すると、スクリーン上に **[Routineを終了]** のチェックを外したSliderコンポーネントを複数配置して「スクリーン上のすべてのSliderコンポーネントに反応すればルーチンを終了する」といった動作をCodeコンポーネントを使って実現することが可能です(「:numref:`{number}:{name} ` 」参照)。 しかし、コードを書くのが少々面倒ですし、なにより複数のSliderコンポーネントをラベルのことまで考慮しながらサイズと位置を調整するのはなかなか骨が折れる作業です。 こういう時に非常に便利なコンポーネントがFormコンポーネントです。 :numref:`tbl-form-properties` に主なプロパティを示します。 .. tabularcolumns:: |p{6zw}|p{30zw}| .. _tbl-form-properties: .. csv-table:: Formコンポーネントの主なプロパティ :header: データ属性, 概要 :widths: 30, 70 :delim: ; **[項目]** ; Formコンポーネントで管理する尺度などのパラメータを定義したxlsxファイルやCSVファイルを指定します。 **[無作為化]** ; 項目をxlsxファイル等で定義したとおりの順序で配置するか、実行の度に無作為に並び替えるかを選択します。 **[データフォーマット]** ; 実験記録ファイルに結果を出力する際に、各Sliderの値を行方向に出力するか列方向に出力するかを選択します。 **[項目間の余白]**; 項目間の余白を指定します。CSSなどに詳しい方はtableのpaddingに対応すると考えてください。 **[スタイル]**; 配色をdark, light, カスタム...の中から選択します。カスタム...にした場合、「外観」タブの色に関する項目が設定可能になります。 Formコンポーネントを使いこなす鍵は **[項目]** に指定する設定ファイルです。具体例を見てみましょう。 :numref:`fig-form-sample` 上は設定ファイルの例です。条件ファイルと同様、1行目はパラメータ名で、2行目以降は1行につきひとつの見出しや尺度などの項目に対応しています。 この設定ファイルを実際にFormコンポーネントの **[項目]** に指定した実行した結果が :numref:`fig-form-sample` 下です。 Sliderコンポーネントで作成できるような尺度が整然と並んで表示されています。もちろん、マウスを使ってそれぞれの項目を操作することが可能です。 右端にある縦長の長方形はスクロールバーです。 項目数が多くてFormコンポーネントの **[サイズ [w, h] $]** に入りきらなかった場合、このように自動的にスクロールバーが出現して上下にスクロールさせることができます。 Sliderコンポーネントを複数並べて自分で位置調整するのとは比べ物にならない簡単さです。 .. _fig-form-sample: .. figure:: fig08/form-sample.png :width: 100% 上:項目ファイルの内容。下:Formコンポーネントで実行した例。 設定ファイルでは、 :numref:`fig-form-sample` で示したものだけでなくさまざまなパラメータを指定することができます(:numref:`tbl-form-items-file`)。 typeでSliderコンポーネントのrating, slider, radio, choice(ただしFormコンポーネントではradioと同じ)を選べるほか、headingで見出し行、descriptionで教示文を配置することもできます。 Textコンポーネントの **[文字列]** を条件ファイルで変更するときと同様、複数行のテキストを入力することも可能です。一方、Textコンポーネントとは異なり、長い文字列を入力すると日本語であっても表示範囲に収まるように自動的に改行されます。ただし、改行位置の調整があまりスマートではないことや、英単語の途中で改行するときのようにハイフンが入ってしまったりしますので、手作業で改行した方がよい出力が得られるでしょう。 xlsx形式で設定ファイルを作成する場合、セル内で改行すればその通りに反映されますが、CSV形式で設定ファイルをする場合は、 :numref:`fig-form-sample` の「ratingの例」のtickLabelsのように改行する位置に\\nと書いて改行を表すことができます(「:numref:`{number}:{name} ` 」参照)。 .. tabularcolumns:: |p{6zw}|p{30zw}| .. _tbl-form-items-file: .. csv-table:: Formコンポーネントの項目ファイルで指定する内容 :widths: 24, 64 :delim: ; :class: longtable index;項目の順序を整数で指定します。省略すると自動的に番号が割り振られます。刺激の描画には影響しませんが、 **[無作為化]** した時にデータファイルの出力を項目順に並び替えるときに役に立ちます。 itemText;表示したい文を指定します。 type;項目の種類を指定します。rating,slider,radioはSliderコンポーネントと同じです。choiceも指定できますがradioと同じになります(2024.2.5で確認)。heading,descriptionは教示などの文だけを表示したいときに使います(headingはやや大き目でボールド体になる)。heading, descriptionを選んだ場合はoptions, ticks, ticklabels, layout, responseColor, responseWidth, granularityは意味を持ちません。ほかに文字列を入力できるfree textを指定できますが、現状では日本語入力には使い物になりません(「:numref:`{number}:{name} ` 」参照)。 ticks;スライダーの目盛をカンマ区切りで指定します。 tickLabels;スライダーの目盛をカンマ区切りで指定します。 options;スライダーのラベルをカンマ区切りで指定します。ラベルに対応する値は自動的に決まります。 **旧バージョンではticks, tickLabelsがなかったためoptionsで指定していましたが、現在はticks, tickLabelsで指定する方がよいでしょう。** layout;スライダーの方向をhoriz、vertのいずれかで指定します。horizなら水平方向、vertなら垂直方向です。省略するとhorizになります。 itemColor;質問文の文字色を指定します。省略するとwhiteになります。Formコンポーネントの **[スタイル]** がcustom...の場合のみ有効です。 itemWidth;Formコンポーネントの **[サイズ$]** で指定した幅のうち、質問文が占める幅の割合を0.0から1.0指定します。 responseColor;スライダーの色を指定します。省略するとwhiteになります。Formコンポーネントの **[スタイル]** がcustom...の場合のみ有効です。 responseWidth;Formコンポーネントの **[サイズ$]** で指定した幅のうち、スライダーが占める幅の割合を0.0から1.0指定します。itemWidthとresponseWidthの合計が1.0を越えるとレイアウトが崩れますので注意してください。 granularity;Sliderコンポーネントの **[精度 $]** に対応します。typeがsliderの時のみ有効です。rating, radio, choiceの時は目盛の位置しか選択できません。 font;使用するフォントはFormコンポーネントのプロパティダイアログの **[フォント]** で指定しますが、特定の項目だけ別のフォントを指定したい場合はここでフォント名を指定します。 Sliderの左側のテキストとSliderの幅のバランスはitemWidthとresponseWidthで調整します。それぞれの項目で異なる値を指定できるので、 :numref:`fig-form-sample` の例のように複数の種類のSliderを使い分ける際に種類ごとに異なるバランスにしたり、左側のテキストがどうしても長くする必要がある項目だけすこしitemWidthの幅を広げるといったこともできます。 itemWidth, responseWidthはFormコンポーネントの幅に対する相対値ですので、合計で1.0を超えないように注意してください。1.0を超えた場合、エラーにはなりませんが表示が崩れる場合があります。 項目ごとに色を指定したい場合のためにitemColor, responseColorというパラメータが用意されていますが、これらのパラメータを有効にするにはFormコンポーネントの **[スタイル]** をcustom...にする必要があります。それ以外の場合、itemColorとresponseColorは無視されます(書いてあってもエラーにはなりません)。 fontによる項目ごとのフォント指定は **[スタイル]** の設定に関係なく有効です。 設定ファイルの説明はこのくらいにして、その他の注意点を挙げていきましょう。まず **[データフォーマット]** ですが、これは実験記録ファイルへのデータの出力形式を選択するパラメータです。 言葉で説明するとわかりにくいので :numref:`fig-form-datafile` をご覧ください。 「行」であれば、Formコンポーネントに含まれる各Sliderの値が1行ずつ出力されていきます。「列」の場合は、実験内の他のパラメータと同様に、各Sliderの値が列方向にずらずらと出力されていきます。 おそらく、人の目で確認するときに見やすいのは「行」です。「列」にした場合、Sliderひとつに対して複数列の値が出力されるため、実験記録ファイルの列数が非常に多くなり一画面におさまらない可能性が高いですし、評定値が出力されているセルが飛び飛びになるためExcel上でマウスで選択して別のファイルにコピーするといった操作が非常に面倒になります。 一方、本書では解説しませんがPythonやRのスクリプトを書いてデータをまとめて処理する場合は、「1試行=1行」の鉄則が守られる「列」形式の方がプログラムを書くのが楽かも知れません。実験が完成してしまえば何列目が目的の評定値なのかは変化しないのですから、その列が飛び飛びであろうとプログラムで処理する際には問題にならないでしょう。好みに応じて使い分けてください。 .. _fig-form-datafile: .. figure:: fig08/form-datafile.png :width: 80% **[データフォーマット]** の設定によるFormコンポーネントの出力の違い 二つめの注意点は、 **Formコンポーネントには [Routineを終了] がないので他の方法でルーチンを終了させる必要がある** という点です。 この点はKeyboardコンポーネントやButtonコンポーネントの **[Routineを終了]** を併用すれば解決できますが、何も考えずにこれらのコンポーネントを配置すると「(故意か誤操作かはともかく)参加者がまだフォームに回答していないのに次のルーチンへ進んでしまう」ということが起こりかねません。 実験の内容によってはそれでもいい場合もあるでしょうが、多くの用途では「すべて回答してからでなければ次へ進まない」方が望ましいのではないでしょうか。 こういった時に便利なのがFormコンポーネントに対応するFormオブジェクトのデータ属性completeです。このデータ属性は、当該フォームのすべての項目が回答済みであればTrue、未回答の項目があればFalseとなります。これを利用して、 1. **[開始]** を「条件式」にしてform.completeにすれば、formという名前のFormコンポーネントのすべての項目に回答した時点でそのコンポーネントが有効になる 2. **[終了]** を「条件式」にしてform.completeにすれば、formという名前のFormコンポーネントのすべての項目に回答した時点でそのコンポーネントが終了する といった動作が可能になります。 1はKeyboardコンポーネントやButtonコンポーネントなど、 **[Routineを終了]** を設定しているコンポーネントに適用すれば、すべての項目に回答し終えた後に参加者が自分でキーを押すなりボタンを押すなりしてルーチンを終了させるという使い方がよいでしょう。 特にButtonコンポーネントの場合、 **ルーチンの開始時にはボタンが表示されていなくて、回答を終えた瞬間に次のルーチンへ進むためのボタンが出現する** という動作になるので参加者にとってもわかりやすいと思います(Keyboardコンポーネントだとキーが押せるようになったことがわかりにくい)。 2はFormコンポーネント自身に対して適用して、すべての項目に回答し終えたらFormコンポーネントが終了するという使い方がよいでしょう。 Formコンポーネント以外に教示用のTextコンポーネントなどが配置されている場合、それらのコンポーネントがすべて終了するように設定しないとルーチンが終了しない点に注意してください。 最後の項目に回答した瞬間にフォームが消えてしまうので参加者は最初少し驚くかもしれませんが、いちいち回答終了のためにキーやボタンを押すといった操作をしなくて済むのは楽かもしれません。 最後の項目に回答した後にも参加者に回答の変更を許可したい場合は、1の方法がよいでしょう。 最後の注意点は、 **Formコンポーネントを配置したルーチンをループで繰り返す場合、2回目以降の繰り返しでは前回の回答が残ったままになっている** という点です。回答済みを表すデータ属性completeもTrueのままです。 おそらくフォームへの入力内容を後続のルーチンで使用することを考えての仕様なのでしょうが、繰り返し毎に未入力の状態に戻ってほしい場面も多いでしょう。 そういった場合はFormオブジェクトのreset( )というメソッドを使用します。Codeコンポーネントを配置して **[Routine開始時]** に .. code-block:: Python form.reset( ) というコードを実行するとよいでしょう(formの部分は初期化したいFormコンポーネントの **[名前]** にする)。 コンポーネントの解説はこのくらいにして、次はSliderコンポーネントとFormコンポーネントを使用して :numref:`第%s章 ` の実験をマウスで反応できるようにしてみましょう。 チェックリスト - Sliderコンポーネントで縦向き・横向きのスライダーを描画できる。 - Sliderコンポーネントでラジオボタンを用いた多肢選択を描画できる。 - Sliderコンポーネントで数値を回答させる際にマーカーの最小間隔を設定できる。 - Sliderコンポーネントで参加者の回答を取得するコードを書ける。 - Formコンポーネントで複数の質問項目とスライダーのペアをルーチンに配置することができる。 - Formコンポーネントのすべての項目に回答したらルーチン終了のボタンを表示させることができる。 - Formコンポーネントのすべての項目に回答したらフォームを終了させることができる。 - ループの中でFormコンポーネントを使う時に繰り返しのたびに反応を初期化することができる。 .. _section-cf-mouse: 概念識別の実験をマウスで反応できるようにしよう ------------------------------------------------------------------------------ :numref:`第%s章 ` の作業を最後までおこなった状態(練習問題は除く)から作業する前提で手順を解説します。 - 準備作業 - :numref:`第%s章 ` の実験をフォルダごとコピーして(つまり中のpsyexpファイルやxlsxファイルもすべて含めて)、わかりやすい名前で貼り付ける。例えば :numref:`第%s章 ` の実験をexp06というフォルダにまとめている場合、まるごとコピーしてexp06_formなどの名前で貼り付ければよい。 - 実験設定ダイアログ - 「スクリーン」タブの **[マウスカーソルを表示]** をチェックする。 - instructionルーチン - 配置済みのKeyboardコンポーネントを削除する。 - Buttonコンポーネントをひとつ配置し、以下のように設定する。 - 「基本」タブの **[名前]** を ``buttonInst`` にして、 **[終了]** を空欄にする。 **[ボタンのテキスト]** を ``OK`` に変更する。 - 「レイアウト」タブの **[サイズ [w, h] $]** を ``(0.8, 0.04)`` にする。 **[位置 [x, y] $]** を ``(0, -0.4)`` にする。 - 「書式」タブの **[フォント]** を日本語に対応したフォントにする。 **[文字の高さ $]** を ``0.03`` にする。 - 「データ」タブの **[クリックを記録]** を「なし」にする。 - trialルーチン - 配置済みのtextYes, textNo (それぞれTextコンポーネント)、key_choice (Keyboardコンポーネント)を削除する。 - Sliderコンポーネントを配置し、以下のように設定する。 - 「基本」タブの **[名前]** を ``sliderResponse`` にする。 **[Routineを終了]** がチェックされていること、 **[スタイル]** が rating であることを確認する(いずれも初期値でそうなっているはず)。 - 「基本」タブの **[目盛 $]** を ``(0, 1, 2, 3)`` にする。 **[ラベル $]** に ``'いいえ','多分いいえ','多分はい','はい'`` と入力する。区切りのカンマや各ラベルを囲む' 'は日本語入力をOFFにして(つまり半角で)入力すること。 - 「基本」タブの **[精度 $]** を ``1`` にする。 - 「レイアウト」タブの **[サイズ [w, h] $]** を ``(0.8, 0.03)`` にする。 **[位置 [x, y] $]** を ``(0, -0.3)`` にする。 **[反転]** をチェックする。 - 「書式」タブの **[フォント]** を日本語に対応したフォントにする。 **[文字の高さ $]** を ``0.03`` にする。 - 配置済みのCodeコンポーネントを一番下に移動させて(通常、sliderResponseを追加したことでsliderResponseが一番下になっている)、 **[Routine終了時]** のコードを以下のように変更する。 .. code-block:: python res = sliderResponse.getRating() if res>1.5 and correctAns=='left': feedbackMsg = '正解' elif res<1.5 and correctAns=='right': feedbackMsg = '正解' else: feedbackMsg = '不正解' - feedbackルーチン - 配置済みのKeyboardコンポーネントを削除する。 - instructionルーチンに配置済みの buttonInst をコピーして、 ``buttonFeedback`` という名前でfeedbackルーチンに貼り付ける。 - reportルーチン - 配置済みのコンポーネントを全て削除する。 - Formコンポーネントを配置して、以下のように設定する。 - 「基本」タブの **[名前]** を ``formReport`` にする。 **[項目]** は後で設定する。 - 「レイアウト」タブの **[サイズ [w, h] $]** を ``(1.3, 0.7)`` にする。 **[項目間の余白]** を ``0.01`` にする。 - 「書式」タブの **[テキストの高さ $]** を ``0.03`` 、 **[フォント]** を日本語に対応したフォントにする。 - instructionルーチンに配置済みの buttonInst をコピーして、 ``buttonReport`` という名前でfeedbackルーチンに貼り付けて以下のように設定する。 - 「基本」タブの **[終了]** を「条件式」にして、 ``formReport.complete`` と入力する。 - 項目定義ファイル - 実験のpsyexpファイルと同じフォルダに :numref:`fig-form-params` に示す内容のxlsxファイルを作成し、reportルーチンの **[項目]** に設定する。 .. _fig-form-params: .. figure:: fig08/form-params.png :width: 80% 実験用の項目定義ファイル 以上で作業は終了です。実行すると、 :numref:`fig-cf-form` 左のように、顔画像を見て判断する画面でスクリーンの下の方に「いいえ」「多分いいえ」「多分はい」「はい」の4段階の尺度が表示され、尺度上をクリックすることで回答できます。 選択肢の中間をクリックすると、最も近い位置の選択肢が記録されます。 すべての画像について回答を終えると、 :numref:`fig-cf-form` 右のように「はい」が正答となる条件はなんだったかを選択式で回答するフォームが表示されます。すべての項目に回答すると画面下にOKボタンが表示されて、このOKボタンをクリックすると実験が終了します。 .. _fig-cf-form: .. figure:: fig08/cf-form.png :width: 80% 実験の実行画面。左は各画像について回答中。右は最後の質問フォーム。すべての項目が回答済みでOKボタンが表示されている。 おおむね前節の解説でおわかりいただける作業内容だと思いますが、OKボタンの位置と大きさについて少し補足しておきます。 sliderResponseは(0, -0.3)の位置、buttonFeedbackは(0, -0.4)の位置に置かれているため、参加者は刺激画像を判断して尺度をクリックした後、フィードバックを見て少しマウスを下に動かしてOKボタンをクリックしないといけません。そして次の画像について反応するためにまた少しマウスカーソルを上に動かす必要があります。 この実験を実行してみて、この上下移動を面倒に感じた方もおられるのではないでしょうか。 両者のY座標を揃えればこの上下移動をせずに済みますが、もし **sliderResponseをクリックしたときのマウスカーソルの位置が、フィードバック画面でのbuttonFeedbackと重なっていたら、フィードバック画面に切り替わった瞬間にbuttonFeedbackをクリックしたと判定されてフィードバック画面が終了する** という問題が生じます。 本節で作成したように、sliderResponseとbuttonFeedbackが重ならないように位置をずらしておけば、この問題を避けるためなのです。 少しでもマウスカーソルを動かす負担を軽減できるよう、buttonFeedbackの幅をsliderResponseと同じ幅にしておいて、sliderResponseのどの位置をクリックしても左右方向にほとんどカーソルを動かさずにbuttonFeedbackをクリックできるようにしてあります。 どうしても上下移動を避けたい場合は、buttonFeedbackの **[開始]** を0.5秒や1.0秒などに設定して、画面が切り替わった直後にはボタンが表示されないようにしておきましょう。 **[開始]** を遅らせるほど問題が生じる危険性が低下しますが、参加者を待たせることになります。フィードバック画面のOKボタンの表示を遅らせるのではなく、次の試行に入る時に完全に画面がブランクになる時間を設ける(つまりtrialルーチンのすべてのコンポーネントの **[開始]** を遅くする)方が違和感が少ないかも知れません。 「こうすれば間違いない」といった絶対的な方法はないと思いますので、各自でいろいろと試してみられるとよいと思います。 この章のトピックス -------------------------- .. _topic-check-multiple-sliders: 同一ルーチンに配置されている複数のSliderコンポーネントが回答済みか判定する ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trialルーチンにslider1, slider2というSliderルーチンが配置されているとします。参加者がslider1に回答していれば、slider1.ratingの値はNone以外になっています。NoneはPythonの文法において特殊な値で、変数xの値がNoneであるか判定するにはx is Noneという式を書きます(NoneであればTrue, なければFalse)。Noneではないときに真になるようにしたければ、否定演算子notを使ってx is not Noneと書きます。従って、以下のような式をif文を書けば、slider1が回答済みの時に処理を行わせるコードが書けます。 .. code-block:: python if slider1.rating is not None: # slider1が回答済みのときにしたい処理 slider1, slider2の両方が回答済みという条件にしたい場合は、論理演算子andを使って .. code-block:: python if slider1.rating is not None and slider2.rating is not None: # slider1と2が回答済みのときにしたい処理 と書くことができます。さらにSliderコンポーネントの数が増えると、いちいち is not None と書くのは面倒ですし、式が長いです。 そんな場合はin演算子を使って .. code-block:: python if None not in (slider1.rating, slider2.rating, slider3.rating, slider4.rating): # slider1から4が回答済みのときにしたい処理 という具合に「( )内にNoneがない」という条件として書くと、is not Noneを繰り返さなくていい分簡潔になります。 .. _topic-form-free-text: 日本語環境におけるFormコンポーネント(およびTextBoxコンポーネント)の文字入力機能 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :numref:`第%s章 ` の実験では最後に「答えが『はい』になる条件は何か?」ということを口頭で自由報告させていましたが、本章ではこれを選択問題に変更しました。 Formコンポーネントの使い方を解説するためとはいえ、これは実験の結果に影響しかねない大きな変更です。 「自由報告させたいけど、実験者が横で聞き取って記録しなくても済むようにしたい」という場合、Builderにこだわらなければ色々方法があると思いますが(例えば紙に書かせて後から紙を回収するなど)、本書はBuilderの解説本ですので、なんとかBuilderで実現できる方法を考えたいところです。 とりあえずぱっと思いつくのは、PCに接続されたマイクを利用して音声を録音するMicrophoneコンポーネントを使うことです。ただ、音声データはファイルサイズが大きいですし、後で再生して文字起こしする手間もかかりますので、参加者が多い実験ではあまり使いたくありません(筆者の個人的な意見ですが)。 他に良さげな候補は、ということで挙がってくるのがFormコンポーネントのfree textです。これはフォーム上にキーボードを使って文字を入力できるボックスを設けるもので、自由に回答できるという点はクリアしていますし、録音のようにファイルサイズが大きくなる問題も後で文字起こしをするという手間もありません。 :numref:`第%s章 ` の実験の改良として非常に魅力的なのですが、残念ながら現在(執筆時点でバージョン2024.2.5)のFormコンポーネントでは日本語入力のようにIME(Imput Method Editor: ローマ字入力やかな入力、漢字変換などの機能を提供するソフトウェア)を使った文字入力画面をうまく表示できないのです。 .. _fig-input-free-text: .. figure:: fig08/input-free-text.png :width: 80% Formコンポーネントのfree textで日本語を入力するとIMEの表示がおかしくなる。 :numref:`fig-input-free-text` はfree textを持つFormコンポーネントをPilotモードで実行して日本語を入力しようとした様子ですが(OSはWindows 11)、タイプした文字が表示されるべき右下の白い枠内には文字が表示されず、画面左上の角に小さなウィンドウが開いてその中に表示されてしまっています。 この状態は不便ではありますが、「入力注の文字列が左上に表示されている」ということに気づきさえすれば、通常の操作で文字の入力は一応可能です。 問題はフルスクリーンモードで実験を実行した場合で、左上に出現するウィンドウが隠れて全く見えなくなってしまいます。ひらがな程度ならなんとか入力できなくもありませんが、漢字変換は変換候補が見えないためお手上げです。 本文で「free textは現状では日本語入力には使い物になりません」と書いたのはこのためです。 PsychoPyはマルチプラットフォームであり、Windows、MacOS、Linuxなどさまざまな環境をサポートしなければいけないため、IMEのようにOSに大きく依存する機能に対応するのは難しいです。 残念ながら、おそらく数年内にこの問題が解決されて自然な日本語入力が可能になることはないでしょう。 本実験のように、実験の最後に一度だけ文字入力を求めるのであれば、Builderでの実験をいったん終了させてしまって何か別の方法で文字入力してもらう方が簡単でしょう。 実験手続きの途中で入力が必要な場合、ちょっと抜け道的な使い方ですが「オンライン実験」をローカルブラウザで実行するという方法もあります。詳しくは「 :numref:`{number}:{name} ` 」をご覧ください。