https://zhuanlan.zhihu.com/p/107473067html
本文基於ImagePy做者給羣友回答問題的討論中整理而來,閱讀須要發散思惟,若有不解,能夠查看代碼連接。前端
ImagePy的canvas解耦對程序員是好事,而後imagepy能夠再與後面的深度學習結合,這對小白是好事,由於深度學習有不少的工做是前面對圖像的處理,好比裁剪、旋轉以及標註等。如今的深度學習庫着眼於後面的訓練,前面的圖像操做是通常用的是第三方庫,好比cv,二者是分離的。node
其實imagepy有個問題是,開發者用容易喧賓奪主。基礎功能太多了,本身加的插件都不太起眼。若是能解耦,又能快速集成,就會有開發者願意選擇.就是打開沒那麼多功能,就只有本身加的少數功能,python
就是把現有的插件都幹掉,只保留框架.我能深入體會這句話,以前就想用imagepy作開發,可是工具太多了,給別人用也找不到,git
分紅兩個項目,一個空殼,一個插件系統。程序員
不是重量問題,有一些開發者的目的是,樹立品牌概念。github
作一個插件太不起眼,沒動力。算法
若是基礎組件能像plt和napari那樣使用,而後又能夠在空殼上快速搭建插件應用。就比較好了,canvas
其實如今的imagepy只要把menus菜單裏面的東西刪光,也是同樣的。。。後端
那就變成了帶標記和顯示功能的ui。
sciwx是相似matplotlib的一套繪圖庫,不一樣的是組件化更好,能夠快速搭建上層應用。
其實就是把imagepy裏面的canvas,3dmyvi,gridtable,markdown,還有直方圖,曲線面板,鷹眼什麼的作成了獨立控件。
各類組件,畫布,表格,參數對話框生成,都放到sciwx裏面了。
主要是imagepy裏面的ui部分,獨立出來了
又加了一個matplotlib式的用法
如今imagepy裏面的三維,二維面板,均可以相似matplotlib的簡單語法獨立調用了。
還能夠經過實現接口,set給工具欄實現交互。
除了napari的三維功能,其餘的把imagepy的canvas獨立出來,均可以實現。
其實napari也是用了一個比較成熟的三維可視化工具包裝的
畫圖和普通的處理有點區別,其餘的處理能夠用很長時間作處理,最後刷新一次。
可是畫圖是動一下鼠標就要更新一次,就要不停的從cpu送顯存。因此napari那種用顯卡可視化的,就有問題了。
除非開發局部紋理節點更新,這個水就深了。
圖像在opengl裏面是紋理。畫圖若是高效一些,應該局部更新紋理數據。
這個應該不是python層面能夠解決的問題了
Amira優化的彷佛還不錯,不過是商業軟件了,無法比
路線不一樣,napari是gpu渲染,我如今的是cpu渲染。
不過優化得還不錯了,最近支持了複數,對數展現。
複數,不是提早求模,log送過去的。
畫布實時作採樣,求模,對數。依然有上百幀率。全屏狀態下40幀。
其實z和c的支持都已經很成熟了,獨立出來應該仍是有用。
就像plt那樣,三五行,show出來,多圖層多通道,能夠設置假彩色。
canvas裏面都是很極端的代碼,一切性能優先
box只是控制顯示區域邏輯,還不涉及太多性能
imutil裏面,寫法都是在我能力範圍內,優化到極致了。
複數的場景是用在什麼地方?傅里葉變換吧,其餘的我也沒想到
此次重構的目的是
1. 組件能夠當成wx的panel,放在其餘UI項目中使用
2. 能夠像plt,napari那樣,簡短的代碼show出圖像或表格
3. 測試插件或工具能夠不須要拷貝到特定目錄,能夠用代碼快捷加載測試
4. 能夠從純淨版環境快速搭建特定的簡單應用,好比標記工具
5. 現有插件所有以一個插件管理項目外部加載到純淨版
簡單應用若是不想準備圖標,能夠用一個字母代替做爲工具的logo
https://github.com/Image-Py/cellpose-plgs
cellpose這個例子很好,imagepy正好做爲膠水框架,把整個流程串起來:前端提供縮放、裁剪、標註、顯示,後端集成深度學習模型,提供訓練和預測
如今只是一個訓練的模型,而後cellpose本身也帶一個簡單的ui,上面有標記功能,很簡陋。先把這個提交了,而後問一下做者標記以後怎麼用。
https://github.com/MouseLand/cellpose/issues/11
cellpose的那個UI,若是在ImagePy裏面作,很輕鬆的。插件作好了,最多再作一個widget,就能夠作到很定製化的程度了。
不過他這個我記得半徑那個參數挺重要的,有些比較難的例子還須要調一下
那個不是磁性套索,就是鼠標軌跡。不過感受不是用來修復的
他們這個工做有幾個很是簡單的點比較有意思,第一個是他們的訓練數據不是一個類別的,是很是多,很是廣塊狀物體,第二個是他們輸入以前的縮放,針對不一樣塊狀物體,能夠先縮放大小再輸入,輸出再縮放回來。
他們好像就是用的cellprofiler+cellpose標記的他們本身的數據
for i in range(n):
mask = img==n
xxxx 找mask的輪廓
這麼寫,若是圖很大,而且裏面標記又多,就很是慢了。
mask = img==n 這一句要反覆分配大內存。
沒有工程化的優化思惟的
畢竟重點在機器學習
不過那個寫法真的不是電腦好能搶回來的。若是圖像2048平方,裏面1024個碎片。這個目測就幾十秒了。
爲了分析一個小塊label,先複製整個圖像,而後作成bool掩膜。
我估計他們可能不care這些,因此問了一下需不須要幫忙優化。用的話就套個近乎,不用就算了。
https://github.com/Image-Py/cellpose-plgs 我剛寫的,七牛雲,我這裏看不到圖,不知道什麼緣由。
github作圖牀,怎麼拿到絕對連接呢
在issue裏傳圖,傳完後就有個markdown格式的連接
githubusercontent.com 可能被牆了
土鹽:ImagePy_Learn | 圖形學繪製代碼學習:core\draw\fill.py
土鹽:ImagePy_Learn | 圖形學繪製代碼學習:paint.py
土鹽:ImagePy_Learn | 圖形學繪製代碼學習:core\draw\polygonfill.py
底下這根分界線有什麼方法能識別出來?
https://forum.image.sc/t/weak-boundaries-extraction/34048/2
https://forum.image.sc/t/weak-boundaries-extraction/34048 forum.image.sc其實最短路徑更直接可行一些
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,意思是無成本移動。而後左上角到右下角作路徑規劃。
圖像,矢量,圖論 三條腿走路
不少形態學,距離,輪廓描述,特徵檢測,實際上是矢量問題。須要用計算幾何算法。
圖像的距離爲何是矢量問題,不是像素之間計算麼?
單次距離運算確定是矢量更快,圖像是採樣過的距離場。
好比你剛纔那個圖,標註的幾個參數,就應該用矢量運算。
idx = np.argmin(np.linalg.norm(left_broundary - imgpoint, axis=1))
the left_broundary[idx] is the start point. (the right is similar)
拿到上輪廓,而後中下方虛擬點,求左右兩側的最近點
這個已是很純正的矢量問題了
拿到上下輪廓,後續的全部測量,都是矢量問題了。
能夠虛擬一箇中間下方較遠的一個點,而後起點,終點分別就是上輪廓,左右兩邊,最靠近虛擬位置的點。
若是必定用圖像實現,是這樣的.可是就很是低效了
圖像的距離是計算距離場,少數點的距離,用矢量更高效。
用這兩個點作最短路徑,應該穩了
擴充功能,零碎時間都好說。重構這種事情,沒弄完會分散精力。
又一個找起點的方法 反正是求最近點,因此仍是能夠用這個工具。
不能在原圖上作,閾值,或者和某個數max一下。目的是消除特別暗的影響。 dog實際上是把尺度映射成亮度的方法
若是走最大值,先乘以-1,而後減最小值,+單步距離代價。
其實平時寫的也比較隨意,以前給亓總的,
(arr-arr.min())/np.ptp(arr)
不過放到imagepy裏面,我通常都會作內存優化。
內置一個sobel,就是磁性套索工具了
imagej 和imagepy的水平集
https://mp.weixin.qq.com/s/8qmw62tqZuMs3Vm5yXS5OA
接着上面的那個素材整合到ImagePy
https://github.com/Image-Py/imagepy/blob/master/imagepy/menus/Analysis/Skeleton%20Network/graph_plgs.py github.com起點終點用roi就能夠
ips.roi能夠拿到,而後load裏面判斷一下是否是point類型而且只有兩個點,若是知足,run裏面求路徑,而且高亮繪製。
那裏面其實還有一個原理,就是dog能夠凸顯特定尺度的目標。
加載analysis菜單下面吧
Shortest Route
這兩個要作個參數彈窗麼
支持單通道8-bit, 16-bit, int, float
還有req_roi,而且還不夠,還要再load裏面斷定一下
必須是point類型,只有2點,不然alert一下
其實再細緻一點,應該先檢測起點,終點
想着點兩個點起碼比描個線要輕鬆吧
這個確定有辦法很穩定的全自動完成的
不用角點,用最兩端的也好啊
其實中間兩個點也好說,這就是昨天我說的,圖像,矢量,圖論,三條腿走路。
屬於filter類
能夠參考分水嶺那個,加一個輸出選項
三個選項:掩膜,高亮在原圖上,擦除背景
對了,圖像最大最小值,能夠用minv, maxv = ips.range得到
由於對於其餘類型,可能不是0,255
route_through_array函數返回的是座標,我想的是新建一個全是0的矩陣,而後把對應座標的值寫255
索引,img[rs,cs] = 255
這個應用不必新建數組吧,能夠原圖上操做。
爲何ips.roi.body獲取的點的座標有小數?
roi是矢量數據,有小數正常。
截取一下吧,而且不保證在圖像內部。
不在圖像內部的問題,能夠暫時忽略
我理解的用戶從座標上鼠標選取的應該是某一個像素的座標,理論上不該該是小數把
若是通過放大,再選取呢?或者是從shapefile,gps等導入的地理數據呢
其實應該這樣作的,用line對象
而後依次計算,連線
好比一個圓環,能夠點三個點
這樣也不必限定兩個了
反正有幾條,就順着依次作
能夠支持多線
判斷一下是line類型的roi,就能夠放過去了
不用支持point了
point也能夠用一條單線
支持多條多段line就能夠了,不支持point
一個line上的是一組?
對,兩重循環,第一層是每條line,第二層是每條line上的點。
可是有個問題,就是一個line上有3個point的話,好比p0,p1,p2。那要算多少次?
兩次啊
0-1, 1-2
p0,p2不算一次?那若是是想獲得個封閉的路徑就要P0,P2再算一次?
不用了
若是須要閉合,用戶本身再點會起點就能夠了
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
line的座標放在哪一個屬性了?我打印了ips.roi.body是空的
就是body吧
右鍵落下去纔算完成,這個只是mark
我說顏色怎麼不同
可是若是勾選了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']
roi是有功能的,好比生成選區掩膜,還要實現交併補運算。
mark只是用來顯示的
距離怎麼算的?直接用座標會有問題.就是你昨天問的,爲何roi有小數
key['canvas'].scale能夠拿到比例,畫布當前的比例尺,會隨着放大鏡改變,就是真實的屏幕座標和像素座標之間的關係。
5個像素支內算選中
可是若是不帶比例尺,就會隨着畫布縮放變化。
key['canvas'].scale
就是選中某個點的邏輯,直接用x,y計算,不太對
由於x,y會受到畫布比例尺影響
拖動要判斷是否選中吧,就須要計算距離。
由於x,y都是像素座標系。因此攝取的時候會出問題
好比你放大到很大的時候,離很遠就選中了。
除以canvas.scale就回到屏幕座標系了。
經過調控sigma能夠凸顯不一樣尺度的目標,那個問題是用dog凸顯細線,而後再作路徑規劃。
dog會遇到類型溢出問題,要不你就轉float吧,而後
img - gaussian(img, 2)
以後標準化到0-1,兩端擦除
圖像最小值若是是0,那麼0表明無代價
這樣會致使線路曲折,只要有通路,就鑽空子。
可是若是總體加一個常數,這個表明距離代價
意思是,通過實際距離是有代價的。不能無限制的任意走。
這個常數n,比如最優路徑,加上幾何距離*n
這個n其實必定程度上,是限制線條流暢性的參數。若是n很是大,那麼規化出來的路線,就趨於兩點之間的直線。這個能夠理解嗎?
好比原圖原本是0-1之間波動的,若是我全圖都+10,那麼規劃出來的路徑,就會成爲直線。由於相對10來講,波動很小,那麼決策比如儘可能走少的路。
因此,那個插件,建議在note裏面加上'2int',而後添加一個step cost參數。
但願路徑除了代價小,還但願路徑是越短越好
常數越大,越接近直線,由於繞路不是0成本的
這個,畫了一條幹擾線
0代價的時候,確定就是從這條幹擾線通過了
加上路徑成本,就對了
這個值人工選取,能夠自動選取麼?
在圖像的值域內,無法徹底自動,這個約束就是,你但願路徑直的約束力度
越大其實就越接近直線
路徑對角線走和正交走,都是通過一個像素
加了個反轉
process > classify 下面有一個label panel
專門用於標記的,不過有點繞,提示沒作好。
大體原理是,對當前圖像新建一個掩膜,把原圖設置成掩膜的背景,而後選擇一下混合模式。
那個上面能夠方便的擦除,覆蓋,不會破壞原圖
dl第一次加載model都很耗時,每次run都是第一次
其實應該作成類成員
Plugin.model = xxx
下次判斷是否是已經加載
不過比較扯的是,這裏有多種模型,而且每次是根據用戶選擇加載的。因此就沒作處理。
單例調用意義不大,由於ipy主要就是作交互上的事情。卻是能夠當字典用,把代碼拷貝出來。
用字典cache下就行
這裏能夠查看每一個功能的源碼,通常都很是簡短。
這種類成員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
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鄰域
lab是公認的,座標系距離和視覺感知差別最一致的模型。
rgb和cmyk之間,實際上是邏輯或,邏輯且的關係
lab其實只有數值概念,無法可視化。
要可視化也只有把l,a,b強制當成r,g,b.值域要本身變換一下
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]
# -*- 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]
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()