Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ Runtime Changes:
prior to the start of the first block, and likewise ensures that demographics
collection is not performed more than once.
* Demographics collection has been changed so that queries in
`user_queries.json` are skipped if they do not correspond to a column in the
``user_queries.json`` are skipped if they do not correspond to a column in the
`participants` table of the database. Additionally, the query for the
participant's unique identifer is now always collected first.
* Demographics collection now fails immediately with an informative message if
a required column in the `participants` table does not have a corresponding
query.
* Custom EVAL statements in ``user_queries.json`` files are no longer supported.
* EyeLink EDF files collected while in development mode are now saved in the
subfolder ``EDF/devmode/`` to separate them from actual participant data.
* ``klibs export`` no longer exports data collected in development mode unless
Expand Down Expand Up @@ -56,6 +57,11 @@ Runtime Changes:

API Changes:

* Added a new module :mod:`~klibs.KLQueries` which provides a set of classes
for prompting participants for input and collecting/validating responses.
* The :func:`~klibs.KLCommunication.query` function has been deprecated in
favour of the :mod:`~klibs.KLQueries` module and should be not be used for
new projects.
* Added a new attribute :attr:`~klibs.Experiment.participant_info` to the
Experiment class that provides a dictionary for easy access to participant
information collected during demographics (e.g. handedness).
Expand Down
1 change: 1 addition & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Useful Modules
api/eyetracking.rst
api/eventinterface.rst
api/structure.rst
api/queries.rst


Advanced Modules
Expand Down
5 changes: 5 additions & 0 deletions docs/source/api/queries.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
`klibs.KLQueries` - Simple text input prompts
=============================================

.. automodule:: klibs.KLQueries
:members:
68 changes: 26 additions & 42 deletions klibs/KLCommunication.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,26 @@
from klibs.KLRuntimeInfo import runtime_info_init, TaskArchiver
from klibs.KLGraphics import blit, clear, fill, flip
from klibs.KLUserInterface import ui_request, any_key
from klibs.KLText import _get_text_style, add_text_style, message


user_queries = None
default_strings = None
from klibs.KLText import _get_text_style, message

user_queries = None # [Compat]: Only needed for TraceLab and BaseballTOJ


def _parse_eval(statement):
# Parse legacy eval statements without actually using eval
if not isinstance(statement, str):
return statement
elif statement[:5] != "EVAL:":
return statement
elif statement == "EVAL: now()":
return now()
elif statement == "EVAL: int(now())":
return int(now())
elif statement[:9] == "EVAL: 'm'":
return ("m" if now() % 2 > 1 else "f")
else:
e = "Custom EVAL statements are no longer supported."
raise RuntimeError(e)


def _get_demographics_queries(db, queries):
Expand Down Expand Up @@ -155,11 +170,10 @@ def collect_demographics(anonymous=False):


def init_messaging():
# This function should be removed somehow: user queries should be a) redone, and
# b) loaded in with a regular function
# Need to do in a way that won't break TraceLab
# This function should be removed as soon as use of 'experimental' queries
# is removed from BaseballTOJ and TraceLab and replaced with an internal
# _load_demographics function.
global user_queries
global default_strings

# Load the user_queries file in and store the queries in an object
try:
Expand All @@ -171,45 +185,14 @@ def init_messaging():
err = "<red>Error: user_queries.json file missing required section '{0}'.</red>"
cso(err.format(req))
raise ValueError()
default_strings = user_queries.default_strings # for easy accessiblity
except ValueError:
raise ValueError("User queries file has at least one formatting error; cannot continue.")


