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
ParameterandPipeline.paramsto build smaller widgetsSet
Pipeline.paramsbased on the inputs of corresponding widgets
Tutorial: Implement Random Distribution Histogram Workflow GUI#
Prepare
workflowinterface that returns asciline.PipelineWe are going to use this random number histogram workflow as an example. We would like to implement a widget that users can putNumBinsas an input.If you want to specify
typical_outputs, thePipelineobject should have a property calledtypical_outputs: tuple[type, ...]. If it does not have atypical_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 |
Register workflow to the
workflow_registry.
[2]:
from ess.reduce.workflow import register_workflow
register_workflow(RandomDistributionWorkflow)
[2]:
<function __main__.RandomDistributionWorkflow() -> sciline.pipeline.Pipeline>
Register
domain-type-Parameterinstance mapping toparameter_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)
Register
type[Parameter]-type[Widget]mapping tocreate_parameter_widgetdistpatch.
[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()
Note that the order of operations here is important. We have to first set the workflow, then display the widget, and then interact with the widget programmatically. Otherwise, the docs build can freeze. It is unclear why this happens.
[6]:
from ess.reduce.ui import WorkflowWidget
ess_widget.children[0].children[0].value = RandomDistributionWorkflow
[7]:
ess_widget
[7]:
[8]:
body_widget: WorkflowWidget = ess_widget.children[1].children[0]
body_widget.output_selection_box.typical_outputs_widget.value = [Histogram]
body_widget.parameter_box.parameter_refresh_button.click()
body_widget.result_box.run_button.click()
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.
[9]:
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
[9]:
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.
[10]:
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
[10]:
If Parameter object is both switchable and optional, the widget is wrapped both in SwitchWidget and OptionalWidget.
[11]:
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
[11]: