Quickstart

Quibbler allows easily creating highly interactive and efficient data analysis applications. Importantly, in Quibbler, efficiency and interactivity are enabled naturally, using completely standard Python, NumPy and Matplotlib programming syntax. There is thereby very little to learn to get started.

This page provides a minimal-demo quickstart to quickly get you up and running.

For additional demos, consider also the Examples.

For a more methodological tour, see the Introduction to Quibbler and the complete User Guide.

Setting up

Install

Before starting, please first install Quibbler.

Import

Quibbler for python, pyquibbler, is conventionally imported as qb. In addition, it is convenient to specifically import some often-used functions such as iquib (which will be explained below). Following import, we execute qb.initialize_quibbler(), which initializes Quibbler and configures NumPy and Matplotlib functions to work with Quibbler. A typical import therefore looks as follows:

# Quibbler import:
import pyquibbler as qb
from pyquibbler import iquib
qb.initialize_quibbler()

# Other imports:
import numpy as np
import matplotlib.pyplot as plt

Graphics backend

pyquibbler works well with the tk backend. In PyCharm, specify matplotlib.use("TkAgg"). In Jupyter Lab, specify:

%matplotlib tk

Example of a minimal app

As a simple example, we will build towards a simple data analysis app for cutting a square sub-image from a source image with a specified width around a chosen x-y point.

Writing such a code classically will require the use of callback functions to implement user interactions. In Quibbler, in contrast, we just need to define our parameters and plot them; the resulting graphics is automatically interactive.

Let’s start with a code for interactively choosing our x-y point:

# Figure setup
plt.figure(figsize=(4, 5))
plt.axis('square')
plt.axis([0, 500, 0, 500]);
# Plot a "live" marker and text at defined xy-coordinates:
xy = iquib([250, 250])
x, y = xy
plt.plot(x, y, 'o', color='Orange', markersize=16)
plt.text(x, y + 30, xy, color='Orange', fontsize=16, ha='center');
_images/Quickstart_assign_xy_and_drag.gif

As we can see, except for the use of the function iquib() (explained below), the rest is a completely standard Python code for plotting a marker at position x=250, y=250 and adding next to it a text label specifying these coordinates. Indeed, running this code plots the marker and the text as classically expected. Yet, unlike in classical programming, in Quibbler the data items and graphics are all bi-directionally linked. First, assigning new values to an upstream variable, say assigning xy[0] = 100, immediately refreshes all downstream graphics (the marker and the text move and the text label refreshes). Second, the plotted marker is in fact draggable and, as we drag it, we see that it leads to changes in the upstream variable xy and in any dependent graphics, such as the text label and location.

In Quibbler, thereby, we can easily get interactive functionality while using completely standard programming syntax and without the need for the tedious programming of event-specific callback functions for implementing each interactive behavior.

How does it work?

Below, we briefly explain the above example, while providing a more general view of Quibbler functionality. You can also skip this section and continue with building our image-cutting example app below.

The quib object

Quibbler functionality is based on the Quib object. The quib is an object that represents an output value as well as the function and arguments used to calculate this value. There are two major types of quibs: input quibs (i-quibs) which take a regular Python object as their argument and present it as their value, and function quibs (f-quibs) that calculate their output value by applying a given function to a given list of arguments, which could include other quibs and any other Python objects.

Input-quibs

Input-quibs are created using the function iquib(), which transforms any regular Python object into a quib. In our case, xy = iquib([250, 250]) creates an i-quib xy whose value is the list [250, 250].

Function-quibs

Function-quibs are created naturally whenever we use quibs as part of standard expressions, functions or statements. Indeed, Quibbler modifies standard functions and operators such that they can work directly with quibs. Such Quibbler-supported functions, also called quiby functions, include not only many standard Python, NumPy and Matplotlib functions (see List of quiby functions), but also operators (such as +, -, <, >, **, @, etc.), and any indexing syntax (e.g., if xy is a quib, xy[0] is a function quib, whose function is to get the 0’th item from xq). We can therefore easily define a chained network of function quibs using standard programming syntax.

In our case, x, y = xy creates the f-quibs x = xy[0] and y = xy[1] whose function is to reference xy at positions 0 and 1, respectively. Next, the command plt.plot(x, y, ...) defines an f-quib whose function is to perform plt.plot on the values of x and y. Similarly, y + 30 is a function quib that adds 30 to the value of y, and then plt.text(...) is a function quib that calls the Matplotlib function plt.text with the values of its quib arguments.

Upstream changes automatically propagate to affect the value of downstream quibs

All of these quibs are created declaratively: they are functional objects whose value changes upon upstream changes. As seen in the video above, we can probe the value of any quib using the get_value method. When we make an assignment to the upstream i-quib xy, say assigning xy[0] = 100, we change the value of xy, which in turn changes the value of x, which in turn changes the plot and the text.

Interaction with the graphics is inverse-propagated, leading to changes in upstream quibs

The relation above can also go backward. Quibbler allows dragging the plotted graphics while translating such user interactions with the graphics into assignments to the corresponding quib arguments of the plt.plot function. Such assignments into the arguments of the plot function are then inverse-propagated further upstream, ultimately actualized as changes to upstream quibs (typically reaching upstream i-quibs; see Assignments). In our case dragging the marker is initially translated into assignments to the x and y quibs. Since x and y are function quibs, these assignments are further inverse-propagated upstream to the i-quib xy where they are actualized. The resulting change in xy then percolates downstream to affect the plotted marker as well as text position and the text label.

Building interactive data analysis apps

The above principles can be used to easily build powerful bi-directional relationships between data, parameters and graphics.

