Creating pre-screening tasks

Warning

This tutorial needs updating to PsyNet v10

Have a look at the below examples and add a new class specifying your new pre-screening task in the file psynet/prescreen.py.

A simple pre-screening task

In general, a pre-screening task is a Module which contains some conditional logic for determining the participant’s suitability for an experiment.

In the following we show an example of a pre-screening task that consists of a single Yes/No question checking for the participant’s suitability for a follow-up hearing test.

The HearingImpairmentCheck class inherits from Module and defines the actual pre-screening. It has a single event (Module) assigned to its events property which consists of a label, a Page (ModularPage) for the participant’s input and the logic (conditional) to determine a positive or negative outcome. In the negative case the UnsuccessfulEndPage is shown and the participant exits the pre-screening. This class also needs to be provided with a value for label.

import psynet.experiment
from psynet.modular_page import ModularPage, PushButtonControl
from psynet.page import InfoPage, Prompt, SuccessfulEndPage, UnsuccessfulEndPage
from psynet.timeline import Module, Timeline, conditional, join

class HearingImpairmentCheck(Module):
    def __init__(
            self,
            label = "hearing_impairment_check",
            time_estimate_per_trial: float = 3.0,
        ):
        self.label = label
        self.elts = join(
            ModularPage(
                self.label,
                Prompt("Do you have any kind of hearing impairment? (I.e., do you have any problems with your hearing?)"),
                control=PushButtonControl(["Yes", "No"]),
                time_estimate=time_estimate_per_trial,
            ),
            conditional(
                "hearing_impairment_check",
                lambda experiment, participant: participant.answer == "Yes",
                UnsuccessfulEndPage(failure_tags=["performance_check", "hearing_impairment_check"])
            )
        )
        super().__init__(self.label, self.elts)

* Another simple example would be a ModularPage with a TextControl where the text provided by the participant is evaluated by some logic determining the positive/negative outcome.

A pre-screening task can then be included in an experiment as follows:

import psynet.experiment
from psynet.page import InfoPage, SuccessfulEndPage
from psynet.timeline import Timeline
# code for importing HearingImpairmentCheck

class Exp(psynet.experiment.Experiment):
    timeline = Timeline(
        HearingImpairmentCheck(),
        InfoPage("Congratulations! You have no hearing impairment.", time_estimate=3),
        SuccessfulEndPage()
    )

For more advanced examples, please refer to the source code of the three static pre-screening tasks ColorVocabularyTest, ColorVocabularyTest, and HugginsHeadphoneTest presented above or continue to the next section where we provide some boilerplate code for building static pre-screening tasks.

Static pre-screening tasks (Boilerplate code)

In this section we provide code snippets for building static pre-screening tasks utilizing TrialMaker and StimulusSet.

A static pre-screening task inherits from Module, e.g.:

from psynet.timeline import Module, join

class SomeStaticPrescreeningTask(Module):
    def __init__(
        self,
        label = "some_static_prescreening_task",
        performance_threshold: int = 4,
    ):
    self.label = label
    self.elts = join(
        self.instruction_page(),
        self.trial_maker(performance_threshold)
    )
    super().__init__(self.label, self.elts)

Set reasonable defaults for performance_threshold and label. Implement the four methods instruction_page(), trial_maker(), trial(), and get_stimulus_set(). The instruction_page() method returns an InfoPage, e.g.:

from markupsafe import Markup
from psynet.page import InfoPage

def instruction_page(self):
    return InfoPage(Markup(
        """
        <p>We will now perform a test to check your ability to ....</p>
        <p>
            Text for explaining the procedure in more detail.
        </p>
        """
    ), time_estimate=10)

The trial_maker() method returns a TrialMaker overriding performance_check(), e.g.:

from psynet.trial.static import StaticTrialMaker

def trial_maker(
        self,
        performance_threshold: int
    ):
    class SomeStaticPrescreeningTrialMaker(StaticTrialMaker):
        def performance_check(self, experiment, participant, participant_trials):
            # Calculate values for ``score`` and ``passed``
            return {
                "score": score,
                "passed": passed
            }

    return SomeStaticPrescreeningTrialMaker(
        id_="some_static_prescreening_trials",
        trial_class=self.trial(time_estimate_per_trial),
        phase="some_prescreening_phase",
        stimulus_set=self.get_stimulus_set(),
        check_performance_at_end=True,
        fail_trials_on_premature_exit=False
    )

Normally static experiments will fail participant trials if they leave the experiment early, so that the final dataset only comprises participants who completed the whole experiment. However, this logic doesn’t apply to pre-screening tasks, where we are not trying to collect a specific quota of data. We therefore disable this behavior, setting fail_trials_on_premature_exit=False in the above code.

The trial() method returns a StaticTrial which implements show_trial() that in turn returns a ModularPage e.g.:

from psynet.page import ModularPage
from psynet.trial.static import StaticTrial

def trial(self, time_estimate_: float):
    class SomeStaticPrescreeningTrial(StaticTrial):
        __mapper_args__ = {"polymorphic_identity": "some_prescreening_trial"}

        time_estimate = time_estimate_

        def show_trial(self, experiment, participant):
            return ModularPage(
                "some_static_prescreening_trial",
                # Define what is presented to the participant and how participants
                # may respond utilizing the two principal ``ModularPage``
                # components ``Prompt`` and ``Control``.
                #
                # Prompt(
                #     "Choose between 1, 2, and 3!"
                # ),
                # PushButtonControl(
                #     ["1", "2", "3"]
                # ),
                time_estimate=self.time_estimate
            )
    return SomeStaticPrescreeningTrial

The get_stimulus_set() method returns a StimulusSet, e.g.:

from psynet.trial.static import StimulusSet, StimulusSpec

def get_stimulus_set(self):
    stimuli = []
    # Construct a list of ``StimulusSpec`` objects and pass it to
    # the ``StimulusSet`` constructor.
    return StimulusSet("some_prescreening_task", stimuli)

For concrete implementations, refer to the source code of the three static pre-screening tasks ColorVocabularyTest, ColorVocabularyTest, and HugginsHeadphoneTest.