图论解油瓶分油问题_ImagePy解耦SCIWX,便于开发者树立独立品牌

a8d557f6d7b4e7755519f5eed71f9152.png

本文基于ImagePy作者给群友回答问题的讨论中整理而来,阅读需要发散思维,如有不解,可以查看代码链接。

正文:

ImagePy的canvas解耦对程序员是好事,然后imagepy可以再与后面的深度学习结合,这对小白是好事,因为深度学习有很多的工作是前面对图像的处理,比如裁剪、旋转以及标注等。现在的深度学习库着眼于后面的训练,前面的图像操作是一般用的是第三方库,比如cv,两者是分离的。

其实imagepy有个问题是,开发者用容易喧宾夺主。基础功能太多了,自己加的插件都不太起眼。如果能解耦,又能快速集成,就会有开发者愿意选择.就是打开没那么多功能,就只有自己加的少数功能,

就是把现有的插件都干掉,只保留框架.我能深刻体会这句话,之前就想用imagepy做开发,但是工具太多了,给别人用也找不到,

分成两个项目,一个空壳,一个插件系统。

不是重量问题,有一些开发者的目的是,树立品牌概念。

做一个插件太不起眼,没动力。

如果基础组件能像plt和napari那样使用,然后又可以在空壳上快速搭建插件应用。就比较好了,

其实现在的imagepy只要把menus菜单里面的东西删光,也是一样的。。。

那就变成了带标记和显示功能的ui。

sciwx初步完成

https://github.com/Image-Py/sciwx/tree/master/sciwx​github.com

sciwx是类似matplotlib的一套绘图库,不同的是组件化更好,可以快速搭建上层应用。

其实就是把imagepy里面的canvas,3dmyvi,gridtable,markdown,还有直方图,曲线面板,鹰眼什么的做成了独立控件。

各种组件,画布,表格,参数对话框生成,都放到sciwx里面了。

主要是imagepy里面的ui部分,独立出来了

e44eed39e0e644454de5270656285322.png

f0499ec50c1bcb26b9bc6f7295680843.png

又加了一个matplotlib式的用法

现在imagepy里面的三维,二维面板,都可以类似matplotlib的简单语法独立调用了。

还可以通过实现接口,set给工具栏实现交互。

63869506c4c3b71e4068a078ffcedf36.png

除了napari的三维功能,其他的把imagepy的canvas独立出来,都可以实现。

其实napari也是用了一个比较成熟的三维可视化工具包装的

画图和普通的处理有点区别,其他的处理可以用很长时间做处理,最后刷新一次。

但是画图是动一下鼠标就要更新一次,就要不停的从cpu送显存。所以napari那种用显卡可视化的,就有问题了。

除非开发局部纹理节点更新,这个水就深了。

图像在opengl里面是纹理。画图如果高效一些,应该局部更新纹理数据。

这个应该不是python层面可以解决的问题了

Amira优化的似乎还不错,不过是商业软件了,没法比

路线不同,napari是gpu渲染,我现在的是cpu渲染。

不过优化得还不错了,最近支持了复数,对数展示。

8ee9c1eca588c424051ce05dd4368e8b.png

复数,不是提前求模,log送过去的。

画布实时做采样,求模,对数。依然有上百帧率。全屏状态下40帧。

其实z和c的支持都已经很成熟了,独立出来应该还是有用。

就像plt那样,三五行,show出来,多图层多通道,可以设置假彩色。

canvas里面都是很极端的代码,一切性能优先

box只是控制显示区域逻辑,还不涉及太多性能

imutil里面,写法都是在我能力范围内,优化到极致了。

复数的场景是用在什么地方?傅里叶变换吧,其他的我也没想到

toolbar重构

79dfd50562ed8f3fc8209aa33f3a72a8.png

这次重构的目的是

1. 组件可以当成wx的panel,放在其他UI项目中使用

2. 可以像plt,napari那样,简短的代码show出图像或表格

3. 测试插件或工具可以不需要拷贝到特定目录,可以用代码快捷加载测试

4. 可以从纯净版环境快速搭建特定的简单应用,比如标记工具

5. 现有插件全部以一个插件管理项目外部加载到纯净版

简单应用如果不想准备图标,可以用一个字母代替作为工具的logo

imagepy的定位可以是“一站式图像处理及智能识别库”


https://github.com/Image-Py/cellpose-plgs

cellpose这个例子很好,imagepy正好作为胶水框架,把整个流程串起来:前端提供缩放、裁剪、标注、显示,后端集成深度学习模型,提供训练和预测

现在只是一个训练的模型,然后cellpose自己也带一个简单的ui,上面有标记功能,很简陋。先把这个提交了,然后问一下作者标记之后怎么用。

https://github.com/MouseLand/cellpose/issues/11

67d9ee36981640739bcc133ef78bf7e9.png

cellpose的那个UI,如果在ImagePy里面做,很轻松的。插件做好了,最多再做一个widget,就可以做到很定制化的程度了。

不过他这个我记得半径那个参数挺重要的,有些比较难的例子还需要调一下

那个不是磁性套索,就是鼠标轨迹。不过感觉不是用来修复的

他们这个工作有几个非常简单的点比较有意思,第一个是他们的训练数据不是一个类别的,是非常多,非常广块状物体,第二个是他们输入之前的缩放,针对不同块状物体,可以先缩放大小再输入,输出再缩放回来。

他们好像就是用的cellprofiler+cellpose标记的他们自己的数据

for i in range(n):

mask = img==n

xxxx 找mask的轮廓

这么写,如果图很大,并且里面标记又多,就非常慢了。

mask = img==n 这一句要反复分配大内存。

没有工程化的优化思维的

毕竟重点在机器学习

不过那个写法真的不是电脑好能抢回来的。如果图像2048平方,里面1024个碎片。这个目测就几十秒了。

为了分析一个小块label,先复制整个图像,然后做成bool掩膜。

我估计他们可能不care这些,所以问了一下需不需要帮忙优化。用的话就套个近乎,不用就算了。

