Starting my first ewoks orange widget#
In this chapter we will:
see how to do a basic connection between an ewoks task and an orangecontrib widget
Setting up ewoksorange#
Please make sure you already have ewoksorange install on your python environment. Else please see installation
Defining an ewoks task#
For this tutorial we would like to add a dedicated interface for the a task performing some data clipping from percentiles. We already have implemented it with ewokscore.
"""
clipdata.py: Core code for the ClipDataTask, which is a task to rescale 'data' (numpy array) to the given percentiles.
Includes the ewoks task and the pydantic models.
"""
import numpy
from ewokscore.model import BaseInputModel
from ewokscore.model import BaseOutputModel
from ewokscore.task import Task
from pydantic import Field
class InputModel(BaseInputModel):
data: numpy.ndarray = Field(..., description="data to rescale")
percentiles: tuple[float, float] = Field(
...,
description="""percentiles to use for rescaling, must be a tuple of two values (p_min, p_max) with p_min <= p_max""",
)
class OutputModel(BaseOutputModel):
data: numpy.ndarray = Field(..., description="rescaled data")
class ClipDataTask(
Task,
input_model=InputModel,
output_model=OutputModel,
):
"""
Task to rescale 'data' (numpy array) to the given percentiles.
"""
def run(self):
data = self.inputs.data
# compute data min and max
percentiles = self.inputs.percentiles
self.outputs.data = numpy.clip(
data,
a_min=numpy.percentile(data, percentiles[0]),
a_max=numpy.percentile(data, percentiles[1]),
)
Note
While input_model and output_model are optional, using them ensures type consistency and improves integration with Orange (allows link type data checking for example).
For this tutorial, assume they are defined.
Hint
For simplicity, this tutorial keeps models in the same file as the task. For complex projects please consider defining them in a dedicated file (e.g., my_project.models.clip_data) and import them.
Associate a task to a dedicated orange widget#
Now we want to create a widget associated to the task.
There is different ways to define the ewoks tasks execution with orange (see Ewoks widgets and execution).
On this example we will take the ewoks widget doing the processing in a single thread (Execute the associated Ewoks task in a single dedicated thread). Because this will make sure the gui will not freeze with it and we don’t need concurrent execution.
Widget ‘skeleton’ is the following:
1from ewoksorange.gui.owwidgets.threaded import OWEwoksWidgetOneThread
2from [my_project].tasks.clipdata import ClipDataTask
3
4class OWClipData(
5 OWEwoksWidgetOneThread,
6 ewokstaskclass=ClipDataTask,
7):
8 name = "rescale data"
9 id = "orange.widgets.my_project.ClipDataTask"
10 description = (
11 "widget to clip data (numpy array) within a percentile range."
12 )
13 pass
Hint
l1: import of the required base class for the widget.
l2: import of the task to bind with the widget.
l4: OW stand for Orange Widget.
l5: inheritance with the ewoks orange widget.
l6: definition of the ewoks task to bind. This is usually given with the full module path. For example if RescaleDataTask is saved in my_project.tasks.rescale the value would be my_project.tasks.rescale.RescaleDataTask.
l8: the name of the widget (will be displayed in the canvas).
l9: id from the orange point of view. It should be constant with time to ensure workflow compatibility.
l10: tooltip of the widget.
Tip
If Orange is installed, you can preview the widget by running
from Orange.widgets.utils.widgetpreview import WidgetPreview
WidgetPreview(OWClipData).run()
Results
"""
clipdata.py: Core code for the ClipDataTask, which is a task to rescale 'data' (numpy array) to the given percentiles.
Includes the ewoks task and the pydantic models.
"""
import numpy
from ewokscore.model import BaseInputModel
from ewokscore.model import BaseOutputModel
from ewokscore.task import Task
from pydantic import Field
class InputModel(BaseInputModel):
data: numpy.ndarray = Field(..., description="data to rescale")
percentiles: tuple[float, float] = Field(
...,
description="""percentiles to use for rescaling, must be a tuple of two values (p_min, p_max) with p_min <= p_max""",
)
class OutputModel(BaseOutputModel):
data: numpy.ndarray = Field(..., description="rescaled data")
class ClipDataTask(
Task,
input_model=InputModel,
output_model=OutputModel,
):
"""
Task to rescale 'data' (numpy array) to the given percentiles.
"""
def run(self):
data = self.inputs.data
# compute data min and max
percentiles = self.inputs.percentiles
self.outputs.data = numpy.clip(
data,
a_min=numpy.percentile(data, percentiles[0]),
a_max=numpy.percentile(data, percentiles[1]),
)
"""
OWClipData.py: Code for the orange add-on binding.
"""
from ewokstesttuto.tasks.clipdata import ClipDataTask
from ewoksorange.gui.owwidgets.threaded import OWEwoksWidgetOneThread
class OWClipData(
OWEwoksWidgetOneThread,
ewokstaskclass=ClipDataTask,
):
name = "rescale data"
id = "orange.widgets.my_project.ClipDataTask"
description = "widget to clip data (numpy array) within a percentile range."
pass