ImagePy引擎初探

mac2024-12-02  24

最近在学图形学绘制,想到了ImagePy框架的ROI涂抹交互很方便,于是啃起了绘制代码。啃完了绘制代码,接下来对绘制代码的邻居引擎代码进行了开啃。

这里主要对ImagePy中一个Filter滤波器引擎进行难点讲解。

让我们好好学习Python中高手的代码吧。

Image-Py/imagepy​github.com

源码在此

# -*- coding: utf-8 -*- """ Created on Fri Dec 2 23:48:33 2016 @author: yxl """ import wx import threading import numpy as np from ... import IPy from ...ui.panelconfig import ParaDialog from ...core.manager import TextLogManager, ImageManager, \ WindowsManager, TaskManager, WidgetsManager, DocumentManager from time import time def process_channels(plg, ips, src, des, para): if ips.channels>1 and not 'not_channel' in plg.note: for i in range(ips.channels): rst = plg.run(ips, src if src is None else src[:,:,i], des[:,:,i], para) if not rst is des and not rst is None: des[:,:,i] = rst else: rst = plg.run(ips, src, des, para) if not rst is des and not rst is None: des[:] = rst return des def process_one(plg, ips, src, img, para, callafter=None): TaskManager.add(plg) start = time() transint = '2int' in plg.note and ips.dtype in (np.uint8, np.uint16) transfloat = '2float' in plg.note and not ips.dtype in (np.complex128, np.float32, np.float64) if transint: buf = img.astype(np.int32) src = src.astype(np.int32) if transfloat: buf = img.astype(np.float32) src = src.astype(np.float32) rst = process_channels(plg, ips, src, buf if transint or transfloat else img, para) if not img is rst and not rst is None: imgrange = {np.uint8:(0,255), np.uint16:(0, 65535)}[img.dtype.type] np.clip(rst, imgrange[0], imgrange[1], out=img) if 'auto_msk' in plg.note and not ips.get_msk() is None: msk = True ^ ips.get_msk() img[msk] = src[msk] IPy.set_info('%s: cost %.3fs'%(ips.title, time()-start)) ips.update() TaskManager.remove(plg) if not callafter is None:callafter() def process_stack(plg, ips, src, imgs, para, callafter=None): TaskManager.add(plg) start = time() transint = '2int' in plg.note and ips.dtype in (np.uint8, np.uint16) transfloat = '2float' in plg.note and not ips.dtype in (np.complex128, np.float32, np.float64) if transint: buf = imgs[0].astype(np.int32) src = src.astype(np.int32) elif transfloat: buf = imgs[0].astype(np.float32) src = src.astype(np.float32) else: src = src * 1 for i,n in zip(imgs,list(range(len(imgs)))): #sleep(0.5) plg.progress(n, len(imgs)) if 'auto_snap' in plg.note : src[:] = i if transint or transfloat: buf[:] = i rst = process_channels(plg, ips, src, buf if transint or transfloat else i, para) if not i is rst and not rst is None: imgrange = {np.uint8:(0,255), np.uint16:(0,65535)}[i.dtype.type] np.clip(rst, imgrange[0], imgrange[1], out=i) if 'auto_msk' in plg.note and not ips.get_msk() is None: msk = True ^ ips.get_msk() i[msk] = src[msk] IPy.set_info('%s: cost %.3fs'%(ips.title, time()-start)) ips.update() TaskManager.remove(plg) if not callafter is None:callafter() class Filter: title = 'Filter' modal = True note = [] 'all, 8-bit, 16-bit, int, rgb, float, not_channel, not_slice, req_roi, auto_snap, auto_msk, preview, 2int, 2float' para = None view = None prgs = (None, 1) def __init__(self, ips=None): if ips==None:ips = IPy.get_ips() self.dialog = None self.ips = ips def progress(self, i, n): self.prgs = (i, n) def show(self, temp=ParaDialog): self.dialog = temp(WindowsManager.get(), self.title) self.dialog.init_view(self.view, self.para, 'preview' in self.note, modal=self.modal) self.dialog.on_help = lambda : IPy.show_md(self.title, DocumentManager.get(self.title)) self.dialog.set_handle(lambda x:self.preview(self.ips, x)) if self.modal: return self.dialog.ShowModal() == wx.ID_OK self.dialog.on_ok = lambda : self.ok(self.ips) self.dialog.on_cancel = lambda : self.cancel(self.ips) self.dialog.Show() def run(self, ips, snap, img, para = None): return 255-img def check(self, ips): note = self.note if ips == None: IPy.alert('No image opened!') return False elif 'req_roi' in note and ips.roi == None: IPy.alert('No Roi found!') return False elif not 'all' in note: if ips.get_imgtype()=='rgb' and not 'rgb' in note: IPy.alert('Do not surport rgb image') return False elif ips.get_imgtype()=='8-bit' and not '8-bit' in note: IPy.alert('Do not surport 8-bit image') return False elif ips.get_imgtype()=='16-bit' and not '16-bit' in note: IPy.alert('Do not surport 16-bit uint image') return False elif ips.get_imgtype()=='32-int' and not 'int' in note: IPy.alert('Do not surport 32-bit int uint image') return False elif 'float' in ips.get_imgtype() and not 'float' in note: IPy.alert('Do not surport float image') return False return True def preview(self, ips, para): process_one(self, ips, ips.snap, ips.img, para, None) def load(self, ips):return True def ok(self, ips, para=None, callafter=None): if para == None: para = self.para if not 'not_slice' in self.note and ips.get_nslices()>1: if para == None:para = {} if para!=None and 'stack' in para:del para['stack'] win = WidgetsManager.getref('Macros Recorder') if ips.get_nslices()==1 or 'not_slice' in self.note: # process_one(self, ips, ips.snap, ips.img, para) if IPy.uimode() == 'no': process_one(self, ips, ips.snap, ips.img, para, callafter) else: threading.Thread(target = process_one, args = (self, ips, ips.snap, ips.img, para, callafter)).start() if win!=None: win.write('{}>{}'.format(self.title, para)) elif ips.get_nslices()>1: has, rst = 'stack' in para, None if not has: rst = IPy.yes_no('Run every slice in current stacks?') if 'auto_snap' in self.note and self.modal:ips.reset() if has and para['stack'] or rst == 'yes': para['stack'] = True #process_stack(self, ips, ips.snap, ips.imgs, para) if IPy.uimode() == 'no': process_stack(self, ips, ips.snap, ips.imgs, para, callafter) else: threading.Thread(target = process_stack, args = (self, ips, ips.snap, ips.imgs, para, callafter)).start() if win!=None: win.write('{}>{}'.format(self.title, para)) elif has and not para['stack'] or rst == 'no': para['stack'] = False #process_one(self, ips, ips.snap, ips.img, para) if IPy.uimode() == 'no': process_one(self, ips, ips.snap, ips.img, para, callafter) else: threading.Thread(target = process_one, args = (self, ips, ips.snap, ips.img, para, callafter)).start() if win!=None: win.write('{}>{}'.format(self.title, para)) elif rst == 'cancel': pass #ips.update() def cancel(self, ips): if 'auto_snap' in self.note: ips.swap() ips.update() def start(self, para=None, callafter=None): ips = self.ips if not self.check(ips):return if not self.load(ips):return if 'auto_snap' in self.note:ips.snapshot() if para!=None: self.ok(self.ips, para, callafter) elif self.view==None: if not self.__class__.show is Filter.show: if self.show(): self.ok(self.ips, para, callafter) else: self.ok(self.ips, para, callafter) elif self.modal: if self.show(): self.ok(ips, None, callafter) else:self.cancel(ips) self.dialog.Destroy() else: self.show() def __del__(self): print('filter del')

 

