Slider RT mismatch: `store_rating_time` vs `mouse.getPressed(getTime=True)`

OS (Ubuntu 24.04.3 LTS):
PsychoPy version (e.g. 2024.2.4 Py 3.10.17):
Standard Standalone Installation? yes
Do you want it to also run online? no

Hi everyone,

I’m piloting a Builder experiment and want to record reaction times for responses made on a Slider. I noticed that the RT from the Slider option store_rating_time (Slider → Data tab) differs from the RT I get using mouse.getPressed(getTime=True).

Setup

  • Builder routine contains a Slider (participant responds by clicking/dragging on the slider)
  • store_rating_time enabled
  • Force end routine enabled for the Slider
  • Sync timing with screen refresh” enabled (default)
  • In the same routine, I added a Code component to also capture mouse-based RT and print it to the console (just for debugging/testing).

Custom code (Code component)

# Begin experiment
from psychopy.event import Mouse
mouse = Mouse()

# Begin Routine
mouse.clickReset()

# End Routine
buttons, times = mouse.getPressed(getTime=True)
print(times)
mouse.clickReset()

Observation

  • The RT from store_rating_time is consistently (slightly) different from the times returned by getPressed(getTime=True).
  • The difference is sometimes small, sometimes large, depending on how i “use” the slider, but getPressed(getTime=True) is always “faster” (i.e., RT is smaller).

Questions

  1. What exactly does the Slider’s store_rating_time represent, and how does it differ from what mouse.getPressed(getTime=True) returns?

  2. Could the difference be explained by which event is being timed (e.g., first click vs first drag movement vs mouse release / “final commit” of the rating), especially when participants click-and-drag the marker?

    • If so, it might be important for us to store both “first interaction” and “final selection” times.
  3. For “RT to slider response”, is there a recommended approach: rely on store_rating_time, rely on mouse.getPressed(getTime=True), use both, or measure something else (e.g., time to first interaction vs time to final committed rating)? is anyone else looking at these differences?

Can you experiment with long mouse button presses to check whether the slider time is first release?

I ran a quick check comparing a normal click vs. clicking and holding/dragging for ~2 seconds; store_rating_time increased by ~2 s while getPressed(getTime=True)[0] stayed near the initial press time (I used the mouse’s left button, see below)

action store_rating_time (s) mouse.getPressed time[0] (s)
click and drag 3.83523154258728 2.485521078109741
click and drag 4.28529739379883 2.6788330078125
click 2.38721442222595 2.3541057109832764
click 2.38634037971497 2.3634400367736816
click and hold 4.30446743965149 2.3636739253997803
fast click and hold 2.28601169586182 0.8849456310272217
click and drag 4.16900944709778 2.7169923782348633
click 2.38284516334534 2.362399101257324
click 2.55040287971497 2.5295770168304443
click and hold 4.32037091255188 2.5134024620056152

I also looked at the slider source code (see here) and, from what I can tell, the marker position updates while the button is down, but the actual rating is only set on mouse release. That matches my click vs. hold/drag test.

Could someone confirm that this is indeed the behaviour I’m observing? If so, would it be worth documenting this more explicitly (e.g., that store_rating_time reflects the commit on release time) to avoid misunderstandings?

1 Like

Here’s some relevant sections of the slider code.

        self.params['forceEndRoutine'] = Param(
                forceEndRoutine, valType='bool', inputType="bool", allowedTypes=[], categ='Basic',
                updates='constant', allowedUpdates=[],
                hint=_translate("Should setting a rating (releasing the mouse) "
                                "cause the end of the Routine (e.g. trial)?"),
                label=_translate("Force end of Routine"))
    def recordRating(self, rating, rt=None, log=None):
        """Sets the current rating value
        """
        rating = self._granularRating(rating)
        setAttribute(self, attrib='rating', value=rating, operation='', log=log)
        if rt is None:
            self.rt = self.responseClock.getTime()
        else:
            self.rt = rt
        self.history.append((rating, self.rt))
        self._updateMarkerPos = True
    def getMouseResponses(self):
        """Instructs the rating scale to check for valid mouse responses.

        This is usually done during the draw() method but can be done by the
        user as well at any point in time. The rating will be returned but
        will ALSO automatically be set as the current rating response.

        While the mouse button is down we will alter self.markerPos
        but don't set a value for self.rating until button comes up

        Returns
        ----------
        A rating value or None
        """
        if self.readOnly:
            return
        click = bool(self.mouse.getPressed()[0])
        xy = self.mouse.getPos()

        if click:
            # Update current but don't set Rating (mouse is still down)
            # Dragging has to start inside a "valid" area (i.e., on the
            # slider), but may continue even if the mouse moves away from
            # the slider, as long as the mouse button is not released.
            if (self.validArea.contains(self.mouse, units=self.units) or
                    self._dragging):
                self.markerPos = self._posToRating(xy)  # updates marker
                self._dragging = True
            self._updateMarkerPos = True
        else:  # mouse is up - check if it *just* came up
            if self._dragging:
                self._dragging = False
                if self.markerPos is not None:
                    self.recordRating(self.markerPos)
                return self.markerPos
            else:
                # is up and was already up - move along
                return None


This code notes that if a slider is set to force end of routine, it does so on mouse release.

Logically, if the slider response isn’t set until mouse release, then the slider time has to be at this point, not at first interaction.

1 Like

Thanks @wakecarter for posting the relevant code excerpts.

The first snippet you included is the code behind the hint text shown when hovering over Force end of Routine in the Builder Slider properties (“…(releasing the mouse)…”). The getMouseResponses() part is the one I was referring to in my previous post too. This makes it clear that while the mouse button is held down, the marker position can update, but the rating (and therefore the recorded RT) is only set when the mouse button is released.

That matches what I saw in my little click vs. hold/drag test: store_rating_time reflects the commit-on-release time, not the initial interaction.

It makes total sense now, but it wasn’t immediately intuitive during piloting, because the key cue (“releasing the mouse”) is in the Force end of Routine hover hint rather than near the Store rating time setting. It might be worth making that explicit in the docs (or having an RT-related hint) to avoid confusion.

Thanks again for confirming and for the pointers!