New Contributors: Required "Experiment info" fields #4412

Hi, my partner and I would like to contribute to Psychopy. We’ve been looking into this issue: Required "Experiment info" fields · Issue #4412 · psychopy/psychopy · GitHub. There are many files to look through, but we think this enhancement can be made by making changes to init.py in the settings folder. (psychopy → experiment → components → settings → init.py).

We are thinking of making changes inside the getInfo() function on line 630 since it stores the experimental information as a dictionary. We will iterate through the dictionary and for every key we will check if the last char is *, and if the key does end with * then we will check if its value is empty. If the value is empty (assuming the key ends with *) then we will exit the code here and prevent the participant from starting the experiment until they input something into all of the required fields.

Also, we found that this isn’t an issue for online experiments because the core-3.0.7.js file checks for . The code has:
If (key.slice(-1) == ‘
’)
self.requiredKeys.push(key)

Where does “self.requiredKeys.push(key)” come from? I cannot find it in the psychopy files I cloned from the release branch. I was wondering if it is something we can use in our implementation?

Does this seem like a good solution or is there a different file or function we should think about modifying instead?

Thank you!

1 Like

Hi,

Welcome aboard. That would be great if you could address this, as it has been a long-standing minor inconsistency between the local and online experiment process. From a coding point of view, this would be an excellent first issue to pick up as the coding changes will be relatively simple, but there will be a learning curve in figuring out exactly where make the changes. Interface-related code can be more complex to work with than elsewhere in the PsychoPy library (e.g. in stimulus-related modules), because there can be a need to understand a web of relationships rather than just leap into editing a single, self-contained function.

I might be off-base here, but I think the experiment → components → settings → init.py file you are pointing to is for the experiment settings dialog that appears when you click the “Experiment settings” button in the Builder toolbar. i.e. this a dialog that you only use when creating an experiment, not when you are running an experiment. I think the Github issue is referring to the experiment info dialog box that appears at runtime (i.e. immediately before each run of the experiment).

This is generated from the gui module:

If you look at a Builder-generated script, you’ll see a line like this:

dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)

That is the function that instantiates the experiment info dialog box, based on a dictionary of settings. The keys and default values for that dictionary are indeed defined earlier, within the “Experiment settings” dialog, but that is a one-off process, whereas the experiment info dialog is shown on every run of the experiment.

I think what you want to do is intercept the code that would otherwise immediately close the dialog box when “OK” is pushed. Now I don’t really understand much of this UI code, but I suspect the best place to intercept the “OK” button would be in this function (for a qt interface at least):

def exec_(self):

Note that there seem to be two versions of the Python UI code, for the qt and wx approaches. Again, I can’t offer much guidance on which is actually used (maybe one is redundant these days?), but you might need to make similar changes in each version (i.e. in wxgui.py and in qtgui.py). These two GUI approaches seem to work differently and perhaps this would be the equivalent relevant function in wxgui.py:

We are thinking of making changes inside the getInfo() function on line 630 since it stores the experimental information as a dictionary. We will iterate through the dictionary and for every key we will check if the last char is *, and if the key does end with * then we will check if its value is empty. If the value is empty (assuming the key ends with *) then we will exit the code here and prevent the participant from starting the experiment until they input something into all of the required fields.

If I’m right, that isn’t the place to enforce mandatory fields, as this won’t impact what happens at runtime. i.e. in the “Experiment settings” dialog, you might want to define a field that is compulsory, but leave the corresponding value field blank, precisely because you want to enforce the user to enter their own value in that field. However, it would still be a good idea to alter things in the construction of the “Experiment settings” dialog, if only to put a note in the interface explaining the convention that an “Experiment info” field name that ends in “*” will correspond to a compulsory field at runtime.

Note that if you patch the behaviour of the gui.DlgFromDict, that would impact on all use of the function, not just in the experiment settings dialog that Builder shows (i.e. some people might create a dialog in their own code, for purposes other than getting experiment-related info). Although the change being proposed is highly unlikely to break such code, the documentation for that function will also need to be updated to reflect that the “OK” button won’t work if the dialog contains empty fields corresponding to labels ending in “*”. Realistically, that Builder-inspired behaviour would probably be a useful thing to make universal - I can’t see there being any pushback on it.

Not sure if this actually helps or if I’m sending you in the wrong direction, but good luck.

1 Like

Hi,

Also note in the banner at the top that we have another free Contributor Workshop set for next week on 26 and 28 April (Wednesday and Friday) in the afternoon, both days (2-5 pm, UK time). I’m not sure if this would help, but you’re welcome to attend if the time is convenient for you. The link is in the banner, but I’ve included it here too.

1 Like

Hi Michael,

Thanks a lot for your detailed response, it helped me get on the right track and pointed me to files I hadn’t considered!

I’m still a little confused on some parts and was wondering if you or anyone else had any clarification on them.

  1. there’s two different dlg classes in the qtgui file. What do they both represent, and which one should I be using to manipulate the experiment info?
  2. Is self.dictionary the expInfo (as in will it have the experiment information such as [“participant*” : 1, “session*” : 0]? Is that the updated version (I.e, it gets updated after receiving user input?). also, I believe things only get updated when the user presses “OK.” Which function, if any, keeps track of user input before either pressing OK or cancel?

Thanks again for your help, it’s really appreciated!

The class Dlg provides a window for gathering input. The window is blank and you have to populate it by adding individual text fields and input fields.

The class DlgFromDict takes out some of the legwork, by automatically creating the fields and labels simply by providing a dictionary of information that describes what you want to see.

These aren’t completely separate classes. Notice the class definition for DlgFromDict:

class DlgFromDict(Dlg):

ie this class is a subclass of Dlg, or equivalently is said to inherit from Dlg. So a DlgFromDict can do everything a Dlg can do (ie it inherits the same functions and properties), but can do a little extra (ie it has the ability to populate itself from a dictionary). This is a common pattern in object-oriented programming: define a base class with some core functionality, and then create sub-classes that can go further, without having to reinvent the wheel by duplicating functions and properties. And if you improve or add to the functions in the base class, all of its subclasses inherit those improvements automatically. This makes the code much more maintainable.

PsychoPy makes extensive use of sub-classing – eg a lot of stimuli in the visual module inherit from simpler, more generic stimuli. eg the Polygon, Rect, Pie, etc classes all inherit from BaseShapeStim, which defines some core functionality that all such stimuli should share (such as setting a colour or line width), and BaseShapeStim itself inherits from an even more generic BaseVisualStim, and so on.

That dialog could be passed any dictionary at all. However, in a Builder script, the call is this:

dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)

So in a Builder script, yes, it is the expInfo dictionary that is used. Without checking, yes I guess the pointer of this class is to update the passed dictionary after the OK button is clicked.

It’s quite possible that no PsychoPy function at all is handling user input at that stage. My suspicion is that is all passed over to the operating system to handle, and control only returns to PsychoPy when the “OK” button press event is received. So that is probably the point where you need to intercept the code and prevent th3 window closing, based on your requirements.

1 Like