推荐大神写的Filter引擎解析文章

ImagePy解析:9 -- Filter引擎及其衍生的图像取反插件​qixinbo.info

 

ImagePy_Learn学习系列

土盐:ImagePy_Learn | 图形学绘制代码学习:core\draw\fill.py

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

土盐:ImagePy_Learn | 图形学绘制代码学习:core\draw\polygonfill.py

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

 

下面是难点讲解:

from ...ui.panelconfig import ParaDialog

import //模块.函数 from…import // 直接使用函数名使用就可以了

(1) 调用模块属性的区别

import 模块名 模块名.xxx = 引用 from 模块名 import * xxx = 拷贝 # 能修改属性值  

函数,类... : "import 模块名" 和 "from 模块名 import *" 都是引用。

(2)私有属性两种导入的区别

# . 类中的私有属性 # 本质做了一个名字重整 class test() self.__name

__name 名字重整成 _test__name。

_littlethree : 模块的私有属性(数据)。

from 模块 import * : 导入模块时,会跳过私有属性;import 模块 : 通过引用可以访问私有属性

from ...

core文件夹内的filter.py从core的上级目录里面找ui.panelconfig等等模块

参考原文链接:from…import * 语句与 import 区别

src if src is None else src[:,:,i]

if not callafter is None:callafter()

