From b9b2727a2a6cfece573c0fb54598a72df209f4e9 Mon Sep 17 00:00:00 2001 From: Alexandros Evangelou Date: Fri, 8 May 2026 18:10:39 +0300 Subject: [PATCH 1/2] Fix Panda subclasses being saved as 'Panda' in stimulus_class Replace the hardcoded name() override on Panda/OpenfieldPanda (which returned the literal 'Panda' for every subclass) with a class-attribute flag for ShowBase mix-in tracking. The new init() preserves the original class name, so the base Stimulus.name() returns the correct subclass name (e.g. TonesPanda) automatically. Also fixes typos in tones_panda.py default_key (defalut_key, tone_ulse_freq, tone_pcolor, light_volume) and adds 'Tones' to cond_tables so tone parameters are saved completely. --- stimuli/openfield_panda.py | 11 ++++------- stimuli/panda.py | 11 ++++------- stimuli/tones_panda.py | 12 ++++-------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/stimuli/openfield_panda.py b/stimuli/openfield_panda.py index 62aaf7f..4cb7e7d 100644 --- a/stimuli/openfield_panda.py +++ b/stimuli/openfield_panda.py @@ -137,10 +137,10 @@ def init(self, exp): self.object_files, self.is_recording = dict(), False cls = self.__class__ - if "ShowBase" not in cls.__name__: - self.__class__ = cls.__class__( - cls.__name__ + "ShowBase", (cls, ShowBase), {} - ) + if not getattr(cls, "_showbase_mixed", False): + new_cls = cls.__class__(cls.__name__, (cls, ShowBase), {}) + new_cls._showbase_mixed = True + self.__class__ = new_cls if self.logger.is_pi: self.fStartDirect = True self.windowType = None @@ -168,9 +168,6 @@ def init(self, exp): self.fill_colors.background_color = (0, 0, 0) self.sm = SharedMemory("pose") - def name(self): - return "Panda" - def setup(self): ShowBase.__init__( self, fStartDirect=self.fStartDirect, windowType=self.windowType diff --git a/stimuli/panda.py b/stimuli/panda.py index 34ac719..4fb0b2a 100644 --- a/stimuli/panda.py +++ b/stimuli/panda.py @@ -151,10 +151,10 @@ def init(self, exp): self.object_files, self.is_recording = dict(), False cls = self.__class__ - if "ShowBase" not in cls.__name__: - self.__class__ = cls.__class__( - cls.__name__ + "ShowBase", (cls, ShowBase), {} - ) + if not getattr(cls, "_showbase_mixed", False): + new_cls = cls.__class__(cls.__name__, (cls, ShowBase), {}) + new_cls._showbase_mixed = True + self.__class__ = new_cls if self.logger.is_pi: self.fStartDirect = True self.windowType = None @@ -181,9 +181,6 @@ def init(self, exp): self.fill_colors.background_color = (0, 0, 0) - def name(self): - return "Panda" - def setup(self): ShowBase.__init__( self, fStartDirect=self.fStartDirect, windowType=self.windowType diff --git a/stimuli/tones_panda.py b/stimuli/tones_panda.py index cefe417..cddf557 100644 --- a/stimuli/tones_panda.py +++ b/stimuli/tones_panda.py @@ -7,18 +7,15 @@ class TonesPanda(Panda): def __init__(self): super().__init__() - # self.sound_in_operation = False - - """ This class handles the presentation of Objects (Panda) and tone stimuli""" - self.cond_tables = ['Tones','Panda', 'Panda.Object', 'Panda.Environment', 'Panda.Light', 'Panda.Movie'] - self.required_fields = ['tone_duration', 'tone_frequency','obj_id', 'obj_dur'] + self.cond_tables = ['Tones', 'Panda', 'Panda.Object', 'Panda.Environment', 'Panda.Light', 'Panda.Movie'] + self.required_fields = ['tone_duration', 'tone_frequency', 'obj_id', 'obj_dur'] self.default_key = { 'background_color' : (0, 0, 0), 'ambient_color' : (0.1, 0.1, 0.1, 1), 'light_idx' : (1, 2), 'tone_pulse_freq' : 0, - 'light_volume' : 50, - 'tone_pcolor' : (np.array([0.7, 0.7, 0.7, 1]), np.array([0.2, 0.2, 0.2, 1])), + 'tone_volume' : 50, + 'light_color' : (np.array([0.7, 0.7, 0.7, 1]), np.array([0.2, 0.2, 0.2, 1])), 'light_dir' : (np.array([0, -20, 0]), np.array([180, -20, 0])), 'obj_pos_x' : 0, 'obj_pos_y' : 0, @@ -31,7 +28,6 @@ def __init__(self): 'perspective' : 0 } - def start(self): tone_frequency = self.curr_cond['tone_frequency'] tone_volume = self.curr_cond['tone_volume'] From 4c0c796cda66926d88dcb1f608bf2016627a069d Mon Sep 17 00:00:00 2001 From: Alexandros Evangelou Date: Mon, 11 May 2026 14:40:57 +0300 Subject: [PATCH 2/2] docs: explain lazy ShowBase mixin in Panda init Add an inline comment describing why init() rebinds self.__class__ to a (cls, ShowBase) subclass at runtime, and why the _showbase_mixed flag guards against re-wrapping on repeated init() calls. --- stimuli/panda.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stimuli/panda.py b/stimuli/panda.py index 4fb0b2a..f8439fb 100644 --- a/stimuli/panda.py +++ b/stimuli/panda.py @@ -150,6 +150,9 @@ def init(self, exp): super().init(exp) self.object_files, self.is_recording = dict(), False + # Lazily mix ShowBase into this instance's class so the Panda3D window + # only opens when the stimulus runs. Flag-guarded with _showbase_mixed + # to avoid re-wrapping on repeated init() calls. cls = self.__class__ if not getattr(cls, "_showbase_mixed", False): new_cls = cls.__class__(cls.__name__, (cls, ShowBase), {})