github图看不到问题

https://github.com/Image-Py/cellpose-plgs 我刚写的,七牛云,我这里看不到图,不知道什么原因。

github做图床,怎么拿到绝对链接呢

在issue里传图,传完后就有个markdown格式的链接

githubusercontent.com 可能被墙了

推荐阅读ImagePy_Learn学习系列

土盐:ImagePy智能标注工具源码学习记录

土盐:ImagePy_Learn | 图形学绘制代码学习:coredrawfill.py

土盐:ImagePy_Learn | 图形学绘制代码学习:paint.py

土盐:ImagePy_Learn | 图形学绘制代码学习:coredrawpolygonfill.py

土盐:基于ImagePy编写插件之迷途探索WXPython

土盐:ImagePy起手式IPy.py代码解析

金属材料的熔池图像弱分界线提取问题

882df5950e4e7d5d64b4b8fa36d49227.png

底下这根分界线有什么方法能识别出来?

https://forum.image.sc/t/weak-boundaries-extraction/34048/2

https://forum.image.sc/t/weak-boundaries-extraction/34048​forum.image.sc

方法1 最短路径方法

其实最短路径更直接可行一些

1a6254d2a5a1dd87c35a1326b3940e0a.png

https://scikit-image.org/docs/stable/api/skimage.graph.html#shortest-path

from skimage.io import imread
from skimage.graph import route_through_array
import numpy as np
import matplotlib.pyplot as plt

arr = imread('path.png')
arr = (arr-arr.min())/np.ptp(arr)
arr[:,[0,-1]] = 0

indices, weight = route_through_array(arr, (0, 0), (-1,-1))
path = np.array(indices).T

plt.imshow(arr)
plt.plot(path[1], path[0], 'red')
plt.show()

路径计算大概0.5s吧


ImagePy宏命令

8-bit>None
Max>{'num': 170.0}
DOG>{'sigma1': 0.0, 'sigma2': 3.0, 'uniform': True}

做一些预处理,也就是一个全局最小值限定

拉通的意思就是求从左到右的最短路径

思路是,对图像进行预处理,使得黑白尽量分明,并且排除上方特别黑的干扰。然后缩放到0-1之间,再把最左边最右边两条竖线设定成0,意思是可以没有代价的移动。然后求左到右的最短路径。

这个graph的方法,就考虑了路径代价。有了路径代价的约束,就加入了线条平滑的这个先验信息。

像素亮度代表经过的代价,从左到右做路径规划。

但是起点钟点不好设定,所以人工把左右竖条设置成0,意思是无成本移动。然后左上角到右下角做路径规划。

图像,矢量,图论 三条腿走路

很多形态学,距离,轮廓描述,特征检测,其实是矢量问题。需要用计算几何算法。

图像的距离为什么是矢量问题,不是像素之间计算么?

单次距离运算肯定是矢量更快,图像是采样过的距离场。

比如你刚才那个图,标注的几个参数,就应该用矢量运算。

80e04659d135e9412f52d45f1acb4557.png
idx = np.argmin(np.linalg.norm(left_broundary - imgpoint, axis=1))
the left_broundary[idx] is the start point. (the right is similar)

拿到上轮廓,然后中下方虚拟点,求左右两侧的最近点

这个已经是很纯正的矢量问题了

拿到上下轮廓,后续的所有测量,都是矢量问题了。

可以虚拟一个中间下方较远的一个点,然后起点,终点分别就是上轮廓,左右两边,最靠近虚拟位置的点。

如果一定用图像实现,是这样的.但是就非常低效了

图像的距离是计算距离场,少数点的距离,用矢量更高效。

799d0028938a983b2f7d78d93d134414.png

用这两个点做最短路径,应该稳了

扩充功能,零碎时间都好说。重构这种事情,没弄完会分散精力。

229ec395c460f78056aec79aad5dbb91.png

又一个找起点的方法 反正是求最近点,所以还是可以用这个工具。

不能在原图上做,阈值,或者和某个数max一下。目的是消除特别暗的影响。 dog其实是把尺度映射成亮度的方法

1efa3e0b87fe79497fd3f69f6c131a97.png

如果走最大值,先乘以-1,然后减最小值,+单步距离代价。

其实平时写的也比较随意,之前给亓总的,

(arr-arr.min())/np.ptp(arr)

不过放到imagepy里面,我一般都会做内存优化。

71877e1cb6c06dd1c0dee44f6cb0c0dc.png

内置一个sobel,就是磁性套索工具了

方法2 水平集方法

https://github.com/Image-Py/imagepy/blob/e82f80ba4d8ccfe562c83f6dd878902223727e2d/imagepy/menus/Process/Segment/active_plgs.py

https://imagej.net/Level_Sets

imagej 和imagepy的水平集

方法3 构造方向滤波器

https://mp.weixin.qq.com/s/8qmw62tqZuMs3Vm5yXS5OA

ImagePy路径分析插件一教一实现之旅

接着上面的那个素材整合到ImagePy

https://github.com/Image-Py/imagepy/blob/master/imagepy/menus/Analysis/Skeleton%20Network/graph_plgs.py​github.com https://github.com/Image-Py/imagepy/blob/master/imagepy/menus/Analysis/Region%20Analysis/regionprops_plgs.py​github.com

78f3757994b00dde24e71750e200ef92.png

起点终点用roi就可以

ips.roi可以拿到,然后load里面判断一下是不是point类型并且只有两个点,如果满足,run里面求路径,并且高亮绘制。

那里面其实还有一个原理,就是dog可以凸显特定尺度的目标。

加载analysis菜单下面吧

Shortest Route

7b2025907ecb590cd9877b94c8431492.png

这两个要做个参数弹窗么

支持单通道8-bit, 16-bit, int, float

还有req_roi,并且还不够,还要再load里面判定一下

必须是point类型,只有2点,否则alert一下

其实再细致一点,应该先检测起点,终点

想着点两个点起码比描个线要轻松吧

