Orange的扩展插件Widgets开发(一)-快速入门

原创
2016/01/01 10:05
阅读数 5.4K

亲手翻译,欢迎转载。动态修订,请附原址:http://my.oschina.net/u/2306127/admin/edit-blog?blog=595479

原文(英)来自于:http://orange-development.readthedocs.org/tutorial.html 

关于Orange Widgets的开发完整教程参见:http://orange-development.readthedocs.org/

注意:原文有一些错误,经作者试验后此文已经修正,并附上一些经验和运行结果图。

快速开始

Orange Canvas是Orange的可视化程序环境,而Widgets 是Orange Canvas中运行的组件。Canvas提供了Widgets自包含的功能性函数,并且提供了一个图形用户界面,可以通过拖拽来快速构建数据处理流程和数据分析的工作流。Widgets互相之间可以通讯、可以传递对象,通过一个通讯Channel来实现。

这里,我们将介绍一个简单的例子,并且展示如何构建一个简单的Widgets的方法,然后让它在Canvas中运行起来。

本文的例程完整运行的情况(译者注:此处为作者试验的结果,将输入和加法操作执行了两遍):

预备知识

每一个Orange widget属于category并且有一个在category中的优先级。当打开Orange Canvas,在Orange的可视化设计环境,widgets就在左边的toolbox列出来:

_images/widgettoolbox.png

每一个widget名字描述和input/outputs集合,这是widgets的元数据。

该元数据在Orange Canvas应用启动时自动发现和加载。通过setuptools发布的 entry points 协议在python中加载。Orange Canvas使用orange.widgets入口点来寻找widgets并加载。

[译者注:默认安装环境下,第三方的widgets一般位于Orange/orange3env/lib/python3.4/site-packages/orangecontrib/下,而加载该插件的元数据在其上级目录中*.egg_info的目录下的entry_points.txt文件中,该目录的其它文件与python的安装文件完全相同。Canvas启动时首先寻找*.egg_info文件,然后依据entry_points.txt去加载插件代码。

典型的entry_points.txt文件内容如下:

[orange.widgets]
MyWorker = orangecontrib.myworker.widgets

[orange.widgets.tutorials]
exampletutorials = orangecontrib.myworker.tutorials

[orange3.addon]
myworker = orangecontrib.myworker

定义一个输入widget

OWWidget是可以被Orange Canvas工作流加载的widget的基类。

在Canvas框架中的每一个 widget 都需要定义其元数据,包括widget名称和文本描述以及更重要的input/output指定。通过在widget的class namespace定义常量来提供(译者注:这个方式将定义与代码写在一个文件中,更方便使用,与传统的将其写在一个定义文件中有所不同)。下面开始一个简单的例子,widget 输出一个简单的用户指定的integer。

#译者注:此处有修改,原文的运行不过。
#图标可以从其它目录拷贝,放在./icons/下即可。

from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtGui import *
from PyQt4.QtCore import *

from Orange.widgets import gui
from Orange.widgets.utils import vartype
from Orange.widgets.widget import OWWidget
from Orange.widgets.settings import Setting, ContextSetting

class IntNumber(OWWidget):
    # Widget's name as displayed in the canvas
    name = "Integer Number"
    # Short widget description
    description = "Lets the user input a number"

    # An icon resource file path for this widget
    # (a path relative to the module where this widget is defined)
    icon = "icons/number.svg"

    # A list of output definitions (here on output named "Number"
    # of type int)
    outputs = [("Number", int)]

按照设计原则,Orange widgets通常有一个界面,由控制和主体区域组成。控制区一般出现在左边,包含各种widgets使用的控制和选项。主体区域则经常包含图形、表格等各种依据选项和操作绘制出来的内容。OWWidget通过 self.controlAreaself.mainArea使其可用。需要注意到的是,这样做可以让所有的widgets有一个统一的好的外观,但实际上可以将其用于任何的用途,即便与Orange的其它widgets完全不同。

我们通过class的属性置顶缺省的外观。下面的代码指定一个单列的只有控制区的GUI外观。

# Basic (convenience) GUI definition:
#   a simple 'single column' GUI layout
want_main_area = False
#   with a fixed non resizable geometry.
resizing_enabled = False

我们希望用户输入的数字能够在工作流保存和载入时自动保存和恢复。我们通过声明一个特殊的property/member 在widget的类定义中完成,如下:

number = Setting(42)

然后,定义GUI和相应的功能:

def __init__(self):
    super().__init__()

    gui.lineEdit(self.controlArea, self, "number", "Enter a number",
                 orientation="horizontal", box="Number",
                 callback=self.number_changed,
                 valueType=int, validator=QIntValidator())
    self.number_changed()def number_changed(self):
    # Send the entered number on "Number" output
    self.send("Number", self.number)

参考:Orange.widgets.gui.lineEdit(),Orange.widgets.widget.OWWidget.send()

这个widget看起来有点无趣(%%%)。

定义一个输出Widget

再加入一些东西,如何显示这个数字呢?再加入一个新的Widgets:

from Orange.widgets import widget, gui

class Print(widget.OWWidget):
    name = "Print"
    description = "Print out a number"
    icon = "icons/print.svg"

    inputs = [("Number", int, "set_number")]
    outputs = []

    want_main_area = False

    def __init__(self):
        super().__init__()
        self.number = None

        self.label = gui.widgetLabel(self.controlArea, "The number is: ??")

    def set_number(self, number):
        """Set the input number."""
        self.number = number
        if self.number is None:
            self.label.setText("The number is: ??")
        else:
            self.label.setText("The number is {}".format(self.number))

注意,set_number方法如何被检查,是否number是None当两个连接的Widgets被移除或者内部被清空等改变时,None被发送给widget。

现在我们使用一个widget输入而另外一个来显示输入的结果。

定义一个加法Widget

现在,我们要加入一个加法操作的Widgets:

from Orange.widgets import widget

class Adder(widget.OWWidget):
    name = "Add two integers"
    description = "Add two numbers"
    icon = "icons/add.svg"

    inputs = [("A", int, "set_A"),
              ("B", int, "set_B")]
    outputs = [("A + B", int)]

    want_main_area = False

    def __init__(self):
        super().__init__()
        self.a = None
        self.b = None

    def set_A(self, a):
        """Set input 'A'."""
        self.a = a

    def set_B(self, b):
        """Set input 'B'."""
        self.b = b

    def handleNewSignals(self):
        """Reimplemeted from OWWidget."""
        if self.a is not None and self.b is not None:
            self.send("A + B", self.a + self.b)
        else:
            # Clear the channel by sending `None`
            self.send("A + B", None)

参考:handleNewSignals()

嗯,到现在为止,添加几个widgets,然后相互组合、传递数据、在数据改变时进行通知等操作,全部都可以搞定了。

加入启动时载入信息

运行之前,需要到Orange/orange3env/lib/python3.4/site-packages/目录下,将Orange3_spark-0.2.2.dist-info拷贝一份,改名为Orange3_First-0.1.0.dist-info,然后进去将entry_points.txt改为一下内容:

[orange.addons]
First = orangecontrib.first

[orange.widgets]
First = orangecontrib.first.widgets.owfirst
Show = orangecontrib.first.widgets.owshow
Add = orangecontrib.first.widgets.owadd

[orange.widgets.tutorials]
exampletutorials = orangecontrib.first.tutorials

然后,Orange重启,即可看到Widgets这个目录,将里面的内容拖放到右侧的窗口,输入数据、查看结果。


展开阅读全文
打赏
1
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
1
分享
返回顶部
顶部