API Docs for: 0.5.1
Show:

File: addon/randomizers/random-parameter-set.js

  1. /**
  2. * @module exp-player
  3. * @submodule randomizers
  4. */
  5.  
  6. /**
  7. * Randomizer to implement flexible condition assignment and counterbalancing by
  8. * allowing the user to specify an arbitrary sequence of frames to create. A
  9. * set of parameters is randomly selected from a list of available parameterSets,
  10. * and these parameters are substituted in to the parameters specified in the
  11. * list of frames.
  12. *
  13. * To use, define a frame with "kind": "choice" and "sampler": "random-parameter-set",
  14. * as shown below, in addition to the parameters described under 'properties'.
  15. *
  16. * This
  17. *
  18. ```json
  19. "frames": {
  20. "test-trials": {
  21. "sampler": "random-parameter-set",
  22. "kind": "choice",
  23. "id": "test-trials",
  24. "commonFrameProperties": {
  25. "kind": "exp-lookit-story-page",
  26. "baseDir": "https://s3.amazonaws.com/lookitcontents/ingroupobligations/",
  27. "audioTypes": ["mp3", "ogg"],
  28. "doRecording": true,
  29. "autoProceed": false,
  30. "parentTextBlock": {
  31. "title": "Parents!",
  32. "text": "Common instructions across test trials here",
  33. "emph": true
  34. }
  35. },
  36. "frameList": [
  37. {
  38. "images": [
  39. {
  40. "id": "agent",
  41. "src": "AGENTIMG1",
  42. "left": "40",
  43. "width": "20",
  44. "top": "10"
  45. },
  46. {
  47. "id": "left",
  48. "src": "LEFTIMG1",
  49. "left": "10",
  50. "width": "20",
  51. "top": "50"
  52. },
  53. {
  54. "id": "right",
  55. "src": "RIGHTIMG1",
  56. "left": "70",
  57. "width": "20",
  58. "top": "50"
  59. }
  60. ],
  61. "audioSources": [
  62. {
  63. "audioId": "questionaudio",
  64. "sources": [{"stub": "QUESTION1AUDIO"}],
  65. "highlights": "QUESTION1HIGHLIGHTS"
  66. }
  67. ]
  68. },
  69. {
  70. "images": [
  71. {
  72. "id": "agent",
  73. "src": "AGENTIMG2",
  74. "left": "40",
  75. "width": "20",
  76. "top": "10"
  77. },
  78. {
  79. "id": "left",
  80. "src": "LEFTIMG2",
  81. "left": "10",
  82. "width": "20",
  83. "top": "50"
  84. },
  85. {
  86. "id": "right",
  87. "src": "RIGHTIMG2",
  88. "left": "70",
  89. "width": "20",
  90. "top": "50"
  91. }
  92. ],
  93. "audioSources": [
  94. {
  95. "audioId": "questionaudio",
  96. "sources": [{"stub": "QUESTION2AUDIO"}],
  97. "highlights": "QUESTION2HIGHLIGHTS"
  98. }
  99. ]
  100. }
  101. ],
  102. "parameterSets": [
  103. {
  104. "AGENTIMG1": "flurpagent1.jpg",
  105. "LEFTIMG1": "flurpvictim1.jpg",
  106. "RIGHTIMG1": "zazzvictim1.jpg",
  107. "QUESTION1AUDIO": "flurpleftmean1",
  108. "QUESTION1HIGHLIGHTS": [
  109. {"range": [0.399293, 3.617124], "image": "agent"},
  110. {"range": [5.085112, 6.811467], "image": "left"},
  111. {"range": [6.905418, 8.702236], "image": "right"}
  112. ],
  113. "AGENTIMG2": "flurpagent2.jpg",
  114. "LEFTIMG2": "flurpvictim2.jpg",
  115. "RIGHTIMG2": "zazzvictim2.jpg",
  116. "QUESTION2AUDIO": "flurpleftinduct1",
  117. "QUESTION2HIGHLIGHTS": [
  118. {"range": [0.372569, 5.309110], "image": "agent"},
  119. {"range": [5.495395, 7.209213], "image": "left"},
  120. {"range": [5.495395, 7.209213], "image": "right"},
  121. {"range": [9.966225, 11.922212], "image": "left"},
  122. {"range": [12.052612, 14.008600], "image": "right"}
  123. ]
  124. },
  125. {
  126. "AGENTIMG1": "zazzagent1.jpg",
  127. "LEFTIMG1": "flurpvictim1.jpg",
  128. "RIGHTIMG1": "zazzvictim1.jpg",
  129. "QUESTION1AUDIO": "zazzrightnice1",
  130. "QUESTION1HIGHLIGHTS": [
  131. {"range": [0.348454, 3.736871], "image": "agent"},
  132. {"range": [5.395033, 6.884975], "image": "left"},
  133. {"range": [6.969085, 8.975701], "image": "right"}
  134. ],
  135. "AGENTIMG2": "zazzagent2.jpg",
  136. "LEFTIMG2": "flurpvictim2.jpg",
  137. "RIGHTIMG2": "zazzvictim2.jpg",
  138. "QUESTION2AUDIO": "zazzrightinduct1",
  139. "QUESTION2HIGHLIGHTS": [
  140. {"range": [0.572920, 5.138376], "image": "agent"},
  141. {"range": [5.335317, 7.089884], "image": "left"},
  142. {"range": [5.335317, 7.089884], "image": "right"},
  143. {"range": [9.721735, 11.565821], "image": "left"},
  144. {"range": [11.655340, 13.535233], "image": "right"}
  145. ]
  146. }
  147. ],
  148. "parameterSetWeights": [1, 1]
  149. }
  150. }
  151.  
  152. * ```
  153. * @class randomParameterSet
  154. */
  155.  
  156. function getRandomElement(arr, weights) {
  157. var totalProb = weights.reduce((a, b) => a + b, 0);
  158. var randPos = Math.random() * totalProb;
  159.  
  160. var weightSum = 0;
  161. for (var i = 0; i < arr.length; i++) {
  162. weightSum += weights[i];
  163. if (randPos <= weightSum) {
  164. return [i, arr[i]];
  165. }
  166. }
  167. }
  168.  
  169. function replaceValues(obj, rep) {
  170. for (var property in obj) {
  171. if (obj.hasOwnProperty(property)) {
  172. if (typeof obj[property] === 'object') {
  173. obj[property] = replaceValues(obj[property], rep);
  174. } else {
  175. if (rep.hasOwnProperty(obj[property])) {
  176. obj[property] = rep[obj[property]];
  177. }
  178. }
  179. }
  180. }
  181. return obj;
  182. }
  183.  
  184. var randomizer = function(frameId, frameConfig, pastSessions, resolveFrame) {
  185.  
  186. // Data provided to randomizer (properties of frameConfig):
  187.  
  188. /**
  189. * Object describing common parameters to use in EVERY frame created
  190. * by this randomizer. Parameter names and values are as described in
  191. * the documentation for the frameType used.
  192. *
  193. * @property {Object} commonFrameProperties
  194. */
  195.  
  196. /**
  197. * Unique string identifying this set of frames
  198. *
  199. * @property {String} id
  200. */
  201.  
  202. /**
  203. * List of frames to be created by this randomizer. Each frame is an
  204. * object with any necessary frame-specific properties specified. The
  205. * 'kind' of frame can be specified either here (per frame) or in
  206. * commonFrameProperties. If a property is defined for a given frame both
  207. * in this frame list and in commonFrameProperties, the value in the frame
  208. * list will take precedence.
  209. *
  210. * (E.g., you could include 'kind': 'normal-frame' in
  211. * commmonFrameProperties, but for a single frame in frameList, include
  212. * 'kind': 'special-frame'.)
  213. *
  214. * Any property VALUES within any of the frames in this list which match
  215. * a property NAME in the selected parameterSet will be replaced by the
  216. * corresponding parameterSet value. E.g., suppose a frame in frameList is
  217. *
  218. * ```
  219. * {'leftImage': 'LEFTIMAGE1',
  220. * 'rightImage': 'frog.jpg',
  221. * 'size': 'IMAGESIZE'}
  222. * ```
  223. *
  224. * and the row that has been selected randomly of parameterSets is
  225. *
  226. * ```
  227. * {'LEFTIMAGE1': 'toad.jpg',
  228. 'LEFTIMAGE2': 'dog.jpg',
  229. 'IMAGESIZE': 250}
  230. * ```
  231. *
  232. * Then the frame would be transformed into:
  233. * ```
  234. * {'leftImage': 'toad.jpg',
  235. * 'rightImage': 'frog.jpg',
  236. * 'size': 250}
  237. * ```
  238. *
  239. * The same values may be applied across multiple frames. For instance,
  240. * suppose frameList is
  241.  
  242. ```
  243. [
  244. {
  245. 'leftImage': 'LEFTIMAGE1',
  246. 'rightImage': 'frog.jpg',
  247. 'size': 'IMAGESIZE'
  248. },
  249. {
  250. 'leftImage': 'LEFTIMAGE2',
  251. 'rightImage': 'frog.jpg',
  252. 'size': 'IMAGESIZE'
  253. }
  254. ]
  255. ```
  256.  
  257. * Then the corresponding processed frames would include the values
  258. ```
  259. [
  260. {
  261. 'leftImage': 'toad.jpg',
  262. 'rightImage': 'frog.jpg',
  263. 'size': 250
  264. },
  265. {
  266. 'leftImage': 'dog.jpg',
  267. 'rightImage': 'frog.jpg',
  268. 'size': 250
  269. }
  270. ]
  271. ```
  272. * A property value like 'IMAGESIZE' may be placed in a frame definition
  273. * nested within another object (at any depth) or within a list and
  274. * will still be replaced.
  275.  
  276. * @property {Object[]} frameList
  277. */
  278.  
  279. /**
  280. * Array of parameter sets to randomly select from in order to determine
  281. * the parameters for each frame in this session.
  282. *
  283. * A single element of parameterSets will be applied to a given session.
  284. *
  285. * @property {Object[]} parameterSets
  286. */
  287.  
  288. /**
  289. * [Optional] Array of weights for parameter sets; elements correspond to
  290. * elements of parameterSets. The probability of selecting an element
  291. * parameterSets[i] is parameterSetWeights[i]/sum(parameterSetWeights).
  292. *
  293. * If not provided, all parameterSets are weighted equally.
  294. *
  295. * This is intended to allow manual control of counterbalancing during
  296. * data collection, e.g. to allow one condition to "catch up" if it was
  297. * randomly selected less often.
  298. *
  299. * @property {Number[]} parameterSetWeights
  300. */
  301.  
  302. // Select a parameter set to use for this trial.
  303. if (!(frameConfig.hasOwnProperty('parameterSetWeights'))) {
  304. frameConfig.parameterSetWeights = new Array(frameConfig.parameterSets.length).fill(1);
  305. }
  306.  
  307. var parameterData = getRandomElement(frameConfig.parameterSets, frameConfig.parameterSetWeights);
  308. var parameterSetIndex = parameterData[0];
  309. var parameterSet = parameterData[1];
  310.  
  311. var frames = [];
  312. var thisFrame = {};
  313.  
  314. for (var iFrame = 0; iFrame < frameConfig.frameList.length; iFrame++) {
  315.  
  316. // Assign parameters common to all frames made by this randomizer
  317. thisFrame = {};
  318. Object.assign(thisFrame, frameConfig.commonFrameProperties);
  319.  
  320. // Assign parameters specific to this frame (allow to override
  321. // common parameters assigned above)
  322. Object.assign(thisFrame, frameConfig.frameList[iFrame]);
  323.  
  324. // Substitute any properties that can be replaced based on
  325. // the parameter set.
  326. thisFrame = replaceValues(thisFrame, parameterSet);
  327.  
  328. // Assign frame ID
  329. thisFrame.id = `${frameId}`;
  330.  
  331. thisFrame = resolveFrame(thisFrame.id, thisFrame)[0];
  332. frames.push(...thisFrame); // spread syntax important here -- a list of frames is returned by resolveFrame.
  333. }
  334.  
  335. return [frames, {'conditionNum': parameterSetIndex, 'parameterSet': parameterSet}];
  336.  
  337. };
  338. export default randomizer;
  339.  
  340. // Export helper functions to support unit testing
  341. export { getRandomElement, replaceValues};
  342.