这个肯定有办法很稳定的全自动完成的

不用角点,用最两端的也好啊

6ef144e19edc95e0f389c7a24005ab1a.png

其实中间两个点也好说,这就是昨天我说的,图像,矢量,图论,三条腿走路。

属于filter类

可以参考分水岭那个,加一个输出选项

三个选项:掩膜,高亮在原图上,擦除背景

对了,图像最大最小值,可以用minv, maxv = ips.range获得

因为对于其他类型,可能不是0,255

route_through_array函数返回的是坐标,我想的是新建一个全是0的矩阵,然后把对应坐标的值写255

索引,img[rs,cs] = 255

这个应用没必要新建数组吧,可以原图上操作。

为什么ips.roi.body获取的点的坐标有小数?

roi是矢量数据,有小数正常。

截取一下吧,并且不保证在图像内部。

不在图像内部的问题,可以暂时忽略

我理解的用户从坐标上鼠标选取的应该是某一个像素的坐标,理论上不应该是小数把

如果经过放大,再选取呢?或者是从shapefile,gps等导入的地理数据呢

c6d8d1c47e2832927cb54ed7b69e4cdb.png

其实应该这样做的,用line对象

然后依次计算,连线

比如一个圆环,可以点三个点

这样也没必要限定两个了

反正有几条,就顺着依次做

可以支持多线

46610667e4e606860c79834c0cae8c81.png

判断一下是line类型的roi,就可以放过去了

不用支持point了

point也可以用一条单线

支持多条多段line就可以了,不支持point

一个line上的是一组?

对,两重循环,第一层是每条line,第二层是每条line上的点。

但是有个问题,就是一个line上有3个point的话,比如p0,p1,p2。那要算多少次?

两次啊

0-1, 1-2

p0,p2不算一次?那如果是想得到个封闭的路径就要P0,P2再算一次?

不用了

5da3aada248e844c8ef99661f677a93c.png

如果需要闭合,用户自己再点会起点就可以了

1-2,2-3,3-4 配对,可以这样写

for p1,p2 in zip(line[:-1], line[1:])

一条确定了,其实接下来应该是描边,然后完成

效果和polygon tool效果基本一样的

就是加了删除点和拖动

polygon也支持拖动,拖动之后,要动态做相邻的轨迹更新

polygon可以整体拖动,也可以节点拖动

拖动摄取,你用的x,y直接计算的吧

不用整体拖动

展示就暂时这两种可以吗?要加个mark的么?

filter貌似没法另外show

因为如果是批量,就会爆屏

所以,都在原图上动吧

我记得是有个白线得到mark的功能,如果需要得到mark直接用那个功能就行,在流体插件好像

不需要吧,这里面更简单

1. img[r,c] = max

2. img[:] = 0, img[r,c] = max

3. img[:] = 0, img[r,c] = snape[r,c]

这三种方式展示

0其实应该是min,不是0

885e40e0d077f6c19928a5a6a7009d84.png

line的坐标放在哪个属性了?我打印了ips.roi.body是空的

就是body吧

93cc2e9236d30c614c52ff7919ef98f8.png

60dde9298d5398b86a44765c4bbdcaaa.png

fda74ba4429d9bff1644636339f3b498.png

右键落下去才算完成,这个只是mark

我说颜色怎么不一样

dced87f21efd3439504819e496772a08.png

f7043ad8e985af3cb46e81b856ba938a.png

但是如果勾选了preview,再换ouput的mode的话会出错

run里面加上img[:] = snap

其实应该分析用snap,展示用img

还可以做一个tool,在智能画笔旁边

就是动态点点,然后中间用mark绘制轨迹,还可以设置线条宽度,最后确认了再绘图。

实时绘制路径


相当于智能套索了,加上填充应该就全了
是的,可以用快捷键加上自动闭合,内部填充,描边等逻辑
然后还要设定一下,是最小值,最大值,还是梯度最大值吧
梯度最大值
先求梯度,再取反,再求最优路径.结果就是沿着变化大的地方走
还是可以左键加控制点,然后实时计算的只是当前距离上一个控制点之间的。

智能套锁就是用的这个路径?我记得ps有个磁性套锁

磁性套锁感觉应该用mark

其实内部的数据结构是自己维护的,mark只是用来展示

最后路径出个mark然后用户还可以手工调整

自己维护两个序列,一个控制点序列,一个智能路线。

控制点序列可以手工拖动

其实内存维护的主要是控制点序列,顺带产生了轨迹序列。

因为mark的结构不一定满足我们的交互逻辑

[控制点1, 控制点2, 。。。]

[序列1, 序列2, 序列3 。。。]

当前鼠标点,当前点到最后控制点之间的序列

有imagepy之后,已经一定程度上隔离了。如果直接面对wx,qt,要看的东西还很多。

画个图都要双缓冲,还有各种闪屏,绘图性能问题

其实绘制可以分层。

1. 绘制确定的路径

2. 绘制确定的控制点

3. 绘制末端路径

用三个mark就可以了,一旦落下鼠标,随即当前点加入控制点,末端路径加入确定的路径

move里面始终计算当前位置到最后一个控制点

不要用mark当核心数据结构,只是需要展示的时候临时组装的。

取消可以用右键啊,比如判断右键点击了,就把最后一个控制点弹出,也把最后一段路径弹出。

然后重新构建mark,set给ips

mark只是负责展示的

核心数据结构肯定是根据需求自己维护,但是这个转换过程应该尽量节省内存。就是打包的时候应该是不需要大量的内存复制的。

自己维护这个逻辑,最后只是数据打包成mark展示

所以维护的轨迹,就可以按照mark可以直接用的方式来维护。mark其实只是一个重载draw方法的替代

如果要自己draw,就需要用gdi,就直接和界面打交道了。

mark底层还是用dc绘图的

界面绘图底层肯定是dc,或者opengl

roi的颜色不对劲。。

直接dog不行,估计会找到上轮廓