src是none,则对src什么也不做

not callafter 是 None,则调用callafter()

Python身份运算符

身份运算符用于比较两个对象的存储单元

运算符描述实例is is 是判断两个标识符是不是引用自一个对象x is y, 类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 Falseis notis not 是判断两个标识符是不是引用自不同对象 x is not y , 类似 id(a) != id(b)。如果引用的不是同一个对象则返回结果 True,否则返回 False。

s 用于判断两个变量是否引用同一个, 会对比其中两个变量的地址。

is not 用于判断两个变量是否引用自不同的对象。也会比较地址

is与== 比较:

==判断变量的值是否一样,不比较地址的。

参考原文链接:Python 运算符 | 菜鸟教程

https://blog.csdn.net/yinjiajia1010/article/details/81260632

 

transint = '2int' in plg.note and ips.dtype in (np.uint8, np.uint16)

transint 是等号后面判断是否的布尔值,0或者1。

Python成员运算符

运算符描述实例in 如果在指定的序列中找到值返回 True,否则返回 False。 x 在 y 序列中 , 如果 x 在 y 序列中返回 True。not in如果在指定的序列中没有找到值返回 True,否则返回 False。x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True。

参考原文链接:Python 运算符 | 菜鸟教程

 

rst = process_channels(plg, ips, src, buf if transint or transfloat else img, para)

rst表示result

列表推导式总共有两种形式:

①[x for x in data if condition]

此处if主要起条件判断作用,data数据中只有满足if条件的才会被留下,最后统一生成为一个数据列表

②[exp1 if condition else exp2 for x in data]

此处if...else主要起赋值作用,当data中的数据满足if条件时将其做exp1处理,否则按照exp2处理,最后统一生成为一个数据列表。

列表推导式的一个示例:

参考原文链接:http://www.mamicode.com/info-detail-1904804.html

人类身份验证 - SegmentFault

imgrange = {np.uint8:(0,255), np.uint16:(0, 65535)}[img.dtype.type]

numpy.dtype.type

dtype.type

用于实例化此数据类型的标量的类型对象。

import numpy as np image = np.array([[35, 37, 39, 36, 34, 31], [33, 32, 32, 33, 31, 33], [30, 34, 36, 37, 36, 32], [33, 30, 28, 30, 28, 28]], dtype=np.uint32) dtype_range = {np.bool_: (False, True), np.bool8: (False, True), np.uint8: (0, 255), np.uint16: (0, 65535), np.int8: (-128, 127), np.int16: (-32768, 32767), np.int64: (-2**63, 2**63 - 1), np.uint64: (0, 2**64 - 1), np.int32: (-2**31, 2**31 - 1), np.uint32: (0, 2**32 - 1), np.float32: (-1, 1), np.float64: (-1, 1)} print(image.dtype.type) print(dtype_range[image.dtype.type]) <type 'numpy.uint32'> (0, 4294967295L)

