Chapter 2 Get started

2.1 Download and import OCTA

The OCTA Python toolbox was created in Python 3.8 and is dependent on the following Python libraries: svgwrite, svg.path, svgpathtools, svgutils, jsonpickle, html2image, svglib, reportlab, colour, and IPython. To start using the OCTA Python toolbox, install octa as well as its dependencies.

pip install octa

Once everything is installed, it can be helpful to import specific functions from the octa library:

from octa.Stimulus import Grid, Outline, Concentric, Stimulus
from octa.Positions import Positions
from octa.patterns import GridPattern, Pattern
from octa.shapes import Ellipse, Rectangle, Triangle, Polygon, RegularPolygon, Path, PathSvg, Image, FitImage, Text
from octa.measurements import Order, Complexity

2.2 Stimulus types & characteristics

The first step when creating a stimulus is to define the type of stimulus one wants to create: a Grid stimulus, an Outline stimulus, or a Concentric stimulus. Whereas a Grid stimulus requires a specified number of rows (n_rows) and columns (n_cols), Outline or Concentric stimuli require a specified number of elements (n_elements). Behind the scenes, the stimulus types Outline and Concentric are defined as Grid stimuli with the number of rows equal to one and the number of columns equal to the number of elements.

If desired, the user can specify additional arguments to customize the stimulus. The following features of the stimulus as a whole can be specified: spacing between rows and columns (in case of a Grid stimulus; row_spacing and col_spacing), shape and shape bounding box (in case of an Outline stimulus; shape and shape_boundingbox), horizontal and vertical margin (when automatic sizing method is used; x_margin and y_margin), stimulus size (when fixed size is needed; size), background color (background_color), background shape (background_shape), stimulus orientation (stim_orientation), mirror value for the stimulus as a whole (stim_mirrorvalue), mask to apply for the stimulus as a whole (stim_mask), and link (stim_link), class label (stim_classlabel), and id label (stim_idlabel) for the stimulus.

By default, stimuli will be autocentered with a horizontal and vertical margin of 20 units in the current user coordinate system (i.e., user units), have a white background color, and an orientation of 0°. The Grid category has a default row and column spacing of 50 user units, and the Outline category by default has a circular shape with a bounding box of 150 by 150 user units. For more information on all stimulus characteristics, please visit the section on stimulus characteristics below.

Besides the Grid, Outline, and Concentric stimulus types, a generic Stimulus type (which can be used for all types of stimuli) is available too. To use the generic Stimulus category, all element characteristics need to be manually specified (positions, boundingboxes, etc.). We strongly recommend against using the generic Stimulus type, as the Grid-based stimulus types have been tested more extensively and are more user-friendly.

2.2.1 Stimulus types

2.2.1.1 Grid

The default Grid stimulus will use octagonal Polygon shapes with a boundingbox of (45,45), a blue fillcolor (#1E90FF), an orientation of 0°, an opacity of 1, and no borderwidth, bordercolor, mirrorvalue, link, classlabel, or idlabel.

Minimal Grid example:

stimulus = Grid(n_rows = 3, n_cols = 3)

   

2.2.1.2 Outline

Minimal Outline example:

stimulus = Outline(n_elements = 9)

   

2.2.1.3 Concentric

Minimal Concentric example:

stimulus = Concentric(n_elements = 9)

   

2.2.1.4 Stimulus

stimulus = Stimulus(x_margin = 5, y_margin = 5, size = (150,150), background_color = "lightgrey")
stimulus.positions = Positions.CreateCustomPositions(x = (10,35,75), 
                                                     y = (10,85,45))
stimulus.boundingboxes = [(45,45), (45,45), (45,45)]
stimulus.shapes = [Rectangle, Triangle, Ellipse]
stimulus.fillcolors = ["red", "green", "blue"]

stimulus.orientations   =  [0,0,0]
stimulus.bordercolors   = ["black", "black", "black"]
stimulus.borderwidths   = [0,0,0]
stimulus.opacities      = [1,1,1]
stimulus.data           = ["", "", ""] 
stimulus.links          = ["", "", ""]
stimulus.classlabels   = ["", "", ""]
stimulus.idlabels      = ["", "", ""]
stimulus.mirrorvalues  = ["", "", ""]
stimulus._element_presentation_order  = [0,1,2]
stimulus._attribute_overrides  = [{},{},{}]

   

2.2.2 Stimulus characteristics

The different stimulus properties can be specified when defining the stimulus type, but can also be changed dynamically after the stimulus has been created by assigning a new value to each parameter.

stimulus = Grid(n_rows = 3, n_cols = 6, background_color = "gainsboro")

   

stimulus = Grid(3,3)
stimulus.n_cols = 6
stimulus.background_color = "gainsboro"

   

2.2.2.1 Add custom row_spacing or col_spacing

stimulus = Grid(3, 3, row_spacing = 60, col_spacing = 45, size = (150,200), background_color = "gainsboro")

   

2.2.2.2 Add custom shape or shape_boundingbox

stimulus = Outline(36, shape = "img/checkmark.svg", shape_boundingbox = (200,200))

stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(15,15)])

   

2.2.2.3 Add custom x_margin or y_margin

stimulus = Grid(3, 3, x_margin = (0,20), y_margin = 20, background_color = "whitesmoke")

The default sizing of the stimulus takes into account the maximum bounding box present in the pattern and adjusts all margins to be that size (maximum_boundingbox). If the user prefers a tight fit, they can change the _autosize_method argument to tight_fit.

Default sizing:

stimulus = Grid(3,3, background_color = "gainsboro", x_margin = 0, y_margin = 0)
stimulus.boundingboxes = GridPattern.RepeatAcrossColumns([(20,20), (40,40)])
stimulus._autosize_method = "maximum_boundingbox"

   

Using tight_fit:

stimulus = Grid(3,3, background_color = "gainsboro", x_margin = 0, y_margin = 0)
stimulus.boundingboxes = GridPattern.RepeatAcrossColumns([(20,20), (40,40)])
stimulus._autosize_method = "tight_fit"

   

2.2.2.4 Specify custom size

Please be aware that in case size is specified, x_margin and y_margin arguments are not taken into account.

stimulus = Grid(3, 3, background_color = "whitesmoke")

stimulus = Grid(3, 3, size = (200,200), x_margin = 0, background_color = "gainsboro")

stimulus = Grid(3, 3, x_margin = (0,20), y_margin = 20, background_color = "whitesmoke")

stimulus = Grid(3, 3, row_spacing = 60, col_spacing = 45, size = (150,200), background_color = "gainsboro")

            

2.2.2.5 Add background_color

stimulus = Grid(3, 3, background_color = "whitesmoke")

   

2.2.2.6 Add background_shape

Adding a background shape clips the stimulus area to a custom shape. This clipping process does not take into account placement of elements outside of the clipping area (these elements will thus become invisible without further notice), so be aware that this process may hamper a correct calculation of the order and complexity measures. The background shape will be centered automatically. The background shape can be given as a character string or an actual shape definition (e.g., Rectangle, Ellipse, Triangle, including a value for the boundingbox property). When the shape name is given as a character string, the bounding box of the background shape is equal to the stimulus size.

stimsize = (300,300)
clipshape = Ellipse(position = (stimsize[0]/2,stimsize[1]/2), boundingbox = stimsize)
stimulus = Grid(6,6, background_color = "lightgrey", size = stimsize, background_shape = clipshape)

   

stimsize = (300,300)
clipshape = "Ellipse"
stimulus = Grid(6,6, background_color = "lightgrey", size = stimsize, background_shape = clipshape)

   

2.2.2.7 Add stim_orientation

Adding a stimulus orientation influences the orientation of the stimulus as a whole (make sure to adapt size or margins of the stimulus accordingly to make the full pattern visible in case of orientation changes). Be aware that the background shape (and its color) will rotate along with the stimulus: you may want to choose a circular background_shape or set background_color to ‘none’. For animation options of the stimulus orientation, check the orientation options as element characteristics.

stimulus = Grid(4,4, stim_orientation = 45, x_margin = 50, y_margin = 50)

   

2.2.2.8 Add stim_mirrorvalue

The stimulus mirrorvalue mirrors the whole stimulus in the horizontal, vertical, or horizontal & vertical dimension.

stimulus = Grid(3, 3, x_margin = 5, y_margin = 5, stim_mirrorvalue = "none")
stimulus = Grid(3, 3, x_margin = 5, y_margin = 5, stim_mirrorvalue = "horizontal")
stimulus = Grid(3, 3, x_margin = 5, y_margin = 5, stim_mirrorvalue = "vertical")
stimulus = Grid(3, 3, x_margin = 5, y_margin = 5, stim_mirrorvalue = "horizontalvertical")

stimulus.shapes = GridPattern.RepeatAcrossColumns([Polygon(5), Triangle]) 
stimulus.orientations = GridPattern.RepeatAcrossColumns([0,0,45,45]) 

            

2.2.2.9 Add stim_mask

When a stimulus mask is added, this adds an image ‘filter’ or mask on top of the stimulus. This mask is not taken into account when calculating the order and complexity measures. The stimulus mask will be centered automatically. The stimulus mask can be given as a character string or an actual shape definition (e.g., Rectangle, Ellipse, Triangle, including a value for the boundingbox property and optionally the fillcolor property). When the shape name is given as a character string, the bounding box of the background shape is equal to the stimulus size.

stimsize = (300,300)
shapemask = Ellipse(position = (stimsize[0]/2,stimsize[1]/2), boundingbox = (stimsize[0],stimsize[1]), fillcolor = ["radial", "white",  "black"])
stimulus = Grid(6,6, background_color = "lightgrey", size = stimsize, background_shape = clipshape, stim_mask = shapemask)

   

2.3 Element position patterns

Having created the stimulus, the resulting x and y coordinates for the positions of the elements can be requested via stimulus.positions.GetPositions(). 1 The position of an element is determined by the center of the element’s bounding box (i.e., the rectangle ‘bounding’ the size of the element shape). The default position patterns used for Grid, Outline, and Concentric stimuli are a rectangular grid with a row and column spacing of 50 user units, a circle outline with radius 150, and identical (0,0) positions respectively. The user can replace the default position pattern for the stimulus type by a custom one (e.g., a sinewave-shaped grid, a custom shape outline, random positions within a specified rectangle, manually specified positions).

stimulus = Grid(3,3, background_color = "gainsboro")
stimulus.positions.GetPositions()
## ([0, 50, 100, 0, 50, 100, 0, 50, 100], [0, 0, 0, 50, 50, 50, 100, 100, 100])

   

2.3.1 Rectangular grid

New rectangular grid element positions can be applied using Positions.CreateRectGrid(n_rows, n_cols, row_spacing, col_spacing). The default value for row_spacing and column_spacing is 50. If you have already specified row and column spacing when defining the stimulus type, please define row and column spacing again within this function (it will not take into account your previously defined row and column spacing).

stimulus.positions = Positions.CreateRectGrid(1,9)

   

2.3.2 Sinewave-shaped grid

Positions for a sinewave-shaped grid can be applied using Positions.CreateSineGrid(n_rows, n_cols, row_spacing, col_spacing, A, f, axis). Parameters A (amplitude), f (frequency), and axis (‘x’, ‘y’, or ‘xy’) have default values of 25, 0.1, and ‘x’ respectively.

stimulus = Grid(3,12)
stimulus.positions = Positions.CreateSineGrid(n_rows = 3, n_cols = 12, A = 25, f = .1, axis = "xy")

   

2.3.3 Circle outline

To get element positions that form a circle, the function Positions.CreateCircle(radius, n_elements, starting_point = “left”) can be used. The starting point can be set to ‘left’, ‘right’, ‘top’, or ‘bottom’.

stimulus = Grid(1,6, background_color = "gainsboro")
stimulus.positions = Positions.CreateCircle(radius = 50, n_elements = 6)

   

2.3.4 Custom shape outline

To get element positions that form a specified path, the function Positions.CreateShape(n_elements, src, path, width = 300, height = 300) can be used. The user either provides the name of an svg file that contains the shape to be used or defines an svg path string manually. The starting point of the path shape depends on how the specified path is defined.

stimulus = Grid(1,100, background_color = "gainsboro")
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(10,10)])

stimulus.positions = Positions.CreateShape(src = "img/checkmark.svg", n_elements = 100, width = 250, height = 250)

   

2.3.5 Random positions

To get random element positions for a fixed number of elements, the function Positions.CreateRandomPositions(n_elements, width, height, min_distance, max_iterations) can be used. Default values for width and height are 300. The default minimum distance between all generated positions (min_distance) is 30. The default maximum number of iterations (max_iterations; i.e., how many times the algorithm should try to generate positions if the min_distance criterion can not be satisfied) is 10.

stimulus = Grid(1,9, background_color = "whitesmoke")
stimulus.positions = Positions.CreateRandomPositions(n_elements = 9, width = 200, height = 150, min_distance = 30, max_iterations = 10)

   

2.3.6 Custom positions

Custom x and y coordinates can be specified using Positions.CreateCustomPositions(x,y):

new_x = [20,20,40, 80,80,100, 160,160,160]
new_y = [20,80,160, 20,80,160, 40,100,160]
stimulus.positions = Positions.CreateCustomPositions(x = new_x, y = new_y)

   

2.4 Element features

Once the stimulus features and element positions are specified, the pattern types, pattern directions, and pattern values for the different element features can be adjusted. The patterns can be applied to the following element characteristics: shapes, boundingboxes (i.e., the size of the bounding box in which the elements fit), fillcolors, orientations, borderwidths, bordercolors, opacities, mirrorvalues (i.e., whether some of the elements need to be mirrored), links, classlabels, and idlabels.

Later in this section (cf. Feature values) we will give a more detailed overview of all these element features and their value options, but first we detail the different patterns that can be applied to the feature dimensions.

2.4.1 Feature patterns

2.4.1.1 GridPatterns