Continuing towards building our image-extracting app, let us extend the coordinate-choosing code above, allowing the interactive specification of a square area around this chosen point. We will define and plot our square area as a box extending length d from our defined x-y coordinates:

# Define and plot a square around the xy-point
d = iquib(120)
plt.plot(d * np.array([ -1,  1,  1, -1, -1]) + x,
         d * np.array([ -1, -1,  1,  1, -1]) + y,
         color='Orange');
_images/Quickstart_assign_d_and_drag.gif

As we can see, this plot too is automatically interactive. Dragging any of the corners of the square is inverted into an assignment to the iquib d which then refreshes all other corners of the plotted square (to further understand how Quibbler chooses to invert these dragging actions into changes in d rather than in x or y, see Assignments, or the Dragging whole object versus individual points example).

Connecting quibs with widgets

Quibs can also readily connect with standard Matplotlib widgets, easily enabling interactive data exploration. Here again, unlike in standard programming, using quibs in widgets automatically creates an interactive behavior without requiring us to specifically implement a callback function for each widget.

As an example, let’s add a slider controlling the size of our square box.

As the box is extending from -d to d, we will define its size by:

box_size = 2 * d + 1

To connect widgets with quibs, we simply use standard Matplotlib widgets, while specifying a quib as the value of the widget. In our case, we will create a slider with its value set to the function quib box_size:

from matplotlib.widgets import Slider
main_ax = plt.gca()
slider_ax = plt.axes([0.3, 0.05, 0.5, 0.04])
Slider(ax=slider_ax, label='box_size', valmin=1, valmax=500,
       valinit=box_size);
_images/Quickstart_widget_box_size.gif

As we can see, the created widget is bi-directionally linked with the data. First, when we drag the square corners, d changes and the slider automatically moves. Conversely, when we drag the slider, these interactions are inverted into assignments to box_size and, since box_size is a function quib, the change further propagates to affect d, thereby refreshing the plotted square. Note that as d is defined as an integer, box_size, which is defined as 2 * d + 1 is, by definition, an odd number. Indeed, as we drag the slider, Quibbler restricts the dragging to odd numbers (to understand more on dragging between restricted values, see Dragging graphics between fixed points).

Loading data from files

Function quibs can also implement functions that read data from files. Simply setting the file name as a quib, a standard file-accessing function will create a function quib that loads the file.

In our example, we will load and plot the source image that we want to cut based on our defined square. Let’s look at an example of a microscopy image of fluorescently-labeled bacteria growing inside small micro-droplets (credit: Einat Tamar, Kishony lab, Technion):

# Load and plot source image
filename = iquib('bacteria_in_droplets.tif')
img = plt.imread(filename)
main_ax.imshow(img);
_images/Quickstart_load_image.gif

Concluding our image cutting app

We are now ready to conclude our app. We will use the quibs x, y and d, defined above, to extract a square region from our source image. To do this, we will simply define img_cut = img[y-d:y+d, x-d:x+d, :], which creates a function quib that performs the slicing operation. A plot of this sub-image will thereby automatically update when we move our center x-y point, or change the width of our defined square.

Here thereby is our entire code:

# Prepare figure
plt.figure(figsize=(8, 5))
plt.subplot(1, 2, 1)
plt.axis('square')
plt.axis([0, 500, 0, 500]);

# Plot a "live" marker and text at defined xy-coordinates:
xy = iquib([250, 250])
x, y = xy
plt.plot(x, y, 'o', color='Orange')
plt.text(x, y + 20, xy, color='Orange', ha='center');

# Define and plot a square around the xy-point
d = iquib(120)
plt.plot(d * np.array([ -1,  1,  1, -1, -1]) + x,
         d * np.array([ -1, -1,  1,  1, -1]) + y,
         color='Orange');

# Define the box-size slider:
box_size = 2 * d + 1
slider_ax = plt.axes([0.2, 0.05, 0.25, 0.04])
Slider(ax=slider_ax, label='box_size', valmin=1, valmax=500,
       valinit=box_size);

# Load and plot source image
filename = iquib('bacteria_in_droplets.tif')
img = plt.imread(filename)
plt.subplot(1, 2, 1).imshow(img);

# Cut and plot the sub-image:
img_cut = img[y-d:y+d, x-d:x+d, :]
plt.subplot(1, 2, 2).imshow(img_cut, origin='lower');
_images/Quickstart_interactive_image_cut.gif

Conclusions

As we have seen, Quibbler provides a fast, easy and intuitive way to analyze data in an inherently interactive way. Notably, such interactive analysis appears naturally in Quibbler without us needing to worry about the traditional, and often tedious, programming of specific callback functions for each interactive behavior. Instead, we can set our mind on the analysis from a data-oriented forward-looking standpoint, and let Quibbler deal with any asynchronous graphics or widget events.

Other major features of Quibbler

Beyond Interactivity, other important capabilities of Quibbler, not demonstrated above, include:

1. Efficiency. Upon changes to parameters, Quibbler tracks and identifies which specific downstream data items, or even specific elements thereof, are affected and only recalculates these affected items. See Diverged evaluation.

2. Traceability Quibbler allows us to easily probe which specific inputs affect a downstream result, and vise versa, which downstream results are affected by a given focal parameter. See ancestors, descendants.

3. Overriding. Function quibs can be overridden, streamlining exception specifications to default behaviors. See Overriding default functionality.

4. Transparency. Inputs, as well as exceptions and overrides, are saved in simple human-readable files. See Project save/load and the Quibbler Jupyter lab extension. See Using Quibbler with Jupyter lab.