def query(query_ob, anonymous=False):
'''Asks the user a question and collects the response via the keyboard. Intended for use with
the queries contained within a project's user_queries.json file. This function is used
internally for collecting demographics at the start of each run, but can also be used during
experiment runtime to collect info from participants based on the queries contained in the
"experimental" section of the user_queries.json file.

Args:
query_ob (:class:`~klibs.KLJSON_Object.AttributeDict`): The object containing the query
to present. See :obj:`~klibs.KLCommunication.user_queries` for more information.
anonymous (bool, optional): If True, will immediately return the query object's anonymous
value without prompting the user (used interally for P.development_mode). Defaults to
False.

Returns:
The response to the query, coerced to the type specified by query_ob.format.type (can be
str, int, float, bool, or None).

Raises:
ValueError: If the query object's type is not one of "str", "int", "float", "bool", or None,
or if a query_ob.format.range value is given and the type is not "int" or "float".
TypeError: If query_ob.accepted is specified and it is not a list of values, or if a
query_ob.format.range is specified and it is not a two-item list.

'''
# [Compat]: Deprecated, only needed by TraceLab and BaseballTOJ
if anonymous:
try:
# Check if anon value is an EVAL statement, and if so evaluate it
eval_statement = re.match(re.compile(u"^EVAL:[ ]*(.*)$"), query_ob.anonymous_value)
if eval_statement:
query_ob.anonymous_value = eval(eval_statement.group(1))
except TypeError:
pass
return query_ob.anonymous_value
return _parse_eval(query_ob.anonymous_value)

f = query_ob.format
if f.type not in ("int", "float", "str", "bool", None):
Expand Down Expand Up @@ -246,6 +229,7 @@ def query(query_ob, anonymous=False):

# Create an informative error message for invalid responses
accepted_responses = query_ob.accepted # for code readability
default_strings = user_queries['default_strings']
try:
if accepted_responses:
try:
Expand Down
3 changes: 3 additions & 0 deletions klibs/KLGraphics/KLNumpySurface.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ def blit(self, source, registration=7, location=(0,0), clip=True, blend=True):
Valid source content types include :obj:`NumpySurface` objects, :obj:`Drawbject` shapes,
and :obj:`numpy.ndarray` or :obj:`Pillow.Image` objects in RGBA format.

See the :func:`~klibs.KLGraphics.blit` documentation for more information on the
`registration` and `location` parameters.

Args:
source: The content to draw to the surface.
registration (int, optional): An integer from 1 to 9 indicating which point on the
Expand Down
41 changes: 32 additions & 9 deletions klibs/KLGraphics/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,42 @@ def fill(color=None, context=None):


def blit(source, registration=7, location=(0,0), flip_x=False):
"""
Draws passed content to the display buffer. All content that is not in already
rendered to Numpy Array format will be rendered when it is passed to this
function, thus it is recommended to render beforehand whenever possible to
avoid performance issues resulting from the extra overhead.
"""Draws a source image or texture to the display buffer.

Valid source content types include :obj:`~NumpySurface` objects, :obj:`~Drawbject`
shapes, and Numpy arrays (:obj:`numpy.ndarray`) in RGBA format.

When drawing to the display buffer, you can specify both a location and a
registration for the texture. The `registration` parameter is a digit from 1 to 9
that defines an anchor point on the texture to align with the given location, with
the different digits corresponding to different anchor points on the texture:

.. code-block:: text

7 -- 8 -- 9 1 = Bottom Left 6 = Middle Right
| | | 2 = Bottom Center 7 = Top Left
4 -- 5 -- 6 3 = Bottom Right 8 = Top Center
| | | 4 = Middle Left 9 = Top Right
1 -- 2 -- 3 5 = Center

For example, the default registration of 7 indicates that the *top-left corner* of
the texture should be drawn to the specified location. If you wanted to draw
something with its `midpoint` aligned to the given location, you would use a
registration of 5::

# Draw fixation to middle of screen
fill()
blit(fixation, 5, P.screen_c)
flip()

Valid source content types include :obj:`NumpySurface` objects, :obj:`Drawbject` shapes,
and :obj:`numpy.ndarray` or :obj:`Pillow.Image` objects in RGBA format.
Note that drawing an image to the display buffer does not cause that image to
appear immediately on screen: anything drawn to the buffer will not be shown to
the user until :func:`~flip` is called.

Args:
source: Image data to draw to the display buffer.
registration (int): An integer from 1 to 9 indicating which location on the
surface will be aligned to the location value (see manual for more info).
registration (int): An integer from 1 to 9 indicating the anchor point on the
source to align with the given location. Defaults to the top-left corner.
location(tuple(int,int)): A tuple of x,y pixel coordinates indicating where to
draw the object to.
flip_x (bool): If True, flips the content along its x-axis before drawing to
Expand Down
Loading
Loading