Repository: psychopy/psychopy

Issue:
afterimage in variable-based setting of image components in loops - possible bug in exported PsychoJS Code from Builder
Hi, 
I often encounter afterimages from previous trials when variables (e.g. from an excel) are used to set a image stimulus.

To understand the problem I have constructed an example case:

Present two different images based on values from an Excel (green.png/red.png) in a loop with a small ITI. Furthermore, there might be images changes during the trial (so the image component path is set to "Each Frame" and a variable called $imagePath is used the image component path field). 

This method leads to the problem that the image component is drawn for a single frame before the new image is set:
https://run.pavlovia.org/luke.boelling/afterimagepoc/ (First Trial: Default.png is shown for one frame, then you see the green/red each time for exactly one frame. 

In the second loop the image component is initiliazed with constant with a placeholder PNG and then set via setImage through code (Begin Routine). This works as it should. 

In the Python Export of the Builder there is no apparent difference in both approaches. 

See the project here:
https://gitlab.pavlovia.org/luke.boelling/afterimagepoc/

I think the export order is here the problem:
The autodraw=True should be set after the image is updated:
![image](https://github.com/psychopy/psychopy/assets/8046474/57d0879e-40d3-46fe-adb9-6a0d3b872457)

Same behaviour is shown for the code-based approach if the code is added not in the "Begin Routine" but in "Each Frame":
![image](https://github.com/psychopy/psychopy/assets/8046474/5fa24ded-6272-4627-9d6d-25c7fed6aaf2)

The issue seems to be related to the chosen order of the script generator. The builder should make sure that everything is set before the autoDraw code. Furthermore I noticed similiar issues for e.g. positions of polygons.

Two proposals: 
1. Everything draw related should be managed after all stimulus variables are set. 
2. I would argue that "Each Frame" code output should be placed directly before    " // update/draw components on each frame" to allow stimulus manipulation before any drawing in the frame. 

This change gives the intended result:
![image](https://github.com/psychopy/psychopy/assets/8046474/18151dd6-14f2-48c2-b34e-602ebedc506e)


I am still wondering if the current output leads to some "one-frame-off" situation if I plan to change my stimulus in every frame of the sitmulus...

I think the problem is just related to the PsychoJS generation and assuming PsychoJS works here as intended.
I clearly see how to avoid the afterImage situation but I think the described approach (variables in excel) is the preferred way for beginners and the script should lead to the expected behaviour. 



Best,
Luke




Discussion:
The weird thing about this is that, in theory, it shouldn't matter what order they're set in... `setAutoDraw` shouldn't actually draw the stimulus, it just flags the stimulus as needing to be drawn on next frame flip. My guess is that this is essentially the time taken to set the image running past the first flip, and because JS isn't linear this set method doesn't pause the frame flip.

So the order you're getting is:
- Tell the image to draw when frame flips
- Set image value
- Frame flips (draws image with default.png)
- Finished setting image value
- Frame flips again (draws image with correct value)

...and in the fixed code:
- Set the image value
- Frame flips (doesn't draw the image)
- Finished setting image value
- Tell the image to draw when frame flips
- Frame flips again (draws image with correct value)

I'm happy to change this in the code generation, you're right that it makes sense to set auto draw after setting values anyway and I don't think doing it this way will break anything else (famous last words...)

Resolving patch:
diff --git a/psychopy/experiment/components/_base.py b/psychopy/experiment/components/_base.py
--- a/psychopy/experiment/components/_base.py
+++ b/psychopy/experiment/components/_base.py
@@ -1112,6 +1112,16 @@ def writeFrameCodeJS(self, buff):
             buff.writeIndented(f"// *{params['name']}* not supported by PsychoJS\n")
             return
 
+        # set parameters that need updating every frame
+        # do any params need updating? (this method inherited from _base)
+        if self.checkNeedToUpdate('set every frame'):
+            buff.writeIndentedLines(f"\nif ({params['name']}.status === PsychoJS.Status.STARTED){{ "
+                                    f"// only update if being drawn\n")
+            buff.setIndentLevel(+1, relative=True)  # to enter the if block
+            self.writeParamUpdatesJS(buff, 'set every frame')
+            buff.setIndentLevel(-1, relative=True)  # to exit the if block
+            buff.writeIndented("}\n")
+
         buff.writeIndentedLines(f"\n// *{params['name']}* updates\n")
         # writes an if statement to determine whether to draw etc
         self.writeStartTestCodeJS(buff)
@@ -1129,16 +1139,6 @@ def writeFrameCodeJS(self, buff):
             buff.setIndentLevel(-1, relative=True)
             buff.writeIndented("}\n")
 
-        # set parameters that need updating every frame
-        # do any params need updating? (this method inherited from _base)
-        if self.checkNeedToUpdate('set every frame'):
-            buff.writeIndentedLines(f"\nif ({params['name']}.status === PsychoJS.Status.STARTED){{ "
-                                    f"// only update if being drawn\n")
-            buff.setIndentLevel(+1, relative=True)  # to enter the if block
-            self.writeParamUpdatesJS(buff, 'set every frame')
-            buff.setIndentLevel(-1, relative=True)  # to exit the if block
-            buff.writeIndented("}\n")
-
 
 def canBeNumeric(inStr):
     """Determines whether the input can be converted to a float


Test patch: