From c6bbc88891c98746d1cf35953192d9ad2bfd9a83 Mon Sep 17 00:00:00 2001 From: Prevalenter <434625142@qq.com> Date: Sun, 10 Feb 2019 17:28:27 +0800 Subject: [PATCH 1/3] add menus Signed-off-by: Prevalenter <434625142@qq.com> --- doc/publish.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/publish.md b/doc/publish.md index 81a12a5..c730dfa 100644 --- a/doc/publish.md +++ b/doc/publish.md @@ -85,7 +85,7 @@ Description: a friendly develop tutorial ## 发布到ImagePy -你也可以将插件项目发布到ImagePy,如果你已经完成你的插件项目,那么发布是一个很简单的过程。只需要fork一份ImagePy,将你的readme文件拷贝到imagepy - Plugins - Contribute - Contributors下,重命名为插件项目名称(不强制,但最好能体现插件功能),然后给ImagePy提Pull Request。当ImagePy组织成员收到pr,并进行测试后,会merge你的pr,如有问题会通过issue进行沟通。一旦合并到ImagePy主分支内,用户即可通过插件管理器对插件进行检索,安装,卸载等操作。 +你也可以将插件项目发布到ImagePy,如果你已经完成你的插件项目,那么发布是一个很简单的过程。只需要fork一份ImagePy,将你的readme文件拷贝到imagepy - Menus - Plugins - Contribute - Contributors下,重命名为插件项目名称(不强制,但最好能体现插件功能),然后给ImagePy提Pull Request。当ImagePy组织成员收到pr,并进行测试后,会merge你的pr,如有问题会通过issue进行沟通。一旦合并到ImagePy主分支内,用户即可通过插件管理器对插件进行检索,安装,卸载等操作。 ![14](http://idoc.imagepy.org/demoplugin/06.png)
Plugins Manager

From 9a7a57cf67dfa1f71112b7b99f00c3f8306bf30d Mon Sep 17 00:00:00 2001 From: Prevalenter <434625142@qq.com> Date: Sun, 10 Feb 2019 20:21:46 +0800 Subject: [PATCH 2/3] Signed-off-by: Prevalenter <434625142@qq.com> --- README.md | 116 ++++---- doc/attention.md | 50 ++-- doc/document.md | 14 +- doc/filter.md | 77 +++--- doc/free.md | 34 ++- doc/macros.md | 32 +-- doc/markdown.md | 6 +- doc/publish.md | 64 ++--- doc/report.md | 69 +++++ doc/simple.md | 247 ++++++++++-------- doc/start.md | 142 +++++----- doc/table.md | 86 +++--- doc/tool.md | 20 +- doc/widget.md | 68 +++-- doc/workflow.md | 16 +- menus/Demos/Macros Demo/__init__.py | 1 + menus/Demos/Report Demo/Coins Report.rpt | Bin 0 -> 26804 bytes .../Report Demo/Personal Information.rpt | Bin 0 -> 13265 bytes menus/Demos/Report Demo/__init__.py | 1 + menus/Demos/__init__.py | 2 +- widgets/DemoWidgets/widgetdemo_wgt.py | 1 - 21 files changed, 593 insertions(+), 453 deletions(-) create mode 100644 doc/report.md create mode 100644 menus/Demos/Report Demo/Coins Report.rpt create mode 100644 menus/Demos/Report Demo/Personal Information.rpt create mode 100644 menus/Demos/Report Demo/__init__.py diff --git a/README.md b/README.md index acc71f9..c4047fd 100644 --- a/README.md +++ b/README.md @@ -12,114 +12,128 @@ **Description:** a friendly develop tutorial. +*这是一个ImagePy插件项目,里面覆盖了各类插件的编写方法和用法,并配有详细的文档,ImagePy的插件开发者可以以此为参考* + ## 安装 -ImagePy菜单:**Plugins > Manager > Plugins Manager** 在输入框内输入demo进行查询,选中Demo Plugin,点击Install,完成后菜单栏出现Demo菜单,工具栏会加入Demo工具,组件栏也会加入Demo组件。 +ImagePy菜单:`Plugins > Manager > Plugins Manager` 在输入框内输入demo进行查询,选中Demo Plugin,点击`Install`,完成后菜单栏出现Demo菜单,工具栏会加入Demo工具,组件栏也会加入Demo组件。 +![06](http://idoc.imagepy.org/demoplugin/06.png) +
Install DemoPlugin