The values for the different element characteristics are applied to the elements by following a specific pattern type and direction. In Grid and Outline stimuli, one value per element characteristic is repeated across all elements in the stimulus by default. In Concentric stimuli, a pattern with two fillcolors is repeated across all elements in the stimulus by default, and the boundingbox sizes follows a decreasing gradient across elements.

Available pattern types for grid structures include pattern repetition (repeat), element repetition (elementrepeat), mirror symmetry (mirror), and a gradient from a start value to an end value (gradient). These patterns can be applied along the following pattern directions: across elements, across rows, across columns, across the left diagonal, across the right diagonal, and across layers. In addition, more complex patterns can be constructed using the TiledGrid and TiledElementGrid options. Finally, a random application of the values for the element characteristic across the elements is possible too (random). By default, the pattern values in the RandomPattern are repeated until the length is equal to the number of elements in the stimulus. Optionally, a list of frequencies can be provided to determine how many times each pattern value has to be present. In Outline and Concentric stimuli, it is strongly advised to apply patterns across elements (as other pattern directions will not be distinguishable in Outline and Concentric stimuli).

The default direction in which the pattern is applied is from top to bottom and from left to right (“tblr”).

We will show the different pattern options here on the basis of the fillcolor argument.

2.4.1.1.1 RepeatAcrossElements, RepeatAcrossRows, RepeatAcrossColumns, RepeatAcrossLeftDiagonal, RepeatAcrossRightDiagonal, RepeatAcrossLayers
stimulus = Grid(12,12, row_spacing = 16, col_spacing = 16)
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(12,12)])