需要新做类似二值化的操作,或者抑制一下特别黑的。

dog只是凸显特定尺度的目标

roi右键是作为结束的,因为默认情况支持多段线。就是不停的左键,会产生多段线

我怎么从鼠标事件获取坐标

还是要获取wx的panel

继承工具,engine下面的Tool

除了写widget应该没有需要直接写wx的地方了

磁性套索我看了下ps的操作方式,ps的好像是按下鼠标左键防止一个锚点,然后用户沿着轮廓放锚点,可以用delete删除前一个点。

我想还支持下放置的锚点可以修改,就鼠标移动到锚点时候鼠标变成一个手,可以拖动

tool类里面只有鼠标和滚轮的的事件,如果我想用delete删除一个点的话,可能要获取键盘的事件

imagepy有类似机制么?我看有的用组合键盘shift键和鼠标左键,但是这样要放在鼠标事件里面才会刷新,单独用键盘好像刷新不了

磁性套索我看了下ps的操作方式,ps的好像是按下鼠标左键防止一个锚点,然后用户沿着轮廓放锚点,可以用delete删除前一个点。

我想还支持下放置的锚点可以修改,就鼠标移动到锚点时候鼠标变成一个手,可以拖动

tool类里面只有鼠标和滚轮的的事件,如果我想用delete删除一个点的话,可能要获取键盘的事件

imagepy有类似机制么?我看有的用组合键盘shift键和鼠标左键,但是这样要放在鼠标事件里面才会刷新,单独用键盘好像刷新不了

键盘事件还没加,除了ctrl, alt, shift

要不你先用功能键做吧,过后再加键盘支持。

比如shift+click,用来删除点

事件函数里面的key, 可以key['alt']来获取是否按下,key['shift'], key['ctrl']

850d7b22786c9c975b6863c64376e39c.png

roi是有功能的,比如生成选区掩膜,还要实现交并补运算。

mark只是用来显示的

距离怎么算的?直接用坐标会有问题.就是你昨天问的,为什么roi有小数

key['canvas'].scale可以拿到比例,画布当前的比例尺,会随着放大镜改变,就是真实的屏幕坐标和像素坐标之间的关系。

28623cdde7eec9fb1956459fc863f54b.png

5个像素支内算选中

但是如果不带比例尺,就会随着画布缩放变化。

key['canvas'].scale

就是选中某个点的逻辑,直接用x,y计算,不太对

因为x,y会受到画布比例尺影响

拖动要判断是否选中吧,就需要计算距离。

因为x,y都是像素坐标系。所以摄取的时候会出问题

比如你放大到很大的时候,离很远就选中了。

除以canvas.scale就回到屏幕坐标系了。

dog问题

通过调控sigma可以凸显不同尺度的目标,那个问题是用dog凸显细线,然后再做路径规划。

e48d82f3c440c90bf8bedeeac580e82d.png

a020f6ae56d539cb7d8d829dd9a7deef.png

5ef61b6c750db12f8a0ba167dd90aee2.png

bcab490c7c105987ab1768aef3f62129.png

dog会遇到类型溢出问题,要不你就转float吧,然后

img - gaussian(img, 2)

之后标准化到0-1,两端擦除

Shortest Route理论

图像最小值如果是0,那么0代表无代价

这样会导致线路曲折,只要有通路,就钻空子。

但是如果整体加一个常数,这个代表距离代价

意思是,经过实际距离是有代价的。不能无限制的任意走。

这个常数n,好比最优路径,加上几何距离*n

这个n其实一定程度上,是限制线条流畅性的参数。如果n非常大,那么规化出来的路线,就趋于两点之间的直线。这个可以理解吗?

比如原图本来是0-1之间波动的,如果我全图都+10,那么规划出来的路径,就会成为直线。因为相对10来说,波动很小,那么决策好比尽量走少的路。

所以,那个插件,建议在note里面加上'2int',然后添加一个step cost参数。

希望路径除了代价小,还希望路径是越短越好

常数越大,越接近直线,因为绕路不是0成本的

这个,画了一条干扰线

09be81cf79e68019f245c5551d017ff1.png

0代价的时候,肯定就是从这条干扰线经过了

405823fe42e91487e076ec4819ba8ed3.png

加上路径成本,就对了

9dcd2bca308b423baabc686dc38f956d.png

这个值人工选取,可以自动选取么?

在图像的值域内,没法完全自动,这个约束就是,你希望路径直的约束力度

23d0f6ae3ee623366c62515b37b103b3.png

越大其实就越接近直线

路径对角线走和正交走,都是经过一个像素

加了个反转

d660e67a0abebaa28d2f7dccb8998d49.png

掩膜问题

process > classify 下面有一个label panel

专门用于标记的,不过有点绕,提示没做好。

大致原理是,对当前图像新建一个掩膜,把原图设置成掩膜的背景,然后选择一下混合模式。

那个上面可以方便的擦除,覆盖,不会破坏原图

ipy的plg除run方法外,支持singleton吗?

e1c8cb3e3006aef1ffe06891c6de24fa.png

dl第一次加载model都很耗时,每次run都是第一次

其实应该做成类成员

Plugin.model = xxx

下次判断是不是已经加载

不过比较扯的是,这里有多种模型,并且每次是根据用户选择加载的。所以就没做处理。

单例调用意义不大,因为ipy主要就是做交互上的事情。倒是可以当字典用,把代码拷贝出来。

用字典cache下就行

06f14638a12dfc31ce1cb4abf7972f01.png

这里可以查看每个功能的源码,一般都非常简短。

这种类成员cache,需要写成

类名.xxx的方式,就不会被释放了。

https://github.com/Image-Py/sciwx

新建了一个项目,准备用来剥离ImagePy的组件,可以关注一下,有精力可以做些测试。

一个兼容性错误是这部分的功能

https://forum.image.sc/t/imagepy-add-some-feature-to-simplified-a-network-structure/23643/2

SCIWX问题

demo中少引入了一个包?

from skimage.draw import line

