1+ """Stimulus registry with lazy instantiation.
2+
3+ Supports decorator-based and YAML/dict-based stimulus definitions, batch
4+ preview, text formatting, and text-to-speech conversion via edge-tts.
5+ """
6+
17from psychopy .visual import TextStim , Circle , Rect , Polygon , ImageStim , ShapeStim , TextBox2 , MovieStim
28from psychopy import event , core
39
@@ -72,7 +78,7 @@ def decorator(func: Callable[[Any], Any]):
7278 return func
7379 return decorator
7480
75- def preload_all (self ):
81+ def preload_all (self ) -> "StimBank" :
7682 """Instantiate all registered stimuli.
7783
7884 Returns
@@ -214,7 +220,7 @@ def get_selected(self, keys: list[str]) -> Dict[str, Any]:
214220 """
215221 return {k : self .get (k ) for k in keys }
216222
217- def preview_all (self , wait_keys : bool = True ):
223+ def preview_all (self , wait_keys : bool = True ) -> None :
218224 """
219225 Preview all registered stimuli one by one.
220226
@@ -227,7 +233,7 @@ def preview_all(self, wait_keys: bool = True):
227233 for i , name in enumerate (keys ):
228234 self ._preview (name , wait_keys = wait_keys )
229235
230- def preview_group (self , prefix : str , wait_keys : bool = True ):
236+ def preview_group (self , prefix : str , wait_keys : bool = True ) -> None :
231237 """
232238 Preview all stimuli that match a name prefix.
233239
@@ -244,7 +250,7 @@ def preview_group(self, prefix: str, wait_keys: bool = True):
244250 for i , name in enumerate (matches ):
245251 self ._preview (name , wait_keys = (i == len (matches ) - 1 ))
246252
247- def preview_selected (self , keys : list [str ], wait_keys : bool = True ):
253+ def preview_selected (self , keys : list [str ], wait_keys : bool = True ) -> None :
248254 """
249255 Preview selected stimuli by name.
250256
@@ -258,29 +264,7 @@ def preview_selected(self, keys: list[str], wait_keys: bool = True):
258264 for i , name in enumerate (keys ):
259265 self ._preview (name , wait_keys = (i == len (keys ) - 1 ))
260266
261- # def _preview(self, name: str, wait_keys: bool = True):
262- # """
263- # Internal utility to preview a single stimulus.
264-
265- # Parameters
266- # ----------
267- # name : str
268- # Stimulus name.
269- # wait_keys : bool
270- # Wait for key press after preview.
271- # """
272- # try:
273- # stim = self.get(name)
274- # self.win.flip(clearBuffer=True)
275- # stim.draw()
276- # self.win.flip()
277- # print(f"Preview: '{name}'")
278- # if wait_keys:
279- # event.waitKeys()
280- # except Exception as e:
281- # print(f"[Preview Error] Could not preview '{name}': {e}")
282-
283- def _preview (self , name : str , wait_keys : bool = True ):
267+ def _preview (self , name : str , wait_keys : bool = True ) -> None :
284268 """
285269 Internal utility to preview a single stimulus (image or sound).
286270
@@ -337,7 +321,7 @@ def has(self, name: str) -> bool:
337321 """
338322 return name in self ._registry
339323
340- def describe (self , name : str ):
324+ def describe (self , name : str ) -> None :
341325 """
342326 Print accepted arguments for a registered stimulus.
343327
@@ -370,7 +354,7 @@ def describe(self, name: str):
370354 default = "required" if v .default is inspect .Parameter .empty else f"default={ v .default !r} "
371355 print (f" - { k } : { default } " )
372356
373- def export_to_yaml (self , path : str ):
357+ def export_to_yaml (self , path : str ) -> None :
374358 """
375359 Export YAML-defined stimuli (but not decorator-defined) to file.
376360
@@ -382,6 +366,8 @@ def export_to_yaml(self, path: str):
382366 yaml_defs = {}
383367 for name , factory in self ._registry .items ():
384368 try :
369+ # Factories created by add_from_dict() capture their source
370+ # dict in a closure. Inspect it to recover the original spec.
385371 source = factory .__closure__ [0 ].cell_contents
386372 if not isinstance (source , dict ):
387373 continue
@@ -393,7 +379,7 @@ def export_to_yaml(self, path: str):
393379 yaml .dump (yaml_defs , f )
394380 print (f"[OK] Exported { len (yaml_defs )} YAML stimuli to { path } " )
395381
396- def make_factory (self , cls , base_kwargs : dict , name : str ):
382+ def make_factory (self , cls : type , base_kwargs : dict , name : str ) -> Callable :
397383 """
398384 Create a factory function for a given stimulus class.
399385
@@ -426,7 +412,7 @@ def _factory(win, **override_kwargs):
426412 raise ValueError (f"[StimBank] Failed to build '{ name } ': { e } " )
427413 return _factory
428414
429- def add_from_dict (self , named_specs : Optional [dict ] = None , ** kwargs ):
415+ def add_from_dict (self , named_specs : Optional [dict ] = None , ** kwargs ) -> "StimBank" :
430416 """
431417 Add stimuli from a dictionary or keyword-based specifications.
432418
@@ -452,7 +438,7 @@ def add_from_dict(self, named_specs: Optional[dict] = None, **kwargs):
452438 self ._registry [name ] = self .make_factory (stim_class , kwargs , name )
453439 return self
454440
455- def validate_dict (self , config : dict , strict : bool = False ):
441+ def validate_dict (self , config : dict , strict : bool = False ) -> None :
456442 """
457443 Validate a dictionary of stimulus definitions.
458444
@@ -506,7 +492,7 @@ def validate_dict(self, config: dict, strict: bool = False):
506492 def convert_to_voice (self ,
507493 keys : list [str ] | str ,
508494 overwrite : bool = False ,
509- voice : str = "zh-CN-YunyangNeural" ):
495+ voice : str = "zh-CN-YunyangNeural" ) -> "StimBank" :
510496 """
511497 Convert specified TextStim/TextBox2 stimuli to speech (MP3) and register them
512498 as new Sound stimuli in this StimBank.
@@ -578,7 +564,7 @@ def add_voice(self,
578564 stim_label : str ,
579565 text : str ,
580566 overwrite : bool = False ,
581- voice : str = "zh-CN-XiaoxiaoNeural" ):
567+ voice : str = "zh-CN-XiaoxiaoNeural" ) -> "StimBank" :
582568 """
583569 Convert arbitrary text to speech (MP3) and register it as a new Sound stimulus.
584570
0 commit comments