+## [基础预备](doc/start.md) -## [基础预备](doc/start.md#基础预备) +**[从这里开始](doc/start.md)** 1. [什么是插件](doc/start.md#什么是插件) - -2. [Hello World](doc/start.md#Hello) (第一个插件) - -3. [Who Are You](doc/start.md#Who) (带有交互) - -4. [Questionnaire](doc/start.md#Questionnaire) (参数对话框详解) - +2. [Hello World(第一个插件)](doc/start.md#Hello-World) +3. [Who Are You(带有交互)](doc/start.md#Who-Are-You) +4. [Questionnaire(参数对话框详解)](doc/start.md#Questionnaire) 5. [一个文件内实现多个插件](doc/start.md#一个文件内实现多个插件) - + ## 插件开发 -**[Markdown: 文档提示](doc/markdown.md#Markdown)** +**[Markdown: 文档提示](doc/markdown.md)** -1. [Markdown Demo](doc/markdown.md#Demo) +1. [Markdown Demo](doc/markdown.md#MarkDown-Demo) **[Macros: 用宏串联已有功能](doc/macros.md#Macros)** -1. [关于插件的加载方式](doc/macros.md#关于插件的加载方式) -2. [高斯模糊再求反](doc/macros.md#高斯模糊再求反) -3. [Coins Segment Macros:硬币分割](doc/macros.md#分割硬币) +1. [高斯模糊再求反](doc/macros.md#高斯模糊再求反) +2. [Coins Segment Macros:硬币分割](doc/macros.md#分割硬币) + +**[Workflow: 可交互的宏](doc/workflow.md)** -**[Workflow: 可交互的宏](doc/workflow.md#Workflow)** +1. [Coins Segment Workflow:按照指引进行硬币分割](doc/workflow.md#硬币分割工作流) -1. [Coins Segment Workflow: 按照指引进行硬币分割](doc/workflow.md#分割硬币) -2. [工作流编写及加载方式](doc/workflow.md#工作流编写及加载方式) +**[Report: 生成报表](doc/report.md)** -**[Filter: 二维图像滤波器](doc/filter.md#Filter)** +1. [Personal Information:填写个人信息](doc/report.md#个人信息) +2. [Coins Report:硬币分割成果](doc/report.md#硬币分割) +3. [Report 插件的设计原则](doc/report.md#报表模板设计原则) + +**[Filter: 二维图像滤波器](doc/filter.md)** 1. [Invert Demo:无参数的插件](doc/filter.md#Invert) 2. [Gaussian Demo:带有参数的插件](doc/filter.md#Gaussian) -3. [Filter运行机制](doc/filter.md#Filter运行机制) +3. [Filter 的运行机制](doc/filter.md#Filter-运行机制) -**[Simple: 图像整体操作](doc/simple.md#Simple)** +**[Simple: 图像整体操作](doc/simple.md)** 1. [Gaussian 3D Demo:三维滤波](doc/simple.md#Gaussian3D) -2. [Red Lut Demo:操作索引表](doc/simple.md#SetLUT) -3. [ROI Inflate Demo:操作ROI](doc/simple.md#ROI) -4. [Unit Demo: 设置比例尺及单位](doc/simple.md#Unit) +2. [Red Lut Demo:设定索引色](doc/simple.md#SetLUT) +3. [ROI Inflate Demo:操作ROI](doc/simple.md#Inflate-ROI) +4. [Unit Demo: 设置比例尺及单位](doc/simple.md#SEt-Scale-And-Unit) 5. [Draw Mark Demo: 设置Overlay Mark](doc/simple.md#Mark) -6. [Simple运行机制](doc/simple.md#Simple运行机制) +6. [Simple 的运行机制](doc/simple.md#Simple-运行机制) -**[Table: 表格数据](doc/table.md#Table)** +**[Table: 表格数据](doc/table.md)** 1. [Generate Table Demo:数据表生成](doc/table.md#生成成绩单) 2. [Sort By Key Demo:排序](doc/table.md#根据某科成绩排序) 3. [Table Plot Demo:绘图](doc/table.md#绘制柱状图) -4. [Table运行机制](doc/table.md#Table运行机制) +4. [Table 运行机制](doc/table.md#Table-运行机制) -**[Free: 没有任何依赖的插件](doc/free.md#Free)** +**[Free: 没有任何依赖的插件](doc/free.md)** -1. [New Image Demo: 新建图像](doc/free.md#创建图像) +1. [New Image Demo: 创建图像](doc/free.md#创建图像) 2. [About Demo:关于对话框](doc/free.md#关于对话框) -3. [Close Demo:退出程序](doc/free.md#退出软件) -4. [Free的运行机制](doc/free.md#Free的运行机制) +3. [Close Demo:退出软件](doc/free.md#退出软件) +4. [Free 的运行机制](doc/free.md#Free-的运行机制) -**[Tool: 鼠标交互工具](doc/tool.md#Tool)** +**[Tool: 鼠标交互工具](doc/tool.md)** 1. [Painter Demo:画笔工具](doc/tool.md#画笔工具) -2. [Tool的运行机制](doc/tool.md#Tool的运行机制) +2. [Tool的运行机制](doc/tool.md#Tool-的运行机制) -**[Widget: 桌面小部件](doc/widget.md#Widget)** +**[Widget: 桌面小部件](doc/widget.md)** -1. [Widget Demo:桌面小部件演示](doc/widget.md#画笔工具) -2. [Tool的运行机制](doc/widget.md#Tool的运行机制) +1. [Widget Demo:桌面小部件演示](doc/widget.md#桌面组件演示) +2. [Tool的运行机制](doc/widget.md#widget-的运行机制) -## [插件项目发布](doc/publish.md#插件项目发布) +## [插件项目发布](doc/publish.md) **[插件的组织方式](doc/publish.md#功能组织)** -1. [功能划分](doc/publish.md#功能划分) -2. [顺序设定](doc/publish.md#顺序设定) +1. [功能划分](doc/publish.md#功能组织) +2. [顺序设定](doc/publish.md#功能组织) + +**[插件项目创建](doc/publish.md#插件项目创建)** -**[插件项目的发布](doc/publish.md#创建插件项目仓库)** +1. [创建插件项目仓库](doc/publish.md#插件项目创建) +2. [编写requirements](doc/publish.md#插件项目创建) +3. [编写readme](doc/publish.md#插件项目创建) +4. [插件的安装](doc/publish.md#插件项目创建) -1. [编写requirements](doc/publish.md#requirements) -2. [为Readme加入的插件头信息](doc/publish.md#编写readme) -3. [发布到ImagePy](doc/publish.md#发布到ImagePy) +**[发布到 ImagePy](doc/publish.md#发布到-ImagePy)** -**插件的安装与管理** +1. [给ImagePy发Pull Request](doc/publish.md#发布到-ImagePy) +2. [关于顶级菜单](doc/publish.md#发布到-ImagePy) -1. 通过连接安装插件](doc/publish.md#Widget -2. 插件管理器](doc/publish.md#Widget +## [文档编写](doc/document.md) -## [文档编写](doc/document.md#文档编写) +**[编写操作手册](doc/document.md#编写操作手册)** -为插件编写操作手册 +**[查阅操作手册](doc/document.md#查阅操作手册)** ## [注意事项](doc/attention.md#注意事项) -1. [用户友好性](doc/attention.md#用户友好性) -2. [开发者友好性](doc/attention.md#开发者友好性) -3. [及时沟通](doc/attention.md#及时沟通) +**[用户友好性](doc/attention.md#用户友好性)** + +**[开发者友好性](doc/attention.md#开发者友好性)** + +**[及时沟通](doc/attention.md#及时沟通)** + + + +**本篇文档相对系统的介绍了ImagePy的插件开发,但是依然无法详尽,关于更多ImagePy使用,开发上的问题请在[forum.Image.sc](https://forum.image.sc/)行进行讨论** \ No newline at end of file diff --git a/doc/attention.md b/doc/attention.md index 38a002e..ad68dd9 100644 --- a/doc/attention.md +++ b/doc/attention.md @@ -1,49 +1,61 @@ -#注意事项 +# 注意事项 虽然细节未能详尽,但我们大致介绍了插件的开发,发布,文档编写。这里提出一些开发过程中的注意事项。 -## 用户友好性 +## 用户友好性 如果是自己用则可以无需过多花费精力,但希望其他用户可以使用自己的插件,那么用户友好性是非常重要的,这包括: -1. 合理的功能组织,将功能合理的划分,并用合适的标题展示。 -2. 符合逻辑的交互方式,如插件的参数暴露能具有一定通用性但又不至于杂乱,工具的使用方式符合使用习惯。 -3. 尽可能优化的性能体验,当然这一点是永无止境的,并且主要取决于算法,固然越快越好,等待总是糟糕的。 -4. 言简意赅的文档,如果有精力,最好为插件编写操作手册,有必要可以图文并茂。 +1. **合理的功能组织** + 将功能合理的划分,并用合适的标题展示。 +2. **合理的交互方式** -## 开发者友好性 + 如插件的参数暴露能具有一定通用性但又不至于杂乱,工具的使用方式符合使用习惯。 -ImagePy将开发者友好性放在与用户友好性等同的地位,因为开发者与用户本是一个教学相长的群体,并且两者之间界线模糊,可以互相转化,这里介绍几个ImagePy的开发理念。 +3. **言简意赅的文档** -1. ImagePy不是算法库,只是连接器 + 如果有精力,最好为插件编写操作手册,有必要可以图文并茂。 + +4. **尽可能优化的性能体验** + + 当然这一点是永无止境的,并且主要取决于算法,固然越快越好,等待总是糟糕的。 + + + +## 开发者友好性 + +**ImagePy将开发者友好性放在与用户友好性等同的地位**,因为开发者与用户本是一个教学相长的群体,并且两者之间界线模糊,可以互相转化,这里介绍几个ImagePy的开发理念。 + +1. **ImagePy不是算法库,只是连接器** ImagePy不做算法,只提供必要的展示与交互,致力于让算法开发者用最少的代码将算法进行配置,整合到ImagePy,将算法转化为工具,给其他人甚至是非程序员使用。 -2. 尽可能隔离UI +2. **尽可能隔离UI** - UI对于科研程序员总是不愉快的,ImagePy尽最大努力隔绝UI,多数的配置功能都可以通过para,view的配置来完成参数对话框的生成,而tool也抽象为mouse_down, mouse_up, mouse_move, mouse_wheel四个接口实现。而一些定制化很强的功能,不得不借助widget,只有这时才需要开发者自己编写UI相关的代码。 + UI对于科研程序员总是不愉快的,ImagePy尽最大努力隔绝UI,多数的配置功能都可以通过`para`,`view`的配置来完成参数对话框的生成,而`tool`也抽象为`mouse_down`, `mouse_up`, `mouse_move`, `mouse_wheel`四个接口实现。而一些定制化很强的功能,不得不借助`widget`,只有这时才需要开发者自己编写UI相关的代码。 -3. 以算法本身为核心 +3. **以算法本身为核心** - ImagePy的图像数据基于Numpy,表格数据基于Pandas,矢量数据基于Shapely,这些都是通用数据结构,相当于Python科学计算的标准,ImagePy作为算法连通器,固然只能支持,而不能干涉算法的编写。因而开发者切不可将核心算法在run函数中实现,这样其他开发者将无法使用,开源的意义就大打折扣。算法,UI,事件混合,这导致ImageJ的算法代码无法被其他开发者调用,不得不大量使用IJ.run(...),或许ImageJ2正在极力改变这种情况,而这在ImagePy中是不被允许的用法。我们推荐的方法是 + ImagePy的图像数据基于`Numpy`,表格数据基于`Pandas`,矢量数据基于`Shapely`,这些都是通用数据结构,相当于Python科学计算的标准,ImagePy作为算法连通器,固然只能支持,而不能干涉算法的编写。因而开发者*切不可将核心算法在`run`函数中实现*,这样其他开发者将无法使用,开源的意义就大打折扣。算法,UI,事件混合,这导致ImageJ1的算法代码无法被其他开发者调用,不得不大量使用`IJ.run(...)`,或许ImageJ2正在极力改变这种情况,而这在ImagePy中是不被允许的用法。我们推荐的方法是 1)如果你的插件只是实现很简单的数据操作,只有几行代码,那么OK,可以在run中实现。 - 2)如果你的算法相对复杂,那么最好将它写在一个单独的文件,并且这个模块中不要引用任何与ImagePy相关的模块,只基于Numpy,Pandas等标准科学计算库,这样以便他人可以拷贝出去,并且import使用。 + 2)如果你的算法相对复杂,那么最好将它写在一个单独的文件,并且这个模块中不要引用任何与ImagePy相关的模块,只基于`Numpy`,`Pandas`等标准科学计算库,这样以便他人可以拷贝出去,并且`import`使用。 - 3)如果你的算法意义重大,通用性很强,那么务必创建一个独立的算法项目,上传pypi,在插件项目中以引用的方式来使用。 + 3)如果你的算法意义重大,通用性很强,那么务必创建一个独立的算法项目,上传`pypi`,在插件项目中以引用的方式来使用。 -4. 关于宏和headless +4. **关于宏和headless** - 相比ImageJ,ImagePy的宏实现的功能非常有限,只支持已有功能流程化调用,无法用宏实现逻辑,这其实与ImagePy的设计理念紧密相关,只要开发者都能做到以算法本身为核心,让ImagePy充当纯粹的连接器,那么任何的算法调用,可以用Python简单的调用。Python已经如此简单强大,为什么还要另外学习一种晦涩而不通用的宏呢?同样,ImagePy也不会支持headless,因为一切功能以算法本身为核心,而ImagePy只提供交互,那么在headless情况下,ImagePy还有什么意义?又有什么非要借助ImagePy的呢?如果一切功能都可以以库的形式提供,那么开发者为什么希望编写headless代码,而不是纯粹的python代码? + 相比ImageJ,ImagePy的宏实现的功能非常有限,只支持已有功能流程化调用,无法用宏实现逻辑,这其实与ImagePy的设计理念紧密相关,只要开发者都能做到以算法本身为核心,让ImagePy充当纯粹的连接器,那么任何的算法调用,可以用Python简单的调用。*Python已经如此简单强大,为什么还要另外学习一种晦涩而不通用的宏呢*?同样,ImagePy也不会支持`headless`,因为一切功能以算法本身为核心,而ImagePy只提供交互,那么在headless情况下,ImagePy还有什么意义?又有什么非要借助ImagePy的呢?如果一切功能都可以以库的形式提供,那么开发者为什么希望编写`headless`代码,而不是纯粹的python代码? -#及时沟通 +## 及时沟通 相比ImageJ,ImagePy还只是一个孩子,框架和功能都还不够完善,但优势是,我们可以自由把控,即时调整,而不需要考虑背在身上的强大且庞大的插件体系。作为插件开发者,如果在插件开发过程中遇到任何困难,请随时与我们联系,尤其是当你觉得一个插件开发的很复杂时,那么很可能是因为我们的框架缺乏某方面的支持,我们可以随时讨论,在插件开发的同时,共同完善主框架。让ImagePy更好的实现连接器的价值,为用户和开发者服务。 -ImagePy是 www.forum.image.sc 的合作伙伴,任何研发,使用方面的问题可以在上面进行讨论。 \ No newline at end of file +ImagePy是 www.forum.image.sc 的合作伙伴,任何研发,使用方面的问题可以在里面进行讨论。 + diff --git a/doc/document.md b/doc/document.md index dc22b8d..1bf200e 100644 --- a/doc/document.md +++ b/doc/document.md @@ -4,9 +4,15 @@ -在插件项目的顶级目录下,创建doc文件夹,在文件夹里即可用markdown为你的插件编写操作文档了,需要注意的是文件名需要与插件的title一致,这样ImagePy的文档管理器即可将文档与插件进行关联。工具类型的插件与菜单类型的插件都可以编写文档。 +## 编写操作手册 +在插件项目的顶级目录下,创建`doc`文件夹,在文件夹里即可用`markdown`为你的插件编写操作文档了,需要注意的是文件名需要与插件的`title`一致,这样ImagePy的文档管理器即可将文档与插件进行关联。工具类型的插件与菜单类型的插件都可以编写文档。 +`doc`下所有的`markdown`文件都会被解析并通过文件名关联给对应插件,所以这里并不强制要求文件组织方式,但我们强烈建议维护一个与插件目录一致的文档目录(创建`menus`,`tools`,`widgets`三个顶级目录),这样方便管理和维护,如果有必要我们可以加入多的目录文件,添加对应的跳转,这样整个`doc`目录也可以在github或其他在线环境中进行阅读。 + + + +## 查阅操作手册 当插件编写完成后,我们有如下方式对文档进行查阅: @@ -14,10 +20,10 @@ 2. 工具栏图标上右键单击 -3. 使用 **Plugins > Manager** 下的各种 **Tree View** +3. 使用 **`Plugins > Manager`** 下的各种 **`Tree View`** ![14](http://idoc.imagepy.org/demoplugin/31.png) -
Hello World

+
view doc from plugin tree view