使用pencil工具时报line错误

可能是少了,需要加一下pythonpath

或者在demo上面加上sys.path.append('../../')

分水岭讲解

其实分水岭就是从各个种子点大家同步上涨,在领地发生冲突的地方划定界线。所以领地冲突是一个重要条件

0代表还没占领的,

0xffff是边界,

0xfffe是已经确定是分水岭的,

msk[p]是自己已经占领的

如果是line=True,就划定界线

其实neibours的形状,也决定了界线的形状

如果neighbor是4邻域,界线就是4邻域,neighbor是8邻域,界线就是8邻域

rgb和cmyk概念

lab是公认的,坐标系距离和视觉感知差异最一致的模型。

rgb和cmyk之间,其实是逻辑或,逻辑且的关系

lab其实只有数值概念,没法可视化。

要可视化也只有把l,a,b强制当成r,g,b.值域要自己变换一下

Shortest Route源码

https://github.com/Image-Py/imagepy/blob/master/imagepy/menus/Analysis/Skeleton%20Network/graph_plgs.py

from imagepy.core.engine import Filter, Simple
from imagepy.ipyalg.graph import sknw
import numpy as np
from numpy.linalg import norm
import networkx as nx, wx
from imagepy import IPy
from numba import jit
import pandas as pd

# build   statistic  sumerise   cut  edit
class Mark:
    def __init__(self, graph):
        self.graph = graph

    def draw(self, dc, f, **key):
        dc.SetPen(wx.Pen((255,255,0), width=3, style=wx.SOLID))
        dc.SetTextForeground((255,255,0))
        font = wx.Font(8, wx.FONTFAMILY_DEFAULT, 
                       wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
        dc.SetFont(font)

        ids = self.graph.nodes()
        pts = [self.graph.nodes[i]['o'] for i in ids]
        pts = [f(i[1], i[0]) for i in pts]
        dc.DrawPointList(pts)
        dc.DrawTextList([str(i) for i in ids], pts)

class BuildGraph(Filter):
    title = 'Build Graph'
    note = ['8-bit', 'not_slice', 'not_channel', 'auto_snap']

    #process
    def run(self, ips, snap, img, para = None):
        ips.data = sknw.build_sknw(img, True)
        sknw.draw_graph(img, ips.data)
        ips.mark = Mark(ips.data)

class Statistic(Simple):
    title = 'Graph Statistic'
    note = ['all']

    def load(self, ips):
        if not isinstance(ips.data, nx.MultiGraph):
            IPy.alert("Please build graph!");
            return False;
        return True;

    def run(self, ips, imgs, para = None):
        edges, nodes = [], []
        ntitles = ['PartID', 'NodeID', 'Degree','X', 'Y']
        etitles = ['PartID', 'StartID', 'EndID', 'Length']
        k, unit = ips.unit
        comid = 0
        for g in nx.connected_component_subgraphs(ips.data, False):
            for idx in g.nodes():
                o = g.nodes[idx]['o']
                print(idx, g.degree(idx))
                nodes.append([comid, idx, g.degree(idx), round(o[1]*k,2), round(o[0]*k,2)])
            for (s, e) in g.edges():
                eds = g[s][e]
                for i in eds:
                    edges.append([comid, s, e, round(eds[i]['weight']*k, 2)])
            comid += 1

        IPy.show_table(pd.DataFrame(nodes, columns=ntitles), ips.title+'-nodes')
        IPy.show_table(pd.DataFrame(edges, columns=etitles), ips.title+'-edges')

class Sumerise(Simple):
    title = 'Graph Summarise'
    note = ['all']

    para = {'parts':False}
    view = [(bool, 'parts', 'parts')]

    def load(self, ips):
        if not isinstance(ips.data, nx.MultiGraph):
            IPy.alert("Please build graph!");
            return False;
        return True;

    def run(self, ips, imgs, para = None):
        titles = ['PartID', 'Noeds', 'Edges', 'TotalLength', 'Density', 'AveConnect']
        k, unit = ips.unit
        
        gs = nx.connected_component_subgraphs(ips.data, False) if para['parts'] else [ips.data]
        comid, datas = 0, []
        for g in gs:
            sl = 0
            for (s, e) in g.edges():
                sl += sum([i['weight'] for i in g[s][e].values()])
            datas.append([comid, g.number_of_nodes(), g.number_of_edges(), round(sl*k, 2), 
                round(nx.density(g), 2), round(nx.average_node_connectivity(g),2)][1-para['parts']:])
            comid += 1
        IPy.show_table(pd.DataFrame(datas, columns=titles[1-para['parts']:]), ips.title+'-graph')

class CutBranch(Filter):
    title = 'Cut Branch'
    note = ['8-bit', 'not_slice', 'not_channel', 'auto_snap', 'preview']

    para = {'lim':10, 'rec':False}
    view = [(int, 'lim', (0,1e6), 0, 'limit', 'uint'),
            (bool, 'rec', 'recursion')]

    def load(self, ips):
        if not isinstance(ips.data, nx.MultiGraph):
            IPy.alert("Please build graph!");
            return False;
        self.buf = ips.data
        return True;

    def run(self, ips, snap, img, para = None):
        g = ips.data = self.buf.copy()
        k, unit = ips.unit
        while True:
            rm = []
            for i in g.nodes():
                if g.degree(i)!=1:continue
                s,e = list(g.edges(i))[0]
                if g[s][e][0]['weight']*k<=para['lim']:
                    rm.append(i)
            g.remove_nodes_from(rm)
            if not para['rec'] or len(rm)==0:break
        img *= 0
        sknw.draw_graph(img, g)

    def cancel(self, ips):
        if 'auto_snap' in self.note:
            ips.swap()
            ips.update()
        ips.data = self.buf

class RemoveIsolate(Filter):
    title = 'Remove Isolate Node'
    note = ['all', 'not_slice', 'not_channel', 'auto_snap']

    def load(self, ips):
        if not isinstance(ips.data, nx.MultiGraph):
            IPy.alert("Please build graph!");
            return False;
        return True;

    def run(self, ips, snap, img, para = None):
        g = ips.data
        for n in list(g.nodes()):
            if len(g[n])==0: g.remove_node(n)
        img *= 0
        sknw.draw_graph(img, g)
        ips.mark = Mark(ips.data)

class Remove2Node(Simple):
    title = 'Remove 2Path Node'
    note = ['all']

    def load(self, ips):
        if not isinstance(ips.data, nx.MultiGraph):
            IPy.alert("Please build graph!");
            return False;
        return True;

    def run(self, ips, imgs, para = None):
        g = ips.data
        for n in list(g.nodes()):
            if len(g[n])!=2 or n in g[n]: continue 
            (k1, e1), (k2, e2) = g[n].items()
            if isinstance(g, nx.MultiGraph):
                if len(e1)!=1 or len(e2)!=1: continue
                e1, e2 = e1[0], e2[0]
            l1, l2 = e1['pts'], e2['pts']
            d1 = norm(l1[0]-g.nodes[n]['o']) > norm(l1[-1]-g.nodes[n]['o'])
            d2 = norm(l2[0]-g.nodes[n]['o']) < norm(l2[-1]-g.nodes[n]['o'])
            pts = np.vstack((l1[::[-1,1][d1]], l2[::[-1,1][d2]]))
            l = np.linalg.norm(pts[1:]-pts[:-1], axis=1).sum()
            g.remove_node(n)
            g.add_edge(k1, k2, pts=pts, weight=l)
        ips.img[:] = 0
        sknw.draw_graph(ips.img, g)
        ips.mark = Mark(ips.data)

@jit(nopython=True)
def floodfill(img, x, y):
    buf = np.zeros((131072,2), dtype=np.uint16)
    color = img[int(y), int(x)]
    img[int(y), int(x)] = 0
    buf[0,0] = x; buf[0,1] = y;
    cur = 0; s = 1;

    while True:
        xy = buf[cur]
        for dx in (-1,0,1):
            for dy in (-1,0,1):
                cx = xy[0]+dx; cy = xy[1]+dy
                if cx<0 or cx>=img.shape[1]:continue
                if cy<0 or cy>=img.shape[0]:continue
                if img[cy, cx]!=color:continue
                img[cy, cx] = 0
                buf[s,0] = cx; buf[s,1] = cy
                s+=1
                if s==len(buf):
                    buf[:len(buf)-cur] = buf[cur:]
                    s -= cur; cur=0
        cur += 1
        if cur==s:break

class CutROI(Filter):
    title = 'Cut By ROI'
    note = ['8-bit', 'not_slice', 'not_channel', 'auto_snap', 'preview']

    def run(self, ips, snap, img, para = None):
        msk = ips.get_msk(3) * (img>0)
        r,c = np.where(msk)
        for x,y in zip(c,r):
            if img[y,x]>0:
                floodfill(img, x, y)
        
class ShortestPath(Simple):
    title = 'Graph Shortest Path'
    note = ['all']

    para = {'start':0, 'end':1}
    view = [(int, 'start', (0,1e8), 0, 'start', 'id'),
            (int, 'end',   (0,1e8), 0, 'end', 'id')]

    def load(self, ips):
        if not isinstance(ips.data, nx.MultiGraph):
            IPy.alert("Please build graph!");
            return False;
        return True;

    def run(self, ips, imgs, para = None):
        nodes = nx.shortest_path(ips.data, source=para['start'], target=para['end'], weight='weight')
        path = zip(nodes[:-1], nodes[1:])
        paths = []
        for s,e in path:
            ps = ips.data[s][e].values()
            pts = sorted([(i['weight'], i['pts']) for i in ps])
            paths.append(((s,e), pts[0]))
        sknw.draw_graph(ips.img, ips.data)
        for i in paths:
            ips.img[i[1][1][:,0], i[1][1][:,1]] = 255
            IPy.write('%s-%s:%.4f'%(i[0][0], i[0][1], i[1][0]), 'ShortestPath')
        IPy.write('Nodes:%s, Length:%.4f'%(len(nodes), sum([i[1][0] for i in paths])), 'ShortestPath')



plgs = [BuildGraph, Statistic, Sumerise, '-', RemoveIsolate, Remove2Node, CutBranch, CutROI, '-', ShortestPath]

https://github.com/Image-Py/imagepy/blob/master/imagepy/menus/Analysis/Region%20Analysis/regionprops_plgs.py

# -*- coding: utf-8 -*-
"""
Created on Tue Dec 27 01:06:59 2016
@author: yxl
"""
from imagepy import IPy
import numpy as np
from imagepy.core.engine import Simple, Filter
from imagepy.core.manager import ImageManager, ColorManager
from scipy.ndimage import label, generate_binary_structure
from skimage.measure import regionprops
from imagepy.core.mark import GeometryMark
import pandas as pd

# center, area, l, extent, cov
class RegionCounter(Simple):
    title = 'Geometry Analysis'
    note = ['8-bit', '16-bit', 'int']
    para = {'con':'8-connect', 'center':True, 'area':True, 'l':True, 'extent':False, 'cov':False, 'slice':False,
            'ed':False, 'holes':False, 'ca':False, 'fa':False, 'solid':False}
    view = [(list, 'con', ['4-connect', '8-connect'], str, 'conection', 'pix'),
            (bool, 'slice', 'slice'),
            ('lab', None, '=========  indecate  ========='),
            (bool, 'center', 'center'),
            (bool, 'area', 'area'),
            (bool, 'l', 'perimeter'),
            (bool, 'extent', 'extent'),
            (bool, 'ed', 'equivalent diameter'),
            (bool, 'ca', 'convex area'),
            (bool, 'holes', 'holes'),
            (bool, 'fa', 'filled area'),
            (bool, 'solid', 'solidity'),
            (bool, 'cov', 'cov')]

    #process
    def run(self, ips, imgs, para = None):
        if not para['slice']:imgs = [ips.img]
        k = ips.unit[0]

        titles = ['Slice', 'ID'][0 if para['slice'] else 1:]
        if para['center']:titles.extend(['Center-X','Center-Y'])
        if para['area']:titles.append('Area')
        if para['l']:titles.append('Perimeter')
        if para['extent']:titles.extend(['Min-Y','Min-X','Max-Y','Max-X'])
        if para['ed']:titles.extend(['Diameter'])
        if para['ca']:titles.extend(['ConvexArea'])
        if para['holes']:titles.extend(['Holes'])
        if para['fa']:titles.extend(['FilledArea'])
        if para['solid']:titles.extend(['Solidity'])
        if para['cov']:titles.extend(['Major','Minor','Ori'])
        buf = imgs[0].astype(np.uint32)
        data, mark = [], {'type':'layers', 'body':{}}
        strc = generate_binary_structure(2, 1 if para['con']=='4-connect' else 2)
        for i in range(len(imgs)):
            label(imgs[i], strc, output=buf)
            ls = regionprops(buf)

            dt = [[i]*len(ls), list(range(len(ls)))]
            if not para['slice']:dt = dt[1:]

            layer = {'type':'layer', 'body':[]}
            texts = [(i.centroid[::-1])+('id=%d'%n,) for i,n in zip(ls,range(len(ls)))]
            layer['body'].append({'type':'texts', 'body':texts})
            if para['cov']:
                ellips = [i.centroid[::-1] + (i.major_axis_length/2,i.minor_axis_length/2, i.orientation+np.pi/2) for i in ls]
                layer['body'].append({'type':'ellipses', 'body':ellips})
            mark['body'][i] = layer

            if para['center']:
                dt.append([round(i.centroid[1]*k,1) for i in ls])
                dt.append([round(i.centroid[0]*k,1) for i in ls])
            if para['area']:
                dt.append([i.area*k**2 for i in ls])
            if para['l']:
                dt.append([round(i.perimeter*k,1) for i in ls])
            if para['extent']:
                for j in (0,1,2,3):
                    dt.append([i.bbox[j]*k for i in ls])
            if para['ed']:
                dt.append([round(i.equivalent_diameter*k, 1) for i in ls])
            if para['ca']:
                dt.append([i.convex_area*k**2 for i in ls])
            if para['holes']:
                dt.append([1-i.euler_number for i in ls])
            if para['fa']:
                dt.append([i.filled_area*k**2 for i in ls])
            if para['solid']:
                dt.append([round(i.solidity, 2) for i in ls])
            if para['cov']:
                dt.append([round(i.major_axis_length*k, 1) for i in ls])
                dt.append([round(i.minor_axis_length*k, 1) for i in ls])
                dt.append([round(i.orientation*k, 1) for i in ls])

            data.extend(list(zip(*dt)))
        ips.mark = GeometryMark(mark)
        IPy.show_table(pd.DataFrame(data, columns=titles), ips.title+'-region')

# center, area, l, extent, cov
class RegionFilter(Filter):
    title = 'Geometry Filter'
    note = ['8-bit', '16-bit', 'int', 'auto_msk', 'auto_snap','preview']
    para = {'con':'4-connect', 'inv':False, 'area':0, 'l':0, 'holes':0, 'solid':0, 'e':0, 'front':255, 'back':100}
    view = [(list, 'con', ['4-connect', '8-connect'], str, 'conection', 'pix'),
            (bool, 'inv', 'invert'),
            ('lab', None, 'Filter: "+" means >=, "-" means <'),
            (int, 'front', (0, 255), 0, 'front color', ''),
            (int, 'back', (0, 255), 0, 'back color', ''),
            (float, 'area', (-1e6, 1e6), 1, 'area', 'unit^2'),
            (float, 'l', (-1e6, 1e6), 1, 'perimeter', 'unit'),
            (int, 'holes', (-10,10), 0, 'holes', 'num'),
            (float, 'solid', (-1, 1,), 1, 'solidity', 'ratio'),
            (float, 'e', (-100,100), 1, 'eccentricity', 'ratio')]

    #process
    def run(self, ips, snap, img, para = None):
        k, unit = ips.unit
        strc = generate_binary_structure(2, 1 if para['con']=='4-connect' else 2)

        lab, n = label(snap==0 if para['inv'] else snap, strc, output=np.uint32)
        idx = (np.ones(n+1)*(0 if para['inv'] else para['front'])).astype(np.uint8)
        ls = regionprops(lab)
        
        for i in ls:
            if para['area'] == 0: break
            if para['area']>0:
                if i.area*k**2 < para['area']: idx[i.label] = para['back']
            if para['area']<0:
                if i.area*k**2 >= -para['area']: idx[i.label] = para['back']

        for i in ls:
            if para['l'] == 0: break
            if para['l']>0:
                if i.perimeter*k < para['l']: idx[i.label] = para['back']
            if para['l']<0:
                if i.perimeter*k >= -para['l']: idx[i.label] = para['back']

        for i in ls:
            if para['holes'] == 0: break
            if para['holes']>0:
                if 1-i.euler_number < para['holes']: idx[i.label] = para['back']
            if para['holes']<0:
                if 1-i.euler_number >= -para['holes']: idx[i.label] = para['back']

        for i in ls:
            if para['solid'] == 0: break
            if para['solid']>0:
                if i.solidity < para['solid']: idx[i.label] = para['back']
            if para['solid']<0:
                if i.solidity >= -para['solid']: idx[i.label] = para['back']

        for i in ls:
            if para['e'] == 0: break
            if para['e']>0:
                if i.minor_axis_length>0 and i.major_axis_length/i.minor_axis_length < para['e']: 
                    idx[i.label] = para['back']
            if para['e']<0:
                if i.minor_axis_length>0 and i.major_axis_length/i.minor_axis_length >= -para['e']: 
                    idx[i.label] = para['back']

        idx[0] = para['front'] if para['inv'] else 0
        img[:] = idx[lab]

# center, area, l, extent, cov
class PropertyMarker(Filter):
    title = 'Property Marker'
    note = ['8-bit', '16-bit', 'auto_msk', 'auto_snap','preview']
    para = {'con':'4-connect', 'pro':'area', 'cm':'gray'}
    view = [(list, 'con', ['4-connect', '8-connect'], str, 'conection', 'pix'),
            (list, 'pro', ['area', 'perimeter', 'solid', 'eccentricity'], str, 'property', ''),
            ('cmap', 'cm', 'color map')]

    def load(self, ips): 
        self.lut = ips.lut
        return True

    def cancel(self, ips):
        ips.lut = self.lut
        Filter.cancel(self, ips)

    #process
    def run(self, ips, snap, img, para = None):
        strc = generate_binary_structure(2, 1 if para['con']=='4-connect' else 2)

        lab, n = label(snap, strc, output=np.uint32)
        idx = (np.zeros(n+1)).astype(np.uint8)
        ls = regionprops(lab)
        
        if para['pro'] == 'area': ps = [i.area for i in ls]
        if para['pro'] == 'perimeter': ps = [i.perimeter for i in ls]
        if para['pro'] == 'solid': ps = [i.solidity for i in ls]
        if para['pro'] == 'eccentricity': ps = [i.major_axis_length/i.minor_axis_length for i in ls]

        ps = np.array(ps)
        if ps.max() != ps.min():
            ps = (ps - ps.min()) / (ps.max() - ps.min())
        else: ps = ps / ps.max()
        idx[1:] = ps * 245 + 10
        img[:] = idx[lab]
        ips.lut = ColorManager.get_lut(para['cm'])

plgs = [RegionCounter, RegionFilter, PropertyMarker]

toolbar源码

import wx

def make_logo(cont, obj):
    if isinstance(obj, str) and len(obj)>1:
        bmp = wx.Bitmap(obj)
    if isinstance(obj, str) and len(obj)==1:
        bmp = wx.Bitmap.FromRGBA(16, 16)
        dc = wx.BufferedDC(wx.ClientDC(cont), bmp)
        dc.SetBackground(wx.Brush((255,255,255)))
        dc.Clear()
        dc.SetTextForeground((0,0,150))
        font = dc.GetFont()
        font.SetPointSize(12)
        dc.SetFont(font)
        w, h = dc.GetTextExtent(obj)
        dc.DrawText(obj, 8-w//2, 8-h//2)
        rgb = bytes(768)
        bmp.CopyToBuffer(rgb)
        a = memoryview(rgb[::3]).tolist()
        a = bytes([255-i for i in a])
        bmp = wx.Bitmap.FromBufferAndAlpha(16, 16, rgb, a)
    img = bmp.ConvertToImage()
    img.Resize((20, 20), (2, 2))
    return img.ConvertToBitmap()

class ToolBar(wx.Panel):
    def __init__(self, parent, vertical=False):
        wx.Panel.__init__( self, parent, wx.ID_ANY,  wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        sizer = wx.BoxSizer( (wx.HORIZONTAL, wx.VERTICAL)[vertical] )
        self.SetSizer( sizer )
        self.toolset = []

    def bind(self, btn, tol):
        btn.Bind( wx.EVT_LEFT_DOWN, lambda x, obj=tol: obj().start())
            
    def add_tool(self, tool, logo):
        btn = wx.BitmapButton(self, wx.ID_ANY, make_logo(self, logo), 
            wx.DefaultPosition, (32,32), wx.BU_AUTODRAW|wx.RAISED_BORDER )
        self.bind(btn, tool)
        self.GetSizer().Add(btn, 0, wx.ALL, 1)

    def add_tools(self, name, tools, fixed=True):
        if not fixed: self.toolset.append((name, []))
        for tool, logo in tools:
            btn = wx.BitmapButton(self, wx.ID_ANY, make_logo(self, logo), 
                wx.DefaultPosition, (32,32), wx.BU_AUTODRAW|wx.RAISED_BORDER )
            self.bind(btn, tool)
            self.GetSizer().Add(btn, 0, wx.ALL, 1)
            if not fixed: self.toolset[-1][1].append(btn)
        if fixed:
            line = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL )
            self.GetSizer().Add( line, 0, wx.ALL|wx.EXPAND, 2 )

    def active_set(self, name):
        for n, tools in self.toolset:
            print('select', name, n)
            for btn in tools:
                if n==name: btn.Show()
                if n!=name: btn.Hide()
        self.Layout()
        
    def add_pop(self, logo, default):
        self.GetSizer().AddStretchSpacer(1)
        btn = wx.BitmapButton(self, wx.ID_ANY, make_logo(self, logo), 
                wx.DefaultPosition, (32,32), wx.BU_AUTODRAW|wx.RAISED_BORDER )
        btn.Bind(wx.EVT_LEFT_DOWN, self.menu_drop)
        self.GetSizer().Add(btn, 0, wx.ALL, 1)
        self.active_set(default)

    def menu_drop(self, event):
        menu = wx.Menu()
        for name, item in self.toolset:
            item = wx.MenuItem(menu, wx.ID_ANY, name, wx.EmptyString, wx.ITEM_NORMAL )
            menu.Append(item)
            f = lambda e, name=name:self.active_set(name)
            menu.Bind(wx.EVT_MENU, f, id=item.GetId())
        self.PopupMenu( menu )
        menu.Destroy()
        
if __name__ == '__main__':
    path = 'C:/Users/54631/Documents/projects/imagepy/imagepy/tools/drop.gif'
    app = wx.App()
    frame = wx.Frame(None)
    tool = ToolBar(frame)
    tool.add_tools('A', [(None, 'A')] * 3)
    tool.add_tools('B', [(None, 'B')] * 3, False)
    tool.add_tools('C', [(None, 'C')] * 3, False)
    tool.add_pop('P', 'B')
    tool.Layout()
    frame.Fit()
    frame.Show()
    app.MainLoop()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值