stimulus.fillcolors = GridPattern.RepeatAcrossElements(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.RepeatAcrossRows(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.RepeatAcrossColumns(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.RepeatAcrossLeftDiagonal(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.RepeatAcrossRightDiagonal(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.RepeatAcrossLayers(["red", "green", "blue"])

                  

2.4.1.1.2 ElementRepeatAcrossElements, ElementRepeatAcrossRows, ElementRepeatAcrossColumns, ElementRepeatAcrossLeftDiagonal, ElementRepeatAcrossRightDiagonal, ElementRepeatAcrossLayers
stimulus = Grid(12,12, row_spacing = 16, col_spacing = 16)
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(12,12)])

stimulus.fillcolors = GridPattern.ElementRepeatAcrossElements(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRows(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossColumns(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossLeftDiagonal(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRightDiagonal(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossLayers(["red", "green", "blue"])

                  

2.4.1.1.3 MirrorAcrossElements, MirrorAcrossRows, MirrorAcrossColumns, MirrorAcrossLeftDiagonal, MirrorAcrossRightDiagonal, MirrorAcrossLayers
stimulus = Grid(12,12, row_spacing = 16, col_spacing = 16)
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(12,12)])

stimulus.fillcolors = GridPattern.MirrorAcrossElements(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.MirrorAcrossRows(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.MirrorAcrossColumns(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.MirrorAcrossLeftDiagonal(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.MirrorAcrossRightDiagonal(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.MirrorAcrossLayers(["red", "green", "blue"])

                  

2.4.1.1.4 GradientAcrossElements, GradientAcrossRows, GradientAcrossColumns, GradientAcrossLeftDiagonal, GradientAcrossRightDiagonal, GradientAcrossLayers
stimulus = Grid(12,12, row_spacing = 16, col_spacing = 16)
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(12,12)])

stimulus.fillcolors = GridPattern.GradientAcrossElements(start_value = "red", end_value = "green")
stimulus.fillcolors = GridPattern.GradientAcrossRows(start_value = "red", end_value = "green")
stimulus.fillcolors = GridPattern.GradientAcrossColumns(start_value = "red", end_value = "green")
stimulus.fillcolors = GridPattern.GradientAcrossLeftDiagonal(start_value = "red", end_value = "green")
stimulus.fillcolors = GridPattern.GradientAcrossRightDiagonal(start_value = "red", end_value = "green")
stimulus.fillcolors = GridPattern.GradientAcrossLayers(start_value = "red", end_value = "green")

                  

2.4.1.1.5 TiledElementGrid

TiledElementGrid provides a way to create more complex patterns by multiplying each value in a pre-existing source grid (source_grid) a specified number of times (tile_multiplier) along both the row and column dimension. This function can thus be used to create a pattern with subgroups.

stimulus = Grid(12,12, row_spacing = 16, col_spacing = 16)
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(12,12)])

stimulus.fillcolors = GridPattern.TiledElementGrid(source_grid = GridPattern.MirrorAcrossRightDiagonal(["red", "green", "blue"], 3, 3), tile_multiplier = 4)
stimulus.fillcolors = GridPattern.TiledElementGrid(source_grid = GridPattern.GradientAcrossLeftDiagonal(start_value = "red", end_value = "blue", n_rows = 3, n_cols = 3), tile_multiplier = 4)
stimulus.fillcolors = GridPattern.TiledElementGrid(source_grid = GridPattern.GradientAcrossLayers(start_value = "red", end_value = "blue", n_rows = 4, n_cols = 4), tile_multiplier = 3)

         

2.4.1.1.6 TiledGrid

TiledGrid provides a way to create more complex patterns by multiplying a pre-existing source grid (source_grid) a specified number of times (tile_multiplier) along both the row and column dimension.

stimulus = Grid(12,12, row_spacing = 16, col_spacing = 16)
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(12,12)])
source_grid = GridPattern.MirrorAcrossLeftDiagonal(["red", "green"], 2, 2)
stimulus.fillcolors = GridPattern.TiledGrid(source_grid, tile_multiplier = 6)

source_grid = GridPattern.GradientAcrossLeftDiagonal("red", "blue", 4, 4)
stimulus.fillcolors = GridPattern.TiledGrid(source_grid, tile_multiplier = 3)

source_grid = GridPattern.ElementRepeatAcrossLayers(["red", "green", "blue"], 3, 3)
stimulus.fillcolors = GridPattern.TiledGrid(source_grid, tile_multiplier = 4)

         

2.4.1.1.7 RandomPattern

By default, the number of times a value from the pattern will occur, is equal to when the pattern would be repeated until the required number of elements is reached. The counts argument can be used to change this default behavior.

stimulus = Grid(12,12, row_spacing = 16, col_spacing = 16)
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(12,12)])

stimulus.fillcolors = GridPattern.RandomPattern(["red", "green", "blue"])
stimulus.fillcolors = GridPattern.RandomPattern(["red", "green", "blue"], counts = [40,4,100])
stimulus.fillcolors = GridPattern.RandomPattern(["red", "green", "blue"], counts = [73,70,1])

         

2.4.2 Basic Patterns

Some basic pattern functions are available in OCTA. These can mainly come in useful when defining custom positions or when specifying complex patterns to feed to any of the GridPattern functions.

p = Pattern(["red", "green", "blue"])
print(p)
## ['red', 'green', 'blue']
q = p.RepeatElements(n_repeats = 2, max_elements = None)
print(q)
## ['red', 'red', 'green', 'green', 'blue', 'blue']
r = p.RepeatPattern(n_repeats = 2, max_elements = None)
print(r)
## ['red', 'green', 'blue', 'red', 'green', 'blue']
s = p.RepeatElementsToSize(count = 5)
print(s)
## ['red', 'red', 'green', 'green', 'blue']
t = p.RepeatPatternToSize(count = 5)
print(t)
## ['red', 'green', 'blue', 'red', 'green']
u = p.RandomizeOrder()
print(u)
## ['blue', 'red', 'green']
p = Pattern([1,2,3])
print(p)
## [1, 2, 3]
q = p + p
print(q)
## [2, 4, 6]
r = p.AddNormalJitter(mu = 0, std = 1)
print(r)
## [1.1306181331600518, 0.021902252016182677, 4.0546284806903925]
s = p.AddUniformJitter(min_val = -.5, max_val = .5)
print(s)
## [0.6944041216694532, 1.8997350363946872, 3.1994112379689494]
p = Pattern.CreateGradientPattern(start_value = "red", end_value = "green", n_elements = 3)
print(p)
## ['#f00', '#bfbf00', '#008000']
p = Pattern.CreateGradientPattern(start_value = (10,5), end_value = (10,10), n_elements = 3)
print(p)
## [(10, 5), (10.0, 7.5), (10.0, 10.0)]
q = Pattern.Create2DGradient(x = Sequence(start = 5, step = 5), y = Sequence(start = 5, step = 5), n_elements = 5)
print(q)
## [(5, 5), (10, 10), (15, 15), (20, 20), (25, 25)]
q = Pattern.Create2DGradient(x = LinearGradient(start = 5, end = 50, n_elements = 5, invert = True), y = Sequence(start = 5, step = 5), n_elements = 5)
print(q)
## [(50, 5), (38.75, 10), (27.5, 15), (16.25, 20), (5.0, 25)]
q = Pattern.Create2DGradient(x = LinearGradient(start = 50, end = 5, n_elements = 5), y = Sequence(start = 5, step = 5), n_elements = 5)
print(q)
## [(50, 5), (38.75, 10), (27.5, 15), (16.25, 20), (5.0, 25)]

2.4.3 Feature values

2.4.3.1 Shapes

Available element shape types are geometric shapes, i.e., Ellipse, Triangle, Rectangle, Polygon(n_sides), RegularPolygon(n_sides); more complex path elements4 based on a provided path string or an existing svg file, i.e., Path(path, xsizepath, ysizepath), PathSvg(src); images based on a provided filename or source url, i.e., Image(src), FitImage(src); and text elements, i.e., Text(text). Keep in mind that for the order and complexity measurements, the shapes argument only takes the shape type into account and does not distinguish between different polygons, different paths, different images, or different texts. Currently no built-in animation options are available for the shape characteristic, but it is possible to include dynamic image files (e.g., dynamic svg or gif file) as shape values in the stimulus.

stimulus = Grid(1,10)
stimulus.shapes = GridPattern.RepeatAcrossElements([Ellipse, Triangle, Rectangle, Polygon(6), RegularPolygon(6), Image("img/gears.svg"), FitImage("img/gears.svg"), 
Path("M37.5,186c-12.1-10.5-11.8-32.3-7.2-46.7c4.8-15,13.1-17.8,30.1-36.7C91,68.8,83.5,56.7,103.4,45 c22.2-13.1,51.1-9.5,69.6-1.6c18.1,7.8,15.7,15.3,43.3,33.2c28.8,18.8,37.2,14.3,46.7,27.9c15.6,22.3,6.4,53.3,4.4,60.2 c-3.3,11.2-7.1,23.9-18.5,32c-16.3,11.5-29.5,0.7-48.6,11c-16.2,8.7-12.6,19.7-28.2,33.2c-22.7,19.7-63.8,25.7-79.9,9.7 c-15.2-15.1,0.3-41.7-16.6-54.9C63,186,49.7,196.7,37.5,186z", 288,288), PathSvg("img/checkmark.svg"), Text("O")])

O   

2.4.3.2 Boundingboxes

Boundingboxes are always rectangular but do not have to be squared. Boundingbox values are defined in user units and can be provided as follows: (xsize, ysize). Although currently no built-in animation options are available for the boundingbox characteristic, it is possible to either generate separate stimuli and combine them in time afterwards, or to include dynamic image files (e.g., dynamic svg or gif file with shape changing in size) as shape values in the stimulus.

stimulus = Grid(1,3)
stimulus.boundingboxes = GridPattern.RepeatAcrossElements([(45,45), (10,45), (45,10)])

   

O   

stimulus = Grid(6,6, row_spacing=50, col_spacing=50)
stimulus.shapes = GridPattern.RepeatAcrossElements([Rectangle])

stimulus.boundingboxes = GridPattern.RepeatAcrossLeftDiagonal(Pattern.Create2DGradient(x = Sequence(start = 10, step = 5), y = Sequence(start = 50, step = -5), n_elements = 12))

stimulus.boundingboxes = GridPattern.RepeatAcrossElements(Pattern.Create2DGradient(x = LinearGradient(start = 5, end = 50, n_elements = 36, invert = True), y = LinearGradient(start = 5, end = 50, n_elements = 36, invert = True), n_elements = 36))

      

2.4.3.3 Fillcolors

Besides static uniform colors defined based on their hexadecimal color code or color name, the user can specify a radial or linear color gradient with multiple colorvalues (i.e., radial, horizontal, vertical, diagonal) or dynamic fillcolors by setting a new color (i.e., ‘set’ ) when a certain action occurs (e.g., when the element is clicked) or by animating the color value (i.e., ‘animate’).

stimulus = Grid(1,9)
stimulus.fillcolors = GridPattern.RepeatAcrossElements([
"blue", 
"#d3d3d3",
["radial", "white", "red"], 
["horizontal", "red", "orange", "green", "blue", "indigo", "violet"], 
["vertical", "green", "white", "green"], 
["diagonal", "red", "white"],
['set', "orange", 'to = "purple", begin = "click", dur = "2s"'],
['animate', "red", 'values = "red;orange;green;blue;indigo;violet;red", begin = "2s", calcMode = "linear", dur = "10s", repeatCount = "indefinite"'],
['animate', "red", 'values = "red;orange;green;blue;indigo;violet;red", begin = "2s", calcMode = "discrete", dur = "10s", repeatCount = "indefinite"']
])

   

2.4.3.4 Orientations

Orientations are defined in degrees. Besides static orientation values, dynamic definition of orientations is possible (i.e., ‘set’ or ‘animate’).

stimulus = Grid(1,4)
stimulus.orientations = GridPattern.RepeatAcrossElements([
    0, 
    30,
    ['animate', '0', '360', "begin = 'click', dur='4s', additive='sum'"],
    ['animate', '0', '360', "dur='4s', repeatCount='indefinite'"]
])

   

2.4.3.5 Borderwidths

Borderwidths are defined in user units. Besides static borderwidth values, dynamic definition of borderwidths is possible (i.e., ‘set’ or ‘animate’). Borderwidths are not taken into account when a shape is fit to a particular boundingbox value, which means that only half of the borderwidth will fall within the specified boundingbox. Keep in mind that the default bordercolor is transparent, so bordercolor needs to be set for the borderwidth value to have a visible effect. In addition, borderwidths are not visible for image elements and may behave unexpectedly for path elements.

stimulus = Grid(1,5)
stimulus.bordercolors = GridPattern.RepeatAcrossElements(["black"])
stimulus.borderwidths = GridPattern.RepeatAcrossElements([
    0,
    5,
    ['set', 0, 'to = 15, begin = "click", dur = "2s"'],
    ['animate', 15, 'values = "10;8;6;4;2;0", begin = "2s", calcMode = "linear", dur = "10s", repeatCount = "indefinite"'],
    ['animate', 15, 'values = "10;8;6;4;2;0", begin = "2s", calcMode = "discrete", dur = "10s", repeatCount = "indefinite"']
])

   

2.4.3.6 Bordercolors

Besides static uniform bordercolors defined based on their hexadecimal color code or color name, the user can specify a radial or linear color gradient with multiple bordercolor values (i.e., radial, horizontal, vertical, diagonal) or dynamic bordercolors by setting a new color (i.e., ‘set’ ) when a certain action occurs (e.g., when the element is clicked) or by animating the color value (i.e., ‘animate’). Keep in mind that the default borderwidth is zero, so borderwidth needs to be set for the bordercolor values to have a visible effect.

stimulus = Grid(1,9, col_spacing = 60)
stimulus.borderwidths = GridPattern.RepeatAcrossElements([10])
stimulus.bordercolors = GridPattern.RepeatAcrossElements([
"blue", 
"#d3d3d3",
["radial", "white", "red"], 
["horizontal", "red", "orange", "green", "blue", "indigo", "violet"], 
["vertical", "green", "white", "green"], 
["diagonal", "red", "white"],
['set', "orange", 'to = "purple", begin = "click", dur = "2s"'],
['animate', "red", 'values = "red;orange;green;blue;indigo;violet;red", begin = "2s", calcMode = "linear", dur = "10s", repeatCount = "indefinite"'],
['animate', "red", 'values = "red;orange;green;blue;indigo;violet;red", begin = "2s", calcMode = "discrete", dur = "10s", repeatCount = "indefinite"']
])

   

2.4.3.7 Opacities

Opacities are defined between 0 (no opacity) and 1 (full opacity). Besides static values, dynamic definition of opacities is possible (i.e., ‘set’ or ‘animate’).

stimulus = Grid(1,8)
stimulus.opacities = GridPattern.RepeatAcrossElements([
    0.05,0.25,0.5,0.75,1,
    ['set', 0.05, 'to = 1, begin = "click", dur = "2s"'],
    ['animate', 1, 'values = ".8;.6;.4;.2;0;.2;.4;.6;.8;1", begin = "2s", calcMode = "linear", dur = "10s", repeatCount = "indefinite"'],
    ['animate', 1, 'values = ".8;.6;.4;.2;0;.2;.4;.6;.8;1", begin = "2s", calcMode = "discrete", dur = "10s", repeatCount = "indefinite"']
])

   

2.4.3.8 Mirrorvalues

As element shapes can be mirrored along the horizontal and/or vertical axis, or not be mirrored, this element characteristic can take any of four different values: ‘none’, ‘horizontal’, ‘vertical’, ‘horizontalvertical’. Currently no built-in animation options are available for the mirrorvalue characteristic, but it is possible to either generate separate stimuli and combine them afterwards, or to include dynamic image files (e.g., dynamic svg or gif file with shape changing in mirrovalue) as shape values in the stimulus.

stimulus = Grid(1,4)
stimulus.shapes = GridPattern.RepeatAcrossElements([PathSvg("img/checkmark.svg")])
stimulus.mirrorvalues = GridPattern.RepeatAcrossElements(["none", "horizontal", "vertical", "horizontalvertical"])

   

2.4.3.10 Class labels and id labels

Although classlabels and idlabels do not have a directly visible effect on the resulting element, they can be used to add additional javascript actions or css style changes to individual elements (using the idlabel) or to a group of elements (i.e., all elements with the same classlabel). This method can for example be used to add sounds when hovering over an element or when an element is clicked.

stimulus = Grid(1,4)
stimulus.idlabels = GridPattern.RepeatAcrossElements(["first", "second", "third", "last"])
stimulus.classlabels = GridPattern.RepeatAcrossElements(["left", "left", "right", "right"])

   

2.4.3.11 Data

Data is a hidden element characteristic not used for direct user interaction. It stores the additional arguments given to any of the shapes (e.g., the number of sides for Polygon objects, the source argument for Image objects). This element characteristic may become important when calculating order and complexity measures within the OCTA toolbox, to distinguish between polygons with a different number of sides, different path definitions, different text elements, or images with different sources.

2.5 Deviations

Once all element feature patterns are specified as desired, the user can add position, element, or feature deviations to decrease the order and/or increase the complexity of the stimulus. Although most of these deviation types affect both objective order and objective complexity, some manipulations specifically target either order or complexity. Position, element, and feature deviations are saved separately from the original patterns used to create the stimulus. If multiple deviations are added, later deviations could overwrite earlier deviations if they concern the same position, element or element characteristic.

2.5.1 Position deviations

Random jitter or specified deviations can be added to the element positions. Although the impact of position deviations is dependent on the specific deviations added as well as the position pattern used as starting point, they generally increase objectivecomplexity in the positions used in the stimulus. Element or feature complexity stays unchanged when deviating position only. Whether the position deviations influence objective order in positions, elements, or features depends on the specific deviation. Random jitter or structured deviations can be added to the element positions. For normally distributed or uniformly distributed jitter across all elements, the SetPositionJitter function can be used. One can specify whether the jitter needs to be applied to the x coordinates (‘x’), y coordinates (‘y’), or both (‘xy’ for independent or ‘x=y’ for equal jitter on both axes) using the axis argument. In the case of uniformly distributed jitter, the user specifies the minimum (min_val) and the maximum value (max_val). In the case of normally distributed jitter, the user specifies the mean (mu) and standard deviation (std). Default values for axis, distribution, min_val, max_val, mu, and std are ‘xy,’ ‘normal,’ -1, 1, 0, and 1 respectively. To add specific position deviations, the user specifies the element ids (starting from 0 until n_elements - 1), x offsets and/or y offsets for each of the elements to which a deviation relative to the predetermined position needs to be added. Element id needs to be given an integer value or a list of integer values. X and Y offsets can be numeric values or a list of numeric values.

stimulus = Grid(3,3, background_color="whitesmoke")
stimulus.positions = stimulus.positions.SetPositionJitter(distribution = "uniform", axis = 'y', min_val = -10, max_val = 10)

   

stimulus = Grid(3,3, background_color="whitesmoke")
stimulus.positions = stimulus.positions.SetPositionJitter(distribution = "normal", axis = 'xy', mu = 0, std = 10)

   

2.5.2 Element deviations

To add element deviations, it is possible to remove a set of random or specified elements from the display, to swap the positions of distinct elements, or to randomize the order of all elements in a particular direction. Bringing an additional element into the pattern is possible as well, but requires the user to add an additional element in the stimulus definition and in the definition of the stimulus positions. Swapping or randomizing the position of distinct elements in the display decreases objective element order, but leaves objective position, element, or feature complexity unchanged. If also non-distinct elements would be swapped or randomized, the objective element order may stay unchanged. Removing elements does complicate the position pattern and potentially reduces element or feature complexity, but more generally also decreases element and feature order.

2.5.2.1 Remove elements

2.5.2.1.1 Remove a defined number of elements
stimulus = Grid(6,6)
stimulus.remove_elements(2)

   

2.5.2.1.2 Remove a specific element
stimulus = Grid(6,6)
stimulus.remove_element(1)
stimulus.remove_element([5,0])

   

2.5.2.2 Swap positions of (distinct) elements

stimulus = Grid(6,6)
stimulus.shapes = GridPattern.ElementRepeatAcrossRows([Triangle, Rectangle, Ellipse])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRows(["red", "green", "blue"])

stimulus.swap_distinct_elements(n_swap_pairs = 2, distinction_features=["fillcolors", "shapes"])

   

stimulus = Grid(6,6)
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRows(["red", "green", "blue"])

stimulus.swap_elements(n_swap_pairs = 2)

   

2.5.2.3 Randomize positions of elements

stimulus = Grid(6,6)
stimulus.shapes = GridPattern.ElementRepeatAcrossRows([Triangle, Rectangle, Ellipse])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRows(["red", "green", "blue"])

stimulus.randomize_elements(direction = "AcrossElements")

      

stimulus = Grid(6,6)
stimulus.shapes = GridPattern.ElementRepeatAcrossRows([Triangle, Rectangle, Ellipse])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRows(["red", "green", "blue"])

stimulus.randomize_elements(direction = "AcrossRows")

      

2.5.3 Feature deviations

To add feature deviations in the stimulus, it is possible to change a feature value for a number of random or specified elements, to swap the feature values for a number of random pairs of (distinct) elements in the display, to randomize the order of all feature values in a particular direction, or to jitter any of the numeric feature values across all elements. Swapping or randomizing the position of distinct feature values in the display decreases objective order for the feature dimensions involved, but also increases objective element complexity. Changing a feature value to a value that is not yet in the pattern values for that feature or jittering numeric feature values will additionally increase objective feature complexity. An advantage of adding feature deviations can be that order is distorted on one feature dimension specifically but preserved for other feature dimensions (contrary to what is the case with element deviations).

stimulus = Grid(6,6)
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRows(["red", "green", "blue"])

   

2.5.3.1 Change the value of an element feature for a specific element

stimulus = Grid(6,6)
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRows(["red", "green", "blue"])

stimulus.set_element_fillcolor(2, "yellow")
stimulus.set_element_fillcolor([3,2], ['animate', "red", 'values = "red;orange;green;blue;indigo;violet;red", begin = "2s", calcMode = "linear", dur = "10s", repeatCount = "indefinite"'])

   

2.5.3.2 Swap positions of distinct features

stimulus = Grid(6,6)
stimulus.shapes = GridPattern.ElementRepeatAcrossRows([Triangle, Rectangle, Ellipse])
stimulus.fillcolors = GridPattern.ElementRepeatAcrossRows(["red", "green", "blue"])

stimulus.swap_distinct_features(n_swap_pairs = 2, feature_dimensions = ['fillcolors'])

   

2.5.3.3 Randomization of feature values in the final pattern

2.5.3.4 Jitter of numeric features

If desired, add random jitter or structured deviations to the numeric element characteristics like size or orientation.

stimulus = Grid(6,6)
stimulus.size = stimulus.size.AddUniformJitter(min_val = -5, max_val = 5)
stimulus.orientation = stimulus.orientation.AddNormalJitter(mu = 0, std = 10)

2.6 Order and complexity measures and manipulations

Although deviations are one way to increase or decrease different types of order and complexity in the stimulus, other approaches are possible too.

2.6.1 Order manipulations

Position order can be changed qualitatively by changing the type of position pattern. Element (and feature) order can be changed qualitatively by changing the different element feature pattern types and directions. Be aware that some feature pattern changes may also impact element complexity because of emerging (non-)congruence between different feature patterns.

To induce quantitative changes in order, the user can swap the positions of distinct elements or randomize the elements in the stimulus (which keep element complexity level constant), or add any other element or feature deviations (but these other deviations may influence element or feature complexity as well; cf. Deviations). The user can also make stimulus features and element features (in)congruent to impact the order level of the stimulus. In addition, the congruence of patterns, pattern types, or pattern directions across feature dimensions can be adapted. Keep in mind that changes in pattern congruence may influence element complexity simultaneously.

2.6.2 Complexity manipulations

Qualitative changes in complexity can be achieved by changing the feature dimension on which the complexity is present (e.g., shape, color, or size complexity). Quantitative element complexity changes can include (a) changing the number of visible elements present in the stimulus (i.e., by removing elements or by changing the position pattern of the stimulus); (b) manipulating the variety of elements (i.e., by including more pattern values on a feature dimension, by choosing more diverse pattern values, by adding feature deviations, or by changing the congruence of patterns across feature dimensions); (c) changing the complexity (familiarity, unintellegibility, etc.) of individual feature pattern values (e.g., use complex path shape instead of rectangles); or (d) changing the complexity of individual stimulus features. Position complexity can be changed quantitatively by adding random position jitter or structured position deviations (cf. Position deviations).

2.6.3 Order measurements

OCTA provides some basic functionality to measure aspects of order and complexity in the created stimulus. For order, the user can request the applied patterns, pattern types, and pattern directions across all feature dimensions; check whether all specified feature dimensions have congruent patterns, pattern types, or pattern directions; calculate how many specified feature dimensions have congruent patterns, pattern types, or pattern directions; calculate the number of deviant elements are present given the specified feature dimensions (e.g., by added element or feature deviations); and calculate the number of deviant positions are present in the stimulus (i.e., by added position jitter or specified position deviations).

2.6.4 Complexity measurements

When it comes to complexity measures, it is possible to calculate (a) the number of elements present in the display (N); (b) how many different types of elements are present in the display based on the feature dimensions specified (LOCE); (c) how many different features are present across all feature dimensions (LOC); and (d) how many different feature dimensions have more than one feature value (i.e., have non-identical values; LOCI).

stimulus = Grid(5,5)
stimulus.shapes = GridPattern.RepeatAcrossElements([Rectangle, Ellipse, Triangle])
stimulus.fillcolors = GridPattern.RepeatAcrossElements(["red", "green", "blue"])
stimulus.Show()
## <IPython.core.display.SVG object>

   

2.6.4.1 LOCE

Complexity.CalculateElementsLOCE(stimulus, distinction_features = ['shapes', 'fillcolors', 'boundingboxes', 'data'])
## 3

2.6.4.2 LOC

Complexity.CalculateElementsLOC(stimulus, distinction_features = ['shapes', 'fillcolors', 'boundingboxes', 'data'])
## 8

2.6.4.3 LOCI

Complexity.CalculateElementsLOCI(stimulus, distinction_features = ['shapes', 'fillcolors', 'boundingboxes', 'data'])
## 2

2.7 Output options

Finally, the user can save the resulting stimulus in the preferred format: as a vector-based image (SVG), as a raster-based image (PNG, JPG, PDF, or TIFF), or as a computer-readable file (JSON). The SVG format is recommended for online use, as it has the same quality at all viewing sizes and keeps the possibility for animated element and stimulus features. For raster-based image output, a scale value can be added to increase the quality when starting from a very small stimulus that would otherwise have pixel artefacts. The JSON output can be used to recreate the stimulus in Python using the OCTA toolbox without the original code (using the LoadFromJSON function). 2 In the Shiny app, the user can also view or download the Python code needed to reproduce the current stimulus with the OCTA toolbox in Python.

stimulus = Grid(5,5)
stimulus.Show()

stimulus.SaveJSON('test_stimulus', folder = "output")
stimulus.SaveSVG('test_stimulus', scale = 1, folder = "output")
stimulus.SavePNG('test_stimulus', scale = 2, folder = "output")
stimulus.SaveJPG('test_stimulus', scale = 2, folder = "output")
stimulus.SaveTIFF('test_stimulus', scale = 2, folder = "output")
stimulus.SavePDF('test_stimulus', scale = 2, folder = "output")

   

stimulus = Grid(5,5)
stimulus.Render()
stimulus.GetSVG()
## '<svg baseProfile="full" height="284" version="1.1" width="284" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs /><g transform=" rotate(0, 142, 142)"><rect fill="white" height="284" width="284" x="0" y="0" /><polygon fill="dodgerblue" opacity="1" points="42.0,19.5 26.09009742330268,26.090097423302677 19.5,41.99999999999999 26.090097423302677,57.90990257669731 41.99999999999999,64.5 57.90990257669731,57.90990257669732 64.5,42.00000000000001 57.909902576697334,26.0900974233027" stroke-width="0" transform=" rotate(0, 42, 42)" /><polygon fill="dodgerblue" opacity="1" points="92.0,19.5 76.09009742330268,26.090097423302677 69.5,41.99999999999999 76.09009742330268,57.90990257669731 92.0,64.5 107.90990257669732,57.90990257669732 114.5,42.00000000000001 107.90990257669733,26.0900974233027" stroke-width="0" transform=" rotate(0, 92, 42)" /><polygon fill="dodgerblue" opacity="1" points="142.0,19.5 126.09009742330268,26.090097423302677 119.5,41.99999999999999 126.09009742330268,57.90990257669731 142.0,64.5 157.9099025766973,57.90990257669732 164.5,42.00000000000001 157.90990257669733,26.0900974233027" stroke-width="0" transform=" rotate(0, 142, 42)" /><polygon fill="dodgerblue" opacity="1" points="192.0,19.5 176.0900974233027,26.090097423302677 169.5,41.99999999999999 176.09009742330267,57.90990257669731 192.0,64.5 207.9099025766973,57.90990257669732 214.5,42.00000000000001 207.90990257669733,26.0900974233027" stroke-width="0" transform=" rotate(0, 192, 42)" /><polygon fill="dodgerblue" opacity="1" points="242.0,19.5 226.0900974233027,26.090097423302677 219.5,41.99999999999999 226.09009742330267,57.90990257669731 242.0,64.5 257.90990257669733,57.90990257669732 264.5,42.00000000000001 257.90990257669733,26.0900974233027" stroke-width="0" transform=" rotate(0, 242, 42)" /><polygon fill="dodgerblue" opacity="1" points="42.0,69.5 26.09009742330268,76.09009742330268 19.5,92.0 26.090097423302677,107.90990257669732 41.99999999999999,114.5 57.90990257669731,107.90990257669732 64.5,92.0 57.909902576697334,76.0900974233027" stroke-width="0" transform=" rotate(0, 42, 92)" /><polygon fill="dodgerblue" opacity="1" points="92.0,69.5 76.09009742330268,76.09009742330268 69.5,92.0 76.09009742330268,107.90990257669732 92.0,114.5 107.90990257669732,107.90990257669732 114.5,92.0 107.90990257669733,76.0900974233027" stroke-width="0" transform=" rotate(0, 92, 92)" /><polygon fill="dodgerblue" opacity="1" points="142.0,69.5 126.09009742330268,76.09009742330268 119.5,92.0 126.09009742330268,107.90990257669732 142.0,114.5 157.9099025766973,107.90990257669732 164.5,92.0 157.90990257669733,76.0900974233027" stroke-width="0" transform=" rotate(0, 142, 92)" /><polygon fill="dodgerblue" opacity="1" points="192.0,69.5 176.0900974233027,76.09009742330268 169.5,92.0 176.09009742330267,107.90990257669732 192.0,114.5 207.9099025766973,107.90990257669732 214.5,92.0 207.90990257669733,76.0900974233027" stroke-width="0" transform=" rotate(0, 192, 92)" /><polygon fill="dodgerblue" opacity="1" points="242.0,69.5 226.0900974233027,76.09009742330268 219.5,92.0 226.09009742330267,107.90990257669732 242.0,114.5 257.90990257669733,107.90990257669732 264.5,92.0 257.90990257669733,76.0900974233027" stroke-width="0" transform=" rotate(0, 242, 92)" /><polygon fill="dodgerblue" opacity="1" points="42.0,119.5 26.09009742330268,126.09009742330268 19.5,142.0 26.090097423302677,157.9099025766973 41.99999999999999,164.5 57.90990257669731,157.90990257669733 64.5,142.0 57.909902576697334,126.0900974233027" stroke-width="0" transform=" rotate(0, 42, 142)" /><polygon fill="dodgerblue" opacity="1" points="92.0,119.5 76.09009742330268,126.09009742330268 69.5,142.0 76.09009742330268,157.9099025766973 92.0,164.5 107.90990257669732,157.90990257669733 114.5,142.0 107.90990257669733,126.0900974233027" stroke-width="0" transform=" rotate(0, 92, 142)" /><polygon fill="dodgerblue" opacity="1" points="142.0,119.5 126.09009742330268,126.09009742330268 119.5,142.0 126.09009742330268,157.9099025766973 142.0,164.5 157.9099025766973,157.90990257669733 164.5,142.0 157.90990257669733,126.0900974233027" stroke-width="0" transform=" rotate(0, 142, 142)" /><polygon fill="dodgerblue" opacity="1" points="192.0,119.5 176.0900974233027,126.09009742330268 169.5,142.0 176.09009742330267,157.9099025766973 192.0,164.5 207.9099025766973,157.90990257669733 214.5,142.0 207.90990257669733,126.0900974233027" stroke-width="0" transform=" rotate(0, 192, 142)" /><polygon fill="dodgerblue" opacity="1" points="242.0,119.5 226.0900974233027,126.09009742330268 219.5,142.0 226.09009742330267,157.9099025766973 242.0,164.5 257.90990257669733,157.90990257669733 264.5,142.0 257.90990257669733,126.0900974233027" stroke-width="0" transform=" rotate(0, 242, 142)" /><polygon fill="dodgerblue" opacity="1" points="42.0,169.5 26.09009742330268,176.09009742330267 19.5,192.0 26.090097423302677,207.9099025766973 41.99999999999999,214.5 57.90990257669731,207.90990257669733 64.5,192.0 57.909902576697334,176.0900974233027" stroke-width="0" transform=" rotate(0, 42, 192)" /><polygon fill="dodgerblue" opacity="1" points="92.0,169.5 76.09009742330268,176.09009742330267 69.5,192.0 76.09009742330268,207.9099025766973 92.0,214.5 107.90990257669732,207.90990257669733 114.5,192.0 107.90990257669733,176.0900974233027" stroke-width="0" transform=" rotate(0, 92, 192)" /><polygon fill="dodgerblue" opacity="1" points="142.0,169.5 126.09009742330268,176.09009742330267 119.5,192.0 126.09009742330268,207.9099025766973 142.0,214.5 157.9099025766973,207.90990257669733 164.5,192.0 157.90990257669733,176.0900974233027" stroke-width="0" transform=" rotate(0, 142, 192)" /><polygon fill="dodgerblue" opacity="1" points="192.0,169.5 176.0900974233027,176.09009742330267 169.5,192.0 176.09009742330267,207.9099025766973 192.0,214.5 207.9099025766973,207.90990257669733 214.5,192.0 207.90990257669733,176.0900974233027" stroke-width="0" transform=" rotate(0, 192, 192)" /><polygon fill="dodgerblue" opacity="1" points="242.0,169.5 226.0900974233027,176.09009742330267 219.5,192.0 226.09009742330267,207.9099025766973 242.0,214.5 257.90990257669733,207.90990257669733 264.5,192.0 257.90990257669733,176.0900974233027" stroke-width="0" transform=" rotate(0, 242, 192)" /><polygon fill="dodgerblue" opacity="1" points="42.0,219.5 26.09009742330268,226.09009742330267 19.5,242.0 26.090097423302677,257.90990257669733 41.99999999999999,264.5 57.90990257669731,257.90990257669733 64.5,242.0 57.909902576697334,226.0900974233027" stroke-width="0" transform=" rotate(0, 42, 242)" /><polygon fill="dodgerblue" opacity="1" points="92.0,219.5 76.09009742330268,226.09009742330267 69.5,242.0 76.09009742330268,257.90990257669733 92.0,264.5 107.90990257669732,257.90990257669733 114.5,242.0 107.90990257669733,226.0900974233027" stroke-width="0" transform=" rotate(0, 92, 242)" /><polygon fill="dodgerblue" opacity="1" points="142.0,219.5 126.09009742330268,226.09009742330267 119.5,242.0 126.09009742330268,257.90990257669733 142.0,264.5 157.9099025766973,257.90990257669733 164.5,242.0 157.90990257669733,226.0900974233027" stroke-width="0" transform=" rotate(0, 142, 242)" /><polygon fill="dodgerblue" opacity="1" points="192.0,219.5 176.0900974233027,226.09009742330267 169.5,242.0 176.09009742330267,257.90990257669733 192.0,264.5 207.9099025766973,257.90990257669733 214.5,242.0 207.90990257669733,226.0900974233027" stroke-width="0" transform=" rotate(0, 192, 242)" /><polygon fill="dodgerblue" opacity="1" points="242.0,219.5 226.0900974233027,226.09009742330267 219.5,242.0 226.09009742330267,257.90990257669733 242.0,264.5 257.90990257669733,257.90990257669733 264.5,242.0 257.90990257669733,226.0900974233027" stroke-width="0" transform=" rotate(0, 242, 242)" /></g></svg>'
stimulus = Grid(5,5)
stimulus.Render()
stimulus.GetJSON()
## {'stimulus': {'stimulustype': 'Grid', 'n_elements': 25, 'n_rows': 5, 'n_cols': 5, 'row_spacing': 50, 'col_spacing': 50, 'shape': None, 'shape_boundingbox': None, 'autosize_method': 'maximum_boundingbox', 'x_margin': (20, 20), 'y_margin': (20, 20), 'size': 'auto', 'width': 284, 'height': 284, 'background_color': 'white', 'background_shape': '"auto"', 'stim_mask': '"none"', 'stim_orientation': 0, 'stim_mirrorvalue': None, 'stim_link': None, 'stim_classlabel': None, 'stim_idlabel': None}, 'positions': {'positiontype': 'RectGrid', 'positionparameters': '{"n_rows": 5, "n_cols": 5, "row_spacing": 50, "col_spacing": 50}', 'jitter': None, 'jitterparameters': '{}', 'deviation': None, 'deviationparameters': '{}'}, 'elements': {'element_id': '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]', 'positions': '{"py/object": "octa.Positions.Positions", "_x": {"py/object": "octa.patterns.Pattern.Pattern", "pattern": [0, 50, 100, 150, 200, 0, 50, 100, 150, 200, 0, 50, 100, 150, 200, 0, 50, 100, 150, 200, 0, 50, 100, 150, 200], "patternclass": "Pattern", "patterntype": "RepeatPattern", "patterndirection": ""}, "_y": {"py/object": "octa.patterns.Pattern.Pattern", "pattern": [0, 0, 0, 0, 0, 50, 50, 50, 50, 50, 100, 100, 100, 100, 100, 150, 150, 150, 150, 150, 200, 200, 200, 200, 200], "patternclass": "Pattern", "patterntype": "RepeatElements", "patterndirection": ""}, "_position_type": "RectGrid", "_position_parameters": {"n_rows": 5, "n_cols": 5, "row_spacing": 50, "col_spacing": 50}, "_deviation": null, "_deviation_parameters": {}, "_jitter": null, "_jitter_parameters": {}}', 'shapes': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [{"py/type": "octa.shapes.Polygon.Polygon_"}], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'boundingboxes': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [{"py/tuple": [45, 45]}], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'fillcolors': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": ["dodgerblue"], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'orientations': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [0], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'borderwidths': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [0], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'bordercolors': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [""], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'opacities': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [1], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'mirrorvalues': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [""], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'links': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [""], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'idlabels': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [""], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'classlabels': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": [""], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'data': '{"py/object": "octa.patterns.GridPattern.RepeatAcrossElements", "pattern": ["8"], "patternclass": "GridPattern.", "patterntype": "Repeat", "patterndirection": "AcrossElements", "n_rows": 5, "n_cols": 5, "_jitter": null, "_jitter_parameters": {}, "_randomization": null}', 'overrides': '[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]', 'element_order': '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]'}}

  1. The x and y coordinates defined in the template (not the actual positions after adding deviations!) can be requested separately by using stimulus.positions.x and stimulus.positions.y respectively.↩︎

  2. Keep in mind that to reproduce the exact same stimulus, one needs to set a seed before running (each line of) the OCTA Python code and also keep this seed (these seeds) along with the JSON file in case any randomization procedures are used in the stimulus generation. The safest way to ensure reproducibility is keeping track of the code and OCTA toolbox version you used to create your stimuli.↩︎