参考原文链接:numpy.dtype.type - NumPy v1.17 Manual

KeyError with np.uint32 and numpy.uint32

np.clip(rst, imgrange[0], imgrange[1], out=img)

将范围外的数强制转化为范围内的数

def clip(a, a_min, a_max, out=None): 将数组a中的所有数限定到范围a_min和a_max中,即az中所有比a_min小的数都会强制变为a_min,a中所有比a_max大的数都会强制变为a_max.其中a_min和a_max可以为一个和a一样大小的数组(列表也可以,只要是类似数组的结构就是可行的),则数组中相应位置的元素进行比较。out 是可选项,表示把强制截取后的结果放到这个数组中,但是out中的数组必须和a形状一样 Examples -------- >>> a = np.arange(10) >>> np.clip(a, 1, 8) array([1, 1, 2, 3, 4, 5, 6, 7, 8, 8]) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> np.clip(a, 3, 6, out=a) array([3, 3, 3, 3, 4, 5, 6, 6, 6, 6]) >>> a = np.arange(10) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> np.clip(a, [3, 4, 1, 1, 1, 4, 4, 4, 4, 4], 8) array([3, 4, 2, 3, 4, 5, 6, 7, 8, 8])

参考原文链接:np.clip截取函数 - cloud&ken - 博客园

msk = True ^ ips.get_msk()

^是按位异或逻辑运算符,比如5^6,其实是101^110,结果是011,所以5^6的答案是3

参考原文链接:https://blog.csdn.net/wxy_csdn_world/article/details/80759915

IPy.set_info('%s: cost %.3fs'%(ips.title, time()-start))

一种字符串格式化的语法, 基本用法是将值插入到%s占位符的字符串中。

%s,表示格式化一个对象为字符

string = "good" #类型为字符串 print("string=%s" %string) #输出的打印结果为 string=good print("string=%3s" %string) # 输出的打印结果为 string=good(数字3的意思是:字符串的长度为3。当字符串的长度大于3时,按照字符串的长度打印出结果) print("string=%(+)6s" %string) # 输出的打印结果为 string= good(当字符串的长度小于6时,在字符串的左侧填补空格,使得字符串的长度为6) print("string=%-6s" %string) # 输出的打印结果为 string=good (当字符串的长度小于6时,在字符串的右侧填补空格,使得字符串的长度为6) #小数点后的数字表示截取的字符串长度 print("string=%.3(6)s" %string) # 输出的打印结果为 string=goo(good)(%.3s的意思是:截取字符串的前3个字符,当截取字符串的字符长度大于字符串时,输出的结果是整个字符串) print("string=%a.bs" %string) # 先是根据小数点后面的数字b截取字符串,当截取的字符串长度小于a时,需要在字符串的左侧填补空格,使得字符串的长度变为a print("string=%*.*s" %(6, 3, string)) # %*.*s表示精度, 两个*的值分别由%string前面被两个逗号隔开的数值来指定

参考原文链接:python 中的" %s"%用法

for i,n in zip(imgs,list(range(len(imgs)))):

zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。

zip([iterable, ...]):参数iterabl -- 一个或多个迭代器

x=["a","1"] y=["b","2"] z = list(zip(x,y)) print (list(zip(x,y))) print (list(zip(*z))) 结果: [('a', 'b'), ('1', '2')] [('a', '1'), ('b', '2')]

参考原文链接:https://blog.csdn.net/qq_28979491/article/details/91356093

self.dialog = temp(WindowsManager.get(), self.title)

Python wxPython库消息对话框MessageDialog用法。

具体如下:

消息对话框即我们平时说的Messagebox,看看它的原型,下面是wxWidgets中的原型定义,C++风格,与python风格的区别就是wx前缀与后面名称直接相连,例如wxMessageDialog,在wxpython中使用时就是wx.MessageDialog

