bead 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bead/__init__.py +11 -0
- bead/__main__.py +11 -0
- bead/active_learning/__init__.py +15 -0
- bead/active_learning/config.py +231 -0
- bead/active_learning/loop.py +566 -0
- bead/active_learning/models/__init__.py +24 -0
- bead/active_learning/models/base.py +852 -0
- bead/active_learning/models/binary.py +910 -0
- bead/active_learning/models/categorical.py +943 -0
- bead/active_learning/models/cloze.py +862 -0
- bead/active_learning/models/forced_choice.py +956 -0
- bead/active_learning/models/free_text.py +773 -0
- bead/active_learning/models/lora.py +365 -0
- bead/active_learning/models/magnitude.py +835 -0
- bead/active_learning/models/multi_select.py +795 -0
- bead/active_learning/models/ordinal_scale.py +811 -0
- bead/active_learning/models/peft_adapter.py +155 -0
- bead/active_learning/models/random_effects.py +639 -0
- bead/active_learning/selection.py +354 -0
- bead/active_learning/strategies.py +391 -0
- bead/active_learning/trainers/__init__.py +26 -0
- bead/active_learning/trainers/base.py +210 -0
- bead/active_learning/trainers/data_collator.py +172 -0
- bead/active_learning/trainers/dataset_utils.py +261 -0
- bead/active_learning/trainers/huggingface.py +304 -0
- bead/active_learning/trainers/lightning.py +324 -0
- bead/active_learning/trainers/metrics.py +424 -0
- bead/active_learning/trainers/mixed_effects.py +551 -0
- bead/active_learning/trainers/model_wrapper.py +509 -0
- bead/active_learning/trainers/registry.py +104 -0
- bead/adapters/__init__.py +11 -0
- bead/adapters/huggingface.py +61 -0
- bead/behavioral/__init__.py +116 -0
- bead/behavioral/analytics.py +646 -0
- bead/behavioral/extraction.py +343 -0
- bead/behavioral/merging.py +343 -0
- bead/cli/__init__.py +11 -0
- bead/cli/active_learning.py +513 -0
- bead/cli/active_learning_commands.py +779 -0
- bead/cli/completion.py +359 -0
- bead/cli/config.py +624 -0
- bead/cli/constraint_builders.py +286 -0
- bead/cli/deployment.py +859 -0
- bead/cli/deployment_trials.py +493 -0
- bead/cli/deployment_ui.py +332 -0
- bead/cli/display.py +378 -0
- bead/cli/items.py +960 -0
- bead/cli/items_factories.py +776 -0
- bead/cli/list_constraints.py +714 -0
- bead/cli/lists.py +490 -0
- bead/cli/main.py +430 -0
- bead/cli/models.py +877 -0
- bead/cli/resource_loaders.py +621 -0
- bead/cli/resources.py +1036 -0
- bead/cli/shell.py +356 -0
- bead/cli/simulate.py +840 -0
- bead/cli/templates.py +1158 -0
- bead/cli/training.py +1080 -0
- bead/cli/utils.py +614 -0
- bead/cli/workflow.py +1273 -0
- bead/config/__init__.py +68 -0
- bead/config/active_learning.py +1009 -0
- bead/config/config.py +192 -0
- bead/config/defaults.py +118 -0
- bead/config/deployment.py +217 -0
- bead/config/env.py +147 -0
- bead/config/item.py +45 -0
- bead/config/list.py +193 -0
- bead/config/loader.py +149 -0
- bead/config/logging.py +42 -0
- bead/config/model.py +49 -0
- bead/config/paths.py +46 -0
- bead/config/profiles.py +320 -0
- bead/config/resources.py +47 -0
- bead/config/serialization.py +210 -0
- bead/config/simulation.py +206 -0
- bead/config/template.py +238 -0
- bead/config/validation.py +267 -0
- bead/data/__init__.py +65 -0
- bead/data/base.py +87 -0
- bead/data/identifiers.py +97 -0
- bead/data/language_codes.py +61 -0
- bead/data/metadata.py +270 -0
- bead/data/range.py +123 -0
- bead/data/repository.py +358 -0
- bead/data/serialization.py +249 -0
- bead/data/timestamps.py +89 -0
- bead/data/validation.py +349 -0
- bead/data_collection/__init__.py +11 -0
- bead/data_collection/jatos.py +223 -0
- bead/data_collection/merger.py +154 -0
- bead/data_collection/prolific.py +198 -0
- bead/deployment/__init__.py +5 -0
- bead/deployment/distribution.py +402 -0
- bead/deployment/jatos/__init__.py +1 -0
- bead/deployment/jatos/api.py +200 -0
- bead/deployment/jatos/exporter.py +210 -0
- bead/deployment/jspsych/__init__.py +9 -0
- bead/deployment/jspsych/biome.json +44 -0
- bead/deployment/jspsych/config.py +411 -0
- bead/deployment/jspsych/generator.py +598 -0
- bead/deployment/jspsych/package.json +51 -0
- bead/deployment/jspsych/pnpm-lock.yaml +2141 -0
- bead/deployment/jspsych/randomizer.py +299 -0
- bead/deployment/jspsych/src/lib/list-distributor.test.ts +327 -0
- bead/deployment/jspsych/src/lib/list-distributor.ts +1282 -0
- bead/deployment/jspsych/src/lib/randomizer.test.ts +232 -0
- bead/deployment/jspsych/src/lib/randomizer.ts +367 -0
- bead/deployment/jspsych/src/plugins/cloze-dropdown.ts +252 -0
- bead/deployment/jspsych/src/plugins/forced-choice.ts +265 -0
- bead/deployment/jspsych/src/plugins/plugins.test.ts +141 -0
- bead/deployment/jspsych/src/plugins/rating.ts +248 -0
- bead/deployment/jspsych/src/slopit/index.ts +9 -0
- bead/deployment/jspsych/src/types/jatos.d.ts +256 -0
- bead/deployment/jspsych/src/types/jspsych.d.ts +228 -0
- bead/deployment/jspsych/templates/experiment.css +1 -0
- bead/deployment/jspsych/templates/experiment.js.template +289 -0
- bead/deployment/jspsych/templates/index.html +51 -0
- bead/deployment/jspsych/templates/randomizer.js +241 -0
- bead/deployment/jspsych/templates/randomizer.js.template +313 -0
- bead/deployment/jspsych/trials.py +723 -0
- bead/deployment/jspsych/tsconfig.json +23 -0
- bead/deployment/jspsych/tsup.config.ts +30 -0
- bead/deployment/jspsych/ui/__init__.py +1 -0
- bead/deployment/jspsych/ui/components.py +383 -0
- bead/deployment/jspsych/ui/styles.py +411 -0
- bead/dsl/__init__.py +80 -0
- bead/dsl/ast.py +168 -0
- bead/dsl/context.py +178 -0
- bead/dsl/errors.py +71 -0
- bead/dsl/evaluator.py +570 -0
- bead/dsl/grammar.lark +81 -0
- bead/dsl/parser.py +231 -0
- bead/dsl/stdlib.py +929 -0
- bead/evaluation/__init__.py +13 -0
- bead/evaluation/convergence.py +485 -0
- bead/evaluation/interannotator.py +398 -0
- bead/items/__init__.py +40 -0
- bead/items/adapters/__init__.py +70 -0
- bead/items/adapters/anthropic.py +224 -0
- bead/items/adapters/api_utils.py +167 -0
- bead/items/adapters/base.py +216 -0
- bead/items/adapters/google.py +259 -0
- bead/items/adapters/huggingface.py +1074 -0
- bead/items/adapters/openai.py +323 -0
- bead/items/adapters/registry.py +202 -0
- bead/items/adapters/sentence_transformers.py +224 -0
- bead/items/adapters/togetherai.py +309 -0
- bead/items/binary.py +515 -0
- bead/items/cache.py +558 -0
- bead/items/categorical.py +593 -0
- bead/items/cloze.py +757 -0
- bead/items/constructor.py +784 -0
- bead/items/forced_choice.py +413 -0
- bead/items/free_text.py +681 -0
- bead/items/generation.py +432 -0
- bead/items/item.py +396 -0
- bead/items/item_template.py +787 -0
- bead/items/magnitude.py +573 -0
- bead/items/multi_select.py +621 -0
- bead/items/ordinal_scale.py +569 -0
- bead/items/scoring.py +448 -0
- bead/items/validation.py +723 -0
- bead/lists/__init__.py +30 -0
- bead/lists/balancer.py +263 -0
- bead/lists/constraints.py +1067 -0
- bead/lists/experiment_list.py +286 -0
- bead/lists/list_collection.py +378 -0
- bead/lists/partitioner.py +1141 -0
- bead/lists/stratification.py +254 -0
- bead/participants/__init__.py +73 -0
- bead/participants/collection.py +699 -0
- bead/participants/merging.py +312 -0
- bead/participants/metadata_spec.py +491 -0
- bead/participants/models.py +276 -0
- bead/resources/__init__.py +29 -0
- bead/resources/adapters/__init__.py +19 -0
- bead/resources/adapters/base.py +104 -0
- bead/resources/adapters/cache.py +128 -0
- bead/resources/adapters/glazing.py +508 -0
- bead/resources/adapters/registry.py +117 -0
- bead/resources/adapters/unimorph.py +796 -0
- bead/resources/classification.py +856 -0
- bead/resources/constraint_builders.py +329 -0
- bead/resources/constraints.py +165 -0
- bead/resources/lexical_item.py +223 -0
- bead/resources/lexicon.py +744 -0
- bead/resources/loaders.py +209 -0
- bead/resources/template.py +441 -0
- bead/resources/template_collection.py +707 -0
- bead/resources/template_generation.py +349 -0
- bead/simulation/__init__.py +29 -0
- bead/simulation/annotators/__init__.py +15 -0
- bead/simulation/annotators/base.py +175 -0
- bead/simulation/annotators/distance_based.py +135 -0
- bead/simulation/annotators/lm_based.py +114 -0
- bead/simulation/annotators/oracle.py +182 -0
- bead/simulation/annotators/random.py +181 -0
- bead/simulation/dsl_extension/__init__.py +3 -0
- bead/simulation/noise_models/__init__.py +13 -0
- bead/simulation/noise_models/base.py +42 -0
- bead/simulation/noise_models/random_noise.py +82 -0
- bead/simulation/noise_models/systematic.py +132 -0
- bead/simulation/noise_models/temperature.py +86 -0
- bead/simulation/runner.py +144 -0
- bead/simulation/strategies/__init__.py +23 -0
- bead/simulation/strategies/base.py +123 -0
- bead/simulation/strategies/binary.py +103 -0
- bead/simulation/strategies/categorical.py +123 -0
- bead/simulation/strategies/cloze.py +224 -0
- bead/simulation/strategies/forced_choice.py +127 -0
- bead/simulation/strategies/free_text.py +105 -0
- bead/simulation/strategies/magnitude.py +116 -0
- bead/simulation/strategies/multi_select.py +129 -0
- bead/simulation/strategies/ordinal_scale.py +131 -0
- bead/templates/__init__.py +27 -0
- bead/templates/adapters/__init__.py +17 -0
- bead/templates/adapters/base.py +128 -0
- bead/templates/adapters/cache.py +178 -0
- bead/templates/adapters/huggingface.py +312 -0
- bead/templates/combinatorics.py +103 -0
- bead/templates/filler.py +605 -0
- bead/templates/renderers.py +177 -0
- bead/templates/resolver.py +178 -0
- bead/templates/strategies.py +1806 -0
- bead/templates/streaming.py +195 -0
- bead-0.1.0.dist-info/METADATA +212 -0
- bead-0.1.0.dist-info/RECORD +231 -0
- bead-0.1.0.dist-info/WHEEL +4 -0
- bead-0.1.0.dist-info/entry_points.txt +2 -0
- bead-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
"""Trial configuration commands for jsPsych deployment.
|
|
2
|
+
|
|
3
|
+
This module provides commands for configuring jsPsych trial parameters,
|
|
4
|
+
including timing, response collection, and trial-type-specific settings.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
|
|
18
|
+
from bead.cli.utils import print_error, print_success
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group(name="trials")
|
|
24
|
+
def deployment_trials() -> None:
|
|
25
|
+
r"""Trial configuration commands for jsPsych experiments.
|
|
26
|
+
|
|
27
|
+
Commands for configuring trial parameters, including timing,
|
|
28
|
+
response collection, and trial-type-specific settings.
|
|
29
|
+
|
|
30
|
+
\b
|
|
31
|
+
Examples:
|
|
32
|
+
$ bead deployment trials configure-rating \\
|
|
33
|
+
--min-value 1 --max-value 7 \\
|
|
34
|
+
--min-label "Completely unnatural" \\
|
|
35
|
+
--max-label "Completely natural" \\
|
|
36
|
+
--output rating_config.json
|
|
37
|
+
|
|
38
|
+
$ bead deployment trials configure-choice \\
|
|
39
|
+
--button-html '<button class="choice-btn">%choice%</button>' \\
|
|
40
|
+
--output choice_config.json
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@click.command()
|
|
45
|
+
@click.argument("output_file", type=click.Path(path_type=Path))
|
|
46
|
+
@click.option(
|
|
47
|
+
"--min-value",
|
|
48
|
+
type=int,
|
|
49
|
+
default=1,
|
|
50
|
+
help="Minimum value of the rating scale (default: 1)",
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--max-value",
|
|
54
|
+
type=int,
|
|
55
|
+
default=7,
|
|
56
|
+
help="Maximum value of the rating scale (default: 7)",
|
|
57
|
+
)
|
|
58
|
+
@click.option(
|
|
59
|
+
"--step",
|
|
60
|
+
type=int,
|
|
61
|
+
default=1,
|
|
62
|
+
help="Step size for the scale (default: 1)",
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--min-label",
|
|
66
|
+
default="Strongly Disagree",
|
|
67
|
+
help="Label for minimum value",
|
|
68
|
+
)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--max-label",
|
|
71
|
+
default="Strongly Agree",
|
|
72
|
+
help="Label for maximum value",
|
|
73
|
+
)
|
|
74
|
+
@click.option(
|
|
75
|
+
"--show-numeric-labels",
|
|
76
|
+
is_flag=True,
|
|
77
|
+
help="Show numeric labels on buttons (Likert only)",
|
|
78
|
+
)
|
|
79
|
+
@click.option(
|
|
80
|
+
"--required",
|
|
81
|
+
is_flag=True,
|
|
82
|
+
default=True,
|
|
83
|
+
help="Require response (slider only)",
|
|
84
|
+
)
|
|
85
|
+
@click.pass_context
|
|
86
|
+
def configure_rating(
|
|
87
|
+
ctx: click.Context,
|
|
88
|
+
output_file: Path,
|
|
89
|
+
min_value: int,
|
|
90
|
+
max_value: int,
|
|
91
|
+
step: int,
|
|
92
|
+
min_label: str,
|
|
93
|
+
max_label: str,
|
|
94
|
+
show_numeric_labels: bool,
|
|
95
|
+
required: bool,
|
|
96
|
+
) -> None:
|
|
97
|
+
r"""Configure rating scale trial parameters.
|
|
98
|
+
|
|
99
|
+
Creates a configuration file for rating scale trials (Likert or slider).
|
|
100
|
+
This configuration can be used when generating jsPsych experiments.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
ctx : click.Context
|
|
105
|
+
Click context object.
|
|
106
|
+
output_file : Path
|
|
107
|
+
Output path for configuration JSON file.
|
|
108
|
+
min_value : int
|
|
109
|
+
Minimum value of the rating scale.
|
|
110
|
+
max_value : int
|
|
111
|
+
Maximum value of the rating scale.
|
|
112
|
+
step : int
|
|
113
|
+
Step size for the scale.
|
|
114
|
+
min_label : str
|
|
115
|
+
Label for minimum value.
|
|
116
|
+
max_label : str
|
|
117
|
+
Label for maximum value.
|
|
118
|
+
show_numeric_labels : bool
|
|
119
|
+
Show numeric labels on buttons (Likert only).
|
|
120
|
+
required : bool
|
|
121
|
+
Require response (slider only).
|
|
122
|
+
|
|
123
|
+
Examples
|
|
124
|
+
--------
|
|
125
|
+
$ bead deployment trials configure-rating rating_config.json
|
|
126
|
+
|
|
127
|
+
$ bead deployment trials configure-rating rating_config.json \\
|
|
128
|
+
--min-value 1 --max-value 5 --step 1 \\
|
|
129
|
+
--min-label "Completely unnatural" \\
|
|
130
|
+
--max-label "Completely natural"
|
|
131
|
+
|
|
132
|
+
$ bead deployment trials configure-rating likert_config.json \\
|
|
133
|
+
--min-value 1 --max-value 7 --show-numeric-labels
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
# Validate parameters
|
|
137
|
+
if min_value >= max_value:
|
|
138
|
+
print_error("min-value must be less than max-value")
|
|
139
|
+
ctx.exit(1)
|
|
140
|
+
|
|
141
|
+
if step <= 0:
|
|
142
|
+
print_error("step must be positive")
|
|
143
|
+
ctx.exit(1)
|
|
144
|
+
|
|
145
|
+
if (max_value - min_value) % step != 0:
|
|
146
|
+
print_error(
|
|
147
|
+
f"Range ({max_value - min_value}) must be divisible by step ({step})"
|
|
148
|
+
)
|
|
149
|
+
ctx.exit(1)
|
|
150
|
+
|
|
151
|
+
# Create configuration
|
|
152
|
+
config: dict[str, Any] = {
|
|
153
|
+
"type": "rating_scale",
|
|
154
|
+
"min_value": min_value,
|
|
155
|
+
"max_value": max_value,
|
|
156
|
+
"step": step,
|
|
157
|
+
"min_label": min_label,
|
|
158
|
+
"max_label": max_label,
|
|
159
|
+
"show_numeric_labels": show_numeric_labels,
|
|
160
|
+
"required": required,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# Create output directory if needed
|
|
164
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
|
|
166
|
+
# Write configuration
|
|
167
|
+
output_file.write_text(
|
|
168
|
+
json.dumps(config, indent=2, ensure_ascii=False),
|
|
169
|
+
encoding="utf-8",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
print_success(f"Rating configuration saved: {output_file}")
|
|
173
|
+
|
|
174
|
+
# Show summary table
|
|
175
|
+
table = Table(title="Rating Scale Configuration", show_header=False)
|
|
176
|
+
table.add_column("Parameter", style="cyan")
|
|
177
|
+
table.add_column("Value", style="green")
|
|
178
|
+
|
|
179
|
+
table.add_row("Min Value", str(min_value))
|
|
180
|
+
table.add_row("Max Value", str(max_value))
|
|
181
|
+
table.add_row("Step", str(step))
|
|
182
|
+
table.add_row("Min Label", min_label)
|
|
183
|
+
table.add_row("Max Label", max_label)
|
|
184
|
+
table.add_row("Show Numeric Labels", "Yes" if show_numeric_labels else "No")
|
|
185
|
+
table.add_row("Required", "Yes" if required else "No")
|
|
186
|
+
table.add_row("Scale Points", str((max_value - min_value) // step + 1))
|
|
187
|
+
|
|
188
|
+
console.print(table)
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
print_error(f"Failed to create rating configuration: {e}")
|
|
192
|
+
ctx.exit(1)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@click.command()
|
|
196
|
+
@click.argument("output_file", type=click.Path(path_type=Path))
|
|
197
|
+
@click.option(
|
|
198
|
+
"--button-html",
|
|
199
|
+
default='<button class="jspsych-btn">%choice%</button>',
|
|
200
|
+
help="HTML template for choice buttons (%choice% is replaced with option text)",
|
|
201
|
+
)
|
|
202
|
+
@click.option(
|
|
203
|
+
"--enable-keyboard",
|
|
204
|
+
is_flag=True,
|
|
205
|
+
default=True,
|
|
206
|
+
help="Enable keyboard shortcuts for choices",
|
|
207
|
+
)
|
|
208
|
+
@click.option(
|
|
209
|
+
"--randomize-position",
|
|
210
|
+
is_flag=True,
|
|
211
|
+
default=False,
|
|
212
|
+
help="Randomize position of choices (for forced choice)",
|
|
213
|
+
)
|
|
214
|
+
@click.pass_context
|
|
215
|
+
def configure_choice(
|
|
216
|
+
ctx: click.Context,
|
|
217
|
+
output_file: Path,
|
|
218
|
+
button_html: str,
|
|
219
|
+
enable_keyboard: bool,
|
|
220
|
+
randomize_position: bool,
|
|
221
|
+
) -> None:
|
|
222
|
+
r"""Configure choice trial parameters.
|
|
223
|
+
|
|
224
|
+
Creates a configuration file for choice trials (binary or forced choice).
|
|
225
|
+
This configuration can be used when generating jsPsych experiments.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
ctx : click.Context
|
|
230
|
+
Click context object.
|
|
231
|
+
output_file : Path
|
|
232
|
+
Output path for configuration JSON file.
|
|
233
|
+
button_html : str
|
|
234
|
+
HTML template for choice buttons.
|
|
235
|
+
enable_keyboard : bool
|
|
236
|
+
Enable keyboard shortcuts for choices.
|
|
237
|
+
randomize_position : bool
|
|
238
|
+
Randomize position of choices.
|
|
239
|
+
|
|
240
|
+
Examples
|
|
241
|
+
--------
|
|
242
|
+
$ bead deployment trials configure-choice choice_config.json
|
|
243
|
+
|
|
244
|
+
$ bead deployment trials configure-choice choice_config.json \\
|
|
245
|
+
--button-html '<button class="my-btn">%choice%</button>' \\
|
|
246
|
+
--randomize-position
|
|
247
|
+
|
|
248
|
+
$ bead deployment trials configure-choice binary_config.json \\
|
|
249
|
+
--button-html '<button class="yes-no-btn">%choice%</button>' \\
|
|
250
|
+
--enable-keyboard
|
|
251
|
+
"""
|
|
252
|
+
try:
|
|
253
|
+
# Validate button HTML
|
|
254
|
+
if "%choice%" not in button_html:
|
|
255
|
+
print_error("button-html must contain %choice% placeholder")
|
|
256
|
+
ctx.exit(1)
|
|
257
|
+
|
|
258
|
+
# Create configuration
|
|
259
|
+
config: dict[str, Any] = {
|
|
260
|
+
"type": "choice",
|
|
261
|
+
"button_html": button_html,
|
|
262
|
+
"enable_keyboard": enable_keyboard,
|
|
263
|
+
"randomize_position": randomize_position,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Create output directory if needed
|
|
267
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
268
|
+
|
|
269
|
+
# Write configuration
|
|
270
|
+
output_file.write_text(
|
|
271
|
+
json.dumps(config, indent=2, ensure_ascii=False),
|
|
272
|
+
encoding="utf-8",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
print_success(f"Choice configuration saved: {output_file}")
|
|
276
|
+
|
|
277
|
+
# Show summary
|
|
278
|
+
kb_status = "Enabled" if enable_keyboard else "Disabled"
|
|
279
|
+
rand_status = "Yes" if randomize_position else "No"
|
|
280
|
+
summary_panel = Panel(
|
|
281
|
+
f"[cyan]Button HTML:[/cyan]\n{button_html}\n\n"
|
|
282
|
+
f"[cyan]Keyboard Shortcuts:[/cyan] {kb_status}\n"
|
|
283
|
+
f"[cyan]Randomize Position:[/cyan] {rand_status}",
|
|
284
|
+
title="[bold]Choice Configuration Summary[/bold]",
|
|
285
|
+
border_style="green",
|
|
286
|
+
)
|
|
287
|
+
console.print(summary_panel)
|
|
288
|
+
|
|
289
|
+
except Exception as e:
|
|
290
|
+
print_error(f"Failed to create choice configuration: {e}")
|
|
291
|
+
ctx.exit(1)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@click.command()
|
|
295
|
+
@click.argument("output_file", type=click.Path(path_type=Path))
|
|
296
|
+
@click.option(
|
|
297
|
+
"--duration-ms",
|
|
298
|
+
type=int,
|
|
299
|
+
help="Duration each chunk is displayed (milliseconds)",
|
|
300
|
+
)
|
|
301
|
+
@click.option(
|
|
302
|
+
"--isi-ms",
|
|
303
|
+
type=int,
|
|
304
|
+
default=0,
|
|
305
|
+
help="Inter-stimulus interval between chunks (milliseconds, default: 0)",
|
|
306
|
+
)
|
|
307
|
+
@click.option(
|
|
308
|
+
"--timeout-ms",
|
|
309
|
+
type=int,
|
|
310
|
+
help="Maximum time to wait for response (milliseconds, optional)",
|
|
311
|
+
)
|
|
312
|
+
@click.option(
|
|
313
|
+
"--mask-char",
|
|
314
|
+
default="",
|
|
315
|
+
help="Character to use for masking hidden text (e.g., '#')",
|
|
316
|
+
)
|
|
317
|
+
@click.option(
|
|
318
|
+
"--cumulative",
|
|
319
|
+
is_flag=True,
|
|
320
|
+
default=False,
|
|
321
|
+
help="Display chunks cumulatively (each chunk remains visible)",
|
|
322
|
+
)
|
|
323
|
+
@click.pass_context
|
|
324
|
+
def configure_timing(
|
|
325
|
+
ctx: click.Context,
|
|
326
|
+
output_file: Path,
|
|
327
|
+
duration_ms: int | None,
|
|
328
|
+
isi_ms: int,
|
|
329
|
+
timeout_ms: int | None,
|
|
330
|
+
mask_char: str,
|
|
331
|
+
cumulative: bool,
|
|
332
|
+
) -> None:
|
|
333
|
+
r"""Configure timing parameters for trials.
|
|
334
|
+
|
|
335
|
+
Creates a configuration file for presentation timing (e.g., self-paced
|
|
336
|
+
reading, RSVP). This configuration can be used when generating jsPsych
|
|
337
|
+
experiments with timed presentation.
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
ctx : click.Context
|
|
342
|
+
Click context object.
|
|
343
|
+
output_file : Path
|
|
344
|
+
Output path for configuration JSON file.
|
|
345
|
+
duration_ms : int | None
|
|
346
|
+
Duration each chunk is displayed (milliseconds).
|
|
347
|
+
isi_ms : int
|
|
348
|
+
Inter-stimulus interval between chunks (milliseconds).
|
|
349
|
+
timeout_ms : int | None
|
|
350
|
+
Maximum time to wait for response (milliseconds).
|
|
351
|
+
mask_char : str
|
|
352
|
+
Character to use for masking hidden text.
|
|
353
|
+
cumulative : bool
|
|
354
|
+
Display chunks cumulatively.
|
|
355
|
+
|
|
356
|
+
Examples
|
|
357
|
+
--------
|
|
358
|
+
$ bead deployment trials configure-timing timing_config.json \\
|
|
359
|
+
--duration-ms 500 --isi-ms 100
|
|
360
|
+
|
|
361
|
+
$ bead deployment trials configure-timing rsvp_config.json \\
|
|
362
|
+
--duration-ms 300 --isi-ms 50 --mask-char "#"
|
|
363
|
+
|
|
364
|
+
$ bead deployment trials configure-timing spr_config.json \\
|
|
365
|
+
--isi-ms 0 --cumulative --mask-char "#"
|
|
366
|
+
"""
|
|
367
|
+
try:
|
|
368
|
+
# Validate parameters
|
|
369
|
+
if isi_ms < 0:
|
|
370
|
+
print_error("isi-ms must be non-negative")
|
|
371
|
+
ctx.exit(1)
|
|
372
|
+
|
|
373
|
+
if duration_ms is not None and duration_ms <= 0:
|
|
374
|
+
print_error("duration-ms must be positive")
|
|
375
|
+
ctx.exit(1)
|
|
376
|
+
|
|
377
|
+
if timeout_ms is not None and timeout_ms <= 0:
|
|
378
|
+
print_error("timeout-ms must be positive")
|
|
379
|
+
ctx.exit(1)
|
|
380
|
+
|
|
381
|
+
# Create configuration
|
|
382
|
+
config: dict[str, Any] = {
|
|
383
|
+
"type": "timing",
|
|
384
|
+
"isi_ms": isi_ms,
|
|
385
|
+
"cumulative": cumulative,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if duration_ms is not None:
|
|
389
|
+
config["duration_ms"] = duration_ms
|
|
390
|
+
|
|
391
|
+
if timeout_ms is not None:
|
|
392
|
+
config["timeout_ms"] = timeout_ms
|
|
393
|
+
|
|
394
|
+
if mask_char:
|
|
395
|
+
config["mask_char"] = mask_char
|
|
396
|
+
|
|
397
|
+
# Create output directory if needed
|
|
398
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
399
|
+
|
|
400
|
+
# Write configuration
|
|
401
|
+
output_file.write_text(
|
|
402
|
+
json.dumps(config, indent=2, ensure_ascii=False),
|
|
403
|
+
encoding="utf-8",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
print_success(f"Timing configuration saved: {output_file}")
|
|
407
|
+
|
|
408
|
+
# Show summary table
|
|
409
|
+
table = Table(title="Timing Configuration", show_header=False)
|
|
410
|
+
table.add_column("Parameter", style="cyan")
|
|
411
|
+
table.add_column("Value", style="green")
|
|
412
|
+
|
|
413
|
+
if duration_ms is not None:
|
|
414
|
+
table.add_row("Duration", f"{duration_ms} ms")
|
|
415
|
+
else:
|
|
416
|
+
table.add_row("Duration", "Self-paced")
|
|
417
|
+
|
|
418
|
+
table.add_row("ISI", f"{isi_ms} ms")
|
|
419
|
+
|
|
420
|
+
if timeout_ms is not None:
|
|
421
|
+
table.add_row("Timeout", f"{timeout_ms} ms")
|
|
422
|
+
|
|
423
|
+
if mask_char:
|
|
424
|
+
table.add_row("Mask Character", f"'{mask_char}'")
|
|
425
|
+
|
|
426
|
+
table.add_row("Cumulative", "Yes" if cumulative else "No")
|
|
427
|
+
|
|
428
|
+
console.print(table)
|
|
429
|
+
|
|
430
|
+
except Exception as e:
|
|
431
|
+
print_error(f"Failed to create timing configuration: {e}")
|
|
432
|
+
ctx.exit(1)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@click.command()
|
|
436
|
+
@click.argument("config_files", nargs=-1, type=click.Path(exists=True, path_type=Path))
|
|
437
|
+
@click.pass_context
|
|
438
|
+
def show_config(ctx: click.Context, config_files: tuple[Path, ...]) -> None:
|
|
439
|
+
"""Display trial configuration files.
|
|
440
|
+
|
|
441
|
+
Shows the contents of one or more trial configuration JSON files
|
|
442
|
+
in a formatted table.
|
|
443
|
+
|
|
444
|
+
Parameters
|
|
445
|
+
----------
|
|
446
|
+
ctx : click.Context
|
|
447
|
+
Click context object.
|
|
448
|
+
config_files : tuple[Path, ...]
|
|
449
|
+
Paths to configuration files to display.
|
|
450
|
+
|
|
451
|
+
Examples
|
|
452
|
+
--------
|
|
453
|
+
$ bead deployment trials show-config rating_config.json
|
|
454
|
+
|
|
455
|
+
$ bead deployment trials show-config rating_config.json choice_config.json
|
|
456
|
+
"""
|
|
457
|
+
try:
|
|
458
|
+
if not config_files:
|
|
459
|
+
print_error("No configuration files specified")
|
|
460
|
+
ctx.exit(1)
|
|
461
|
+
|
|
462
|
+
for config_file in config_files:
|
|
463
|
+
# Load configuration
|
|
464
|
+
config = json.loads(config_file.read_text(encoding="utf-8"))
|
|
465
|
+
|
|
466
|
+
# Create table
|
|
467
|
+
table = Table(
|
|
468
|
+
title=f"Configuration: {config_file.name}",
|
|
469
|
+
show_header=False,
|
|
470
|
+
)
|
|
471
|
+
table.add_column("Parameter", style="cyan")
|
|
472
|
+
table.add_column("Value", style="green")
|
|
473
|
+
|
|
474
|
+
# Display all key-value pairs
|
|
475
|
+
for key, value in sorted(config.items()):
|
|
476
|
+
table.add_row(key, str(value))
|
|
477
|
+
|
|
478
|
+
console.print(table)
|
|
479
|
+
console.print() # Blank line between tables
|
|
480
|
+
|
|
481
|
+
except json.JSONDecodeError as e:
|
|
482
|
+
print_error(f"Invalid JSON in configuration file: {e}")
|
|
483
|
+
ctx.exit(1)
|
|
484
|
+
except Exception as e:
|
|
485
|
+
print_error(f"Failed to show configuration: {e}")
|
|
486
|
+
ctx.exit(1)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# Register commands
|
|
490
|
+
deployment_trials.add_command(configure_rating)
|
|
491
|
+
deployment_trials.add_command(configure_choice)
|
|
492
|
+
deployment_trials.add_command(configure_timing)
|
|
493
|
+
deployment_trials.add_command(show_config)
|