+ -doc下所有的markdown文件都会被解析并通过文件名关联给对应插件,所以这里并不强制要求文件组织方式,但我们强烈建议维护一个与插件目录一致的文档目录,这样方便管理和维护,如果有必要我们可以加入多的目录文件,添加对应的跳转,这样整个doc目录也可以在github上进行阅读。 diff --git a/doc/filter.md b/doc/filter.md index 6d3ce84..abc6a59 100644 --- a/doc/filter.md +++ b/doc/filter.md @@ -1,10 +1,7 @@ -# Filter +# Filter plugin +[Filter](https://en.wikipedia.org/wiki/Filtering_problem_(stochastic_processes)) is one of the most important and basic applications in image processing. It utilizes the stochastic model to de-noise the two or three dimensional images. -Filter是最重要的一类插件,用于对二维图像进行滤波,也是图像处理中最基础,最普遍的一类应用。 - - - -## Invert +## Invert ```python from imagepy.core.engine import Filter @@ -17,14 +14,17 @@ class Invert(Filter): return 255-snap ``` -Invert插件,note指明插件支持任何类型,并且支持roi,支持撤销。我们在run里面返回处理后的结果。关于snap和img,img是当前图像,而如果当note中加入auto_snap标识,在run之前,框架会帮我们把img拷贝给snap,因为多数的滤波器需要一个buffer来卷积,此外撤销和roi支持也必须借助snap。 +**Invert plugin**,`note` indicates that the plugin supported types. It enables us to choose the ROI to deal with and to undo the operations. The results is return with the help of function `run`.
+About `snap` and `img`:
+`img`is the opened image,if the `auto_snap` is added in the `note`,owing to the need of `buffer` for the most of filters to convolve, before `run` acted,the framework will copy the `img`to `snap`. Besides, undo operation and ROI support must rely on `snap`. ![14](http://idoc.imagepy.org/demoplugin/13.png)
Invert

-## Gaussian + +## Gaussian ```python from imagepy.core.engine import Filter @@ -40,63 +40,56 @@ class Gaussian(Filter): gaussian_filter(snap, para['sigma'], output=img) ``` -Gaussian插件,note指明支持任何类型,并且支持roi,支持撤销,并提供预览功能。para和view指明有一个浮点参数sigma,而run里,我们调用scipy.ndimage.gaussian_filter,对snap进行滤波,输出指向img,如果函数不带输出项,我们将处理结果return即可,框架会帮我们赋值给img。 +Gaussian plugin,`note` indicates that the plugin supported types,It enables us to choose the ROI to deal with and to undo the operations. It also provides the preview feature. `para` and `view` determine a float type parameter `sigma`. In function `run`,the `scipy.ndimage.gaussian_filter` is called to filter the `snap`, and the output is directed to `img`. If there is no output in the function, the result can be assigned to `img` with the help with framework imagepy directly: with the `return xxx` at the end of the function. + ![14](http://idoc.imagepy.org/demoplugin/14.png)
Gaussian

-## Filter运行机制 - -**note:** - -note选项是行为控制标识,用于控制插件执行的流程,比如让框架进行类型兼容检测,如不满足自动中止。设定通道和序列支持设定,以及是否需要提供预览,roi等支持。 - -1. all:插件支持任意类型 - -2. 8-bit:插件支持无符号8位 - -3. 16-bit:插件支持无符号16位 - -4. int:插件支持32位,64位整数 -5. rgb:插件支持3通道24位彩色 +## Filter operating mechanism -6. float:插件支持32位,64位浮点 - - ------ +**note:** +`note` option is the behavior control identifier, to control the flow of plugin execution. +**i.e.:** Let the framework perform type compatibility detection, if not satisfied, it will be automatic ended. Set channel and sequence support settings. More : preview, ROI supports and other ones. -7. not_channel:当处理多通道时,不要框架自动遍历通道(默认情况下会将每个通道依次处理) +1. `all`: Plugin supports all types. +2. `8-bit`: Plugin supports unsigned 8 bit +3. `16-bit`: Plugin supports unsigned 16 bit +4. `int`: Plugin supports 32-bit, 64-bit integers +5. `rgb`: Plugin supports 3 channels, 24-bit color +6. `float`: Plugin supports 32-bit, 64-bit floating point. + ------ -8. not_slice:当处理图像序列时,不要询问是否批量处理(默认情况会询问用户) +7. `not_channel`: Do not automatically traverse the channel when processing multiple channels (by default, each channel will be processed in turn) -9. req_roi:是否必须有roi才能够处理 +8. `not_slice`: When processing image sequences, do not ask `if you want to batch process` (users are asked by default) +9. `req_roi`: Whether there must be a ROI to be able to handle --- -10. auto_snap:是否需要框架在处理器对当前图像自动缓冲 - -11. auto_msk:是否自动支持ROI(必须配合auto_snap才生效,原理是用snap恢复ROI以外像素) - -12. preview:是否支持预览,调整参数实时查看结果 +10. `auto_snap`: Whether the framework is required to automatically buffer the current image on the processor -13. 2int:是否在运算过程中将低于int32的数据转为int32再进行处理(例如避免8位的运算溢出) +11. `auto_msk`: Whether to automatically support ROI (**must be effective with `auto_snap`**, the principle is to use `snap` to restore pixels outside the ROI) +12. `preview`: Whether to support preview (view results in real time), to adjust parameters +13. `2int`: Whether to convert data below `int32` to `int32` and then process it (for example, avoid `8-bit` operation overflow) -14. 2float:是否在运算过程中将低于float32的数据自动转为float32再进行处理(一些运算要求精度) +14. `2float`: Whether to automatically convert data below float32 to float32 and then process it during operation (some operations require precision) -**para, view:** 参数字典,具体用法参阅start入门。 +**para, view:** Parameter dictionary, for specific usage, see [start](doc/start.md). **run:** -1. ips:图像封装类,通常在filter中不需要对其进行操作 -2. snap:当note里加入auto_snap标识后,在run之前框架会将当前图像拷贝到snap(尽可能实现使用对snap进行处理,将结果赋值给img) -3. img:当前图像,我们将结果赋值给img,或return结果,由框架赋值给img,并完成界面刷新。 +1. `ips`: Image wrapper class, usually does not need to be operated in `filter` +2. `snap`: as the `auto_snap` is added in `note`, before `run`, the framework imagepy will copy the opened image to `snap` (as much as possible to deal with `snap`, and assign the results to `img`) +3. `img`: opened image, the result is assigned to `img`, or `return` results, assigned `img` by imagepy, and complete the interface refresh **load:** -def load(self, ips) 最先执行,如果return结果为False,插件将中止执行。默认返回True,如有必要,可以对其进行重载,进行一系列条件检验,如不满足,IPy.alert弹出提示,并返回False。 +`def load(self, ips)` is executed at first,if the `False` is returned with `return` , the plugin will be ended. Default return is `True`, if neccesary, it can be overloaded for a series of condition checks. If it is not satisfied, the `IPy.alert` will popup prompt, and return`False` **preview:** -def preview(self, ips, para) 在预览时执行,默认会调用run处理当前图像,如果有必要可以对其进行重载。 \ No newline at end of file +`def preview(self, ips, para)` is executed during preview, the opened image is processed with calling `run` by default, and can be overloaded if necessary. \ No newline at end of file diff --git a/doc/free.md b/doc/free.md index 402dee7..57b277c 100644 --- a/doc/free.md +++ b/doc/free.md @@ -1,11 +1,9 @@ -# Free +# Free plugin -Free是不需要任何依赖就可以独立运行的插件,我们可以在里面做任何事,如创建图像,下载在线资源等。 +Free is a plugin that can run independently without any dependencies. We can do anything in it, such as creating images, downloading online resources, and so on. - -## 创建图像 - +## Create image ```python from imagepy.core.engine import Free from imagepy import IPy @@ -22,15 +20,14 @@ class NewImage(Free): imgs = [np.zeros((para['h'], para['w']), dtype=np.uint8)] IPy.show_img(imgs, para['name']) ``` - -这里通过一个例子,创建一副新图像,同样我们可以通过para,view进行参数交互。 +Here we demonstrate this type of plugin through an example (create a new image). Similarly, we can interact with parameters through `para` and `view`. ![14](http://idoc.imagepy.org/demoplugin/22.png)
New Image

-## 关于对话框 +## About dialog-box ```python from imagepy.core.engine import Free @@ -42,16 +39,14 @@ class About(Free): def run(self, para=None): IPy.alert('ImagePy v0.2') ``` - -早在Hello World时就接触过,这里用作关于对话框。 +It has been introduced in [Hello World](doc/start.md), and it is used as a dialog-box. ![14](http://idoc.imagepy.org/demoplugin/23.png)
ImagePy Alert

-## 退出软件 - +## Quit ```python from imagepy.core.engine import Free from imagepy import IPy @@ -63,23 +58,24 @@ class Close(Free): def run(self, para = None): IPy.curapp.Close() ``` - -退出软件是Free类型插件一个非常典型的应用,但值得一提的是,插件中加入了asyn = False,这个标识告诉ImagePy不要启用异步执行run,因为窗口关闭属于UI操作,必须在主线程进行。 +The **quit** is a typical application in the `Free` type plugin. It is worth noticing that if `asyn = False` is added in the plugin, this sign will tell imagepy not to asynchronous execute `run`. Because 'window close' is a `UI` operation and must be done in the main thread. -## Free的运行机制 +## Operating mechanism of Free +`Free` is the easiest plugin to run compared to other ones, because there is no need for preparation process for `Free`. `run` only has one parameter `para`, obtained through interaction, which is completely open to the developer. -Free相比其他插件是运行机制最简单的,因为Free不需要做任何的流程准备,run也只有通过交互得到的para一个参数,完全放权给开发者。 **para, view:** -参数字典,具体用法参阅start入门。 +Parameter dictionary, for specific usage, see [start](doc/start.md). **run:** -获取交互结果,做任何想做的事 +Get interactive results and do whatever you want. **load:** -def load(self, ips) 最先执行,如果return结果为False,插件将中止执行。默认返回True,如有必要,可以对其进行重载,进行一系列条件检验,如不满足,IPy.alert弹出提示,并返回False。 \ No newline at end of file +`def load(self, ips)` is executed at first,if the `False` is returned with `return` , the plugin will be ended. Default return is `True`, if neccesary, it can be overloaded for a series of condition checks. If it is not satisfied, the `IPy.alert` will popup prompt, and return`False` + + diff --git a/doc/macros.md b/doc/macros.md index 140751b..0b604cf 100644 --- a/doc/macros.md +++ b/doc/macros.md @@ -1,42 +1,41 @@ -# Macros 插件 +# Macros 插件 -Macros是一系列的命令记录,用这些记录可以重演操作,我们一般并不会自己编写命令,而是通过宏录制器来完成记录。宏录制器在 **Plugins > Macros> Macros Recorder**。 我们将录制的命令保存到menus或其子文件夹里,mc后缀,重启即可加载到对应位置。 +Macros是一系列的命令记录,用这些记录可以重演操作,我们一般并不会自己编写命令,而是通过宏录制器来完成记录。宏录制器在**`Plugins > Macros> Macros Recorder`**。 我们将录制的命令保存到menus或其子文件夹里,mc后缀,重启即可加载到对应位置。 +![14](http://idoc.imagepy.org/demoplugin/08.png) +
Macros Recorder

-**关于插件的加载方式** +**关于插件的加载方式** 1. menus及其子文件夹下的mc后缀文件会被解析成宏。 -2. 也可以将宏文件拖拽到ImagePy最下方的状态栏进行质心。 +2. 也可以将宏文件拖拽到ImagePy最下方的状态栏执行。 -**高斯模糊再求反** +## 高斯模糊再求反 -``` +```python Gaussian>{'sigma': 2} Invert>None ``` 以上是两条命令,点击后会依次执行Gaussian,Invert。 -![14](http://idoc.imagepy.org/demoplugin/08.png) - -
Macros

- -1. 宏命令是 **插件名称>参数字典** 这种形式构成 -2. 如果没有参数用 **None** 占位 -3. 如果有参数的命令参数给 **None** ,ImagePy会弹出参数对话框进行交互。 - ![14](http://idoc.imagepy.org/demoplugin/09.png)
Gaussian And Invert

+1. 宏命令是`插件名称>参数字典`这种形式构成 +2. 如果没有参数用 `None`占位 +3. 如果有参数的命令参数给`None`,ImagePy会弹出参数对话框进行交互。 + -**分割硬币** -```markdown +## 分割硬币 + +```python coins>None Up And Down Watershed>{'thr1': 36, 'thr2': 169, 'type': 'up area'} Fill Holes>None @@ -45,6 +44,7 @@ Geometry Analysis>{'con': '8-connect', 'center': True, 'area': True, 'l': True, ``` 以上是一个硬币分割的流程录制成宏,点击后会自动执行硬币分割,预处理,区域过滤,区域分析,生成表格。(当然通常我们不会将图像打开也录制到宏) + ![14](http://idoc.imagepy.org/demoplugin/10.png)
Coins Segment Macros

\ No newline at end of file diff --git a/doc/markdown.md b/doc/markdown.md index 1dcdcc4..209d2a7 100644 --- a/doc/markdown.md +++ b/doc/markdown.md @@ -2,8 +2,12 @@ MarkDown是一种标记语言,简单易读,适合书写文档,ImagePy对Markdown有直接支持,将Markdown文件拷贝到menus或其子目录下,下次启动时会被解析成一个菜单项,文件名即菜单标题,点击后弹出窗口,显示Markdown的内容。 + + +## MarkDown Demo + ```markdown -# Markdown Demo +# Markdown Demo This is a Mark Down Demo ``` ![14](http://idoc.imagepy.org/demoplugin/05.png) diff --git a/doc/publish.md b/doc/publish.md index c730dfa..e79ab28 100644 --- a/doc/publish.md +++ b/doc/publish.md @@ -1,42 +1,53 @@ -# 插件项目发布 +# 插件项目发布 -以上我们讨论了每一种插件的写法,当然,我们可以任意在ImagePy的menus,tools,widgets下创建python脚本以扩展出某个插件功能,但当我们的插件越来越多,功能越来越系统,当然就会希望正式发布成一个插件项目,可以让其他用户方便的安装使用。这里我们讨论如何发布一个插件项目。 +以上我们讨论了每一种插件的写法,当然,我们可以任意在ImagePy的`menus`,`tools`,`widgets`下创建python脚本以扩展出某个插件功能,但当我们的插件越来越多,功能越来越系统,当然就会希望正式发布成一个插件项目,可以让其他用户方便的安装使用。这里我们讨论如何发布一个插件项目。 -## 功能组织 +## 功能组织 -**功能划分** +**功能划分** 如果我们的插件并不是一个单一的功能,而是系统性很强的一系列功能,那么我们应该将这些功能进行划分和聚类,用文件夹来进行组织。例如我们之前编写的全部功能,就可以按照插件类型来进行组织。 ![14](http://idoc.imagepy.org/demoplugin/28.png)
功能划分

-**顺序设定** +**顺序设定** -我们看到,对插件进行了功能划分,看起来就更加清晰,然而插件是以字母顺序进行排序的,我们希望指定顺序,以本项目为例,我们希望以循序渐进的顺序来介绍插件编写方法,在ImagePy中这非常容易实现,我们在init.py文件中加入catlog字段,用一个列表对象指明顺序。 +我们看到,对插件进行了功能划分,看起来就更加清晰,然而插件是以字母顺序进行排序的,我们希望指定顺序,在ImagePy中这非常容易实现,我们在init.py文件中加入`catlog`字段,用一个列表对象指明顺序。 +`__init__.py` + +```python +catlog = ['Start Here', '-', 'Markdown Demo', 'Macros Demo', 'Workflow Demo', '-', 'Filter Demo', 'Simple Demo', 'Table Demo', 'Free Demo', '-', 'WidgetDemo'] +``` + +一旦设定catlog,菜单将按照指定顺序加载,`'-'`被解析成分割线。`catlog`可以出现在任何插件目录下,即可以指定插件,也可以指定文件夹的顺序,对于tools也起作用(另一种设定顺序的方式是之前介绍的一个文件实现多个插件的`plgs`方式)。 ![14](http://idoc.imagepy.org/demoplugin/29.png) -
顺序设定

+
sort by catlog

+ +`catlog`可以出现在任何插件目录下,即可以指定插件,也可以指定文件夹的顺序,对于`tools`也起作用。另外多插件的文件中通过`plgs`也可以指定插件的顺序。 -## 插件项目创建 -**创建插件项目仓库** -如果插件都分散在ImagePy主项目的文件夹下不便于管理,因而我们创建一个单独的插件项目仓库,在仓库的顶层创建menus,tools,widgets三个文件夹,里面存放经过功能划分和顺序设定的插件。 +## 插件项目创建 +**创建插件项目仓库** +如果插件都分散在ImagePy主项目的文件夹下不便于管理,因而我们创建一个单独的插件项目仓库,在仓库的顶层创建`menus`,`tools`,`widgets`三个文件夹,里面存放经过功能划分和顺序设定的插件。 -**编写requirements.txt** -Python最大的好处在于有丰富的库,作为一个插件项目,很可能会依赖其他的库,按照管理,需要把依赖写入到requirements.txt中(ImagePy在安装插件时会自动调用pip解决依赖,暂时只支持pip,后续会考虑支持canda) +**编写 requirements** +Python最大的好处在于有丰富的库,作为一个插件项目,很可能会依赖其他的库,按照惯例,需要把依赖写入到`requirements.txt`中(ImagePy在安装插件时会自动调用pip解决依赖,暂时只支持pip,后续会考虑支持conda) -**编写readme** + + +**编写readme** readme原本只是用于项目介绍,但在ImagePy中将readme的前几行信息用作插件管理,所以readme前几行需要严格按照如下规范,以本项目为例: @@ -61,39 +72,30 @@ Description: a friendly develop tutorial -**插件的安装** +**插件的安装** -**Plugins > Install > Install Plugin** 在对话框中输入插件仓库的github连接,ImagePy即开始下载插件,解决依赖,并自动加载。加载成功后我们可以看到菜单栏,工具栏,组件栏自动更新。这样,把你的插件项目地址发给其他人,就可以安装,使用了。 +**`Plugins > Install > Install Plugin`** 在对话框中输入插件仓库的github连接,ImagePy即开始下载插件,解决依赖,并自动加载。加载成功后我们可以看到菜单栏,工具栏,组件栏自动更新。这样,把你的插件项目地址发给其他人,就可以安装,使用了。 ![14](http://idoc.imagepy.org/demoplugin/30.png)
插件的安装

+## 发布到 ImagePy -**插件项目加载机制** - -我们看到安装插件后,插件项目中的功能也加载到了菜单栏,工具栏,组件栏中,可见插件项目中的menus,tools,widgets目录与ImagePy主项目中的目录具有等同效果。*如果不希望插件功能出现在顶级菜单,可以用文件夹结构进行控制,比如希望出现在Plugin下,就在插件项目的menus目录下加一层Plugin,进而放我们自己的插件,启动后新功能就会出现在Plugins菜单下* - - -其实插件项目安装后会将项目解压到ImagePy下的plugins目录内,而ImagePy启动时,在解析主项目的目录后,也会解析插件项目的目录结构,这样实现了插件项目的相对独立管理。 - - - -*以上流程是假设我们已有插件,进而规范成项目。事实上,正常的流程很可能是,我们首先建立插件项目,clone到ImagePy的plugins下,进行插件编写,运行主程序进行测试,并随时提交。* - - +**给ImagePy发Pull Request** -## 发布到ImagePy +你也可以将插件项目发布到ImagePy,如果你已经完成你的插件项目,那么发布是一个很简单的过程。只需要`fork`一份ImagePy,将你的`readme`文件拷贝到**`imagepy - Plugins - Contribute - Contributors`**下,重命名为插件项目名称(不强制,但最好能体现插件功能),然后给ImagePy提`Pull Request`。当ImagePy组织成员收到`Pull Request`,并进行测试后,会`merge`你的`Pull Request`,如有问题会通过`issue`进行沟通。一旦合并到ImagePy主分支内,用户即可通过插件管理器对插件进行检索,安装,卸载等操作。 -你也可以将插件项目发布到ImagePy,如果你已经完成你的插件项目,那么发布是一个很简单的过程。只需要fork一份ImagePy,将你的readme文件拷贝到imagepy - Menus - Plugins - Contribute - Contributors下,重命名为插件项目名称(不强制,但最好能体现插件功能),然后给ImagePy提Pull Request。当ImagePy组织成员收到pr,并进行测试后,会merge你的pr,如有问题会通过issue进行沟通。一旦合并到ImagePy主分支内,用户即可通过插件管理器对插件进行检索,安装,卸载等操作。 ![14](http://idoc.imagepy.org/demoplugin/06.png)
Plugins Manager

+插件管理器会自动解析`contributors`目录下的插件信息,因而插件项目的`readme`务必按照格式认真书写,这会影响用户的检索体验。如果有更新,请修改版本号,重新`Pull Request`,插件管理器会自动检测更新。 -插件管理器会自动解析contributors目录下的插件信息,因而插件项目的readme务必按照格式认真书写,这会影响用户的检索体验。如果有更新,请修改版本号,重新pr,插件管理器会自动检测更新。 +**关于顶级菜单** +我们看到安装插件后,插件项目中的功能也加载到了菜单栏,工具栏,组件栏中,可见插件项目中的menus,tools,widgets目录与ImagePy主项目中的目录具有等同效果。**如果不希望插件功能出现在顶级菜单,可以用文件夹结构进行控制**,比如希望出现在Plugin下,就在插件项目的menus目录下加一层Plugin,进而放我们自己的插件,启动后新功能就会出现在Plugins菜单下) -*由于顶级菜单空间稀缺,因而如果不是非常丰富,系统性很强的功能,请不要占用顶级菜单,关于位置设定的问题也会在merge之前与开发者进行沟通* \ No newline at end of file +其实插件项目安装后会将项目解压到ImagePy下的`plugins`目录内,而ImagePy启动时,在解析主项目的目录后,也会解析插件项目的目录结构,这样实现了插件项目的相对独立管理。**由于顶级菜单空间稀缺,因而如果不是非常丰富,系统性很强的功能,请不要占用顶级菜单** \ No newline at end of file diff --git a/doc/report.md b/doc/report.md new file mode 100644 index 0000000..5dd3338 --- /dev/null +++ b/doc/report.md @@ -0,0 +1,69 @@ +# Report Plugin + +In many cases, we end up with the results of our analysis in report form, generating PDF documents, or printing them. Although now LaTex, Markdown based on the tag language document tools are very powerful, Excel software still has the widest audience,the quickliest start to fit task , the strongest form layout ability.Moreover Excel has powerful data processing, graph generating function.Thus ImagePy selects Excel as reporting features carrier choice, the user can design their Excel template, and add tags in a particular cell and save, change the suffix to RPT, you can get the report plugin of ImagePy . + + + +**How report plugins are loaded** + +1. The RPT suffix file under the menus and its subfolders is resolved into a report template. +2. You can also drag and drop the template file into the bottom status bar of ImagePy to execute. + + +## Personal information + +Use a personal information card to show how to make templates and tag cells. In order to facilitate printing, we need to set the paper size as A5 according to our own situation. Second, for precise layout, we switched the Excel view to the page layout, so that all cell sizes are set in cm. + +--- + +![06](http://idoc.imagepy.org/demoplugin/33.png) + +
personal information template

+ +--- + +![06](http://idoc.imagepy.org/demoplugin/34.png) + +
personal information result

+ +## Coin segmentation + +We continue to use the example of coin segmentation and measurement to produce reports of the analysis results. Here, we carefully designed an experiment report, including basic information, processing pictures and result tables. We also made statistics on the results in Excel, and drew a histogram for the area column. + +![06](http://idoc.imagepy.org/demoplugin/37.png) + +
do coins segment in ImagePy

+ +![06](http://idoc.imagepy.org/demoplugin/38.png) + +
generate coins report

+ +## Report template design principles + +When executing report plug-in, ImagePy will firstly analyze Excel template, analyze each Work Sheet, detect each cell, extract all variable markers, and interact with users in the form of dialog box. After confirmation, all information will be backfilled and saved. It is very convenient to use, but we must follow certain principles to design the template, which is described in detail here. + + + +*In order to facilitate printing, we need to set the paper size according to our own situation, such as A4, A5. Second, for precise layout, we switched the Excel view to the page layout, so that all cell sizes are set in cm.* + + + +**Universal syntax** + +`{type Var_Name = Default Value # note}`, Variable tags of any type follow this format, with curly braces for variable identifiers and other items. Some items are required and some are optional. + +**Underlying parameter** + +`int,float,str,txt,bool`:these basic parameters, syntax format consistent take STR as an example, `{str Name = YX Dragon # please input your name here}`,where type and variable name are required, default value and comment are optional. The difference between `str` and `txt` is that in the ImagePy interactive dialog, `txt` can receive multiple lines of text. + +**Selection parameter** + +`list`: usage example`{list Favourite_System = [Windows, Linux, Mac] # please select your favourite system}`,for the list, the default value must be provided, the options are separated by commas, the Spaces are ignored, the comments are optional. + +**Image parameter** + +`img`:usage example `{img My_Photo = [4.8,4.8,0.9,0] # you photo here`,for img type, the default value must be provided, it is an brackets, inside four numbers, respectively represents: length (cm), height (cm), white space ratio (0.9 represents 10% around), whether to stretch (0 represents holding ratio, 1 can be stretched), annotation is optional. + +**Table parameter** + +`tab`:usage example `{tab Record = [1,3,0,0] # the result table}`,for tab type, a default value must be provided in a bracket, incleded four numbers respectively: spacing (1 represents not merged cells), column spacing (1 represents not merged cells), the header row relative position (1 represents the data area on a line, 0 represents don't fill the title), indexed column line of position (-1 represents a left column of the data area ,0 represents no filling index), the annotation is optional. \ No newline at end of file diff --git a/doc/simple.md b/doc/simple.md index fb4eb4e..15837bd 100644 --- a/doc/simple.md +++ b/doc/simple.md @@ -1,104 +1,100 @@ -# Simple +# Simple -Simple是Filter之外的又一个比较重要的插件,不同的是Simple不着重处理单张图像,而是把图像序列当作整体进行三维处理,此外也用于操作图像之外的相关属性,如ROI, 色彩索引表,比例尺及单位,或图像Mark。 +Simple is another important addition to Filter. The difference is that Simple does not focus on single images, but treats image sequences as a whole for 3d processing. In addition, Simple is also used to manipulate relevant attributes outside of images, such as ROI, color index table, scale and unit, or image Mark. -## Gaussian3D +## Gaussian3D ```python from imagepy.core.engine import Simple import scipy.ndimage as ndimg class Gaussian3D(Simple): - title = 'Gaussian 3D Demo' - note = ['all', 'stack3d'] + title = 'Gaussian 3D Demo' + note = ['all', 'stack3d'] - para = {'sigma':2} - view = [(float, 'sigma', (0,30), 1, 'sigma', 'pix')] + para = {'sigma':2} + view = [(float, 'sigma', (0,30), 1, 'sigma', 'pix')] - def run(self, ips, imgs, para = None): - imgs[:] = ndimg.gaussian_filter(imgs, para['sigma']) + def run(self, ips, imgs, para = None): + imgs[:] = ndimg.gaussian_filter(imgs, para['sigma']) ``` -note里的stack3d标识是指插件处理一个连续的图像栈,而run函数里,可以通过imgs拿到图像序列,我们对其进行三维高斯滤波,结果重新赋值给imgs。 +`stack3d` in `note` identity is refers to the plug-in processing a continuous image stack. And `run` function can get image sequence of ` ndarray ` object by ` imgs ` ,We carries on the three-dimensional gaussian filter, the results to assign a value to ` imgs `. -ImagePy里图像序列分stack2d,stack3d,其中stack2d是基于list的图像序列,便于增加,删除slice。而stack3d是一个连续的numpy数组,便于三维滤波,分析。我们可以通过 **Image > Type > Trans to stack/list** 进行转换。 +Image sequence of ImagePy include ` stack2d `, ` stack3d `. And ` stack2d ` image sequence is based on ` list` .It is easy to increase or delete ` slice `. While ` stack3d ` is a continuous ` numpy ` array, and facilitate three-dimensional filter to direvtly analyze. We can tansform them through the**`Image > Type > Trans to stack/list`** . +* the 2d gaussian Filter based on Filter can also process sequences, but the processing method is to treat them as multiple 2d images, while the 3d gaussian Filter based on Simple also convolves in the z direction.* -注意:基于Filter的二维高斯滤波也会处理序列,但处理方式是当成多个二维图像处理,而基于Simple的三维高斯滤波,则是在z方向也进行了卷积。 - -## SetLUT +## SetLUT ```python from imagepy.core.engine import Simple import numpy as np class SetLUT(Simple): - title = 'Set LUT Demo' - note = ['all'] - para = {'lut':'red'} - view = [(list, 'lut', ['red', 'green', 'blue'], str, 'look up', 'table')] - - def run(self, ips, imgs, para = None): - cmap = [[i==para['lut']] for i in ['red', 'green', 'blue']] - ips.lut = (cmap*np.arange(256)).astype(np.uint8).T + title = 'Set LUT Demo' + note = ['all'] + para = {'lut':'red'} + view = [(list, 'lut', ['red', 'green', 'blue'], str, 'look up', 'table')] + + def run(self, ips, imgs, para = None): + cmap = [[i==para['lut']] for i in ['red', 'green', 'blue']] + ips.lut = (cmap*np.arange(256)).astype(np.uint8).T ``` -lookup table是一个255*3的色彩映射表,映射表并不改变像素,只改变图像的显示。可以通过ips.lut进行访问或设定。与之相关的还有一个ips.range,是一个二元tuple,设定图像像素的动态范围,对于8位图像,通常是0-255,但对于float类型,range的设定就非常有意义,事实上,在展示时,ImagePy先根据range将图像进行clip,并缩放到0-255,然后再套用索引表。 - +Lookup table is a 255*3 color map that does not change the pixels, only the display of the image. It can be accessed or set via ips.lut. The range is a binary tuple, which sets the dynamic range of image pixels. For 8-bit images, it is usually 0-255, but for float types, the range setting is very meaningful. In fact, when displaying, ImagePy first carries out clip on the image according to the range and scales it to 0-255, and then applies the index table. ![14](http://idoc.imagepy.org/demoplugin/15.png)
SetLUT

-## Inflate ROI +## Inflate ROI ```python from imagepy.core.engine import Simple class Inflate(Simple): - title = 'Inflate ROI Demo' - note = ['all', 'req_roi'] - para = {'r':5} - view = [(int, 'r', (1,100),0, 'radius', 'pix')] + title = 'Inflate ROI Demo' + note = ['all', 'req_roi'] + para = {'r':5} + view = [(int, 'r', (1,100),0, 'radius', 'pix')] - def run(self, ips, imgs, para = None): - ips.roi = ips.roi.buffer(para['r']) + def run(self, ips, imgs, para = None): + ips.roi = ips.roi.buffer(para['r']) ``` -ROI指明哪些区域是我们关心的,ImagePy中的ROI基于Shapely对象,我们可以对其进行操作,以上是对当前ROI进行扩张的例子。 - +` ROI ` indicate what areas is what we care about, the ` ROI ` of ImagePy based on` Shapely ` object. We can operate on the ` ROI ` , the above is to expand the current ` ROI ` example. ![14](http://idoc.imagepy.org/demoplugin/16.png)
Inflate ROI

-## Set Scale And Unit +## Set Scale And Unit ```python from imagepy.core.engine import Simple class Unit(Simple): - title = 'Scale And Unit Demo' - note = ['all'] - para = {'scale':1, 'unit':'mm'} - view = [(float, 'scale', (1e-3,1e3), 3, 'scale', ''), - (str, 'unit', 'scale', '')] - - def run(self, ips, imgs, para = None): - ips.unit = (para['scale'], para['unit']) + title = 'Scale And Unit Demo' + note = ['all'] + para = {'scale':1, 'unit':'mm'} + view = [(float, 'scale', (1e-3,1e3), 3, 'scale', ''), + (str, 'unit', 'scale', '')] + + def run(self, ips, imgs, para = None): + ips.unit = (para['scale'], para['unit']) ``` -默认情况ImagePy中的一切测量,分析结果以像素为单位,但我们可以通过ips.unit对其进行访问和设定。 - +It is in pixels unit to everything in the default ImagePy measurement and analysis results, but we can through `ips.unit` to access and set. ![14](http://idoc.imagepy.org/demoplugin/17.png)
Set Scale And Unit

-## Set Random Point Mark +## Mark ```python from imagepy.core.engine import Simple @@ -106,130 +102,177 @@ from imagepy.core.mark import GeometryMark import numpy as np class Mark(Simple): - title = 'Random Points Demo' - note = ['all'] + title = 'Random Points Demo' + note = ['all'] - def run(self, ips, imgs, para = None): - pts = (np.random.rand(200)*512).reshape((100,2)) - ips.mark = GeometryMark({'type':'points', 'color':(255,0,0), 'lw':1, 'body':pts}) + def run(self, ips, imgs, para = None): + pts = (np.random.rand(200)*512).reshape((100,2)) + ips.mark = GeometryMark({'type':'points', 'color':(255,0,0), 'lw':1, 'body':pts}) ``` -mark是图像上的覆盖物,并不改变图像本身。ImagePy定义了一套几何数据结果用于绘制mark,这里简单介绍: - +` mark ` is covering on the image, the image itself does not change. ImagePy defines a set of geometric data results for drawing ` mark `, here is a brief introduction: ![14](http://idoc.imagepy.org/demoplugin/18.png)
Set Random Point Mark

-**各种Mark类型及用法** +**Various Mark types and usages** -**point:** {'type':'point', 'color':(r,g,b), 'lw':1, 'body':(x,y)} +**point:** -**points:** {'type':'points', 'color':(r,g,b), 'lw':1, 'body':[(x1,y1), (x2,y2), ...]} +```python +{'type':'point', 'color':(r,g,b), 'lw':1, 'body':(x,y)} +``` -**line:** {'type':'line', 'color':(r,g,b), 'lw':1, 'style':'-', 'body':[(x1,y1), (x2,y2), ...]} +**points:** -**lines:** {'type':'lines', 'color':(r,g,b), 'lw':1, 'style':'-', 'body':[[(x1,y1), (x2,y2), ...], [...]]} +```python +{'type':'points', 'color':(r,g,b), 'lw':1, 'body':[(x1,y1), (x2,y2), ...]} +``` -**polygon:** {'type':'polygon', 'color':(r,g,b), 'fcolor':(r,g,b), 'lw':1, 'style':'o', 'body':[(x1,y1), (x2,y2), ...]} +**line:** +```python +{'type':'line', 'color':(r,g,b), 'lw':1, 'style':'-', 'body':[(x1,y1), (x2,y2), ...]} +``` + +**lines:** +```python +{'type':'lines', 'color':(r,g,b), 'lw':1, 'style':'-', 'body':[[(x1,y1), (x2,y2), ...], [...]]} +``` -**polygons:** {'type':'polygons', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'lw':1, 'style':'o', 'body':[[(x1,y1), (x2,y2), ...], [...]]} -**circle:** {'type':'circle', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':(x,y,r)} +**polygon:** +```python +{'type':'polygon', 'color':(r,g,b), 'fcolor':(r,g,b), 'lw':1, 'style':'o', 'body':[(x1,y1), (x2,y2), ...]} +``` -**circles:** {'type':'circles', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':[(x1,y1,r1), (x2,y2,r2)]} +**polygons:** +```python +{'type':'polygons', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'lw':1, 'style':'o', 'body':[[(x1,y1), (x2,y2), ...], [...]]} +``` -**ellipse:** {'type':'ellipse', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':(x,y,l1,l2,ori)} +**circle:** +```python +{'type':'circle', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':(x,y,r)} +``` -**ellipses:** {'type':'ellipses', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':[(x1,y1,a1,b1,ori1), (x2,y2,a2,b2,ori2), ...]} -**rectangle:** {'type':'rectangle', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':True, 'body':(x,y,w,h)} +**circles:** +```python +{'type':'circles', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':[(x1,y1,r1), (x2,y2,r2)]} +``` -**rectangles:** {'type':'rectangles', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':[(x1,y1,w1,h1),(x2,y2,w2,h2),...]} +**ellipse:** +```python +{'type':'ellipse', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':(x,y,l1,l2,ori)} +``` -**text:** {'type':'text', 'color':(r,g,b), 'fcolor':(r,g,b), 'size':8, 'pt':True, 'body':(x,y,txt)} +**ellipses:** +```python +{'type':'ellipses', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':[(x1,y1,a1,b1,ori1), (x2,y2,a2,b2,ori2), ...]} +``` +**rectangle:** +```python +{'type':'rectangle', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':True, 'body':(x,y,w,h)} +``` -**texts:** {'type':'texts', 'color':(r,g,b), 'fcolor':(r,g,b), 'size':8, 'pt':True, 'body':[(x1,y1,txt1),(x2,y2,txt2)]} +**rectangles:** +```python +{'type':'rectangles', 'color':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':[(x1,y1,w1,h1),(x2,y2,w2,h2),...]} +``` ---- +**text:** +```python +{'type':'text', 'color':(r,g,b), 'fcolor':(r,g,b), 'size':8, 'pt':True, 'body':(x,y,txt)} +``` -**layer:** {'type':'layer', 'num':-1, 'clolor':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':[sequence of basic element]} - -**layers:** {'type':'layers', 'num':-1, 'clolor':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':{1:layer1, 2:layer2, ...}} +**texts:** +```python +{'type':'texts', 'color':(r,g,b), 'fcolor':(r,g,b), 'size':8, 'pt':True, 'body':[(x1,y1,txt1),(x2,y2,txt2)]} +``` +--- +**layer:** +```python +{'type':'layer', 'num':-1, 'clolor':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':[sequence of basic element]} +``` -**以上各个基础元素,形式类似,这里统一讲解各个参数的意义:** +**layers:** +```python +{'type':'layers', 'num':-1, 'clolor':(r,g,b), 'fcolor':(r,g,b), 'fill':False, 'body':{1:layer1, 2:layer2, ...}} +``` -**type:** 元素类型 -**color:** 颜色 -**fcolor:** 填充色 (只有面状要素有) +**The above basic elements are similar in form. The meaning of each parameter is explained here:** -**fill:** 是否填充 (只有面状要素有) +**type:** element type -**lw:** 线条宽度 +**color:** colour -**style:** 线条风格,有'-o'代表线条和节点都绘制,当然也可以选择只绘制线条或节点,或都不 +**fcolor:** Fill color (only plane elements) -**size:** 文字高度 (只有文字有) +**fill:** Whether to fill (only plane elements) -**body:** 几何数据,根据各个类型而定 +**lw:** Line width +**style:** Line style, with '-o' for both line and node drawn, or you can choose to draw only line or node, or neither +**size:** Text height (text only) -**要素集合** +**body:** Geometric data, depending on the type -**layer:** layer可以指定color, fcolor, fill, 不同的是,layer的body存放的是其他基础要素,其实以上各种要素,除了type,body其他的属性都是非必须的,如果当前要素没有指定,则会取自其所属的layer,如果没有所属layer,或layer也未指定,则会使用默认。 +**Point collection** -**layers:** layers是更高级的要素集合,与layer不同的是,layers的body是一个字典,而且在图像序列中,只绘制层号所对应的layer,这样可以实现对图像序列的每一张设定一个对应的mark。 +**layer:** ` layer ` can specify ` color `, ` fcolor `, ` fill `, which is different from ` body ` .Because ` body ` of ` layer` deposit is the of other basis elements, actually the above all sorts of elements.In addition to ` type `, ` body ` ,other attributes are not necessary.If the current element is not specified, it will be taken from its ` layer `.If there is no subordinate ` layer `, or ` layer ` is not specified, the default value will be used. +**layers:** ` layers ` is a set of more advanced elements than ` layer `. ` body ` of ` layers ` is a dictionary, whose key is a layer of int. And in the image sequence, only paint layer number corresponding ` layer `, so that we can realize each image of the image sequence a corresponding ` mark `. -## Simple运行机制 +## Simple operating mechanism **note:** -note选项是行为控制标识,用于控制插件执行的流程,比如让框架进行类型兼容检测,如不满足自动中止。设定通道和序列支持设定,以及是否需要提供预览,roi等支持。 +` note ` option is behavior control indicators, which is used to control the plug-in implementation process.Such as allowing framework compatible with the types of tests,if does not meet ,it can be automatical suspensed. ` note ` option include set channel and sequence support settings, and whether to provide preview, ROI and other support. -1. all:插件支持任意类型 +1. `all`:Plug-ins support any type -2. 8-bit:插件支持无符号8位 +2. `8-bit`:The plug-in supports unsigned 8 bits -3. 16-bit:插件支持无符号16位 +3. `16-bit`:The plug-in supports unsigned 16 bits -4. int:插件支持32位,64位整数 +4. `int`:The plug-in supports 32-bit, 64-bit integers -5. rgb:插件支持3通道24位彩色 +5. `rgb`:Plug-ins support 3 channels 24 - bit color -6. float:插件支持32位,64位浮点 +6. `float`:The plug-in supports 32-bit, 64-bit floating point - ------ + --- -7. req_roi:是否必须有roi才能够处理 +7. `req_roi`:Whether there must be ROI to handle -8. stack:要求必须是图像序列 +8. `stack`:The requirement must be a sequence of images -9. stack2d:要求必须是离散图像序列(list) +9. `stack2d`:It has to be a list of discrete images(list) -10. stack3d:要求必须是连续图像序列(ndarray) +10. `stack3d`:It has to be a continuous sequence of images.(ndarray) -11. preview:是否显示预览选项 +11. `preview`:Whether to display preview option **para, view:** -参数字典,具体用法参阅start入门。 +Parameter dictionary, see Start for details. **run:** -1. ips:图像封装类,我们可以通过ips对roi, mark, lut, range, unit等进行操作 -2. imgs:图像序列,对其进行操作,如三维滤波,或分析,可以以表格形式展示结果。 +1. ` ips ` : image wrapper class, we can operate through ` ips ` ,` ROI `, ` mark `, ` lut `, ` range `, ` unit `, etc +2. ` imgs ` : image sequence, carrying on the operation, such as three-dimensional filter, or analysis, can show the results in table form. **load:** -def load(self, ips) 最先执行,如果return结果为False,插件将中止执行。默认返回True,如有必要,可以对其进行重载,进行一系列条件检验,如不满足,IPy.alert弹出提示,并返回False。 +` def load (self, ips) ` are executed first, if ` return ` results for ` False `, plug-in will suspend execution. The default return ` True `,.If necessary, it can be overloaded and design a series of condition inspection.If not meet, ` IPy. Alert ` pop-up prompts, and returns the ` False `. **preview:** -def preview(self, ips, para) Simple虽然定义了preview,但默认是什么也不做的,因为对于三维滤波等操作,往往需要很长的运算时间,因而并不适合预览,如有需要可以重载。 \ No newline at end of file +` def preview (self, ips, para) ` Simple defines the ` preview ` option , but the default is to do nothing.Because it often requires a long operation time for the three dimensional filtering operation, therefore it is not suitable for ` preview ` .It can be overloaded, if necessary. \ No newline at end of file diff --git a/doc/start.md b/doc/start.md index beefafc..6ccb3a5 100644 --- a/doc/start.md +++ b/doc/start.md @@ -1,16 +1,19 @@ -# 基础预备 +# 从这里开始 -## 什么是插件 +这里我们用Hello World的例子来开始,顺带介绍插件的加载机制,以及如何配置参数对话框,为后续的开发做好准备。 -ImagePy是一个扩展性很强的图像处理框架,我们是通过插件来对ImagePy进行功能扩展的,插件是一段代码或一个文件,放在特定的位置,在ImagePy启动时自动加载,其具体形式可以体现为菜单,工具栏,桌面小部件: +## 什么是插件 -其实在ImagePy中一切功能皆插件,ImagePy原生功能并不享受任何特权,这些插件根据目录层级解析成对应UI元素,并在点击时触发相应功能,我们可以使用 **Plugins > Manager > Plugin Tree View** 来查看插件及其对应的源码。 +ImagePy是一个扩展性很强的图像处理框架,我们是通过插件来对ImagePy进行功能扩展的,插件是一段代码或一个文件,放在特定的位置,在ImagePy启动时自动加载,其具体形式可以体现为菜单,工具栏,桌面小部件。其实在ImagePy中一切功能皆插件,ImagePy原生功能并不享受任何特权,这些插件根据目录层级解析成对应UI元素,并在点击时触发相应功能,我们可以使用**`Plugins > Manager > Plugin Tree View`**来查看插件及其对应的源码。 +![31](http://idoc.imagepy.org/demoplugin/31.png) +
Plugins Tree View

-## Hello World + +## Hello World 我们开始写第一个插件,实现一个简单的hello world. @@ -19,27 +22,26 @@ from imagepy.core.engine import Free from imagepy import IPy class Plugin(Free): - title = 'Hello World' + title = 'Hello World' - def run(self, para=None): - IPy.alert('Hello World, I am ImagePy!') + def run(self, para=None): + IPy.alert('Hello World, I am ImagePy!') ``` - -这是一个最简单的插件,首先import Free, IPy. Free是一种插件类型,这种类型插件可以不依赖图像而运行,我们在run里面用IPy.alert弹出一个'Hello world, I am ImagePy!'的提示框。 +这是一个最简单的插件,首先`import Free, IPy`. `Free`是一种插件类型,这种类型插件可以不依赖图像而运行,我们在`run`里面用`IPy.alert`弹出一个'Hello world, I am ImagePy!'的提示框。 ![14](http://idoc.imagepy.org/demoplugin/01.png) -
Hello World

**如何加载** +我们将上面的脚本文件命名为 `hello_plg.py`,拷贝到ImagePy目录下的`Imagepy > menus > Plugins`目录下,重新启动ImagePy,点开`Plugins`菜单,就会看到我们的插件。一些加载原则如下: + +1. `menus`及其子目录会被解析。 +2. `plg.py`的文件会被解析。 +3. 文件内的`Plugins`类会被解析为插件,`title`是菜单显示内容 -我们将上面的脚本文件命名为 hello_plg.py,拷贝到ImagePy目录下的 **Imagepy > menus > Plugins** 目录下,重新启动ImagePy,点开Plugins菜单,就会看到我们的插件。一些加载原则如下: -1. menus及其子目录会被解析。 -2. plg.py的文件会被解析。 -3. 文件内的Plugins类会被解析为插件,title是菜单显示内容 -## Who Are You +## Who Are You 接下来我们为这个插件添加一些参数,邀请用户输入名字和年龄。 @@ -48,84 +50,87 @@ from imagepy.core.engine import Free from imagepy import IPy class Plugin(Free): - title = 'Who Are You' - para = {'name':'', 'age':0} - view = [(str, 'name', 'name', 'please'), + title = 'Who Are You' + para = {'name':'', 'age':0} + view = [(str, 'name', 'name', 'please'), (int, 'age', (0,120), 0, 'age', 'years old')] - def run(self, para=None): - IPy.alert('Name:\t%s\r\nAge:\t%d'%(para['name'], para['age'])) + def run(self, para=None): + IPy.alert('Name:\t%s\r\nAge:\t%d'%(para['name'], para['age'])) ``` -ImagePy框架实现了参数对话框生成机制,可以根据para,view来生成对应的交互,完成交互后,我们在run函数中可以通过para参数获取交互结果,我们在下一个例子中会更加详细的讲解各种类型的参数生成。 -![14](http://idoc.imagepy.org/demoplugin/02.png) +ImagePy框架实现了参数对话框生成机制,可以根据`para`,`view`来生成对应的交互,完成交互后,我们在`run`函数中可以通过`para`参数获取交互结果,我们在下一个例子中会更加详细的讲解各种类型的参数生成。 +![14](http://idoc.imagepy.org/demoplugin/02.png)
Who Are You

-## Questionnaire + + +## Questionnaire ```python from imagepy.core.engine import Free from imagepy import IPy class Plugin(Free): - title = 'Questionnaire' - - para = {'name':'yxdragon', 'age':10, 'h':1.72, 'w':70, 'sport':True, 'sys':'Mac', 'lan':['C/C++', 'Python'], 'c':(255,0,0)} - - view = [('lab', 'lab', 'This is a questionnaire'), - (str, 'name', 'name', 'please'), - (int, 'age', (0,150), 0, 'age', 'years old'), - (float, 'h', (0.3, 2.5), 2, 'height', 'm'), - ('slide', 'w', (1, 150), 0, 'kg'), - (bool, 'sport', 'do you like sport'), - (list, 'sys', ['Windows','Mac','Linux'], str, 'favourite', 'system'), - ('chos', 'lan', ['C/C++','Java','Python'], 'lanuage you like(multi)'), - ('color', 'c', 'which', 'you like')] - - def run(self, para=None): - rst = ['Questionnaire Result', - 'Name:%s'%para['name'], - 'Age:%s'%para['age'], - 'Height:%sm'%para['h'], - 'Weight:%skg'%para['w'], - 'Like Sport:%s'%para['sport'], - 'Favourite System:%s'%para['sys'], - 'Like lanuage:%s'%para['lan'], - 'Favourite Color:%s'%str(para['c'])] - - IPy.alert('\r\n'.join(rst)) + title = 'Questionnaire' + + para = {'name':'yxdragon', 'age':10, 'h':1.72, 'w':70, 'sport':True, 'sys':'Mac', 'lan':['C/C++', 'Python'], 'c':(255,0,0)} + + view = [('lab', 'lab', 'This is a questionnaire'), + (str, 'name', 'name', 'please'), + (int, 'age', (0,150), 0, 'age', 'years old'), + (float, 'h', (0.3, 2.5), 2, 'height', 'm'), + ('slide', 'w', (1, 150), 0, 'kg'), + (bool, 'sport', 'do you like sport'), + (list, 'sys', ['Windows','Mac','Linux'], str, 'favourite', 'system'), + ('chos', 'lan', ['C/C++','Java','Python'], 'lanuage you like(multi)'), + ('color', 'c', 'which', 'you like')] + + def run(self, para=None): + rst = ['Questionnaire Result', + 'Name:%s'%para['name'], + 'Age:%s'%para['age'], + 'Height:%sm'%para['h'], + 'Weight:%skg'%para['w'], + 'Like Sport:%s'%para['sport'], + 'Favourite System:%s'%para['sys'], + 'Like lanuage:%s'%para['lan'], + 'Favourite Color:%s'%str(para['c'])] + + IPy.alert('\r\n'.join(rst)) ``` -![14](http://idoc.imagepy.org/demoplugin/03.png) +这里我们实现一个调查问卷,让用户输入各类信息,同时也是为了向开发者展示,如何通过`para`,`view`来说配置出任何自己想要的参数对话框。 -
Questionnaire

-**label:** para类型:不需要参数, view用法:('lab', 'lab', 'what you want to show') +![14](http://idoc.imagepy.org/demoplugin/03.png) +
Questionnaire

-**str:** para类型:str, view用法:(str, key, prefix, suffix),其中key要和para中的key对应,prefix,suffix用作输入框前后的提示内容。 +**label:** para类型:`不需要参数`, view用法:`('lab', 'lab', 'what you want to show')` -**int:** para类型:int,view用法:(int, key, (lim1, lim2), accu, 'prefix', 'suffix'),其中key要和para中的key对应,limit用于限定输入数值的范围,accu限定小数点位数(0),prefix,suffix用作输入框前后的提示内容。 +**str:** para类型:`str`, view用法:`(str, key, prefix, suffix)`,其中`key`要和`para`中的`key`对应,`prefix`,`suffix`用作输入框前后的提示内容。 -**float:** para类型:float,view用法:(int, key, (lim1, lim2), accu, 'prefix', 'suffix'),其中key要和para中的key对应,limit用于限定输入数值的范围,accu限定小数点位数,prefix,suffix用作输入框前后的提示内容。 +**int:** para类型:`int`,view用法:`(int, key, (lim1, lim2), accu, 'prefix', 'suffix')`,其中`key`要和`para`中的`key`对应,`limit`用于限定输入数值的范围,`accu`限定小数点位数(0),`prefix`,`suffix`用作输入框前后的提示内容。 -**slider:** para类型:int/float,view用法:('slide', key, (lim1, lim2), accu, 'prefix'),其中key要和para中的key对应,limit用于限定输入数值的范围,accu限定小数点位数,prefix用作输入框前后的提示内容。 +**float:** para类型:`float`,view用法:`(int, key, (lim1, lim2), accu, 'prefix', 'suffix')`,其中`key`要和`para`中的`key`对应,`limit`用于限定输入数值的范围,`accu`限定小数点位数,`prefix`,`suffix`用作输入框前后的提示内容。 -**bool:** para类型:bool,view用法:(bool, 'key', 'label'),其中key要和para中的key对应,label用作提示。 +**slider:** para类型:`int/float`,view用法:`('slide', key, (lim1, lim2), accu, 'prefix')`,其中`key`要和`para`中的`key`对应,`limit`用于限定输入数值的范围,`accu`限定小数点位数,`prefix`用作输入框前后的提示内容。 -**list:** para类型:any type,view用法:(list, key, [choices], type, prefix, suffix),其中key要和para中的key对应,choices是字符选项,type是期望输出类型,如str, int,prefix,suffix用作选择框前后的提示内容。 +**bool:** para类型:`bool`,view用法:`(bool, 'key', 'label')`,其中`key`要和`para`中的`key`对应,`label`用作提示。 -**choices:** para类型:str list,view用法:('chos', key, [choices], prefix, suffix),与list类似,不同的是choices可以支持多选,选项以str list形式记录。 +**list:** para类型:`any type`,view用法:`(list, key, [choices], type, prefix, suffix)`,其中`key`要和`para`中的`key`对应,`choices`是字符选项,`type`是期望输出类型,如`str`, `int`,`prefix`,`suffix`用作选择框前后的提示内容。 -**color:** para类型:(r,g,b) 0-255,用法:('color', key, prefix, suffix),其中key要和para中的key对应,prefix,suffix用作输入框前后的提示内容。 +**choices:** para类型:`str list`,view用法:`('chos', key, [choices], prefix, suffix)`,与`list`类似,不同的是`choices`可以支持多选,选项以`list of string`形式记录。 +**color:** para类型:`(r,g,b) 0-255`,用法:`('color', key, prefix, suffix)`,其中`key`要和`para`中的`key`对应,`prefix`,`suffix`用作输入框前后的提示内容。 -**除以上基础数据类型之外,ImagePy还支持一些内部类型的参数,如接收一副图像,接收一个表格,或者对表格的字段进行单选或多选,这些我们在后续的示例中会有所展示** +*除以上基础数据类型之外,ImagePy还支持一些内部类型的参数,如接收一副图像,接收一个表格,或者对表格的字段进行单选或多选,这些我们在后续的示例中会有所展示* -## 一个文件内实现多个插件 +## 一个文件内实现多个插件 以上我们分别实现了三个插件,而Python具有语法精简的优势,所以我们也可以在一个文件内实现多个插件,方法如下。 ```python @@ -147,12 +152,13 @@ class Questionnaire(Free): plgs = [HelloWorld, WhoAreYou, Questionnaire] ``` -我们将三个类写在一个文件内,最后加上plgs = [],文件命名为start_plgs.py。框架加载原则如下: +我们将三个类写在一个文件内,最后加上`plgs = []`,文件命名为`start_plgs.py`。框架加载原则如下: -1. menus目录或子目录下的plgs.py结尾的文件会被当作多插件解析 -2. 插件内的plgs列表会被依次解析 -3. plgs内可以加入'-',会被解析为菜单分隔符 +1. `menus`目录或子目录下的`plgs.py`结尾的文件会被当作多插件解析 +2. 插件内的`plgs`列表会被依次解析 +3. `plgs`内可以加入`'-'`,会被解析为菜单分隔符 ![14](http://idoc.imagepy.org/demoplugin/04.png) -
插件解析

\ No newline at end of file +
parse as menus

+ diff --git a/doc/table.md b/doc/table.md index 3ba9040..1227cff 100644 --- a/doc/table.md +++ b/doc/table.md @@ -1,10 +1,10 @@ -# Table +# Table 插件 -表格是图像之外另一个非常重要的数据类型,某种意义上,科研图像分析的结果最终都会归到表格。ImagePy对表格类型数据有很好的支持。 +表格是图像之外另一个非常重要的数据类型,某种意义上,科研图像分析的结果最终都会归到表格。ImagePy对表格类型数据有很好的支持,其核心数据结构是`pandas.DataFrame`。 -## 生成成绩单 +## 生成成绩单 ```python from imagepy.core.engine import Free @@ -13,80 +13,80 @@ import numpy as np import pandas as pd class Score(Free): - title = 'Student Score' + title = 'Student Score' - def run(self, para=None): - index = ['Stutent%s'%i for i in range(1,6)] - columns = ['Math', 'Physics', 'Biology', 'History'] - score = (np.random.rand(20)*40+60).reshape((5,4)).astype(np.uint8) - IPy.show_table(pd.DataFrame(score, index, columns), 'Scores') + def run(self, para=None): + index = ['Stutent%s'%i for i in range(1,6)] + columns = ['Math', 'Physics', 'Biology', 'History'] + score = (np.random.rand(20)*40+60).reshape((5,4)).astype(np.uint8) + IPy.show_table(pd.DataFrame(score, index, columns), 'Scores') ``` -我们通过一个Free插件生成表格,表格是一个pandas.DataFrame对象,通过IPy.show_table(df, title)来展示。 +我们通过一个`Free`插件生成表格,表格是一个`pandas.DataFrame`对象,通过`IPy.show_table(df, title)`来展示。 ![14](http://idoc.imagepy.org/demoplugin/19.png) -
生成成绩单

+
generate score list

-## 根据某科成绩排序 +## 根据某科成绩排序 ```python from imagepy.core.engine import Table class Sort(Table): - title = 'Table Sort Demo' + title = 'Table Sort Demo' - para = {'by':'Math'} - view = [('field', 'by', 'item', '')] + para = {'by':'Math'} + view = [('field', 'by', 'item', '')] - def run(self, tps, data, snap, para=None): - tps.data.sort_values(by=para['by'], inplace=True) + def run(self, tps, data, snap, para=None): + tps.data.sort_values(by=para['by'], inplace=True) ``` -这里用到了一种新的参数类型,field,这种参数类型其实是一个单选类型,但是不需要我们提供选项,会自动从当前表格的columns中获取。run中通过inplace参数直接改变DataFrame本身,一些操作无法修改本身,可以将结果return。 +这里用到了一种新的参数类型,`field`,这种参数类型其实是一个单选类型,但是不需要我们提供选项,会自动从当前表格的`columns`中获取。`run`中通过`inplace`参数直接改变`DataFrame`本身,一些操作无法修改本身,可以将结果return。 ![14](http://idoc.imagepy.org/demoplugin/20.png) -
排序

+
sort by math

-## 绘制柱状图 +## 绘制柱状图 ```python class Bar(Table): - title = 'Score Chart Demo' + title = 'Score Chart Demo' asyn = False - para = {'item':[]} - view = [('fields', 'item', 'select items')] + para = {'item':[]} + view = [('fields', 'item', 'select items')] - def run(self, tps, data, snap, para = None): - data[para['item']].plot.bar(stacked=True, grid=True, title='Score Chart') - plt.show() + def run(self, tps, data, snap, para = None): + data[para['item']].plot.bar(stacked=True, grid=True, title='Score Chart') + plt.show() ``` -这里又遇到了一种参数类型,fields,这种参数类型其实是一个多选类型,但是不需要我们提供选项,会自动从当前表格的columns中获取。当表格从界面上被选中若干列,参数对话框里对应的项也会被默认勾上。我们用pandas自带的绘图函数,但值得一提的是,插件中加入了asyn = False,这个标识告诉ImagePy不要启用异步执行run,因为这个插件涉及了UI,必须在主线程进行。 +这里又遇到了一种参数类型,`fields`,这种参数类型其实是一个多选类型,但是不需要我们提供选项,会自动从当前表格的`columns`中获取。当表格从界面上被选中若干列,参数对话框里对应的项也会被默认勾上。我们用pandas自带的绘图函数,但值得一提的是,插件中加入了`asyn = False`,这个标识告诉ImagePy不要启用异步执行`run`,因为这个插件涉及了`UI`,必须在主线程进行。 ![14](http://idoc.imagepy.org/demoplugin/21.png) -
绘制柱状图

+
bar chart

-## Table运行机制 +## Table 运行机制 **note:** -note选项是行为控制标识,用于控制插件执行的流程,比如让框架进行类型兼容检测,如不满足自动中止。设定通道和序列支持设定,以及是否需要提供预览,roi等支持。 +`note`选项是行为控制标识,用于控制插件执行的流程,比如让框架进行类型兼容检测,如不满足自动中止,与Filter和simple的note有所区别。 -1. req_sel:需要选区 -2. req_row:需要选中行 -3. req_col:需要选中列 -4. auto_snap:处理前框架自动对数据进行缓冲 -5. row_msk:snap时只缓冲选中的行 -6. col_msk:snap时只缓冲选中的列 -7. num_only:snap时只缓冲数值列 -8. preview:是否显示预览选项 +1. `req_sel`:需要选区 +2. `req_row`:需要选中行 +3. `req_col`:需要选中列 +4. `auto_snap`:处理前框架自动对数据进行缓冲 +5. `row_msk`:snap时只缓冲选中的行 +6. `col_msk`:snap时只缓冲选中的列 +7. `num_only`:snap时只缓冲数值列 +8. `preview`:是否显示预览选项 **para, view:** @@ -94,14 +94,14 @@ note选项是行为控制标识,用于控制插件执行的流程,比如让 **run:** -1. tps:表格封装类,我们可以通过tps对rowmsk, colmsk等进行访问或操作 -2. snap:表格缓冲,如果有auto_snap标识,则可以生效。 -3. data:当前表格,对其进行操作。 +1. `tps`:表格封装类,我们可以通过`tps`对`rowmsk`, `colmsk`等进行访问或操作 +2. `snap`:表格缓冲,如果有`auto_snap`标识,则可以生效。 +3. `data`:当前表格,对其进行操作。 **load:** -def load(self, tps) 最先执行,如果return结果为False,插件将中止执行。默认返回True,如有必要,可以对其进行重载,进行一系列条件检验,如不满足,IPy.alert弹出提示,并返回False。 +`def load(self, tps)` 最先执行,如果`return`结果为`False`,插件将中止执行。默认返回`True`,如有必要,可以对其进行重载,进行一系列条件检验,如不满足,`IPy.alert`弹出提示,并返回`False`。 **preview:** -def preview(self, tps, para) ,默认情况下会自动执行run并更新,有需要可以重载。 \ No newline at end of file +`def preview(self, tps, para)` ,默认情况下会自动执行`run`并更新,有需要可以重载。 \ No newline at end of file diff --git a/doc/tool.md b/doc/tool.md index 4ddc367..4150cfa 100644 --- a/doc/tool.md +++ b/doc/tool.md @@ -1,6 +1,6 @@ -# Tool +# Tool 插件 -Tool插件用来完成鼠标交互,启动时被加载为工具栏上的图标。典型的工具插件是,roi,画笔,测量工具等。 +Tool插件用来完成鼠标交互,启动时被加载为工具栏上的图标。典型的工具插件是,roi编辑,画笔,测量工具等。 @@ -47,7 +47,7 @@ class Plugin(Tool): def mouse_wheel(self, ips, x, y, d, **key):pass ``` -通过重载mouse_down, mouse_up, mouse_move方法,可以实现鼠标交互,这里我们实现一个最常见的画笔工具。鼠标按下时,标记status为True,在鼠标移动过程中进行绘图,并更新显示。 +通过重载`mouse_down`, `mouse_up`, `mouse_move`方法,可以实现鼠标交互,这里我们实现一个最常见的画笔工具。鼠标按下时,标记`status`为`True`,在鼠标移动过程中进行绘图,并更新显示。 ![14](http://idoc.imagepy.org/demoplugin/24.png) @@ -56,8 +56,8 @@ class Plugin(Tool): **Tool的加载方式** -1. 文件必须以_tol.py结尾,类名必须叫Plugin(一个文件只能实现一个工具) -2. 必须位于tools目录下的一级子菜单下 +1. 文件必须以`_tol.py`结尾,类名必须叫`Plugin`(一个文件只能实现一个工具) +2. 必须位于`tools`目录下的一级子菜单下 3. 需要配有一个同名的,16*16的gif文件用于工具栏图标 4. 单击即可选中,并作用与画布,如有配置参数,双击可进行设置 @@ -66,15 +66,15 @@ class Plugin(Tool):
Tool的加载

-## Tool的运行机制 +## Tool 的运行机制 **title:** 标题 -**mouse_down(self, ips, x, y, btn, ******key):** 鼠标按下时触发,ips是当前作用图像的ImagePlus封装类,可以通过ips.img获取当前图像,也可以ips.lut, ips.roi, ips.unit获取图像的索引表,roi,比例尺和单位等附加信息。x, y是当前鼠标在数据坐标系下的位置,btn触发事件的鼠标按键,0:无,1:左键,2:中键,3:右键。可以通过key['alt'], key['ctrl'], key['shift']获取相应功能键是否按下,通过key['canvas']获取触发事件的Canvas对象。 +**mouse_down:** `mouse_down(self, ips, x, y, btn, **key):` 鼠标按下时触发,`ips`是当前作用图像的`ImagePlus`封装类,可以通过`ips.img`获取当前图像,也可以`ips.lut`, `ips.roi`, `ips.unit`获取图像的索引表,roi,比例尺和单位等附加信息。x, y是当前鼠标在数据坐标系下的位置,`btn`触发事件的鼠标按键,0:无,1:左键,2:中键,3:右键。可以通过`key['alt']`, `key['ctrl']``, key['shift']`获取相应功能键是否按下,通过`key['canvas']`获取触发事件的`Canvas`对象。 -**mouse_up(self, ips, x, y, btn, ******key):** 鼠标抬起时触发,具体参数与mouse_down相同。 +**mouse_up:** `mouse_up(self, ips, x, y, btn, **key):` 鼠标抬起时触发,具体参数与`mouse_down`相同。 -**mouse_move(self, ips, x, y, btn, ******key):** 鼠标移动时触发,具体参数与mouse_down相同。 +**mouse_move:** `mouse_move(self, ips, x, y, btn, **key):` 鼠标移动时触发,具体参数与`mouse_down`相同。 -**mouse_wheel(self, ips, x, y, btn, ******key):** 鼠标滚轮滚动时触发,具体参数与mouse_down相同。 +**mouse_wheel(self, ips, x, y, btn, ******key):** 鼠标滚轮滚动时触发,具体参数与`mouse_down`相同。 diff --git a/doc/widget.md b/doc/widget.md index bbc8559..ca1a6c1 100644 --- a/doc/widget.md +++ b/doc/widget.md @@ -1,40 +1,40 @@ -# Widget +# Widget 插件 -Widget插件加载为一块面板,其实是继承与wx.Panel的一个ui对象,这给了我们非常大的自由空间,但代价是,我们无法隔绝UI,必须直接编写wxpython代码。例如右侧的导航栏,鹰眼就是widget扩展出的,而之前见到过的宏录制器同样也是widget。 +Widget插件加载为一块面板,其实是继承与`wx.Panel`的一个ui对象,这给了我们非常大的自由空间,但代价是,我们无法隔绝`UI`,必须直接编写`wxpython`代码。例如右侧的导航栏,鹰眼就是`widget`扩展出的,而之前见到过的宏录制器同样也是`widget`。 -## 画笔工具 +## 桌面组件演示 ```python from imagepy import IPy import wx class Plugin ( wx.Panel ): - title = 'Widget Demo' - def __init__( self, parent ): - wx.Panel.__init__ ( self, parent) - sizer = wx.BoxSizer( wx.VERTICAL ) - self.lable = wx.StaticText( self, wx.ID_ANY, 'This is a widgets demo') - self.lable.Wrap( -1 ) - sizer.Add( self.lable, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - self.btn_invert = wx.Button( self, wx.ID_ANY, 'Invert curent image') - sizer.Add( self.btn_invert, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.SetSizer( sizer ) - self.Layout() - self.Fit() - # Connect Events - self.btn_invert.Bind( wx.EVT_BUTTON, self.on_invert) - - def on_invert(self, event): - ips = IPy.get_ips() - if ips is None: return - ips.img[:] = 255-ips.img - ips.update() + title = 'Widget Demo' + def __init__( self, parent ): + wx.Panel.__init__ ( self, parent) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.lable = wx.StaticText( self, wx.ID_ANY, 'This is a widgets demo') + self.lable.Wrap( -1 ) + sizer.Add( self.lable, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + self.btn_invert = wx.Button( self, wx.ID_ANY, 'Invert curent image') + sizer.Add( self.btn_invert, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.SetSizer( sizer ) + self.Layout() + self.Fit() + # Connect Events + self.btn_invert.Bind( wx.EVT_BUTTON, self.on_invert) + + def on_invert(self, event): + ips = IPy.get_ips() + if ips is None: return + ips.img[:] = 255-ips.img + ips.update() ``` -由于wxpython的内容本身比较多,如有必要自己编写widget,那么免不了对wxpython进行系统性学习,这里仅仅给出一个最简单的例子。 +由于`wxpython`的内容本身比较多,如有必要自己编写`widget`,那么免不了对`wxpython`进行系统性学习,这里仅仅给出一个最简单的例子。 ![14](http://idoc.imagepy.org/demoplugin/27.png) @@ -42,22 +42,14 @@ class Plugin ( wx.Panel ): **widget的加载方式** -1. 文件必须以_wgt.py结尾,类名必须叫Plugin,继承于wx.Panel(一个文件只能实现一个工具) -2. 文件可以位于widgets目录下的一级子菜单内,启动时会按照层级加载到右侧组件栏。也可以位于menus或其子菜单下,当用户点击菜单时加载面板。 +1. 文件必须以`_wgt.py`结尾,类名必须叫`Plugin`,继承于`wx.Panel`(一个文件只能实现一个widget) +2. 文件可以位于`widgets`目录下的一级子菜单内,启动时会按照层级加载到右侧组件栏。也可以位于menus或其子菜单下,当用户点击菜单时以浮动窗口形式加载面板。 ![14](http://idoc.imagepy.org/demoplugin/26.png) -
Widget In ImagePy

+
Widget Demo

-## Tool的运行机制 +## widget 的运行机制 -widgets本质上就是一个wx.Panel的子类,必须有title字段作为插件名称,其他的,就是wxpython编程,这里不做详细讨论。 - -**mouse_down(self, ips, x, y, btn, ******key):** 鼠标按下时触发,ips是当前作用图像的ImagePlus封装类,可以通过ips.img获取当前图像,也可以ips.lut, ips.roi, ips.unit获取图像的索引表,roi,比例尺和单位等附加信息。x, y是当前鼠标在数据坐标系下的位置,btn触发事件的鼠标按键,0:无,1:左键,2:中键,3:右键。可以通过key['alt'], key['ctrl'], key['shift']获取相应功能键是否按下,通过key['canvas']获取触发事件的Canvas对象。 - -**mouse_up(self, ips, x, y, btn, ******key):** 鼠标抬起时触发,具体参数与mouse_down相同。 - -**mouse_move(self, ips, x, y, btn, ******key):** 鼠标移动时触发,具体参数与mouse_down相同。 - -**mouse_wheel(self, ips, x, y, btn, ******key):** 鼠标滚轮滚动时触发,具体参数与mouse_down相同。 +widgets本质上就是一个`wx.Panel`的子类,必须有`title`字段作为插件名称,其他的,就是`wxpython`编程,通过编写`UI`,添加事件实现,这里不做详细讨论。 diff --git a/doc/workflow.md b/doc/workflow.md index 473dd53..13292b1 100644 --- a/doc/workflow.md +++ b/doc/workflow.md @@ -1,13 +1,11 @@ -# Workflow 插件 +# Workflow 插件 -Macros是一系列的命令记录按顺序执行,而Workflow是Macros和Markdown的混合,同样规定了处理流程,但并不会自动顺序执行,而是每一步可以由用户自己调整参数,甚至过程中可以添加或跳过一些流程,此外每个过程开发者可以设定提示信息。 +Macros是一系列的命令记录按顺序执行,而Workflow是Macros和Markdown的混合,同样规定了处理流程,但并不会自动顺序执行,而是每一步可以由用户自己调整参数,甚至过程中可以添加或跳过一些流程,此外每个过程开发者可以设定提示信息,宏是自动化流程,而工作流是向导作用,降低了自动化程度,但更具有指导作用。 -![14](http://idoc.imagepy.org/demoplugin/11.png) -
Workflow

-**硬币分割工作流** +## 硬币分割工作流 ```markdown Coins Segment Workflow Demo @@ -31,18 +29,22 @@ check the indecate we need, here check the "cov", count the cov ellipse. save the measure result as a csv file. ``` +![14](http://idoc.imagepy.org/demoplugin/11.png) + +
Workflow

+ 我们在之前的硬币分割宏基础上编写工作流,按照markdown规范,加上必要的注释和章节,后缀为wf,保存到menus或其子文件夹下,加载时会是一个横向的分块面板,分为不同的章节,从左到右依次点击即可。鼠标放在每个小节会显示相应提示信息,点击右上角,可以浏览全部过程。 ![14](http://idoc.imagepy.org/demoplugin/12.png)
Workflow Demo

-**工作流编写及加载方式** +**工作流编写及加载方式** 1. 工作流是一个markdown格式的文件,后缀为wf 2. 前两行是主标题,解析成面板的主标题 3. 二级标题代表章,是一个功能分块,解析为一个矩形面板 -4. 数字是要质心的命令,用数字序号开头,不带参数,解析为一个按钮 +4. 数字是要执行的命令,用数字序号开头,不带参数,解析为一个按钮 5. 每个命令下面都可以添加一行注释,当用户鼠标移动到某个按钮上,会展示其对应的说明 6. 可以拷贝到menus或其子菜单下,启动时被解析成菜单项 7. 也可以拖拽到ImagePy最下方的状态栏加载工作流 \ No newline at end of file diff --git a/menus/Demos/Macros Demo/__init__.py b/menus/Demos/Macros Demo/__init__.py index e69de29..6956704 100644 --- a/menus/Demos/Macros Demo/__init__.py +++ b/menus/Demos/Macros Demo/__init__.py @@ -0,0 +1 @@ +catlog = ['Macros Gaussian Invert', 'Macros Coins Segment'] \ No newline at end of file diff --git a/menus/Demos/Report Demo/Coins Report.rpt b/menus/Demos/Report Demo/Coins Report.rpt new file mode 100644 index 0000000000000000000000000000000000000000..3dc3d615645aa5d66c6f6a268a27b41f94f53417 GIT binary patch literal 26804 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 literal 0 HcmV?d00001 diff --git a/menus/Demos/Report Demo/Personal Information.rpt b/menus/Demos/Report Demo/Personal Information.rpt new file mode 100644 index 0000000000000000000000000000000000000000..34c606f90f93781f9cf4ae08817021851c892040 GIT binary patch literal 13265 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 literal 0 HcmV?d00001 diff --git a/menus/Demos/Report Demo/__init__.py b/menus/Demos/Report Demo/__init__.py new file mode 100644 index 0000000..8db53f5 --- /dev/null +++ b/menus/Demos/Report Demo/__init__.py @@ -0,0 +1 @@ +catlog = ['Personal Information', 'Coins Report'] \ No newline at end of file diff --git a/menus/Demos/__init__.py b/menus/Demos/__init__.py index 1eb79e6..7ce612f 100644 --- a/menus/Demos/__init__.py +++ b/menus/Demos/__init__.py @@ -1 +1 @@ -catlog = ['Start Here', '-', 'Markdown Demo', 'Macros Demo', 'Workflow Demo', '-', 'Filter Demo', 'Simple Demo', 'Table Demo', 'Free Demo', '-', 'WidgetDemo'] \ No newline at end of file +catlog = ['Start Here', '-', 'Markdown Demo', 'Macros Demo', 'Workflow Demo', 'Report Demo', '-', 'Filter Demo', 'Simple Demo', 'Table Demo', 'Free Demo', '-', 'WidgetDemo'] \ No newline at end of file diff --git a/widgets/DemoWidgets/widgetdemo_wgt.py b/widgets/DemoWidgets/widgetdemo_wgt.py index ed734a8..7132360 100644 --- a/widgets/DemoWidgets/widgetdemo_wgt.py +++ b/widgets/DemoWidgets/widgetdemo_wgt.py @@ -15,7 +15,6 @@ def __init__( self, parent ): self.SetSizer( sizer ) self.Layout() self.Fit() - # Connect Events self.btn_invert.Bind( wx.EVT_BUTTON, self.on_invert) def on_invert(self, event): From 4b8bda7fb875602765adfd212b0536e4e8fafac1 Mon Sep 17 00:00:00 2001 From: Prevalenter <434625142@qq.com> Date: Sun, 10 Feb 2019 20:22:42 +0800 Subject: [PATCH 3/3] add munus Signed-off-by: Prevalenter <434625142@qq.com> --- doc/publish.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/publish.md b/doc/publish.md index e79ab28..77c763a 100644 --- a/doc/publish.md +++ b/doc/publish.md @@ -84,7 +84,7 @@ Description: a friendly develop tutorial **给ImagePy发Pull Request** -你也可以将插件项目发布到ImagePy,如果你已经完成你的插件项目,那么发布是一个很简单的过程。只需要`fork`一份ImagePy,将你的`readme`文件拷贝到**`imagepy - Plugins - Contribute - Contributors`**下,重命名为插件项目名称(不强制,但最好能体现插件功能),然后给ImagePy提`Pull Request`。当ImagePy组织成员收到`Pull Request`,并进行测试后,会`merge`你的`Pull Request`,如有问题会通过`issue`进行沟通。一旦合并到ImagePy主分支内,用户即可通过插件管理器对插件进行检索,安装,卸载等操作。 +你也可以将插件项目发布到ImagePy,如果你已经完成你的插件项目,那么发布是一个很简单的过程。只需要`fork`一份ImagePy,将你的`readme`文件拷贝到**`imagepy- Menus - Plugins - Contribute - Contributors`**下,重命名为插件项目名称(不强制,但最好能体现插件功能),然后给ImagePy提`Pull Request`。当ImagePy组织成员收到`Pull Request`,并进行测试后,会`merge`你的`Pull Request`,如有问题会通过`issue`进行沟通。一旦合并到ImagePy主分支内,用户即可通过插件管理器对插件进行检索,安装,卸载等操作。 ![14](http://idoc.imagepy.org/demoplugin/06.png)