Graphic user interfaces for data reduction.#

ess.reduce.parameter.Parameter is an interface between params of sciline.Pipeline(workflow) and GUI components.

In this page, we will explain how to implement new Parameter and widgets and map those components with each other.

Registries: How to map Parameter, Workflow and Pipeline.#

Parameters, workflows and widgets should be registered once they are implemented in order to be used automatically by workflow-widget building helper.

The helper uses registries to - Find workflows - Find input arguments that can be set by widget - Find output types that can be computed by widget - Map Parameter and Pipeline.params to build smaller widgets - Set Pipeline.params based on the inputs of corresponding widgets

Tutorial: Implement Random Distribution Histogram Workflow GUI#

  1. Prepare workflow interface that returns a sciline.Pipeline We are going to use this random number histogram workflow as an example. We would like to implement a widget that users can put NumBins as an input.

    If you want to specify typical_outputs, the Pipeline object should have a property called typical_outputs: tuple[type, ...]. If it does not have a typical_outputs, leaf nodes will be used as typical outputs.

[1]:
import scipp as sc
import sciline as sl
from typing import NewType
import numpy as np


NumBins = NewType('NumBins', int)
Histogram = NewType('Histogram', sc.Variable)


def histogram(num_bins: NumBins) -> Histogram:
    rng = np.random.default_rng()
    events = sc.array(dims=['event'], values=rng.normal(size=500))
    return Histogram(events.hist(event=num_bins))


def RandomDistributionWorkflow() -> sl.Pipeline:
    wf = sl.Pipeline(providers=(histogram,))
    # wf.typical_outputs = (Histogram, )  # Can be skipped since it's the only leaf node.
    return wf


RandomDistributionWorkflow()

[1]:
Name Value Source
Histogram
histogram __main__.histogram
NumBins
  1. Register workflow to the workflow_registry.

[2]:
from ess.reduce.workflow import register_workflow

register_workflow(RandomDistributionWorkflow)
[2]:
<function __main__.RandomDistributionWorkflow() -> sciline.pipeline.Pipeline>
  1. Register domain-type-Parameter instance mapping to parameter_registry.

Parameter and its subclasses have a class method from_type that helps to create a new parameter instance for a specific domain type.

There are various parameter types already exist in ess.reduce.parameter module, but here we will show you how to make a new one.

Once you know which Parameter to use for the specific domain-type,

you can register the new parameter instance to the domain type in the parameter_registry.

[3]:
from ess.reduce.parameter import parameter_registry, Parameter


class BinNumberParameter(Parameter): ...


parameter_registry[NumBins] = BinNumberParameter(
    name=NumBins.__name__,
    description='Number of bins in the histogram',
    default=NumBins(10),
)

# You can also use ``from_type``(class method) helper to instantiate the parameter.
# parameter_registry[NumBins] = BinNumberParameter.from_type(NumBins)
  1. Register type[Parameter] - type[Widget] mapping to create_parameter_widget distpatch.

[4]:
from ess.reduce.widgets import create_parameter_widget
import ipywidgets as widgets


@create_parameter_widget.register(BinNumberParameter)
def scalar_parameter_widget(param: BinNumberParameter):
    return widgets.IntText(
        value=param.default, description=param.name, tooltip=param.description
    )

Example UI from the tutorial.#

[5]:
from ess.reduce.ui import workflow_widget

ess_widget = workflow_widget()
[7]:
ess_widget
[7]:

Wrapper Widgets#

In order to handle special cases of parameter settings, we have wrapper widgets.

Each wrapper widget is associated with certain attribute of Parameter object.

They are implemented as a decorator around widget type dispatch function like below.

It is because of @singledispatch decorator.

# In ess.reduce.widgets module
@switchable_widget
@optional_widget  # optional_widget should be applied first
@singledispatch
def create_parameter_widget(param: Parameter) -> widgets.Widget: ...

Switchable Widget: Parameter.switchable#

Widgets are wrapped in SwitchableWidget if Parameter is switchable.

The wrapped parameter input widget can be turned off and on.

If the widget is enabled(on), the workflow-compute handling widget should set the value as a parameter into the Pipeline(workflow), but if the widget is not enabled(off), the workflow-compute handling widget should skip setting the value as a parameter.

It means it will either use the default parameter that was set or computed by providers.

[8]:
from ess.reduce.widgets import create_parameter_widget
from ess.reduce.parameter import Parameter

switchable_parameter = Parameter(
    name='SwitchableParameter', description="", default="", switchable=True
)
switchable_widget = create_parameter_widget(switchable_parameter)
switchable_widget.enabled = True
switchable_widget
[8]:

Optional Widget: Parameter.optional#

Widgets are wrapped in a OptionalWidget if Parameter is optional.

The wrapped parameter input widget can select None as a value.

If None is selected, the workflow-compute handling widget should set None as a parameter into the Pipeline(workflow), but if the widget is not None, the workflow-compute handling widget will retrieve the value from the wrapped widget.

This wrapper is for the providers expecting optional arguments and handle the None itself.

[9]:
from ess.reduce.widgets import create_parameter_widget
from ess.reduce.parameter import Parameter

optional_parameter = Parameter(
    name='OptionalParameter', description="", default="", optional=True
)
optional_widget = create_parameter_widget(optional_parameter)
optional_widget.value = "Test String"
optional_widget
[9]:

If Parameter object is both switchable and optional, the widget is wrapped both in SwitchWidget and OptionalWidget.

[10]:
from ess.reduce.widgets import create_parameter_widget
from ess.reduce.parameter import Parameter

switchable_and_optional_widget = create_parameter_widget(
    Parameter(
        name='Parameter', description="", default="", switchable=True, optional=True
    )
)
switchable_and_optional_widget.enabled = True
switchable_and_optional_widget
[10]: