Threading rules

SciQLop’s window runs on one thread, the GUI thread. Your notebook cells run on another, the kernel thread. Qt objects must only be touched from the GUI thread.

  1. You call a user_api function from a cell.
  2. The function sends the work to the GUI thread and waits for it to finish.
  3. You get the result back in your cell, as if the call were local.

So user_api calls are safe from cells, plugins and agents. A few rules keep it that way:

  • Go through user_api. Don’t import SciQLopPlots or Qt objects to change plots, and don’t call methods on them in a loop.
  • Never touch _impl. It is the raw Qt object behind a wrapper. Calling it from a cell can crash SciQLop.
  • Data callbacks run on worker threads. Virtual products, plot_function callbacks, histogram callbacks and layers compute data and return it. They should not drive the GUI.
  • For your own Qt code, use SciQLop.user_api.threading:
from SciQLop.user_api.gui import get_main_window
from SciQLop.user_api.threading import on_main_thread, invoke_on_main_thread
 
@on_main_thread
def my_helper(widget):
    widget.setWindowTitle("Analysis")      # runs on the GUI thread
 
title = invoke_on_main_thread(lambda: get_main_window().windowTitle())

on_main_thread is a decorator. It does nothing when you are already on the GUI thread. invoke_on_main_thread runs one call there and returns its result. The preloaded app and main_window are already wrapped this way.

Next release

Outside the GUI thread, a wrapper’s _impl becomes a proxy that forwards each call to the GUI thread. Code that reaches into _impl stops crashing. It is still internal API.

Common errors

Misuse raises a clear exception instead of failing silently. The messages you are most likely to meet:

ExceptionMessage starts withCause
ValueErrorcannot plot product '...': not found in the products treeWrong path, or the provider is not available.
ValueErrorinvalid product ...Empty path, None, a number.
ValueErrorplot() could not interpret its argumentsplot() got something it can’t plot.
ValueErrory data is required unless x is a SpeasyVariableplot_data(x) with plain arrays.
ValueErrorscalar (0-d) data is not plottableA single number instead of an array.
ValueErrorcomplex data is not plottableTake .real, .imag or np.abs() first.
ValueErrortime range bounds must be finiteA NaN or infinite bound.
ValueErrorzero-width time rangestart == stop.
TypeErrorexpected a TimeRange or a (start, stop) pairA single string or date given as a range.
ValueErrorzero-width y-axis rangeset_y_range(1, 1) and friends.
ValueErroraxis 'w' not available on this plotAxis names are "x", "y", "y2", "z".
IndexErrorplot_index 5 out of range (0..2)remove_plot or add_layer on a plot that doesn’t exist.
ValueErrorThe plot panel does not exist anymore.The panel was closed. Same for plots, graphs and items.
ValueErrorUnsupported format '.svg'save() only writes png, pdf, jpg, jpeg and bmp.
OSErrorfailed to save ...The folder doesn’t exist or is not writable.
ValueErrorzoom_limit_seconds must be >= 0Negative zoom limit.
TypeErrorpanel name must be a strplot_panel(0).
RuntimeErrorthis plot already contains a colormap-style plottableA second colormap, histogram or waterfall in one plot.
ValueErrorhistogram bins must be >= 1 / exceeds the 25,000,000-cell sanity capBad bin counts.
ValueErrorunknown gradient 'viridis'A gradient name that doesn’t exist (or isn’t readable by name in v0.13.1).
TypeErrorline_style must be a GraphLineStyleA Qt pen style or string passed as line_style.
ValueErrorScalar virtual products need exactly one labellabels doesn’t fit the product type.
TypeErrorproduct_type must be a VirtualProductTypeA string like "scalar" instead of the enum.
ValueErrorvirtual product path must be a non-empty product-tree pathEmpty path or empty segment.
KeyErrorCatalog not found / Provider not foundWrong catalog path.
PermissionErrorProvider '...' cannot create catalogsRead-only provider, like Remote.
ValueErrorevent start must be before stopAn event with start > stop.
TypeErrorfiltfilt(data, coeffs, ...) requires a SpeasyVariablePlain arrays given to dsp. Use dsp.arrays.

When a data callback fails, the error is not raised in your cell: the callback runs later, on a worker thread. Look for the red message over the plot. For layers, read renderer.last_error.