wxMessageDialog(wxWindow* parent, const wxString& message, const wxString& caption = "Message box", long style = wxOK | wxCANCEL, const wxPoint& pos = wxDefaultPosition)

其各参数不多做介绍,主要看看ShowModal()方法,它使用应用程序在对话框关闭前不能响应其它窗口的用户事件,返回一个整数,取值如下:

wx.ID_YES, wx.ID_NO, wx.ID_CANCEL, wx.ID_OK。

另外,style的取值主要有以下几种:

#!/usr/bin/env python # -*- coding: utf-8 -*- import wx class MyFrame(wx.Frame): def __init__(self, parent, id): wx.Frame.__init__(self, parent, id, u'测试面板Panel', size = (600, 300)) #创建面板 panel = wx.Panel(self) #在Panel上添加Button button = wx.Button(panel, label = u'关闭', pos = (150, 60), size = (100, 60)) #绑定单击事件 self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) def OnCloseMe(self, event): dlg = wx.MessageDialog(None, u"消息对话框测试", u"标题信息", wx.YES_NO | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_YES: self.Close(True) dlg.Destroy() if __name__ == '__main__': app = wx.PySimpleApp() frame = MyFrame(parent = None, id = -1) frame.Show() app.MainLoop()

测试:

(1) 提示对话框

QMessageBox.information(self,'标题','提示信息','OK','Cancel','其他')

解释:上面参数中的'OK','Cancel','其他'表示对话框的可选项,一般默认是OK.

(2) 询问对话框

QMessageBox.question(self,'标题','询问信息')

(3) 警告对话框

QMessageBox.warning(self,'标题','提示信息')

(4) 严重警告对话框

QMessageBox.critical(self,'标题','提示信息')

(5) 关于对话框

QMessageBox.information(self,'标题','提示信息')

(6) AboutQt对话框

QMessageBox.information(self,'标题','提示信息')

这个是pyqt内置的,所以参数不能修改,只能像下面这样写:

参考原文链接:Python wxPython库消息对话框MessageDialog用法示例

https://blog.csdn.net/u011146423/article/details/85291002

https://blog.csdn.net/chengqiuming/article/details/78601002

5. 对话框

threading.Thread(target = process_one, args = (self, ips, ips.snap, ips.img, para, callafter)).start()

# 导入Python标准库中的Thread模块 from threading import Thread # 创建一个线程 mthread = threading.Thread(target=function_name, args=(function_parameter1, function_parameterN)) # 启动刚刚创建的线程 mthread .start()

function_name: 需要线程去执行的方法名

args: 线程执行方法接收的参数,该属性是一个元组,如果只有一个参数也需要在末尾加逗号。

start() 方法是启动一个子线程,线程名就是我们定义的namerun() 方法并不启动一个新线程,就是在主线程中调用了一个普通函数而已。

参考原文链接:python语言中threading.Thread类的使用方法 - 429512065 - 博客园

Python 多线程 start()和run()方法的区别(三)

if win!=None: win.write('{}>{}'.format(self.title, para))

format 函数可以接受不限个参数,位置可以不按顺序。

基本语法是通过 {} 和 : 来代替以前的 % 。

参考原文链接:Python format 格式化函数

self.dialog.Destroy()

正常退出对话框

dialog = wx.TextEntryDialog(None,"Input the subitem name","Subitem", style=wx.OK|wx.CANCEL) if dialog.ShowModal() == wx.ID_OK: subitem = dialog.GetValue() else: dialog.Destroy()

 

def OnCloseWindow(self,event): dlg = wx.MessageDialog(None,'Exit , Are you sure ?', 'Confirmation',wx.YES_NO|wx.ICON_QUESTION) retCode = dlg.ShowModal() if(retCode == wx.ID_YES): self.Destroy() else: pass

参考原文链接:人类身份验证 - SegmentFault

wxPython如何在退出时弹出提示框-论坛

最新回复(0)