From 1386b9de8fbd58043393525153acc8f7f8c409ca Mon Sep 17 00:00:00 2001 From: Qi Date: Mon, 1 Feb 2021 11:03:40 +0800 Subject: [PATCH 1/8] make canvas independent --- sciwx/canvas/canvas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sciwx/canvas/canvas.py b/sciwx/canvas/canvas.py index ba31e383..2235f269 100644 --- a/sciwx/canvas/canvas.py +++ b/sciwx/canvas/canvas.py @@ -190,7 +190,7 @@ def move(self, dx, dy, coord='win'): self.update() def on_size(self, event): - if max(self.GetClientSize())>20: + if max(self.GetClientSize())>20 and self.images[0].img is not None: self.initBuffer() if len(self.images)+len(self.marks)==0: return if self.conbox[2] - self.conbox[0] > 1: self.update() From 8bc6f45fd90a6b8e0b2a3a660119b8281e964b0f Mon Sep 17 00:00:00 2001 From: Qi Date: Sun, 7 Feb 2021 14:47:25 +0800 Subject: [PATCH 2/8] fix the error in frequency analysis --- imagepy/menus/Analysis/statistic_plg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imagepy/menus/Analysis/statistic_plg.py b/imagepy/menus/Analysis/statistic_plg.py index ee3d041a..0cc1d79c 100644 --- a/imagepy/menus/Analysis/statistic_plg.py +++ b/imagepy/menus/Analysis/statistic_plg.py @@ -96,7 +96,7 @@ def run(self, ips, imgs, para = None): img = imgs[i] if msk is None else imgs[i][msk] maxv = img.max() if maxv==0:continue - ct = np.histogram(img, maxv, [1,maxv+1])[0] + ct = np.histogram(img, maxv+1, [0,maxv])[0] titles = ['slice','value','count'] dt = [[i]*len(ct), list(range(maxv+1)), ct] if not para['slice']: From c941603a2ed9bee87b377af8cd366142ad273808 Mon Sep 17 00:00:00 2001 From: Qi Date: Fri, 19 Feb 2021 13:38:01 +0800 Subject: [PATCH 3/8] add img win to app --- sciapp/app.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sciapp/app.py b/sciapp/app.py index ad974f2e..afb3627b 100644 --- a/sciapp/app.py +++ b/sciapp/app.py @@ -6,7 +6,7 @@ def __init__(self, asyn=True): self.asyn = asyn self.managers = {} self.img_manager = self.manager('img') - #self.wimg_manager = self.manager('wimg') + self.wimg_manager = self.manager('wimg') self.tab_manager = self.manager('tab') #self.wtab_manager = self.manager('wtab') self.mesh_manager = self.manager('mesh') @@ -40,6 +40,9 @@ def show_img(self, img, name): self.img_manager.add(img.name, img) print(img.info) + def add_img_win(self, win, name): + self.wimg_manager.add(name, win) + def close_img(self, name): self.img_manager.remove(name) print('close image:', name) From a128ed72ff907d4ea9e0b05f6792742ece736076 Mon Sep 17 00:00:00 2001 From: Qi Date: Fri, 19 Feb 2021 13:40:38 +0800 Subject: [PATCH 4/8] detect null image when initializing --- sciwx/canvas/mcanvas.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sciwx/canvas/mcanvas.py b/sciwx/canvas/mcanvas.py index 8551bdd8..bafc9be8 100644 --- a/sciwx/canvas/mcanvas.py +++ b/sciwx/canvas/mcanvas.py @@ -258,6 +258,8 @@ def on_scroll(self, event): self.canvas.on_idle(event) def on_idle(self, event): + if self.image.img is None: + return image, info = self.image, self.lab_info.GetLabel() imgs = image.slices, image.channels, image.cn, image.cur selfs = self.pages ,self.chans, self.cn, self.cur From faedbc9446c3cbc691b498bbf5e1affac4cc7006 Mon Sep 17 00:00:00 2001 From: Xin Bo Qi Date: Tue, 23 Feb 2021 16:54:48 +0800 Subject: [PATCH 5/8] fix ribbon bar, but the default is still the original bar --- imagepy/app/imagepy.py | 4 +++- imagepy/menus/File/new_plg.py | 2 +- sciwx/widgets/ribbonbar.py | 42 ++++++++++++++++++++++++++--------- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/imagepy/app/imagepy.py b/imagepy/app/imagepy.py index d5662a0a..042b77f3 100644 --- a/imagepy/app/imagepy.py +++ b/imagepy/app/imagepy.py @@ -2,7 +2,7 @@ import time, threading sys.path.append('../../../') import wx.lib.agw.aui as aui -from sciwx.widgets import MenuBar, ToolBar, ChoiceBook, ParaDialog, WorkFlowPanel, ProgressBar +from sciwx.widgets import MenuBar, RibbonBar, ToolBar, ChoiceBook, ParaDialog, WorkFlowPanel, ProgressBar from sciwx.canvas import CanvasNoteBook from sciwx.grid import GridNoteBook from sciwx.mesh import Canvas3DNoteBook @@ -147,6 +147,8 @@ def load_widget(self, data): def init_menu(self): self.menubar = MenuBar(self) + # self.menubar = RibbonBar(self) + # self.auimgr.AddPane( self.menubar, aui.AuiPaneInfo() .CaptionVisible(False) .Top() .PinButton( True ).Dock().Resizable().MinSize(wx.Size(1000, 130)).FloatingSize( wx.DefaultSize ).Layer(5) ) def init_tool(self): sizer = wx.BoxSizer(wx.VERTICAL) diff --git a/imagepy/menus/File/new_plg.py b/imagepy/menus/File/new_plg.py index 50c8b99d..bd543927 100644 --- a/imagepy/menus/File/new_plg.py +++ b/imagepy/menus/File/new_plg.py @@ -8,7 +8,7 @@ import numpy as np class Plugin(Free): - title = 'New Image' + title = 'New' para = {'name':'Undefined','width':300, 'height':300, 'type':'8-bit','slice':1} view = [(str, 'name', 'name', ''), (int, 'width', (1,10240), 0, 'width', 'pix'), diff --git a/sciwx/widgets/ribbonbar.py b/sciwx/widgets/ribbonbar.py index 80638446..a8e61471 100644 --- a/sciwx/widgets/ribbonbar.py +++ b/sciwx/widgets/ribbonbar.py @@ -69,7 +69,10 @@ def parse(self, ks, vs, pt): def parse(self, ks, vs, pt, short, rst): page = rb.RibbonPage( self, wx.ID_ANY, ks , wx.NullBitmap , 0 ) panel = toolbar = None + for kv1 in vs: + if len(kv1) == 2: + kv1 = (kv1[0], None, kv1[1]) if kv1 == '-': continue pname = kv1[0] if isinstance(kv1[2], list) else '--' if panel is None and not isinstance(kv1[2], list): @@ -85,6 +88,8 @@ def parse(self, ks, vs, pt, short, rst): panel = rb.RibbonPanel( page, wx.ID_ANY, pname , make_logo(kv1[1] or kv1[0][0]) , wx.DefaultPosition, wx.DefaultSize, rb.RIBBON_PANEL_DEFAULT_STYLE ) toolbar = rb.RibbonButtonBar( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) for kv2 in kv1[2]: + if len(kv2) == 2: + kv2 = (kv2[0], None, kv2[1]) if kv2 == '-': continue btn = toolbar.AddSimpleButton( wx.NewId(), kv2[0], make_logo(kv2[1] or kv2[0][0]), wx.EmptyString) toolbar.Bind(rb.EVT_RIBBONBUTTONBAR_CLICKED, lambda e, p=kv2[2]:p().start(self.app), id=btn.id) @@ -98,11 +103,14 @@ def Append(self, id, item, menu): def load(self, data, shortcut={}): rst = [] - for k,l,v in data[1]: - self.parse(k, v, self, shortcut, rst) + for klv in data[1]: + if len(klv) == 2: + self.parse(klv[0], klv[1], self, shortcut, rst) + else: + self.parse(klv[0], klv[2], self, shortcut, rst) + rst = [(*hot_key(i[0]), i[1]) for i in rst] - acc = wx.AcceleratorTable(rst) - self.GetParent().SetAcceleratorTable(acc) + return wx.AcceleratorTable(rst) def on_menu(self, event): print('here') @@ -124,15 +132,27 @@ def start(self, app): def __call__(self): return self + # data = ('menu', [ + # ('File', None, [ + # ('Open CV', (255,0,0), P('O')), + # '-', + # ('Close', None, P('C'))]), + # ('Edit', None, [('Copy', None, P('C')), + # ('A', None, [('B', None, P('B')), + # ('C', None, P('C'))]), + # ('Paste', None, P('P'))])]) + data = ('menu', [ - ('File', None, [ - ('Open CV', (255,0,0), P('O')), + ('File', [ + ('Open', P('O')), '-', - ('Close', None, P('C'))]), - ('Edit', None, [('Copy', None, P('C')), - ('A', None, [('B', None, P('B')), - ('C', None, P('C'))]), - ('Paste', None, P('P'))])]) + ('Close', P('C'))]), + ('Edit', [ + ('Copy', P('C')), + ('A', [ + ('B', P('B')), + ('C', P('C'))]), + ('Paste', P('P'))])]) app = wx.App() frame = wx.Frame(None) From 3b74dfa0f771f65ad5456c956190ff9e771da37f Mon Sep 17 00:00:00 2001 From: Xin Bo Qi Date: Thu, 25 Feb 2021 15:29:34 +0800 Subject: [PATCH 6/8] report plugin fixed --- imagepy/app/loader.py | 8 +- imagepy/data/config.json | 2 +- imagepy/menus/Plugins/Coins Report.rpt | Bin 0 -> 26804 bytes sciapp/action/__init__.py | 2 +- sciapp/action/advanced/__init__.py | 3 +- sciapp/action/advanced/report.py | 66 ++++++++++++++ sciapp/util/xlreport.py | 116 +++++++++++++++++++++++++ sciwx/widgets/propertygrid.py | 88 +++++++++++++++++++ 8 files changed, 278 insertions(+), 7 deletions(-) create mode 100644 imagepy/menus/Plugins/Coins Report.rpt create mode 100644 sciapp/action/advanced/report.py create mode 100644 sciapp/util/xlreport.py create mode 100644 sciwx/widgets/propertygrid.py diff --git a/imagepy/app/loader.py b/imagepy/app/loader.py index 99a34a2e..1a411e18 100644 --- a/imagepy/app/loader.py +++ b/imagepy/app/loader.py @@ -6,7 +6,7 @@ """ import os, sys, os.path as osp from glob import glob -from sciapp.action import Macros, Widget#, Report +from sciapp.action import Macros, Widget, Report from .. import root_dir from .manager import DocumentManager, DictManager from codecs import open @@ -23,9 +23,9 @@ def extend_plugins(path, lst, err): rst = [] for i in lst: if isinstance(i, tuple) or i=='-': rst.append(i) - elif i[-3:] == 'rpt': pass - #pt = os.path.join(root_dir,path) - #rst.append(Report(i[:-4], pt+'/'+i)) + elif i[-3:] == 'rpt': + pt = os.path.join(root_dir,path) + rst.append(Report(i[:-4], pt+'/'+i)) elif i[-3:] in {'.md', '.mc', '.wf'}: p = os.path.join(os.path.join(root_dir, path), i).replace('\\','/') rst.append(Macros(i[:-3], ['Open>{"path":"%s"}'%p])) diff --git a/imagepy/data/config.json b/imagepy/data/config.json index fe7b9a33..b7722ba7 100644 --- a/imagepy/data/config.json +++ b/imagepy/data/config.json @@ -1 +1 @@ -[["uistyle", "imagepy", null], ["mea_style", {"color": [0, 0, 255], "fcolor": [255, 255, 255], "fill": false, "lw": 2, "tcolor": [0, 255, 0], "size": 12}, null], ["mark_style", {"color": [0, 255, 0], "fcolor": [255, 255, 255], "fill": false, "lw": 1, "tcolor": [255, 0, 0], "size": 8}, null], ["recent", ["C:/Users/54631/Desktop/\u6d77\u51b0\u62a5\u4ef7/testmacros.mc", "C:\\Users\\Administrator\\Downloads\\\u7d20\u6750\u2014\u8f66\u5934\\\u6d4b\u8bd5-JK1-\u52ff\u5220\\20170201\\In-20170201151126911-\u6842J73220-\u9ec4-qj-1.jpg", "C:/Users/Administrator/Desktop/imagepy/imagepy/plugins/demoplugin/menus/Demos/Macros Demo/Macros Gaussian Invert.mc", "DEM.mc"], null], ["roi_style", {"color": [255, 255, 0], "fcolor": [255, 255, 255], "fill": false, "lw": 1, "tcolor": [255, 255, 0], "size": 8}, null], ["language", "English", null]] \ No newline at end of file +[["language", "English", null], ["roi_style", {"color": [255, 255, 0], "fcolor": [255, 255, 255], "fill": false, "lw": 1, "tcolor": [255, 255, 0], "size": 8}, null], ["recent", ["C:/Users/54631/Desktop/\u6d77\u51b0\u62a5\u4ef7/testmacros.mc", "C:\\Users\\Administrator\\Downloads\\\u7d20\u6750\u2014\u8f66\u5934\\\u6d4b\u8bd5-JK1-\u52ff\u5220\\20170201\\In-20170201151126911-\u6842J73220-\u9ec4-qj-1.jpg", "C:/Users/Administrator/Desktop/imagepy/imagepy/plugins/demoplugin/menus/Demos/Macros Demo/Macros Gaussian Invert.mc", "DEM.mc"], null], ["mark_style", {"color": [0, 255, 0], "fcolor": [255, 255, 255], "fill": false, "lw": 1, "tcolor": [255, 0, 0], "size": 8}, null], ["mea_style", {"color": [0, 0, 255], "fcolor": [255, 255, 255], "fill": false, "lw": 2, "tcolor": [0, 255, 0], "size": 12}, null], ["uistyle", "imagepy", null]] \ No newline at end of file diff --git a/imagepy/menus/Plugins/Coins Report.rpt b/imagepy/menus/Plugins/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/sciapp/action/__init__.py b/sciapp/action/__init__.py index 3faee3d4..bf720152 100644 --- a/sciapp/action/__init__.py +++ b/sciapp/action/__init__.py @@ -4,4 +4,4 @@ from .plugin.mea_tools import * from .plugin.shp_tools import * from .plugin.roi_tools import * -from .advanced import Filter, Free, Simple, Table, Macros, Widget, dataio \ No newline at end of file +from .advanced import Filter, Free, Simple, Table, Macros, Widget, dataio, Report \ No newline at end of file diff --git a/sciapp/action/advanced/__init__.py b/sciapp/action/advanced/__init__.py index 1ad42725..8ee8c376 100644 --- a/sciapp/action/advanced/__init__.py +++ b/sciapp/action/advanced/__init__.py @@ -3,4 +3,5 @@ from .table import Table from .free import Free from .macros import Macros -from .widget import Widget \ No newline at end of file +from .widget import Widget +from .report import Report \ No newline at end of file diff --git a/sciapp/action/advanced/report.py b/sciapp/action/advanced/report.py new file mode 100644 index 00000000..a42922bd --- /dev/null +++ b/sciapp/action/advanced/report.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Dec 29 01:48:23 2016 +@author: yxl +""" +import wx +from sciapp import app +from sciapp.action.advanced.dataio import ReaderManager +# from imagepy.core.manager import ReaderManager, ViewerManager +from sciwx.widgets.propertygrid import GridDialog +from sciapp.util import xlreport +from time import time +import openpyxl as pyxl + +class Report: + def __init__(self, title, cont): + self.title = title + self.cont = cont + + def __call__(self): return self + + def runasyn(self, wb, info, key, para = None, callback = None): + self.app.add_task(self) + for i in para: + if i in key and key[i][0] == 'img': + ips = self.app.get_img(para[i]) + para[i] = ips if ips is None else ips.img + + if i in key and key[i][0] == 'tab': + tps = self.app.get_table(para[i]) + para[i] = tps if tps is None else tps.data + + start = time() + xlreport.fill_value(wb, info, para) + wb.save(para['path']) + self.app.info('%s: cost %.3fs'%(self.title, time()-start)) + self.app.remove_task(self) + if callback!=None:callback() + + def start(self, app, para=None, callafter=None): + self.app = app + wb = pyxl.load_workbook(self.cont) + xlreport.repair(wb) + info, key = xlreport.parse(wb) + + if para is not None: + return self.runasyn(wb, info, para, callafter) + dialog = GridDialog(self.app, self.title, info, key) + rst = dialog.ShowModal() + para = dialog.GetValue() + + dialog.Destroy() + if rst != 5100: return + filt = ['XLSX', 'xlsx', 'xlsx'] + path = self.app.get_path('Save..', filt, 'save') + if not path: return + para['path'] = path + self.app.record_macros('{}>{}'.format(self.title, para)) + self.runasyn(wb, info, key, para, callafter) + +def show_rpt(data, title): + wx.CallAfter(Report(title, data).start) + +# ViewerManager.add('rpt', show_rpt) +def read_rpt(path): return path +ReaderManager.add('rpt', read_rpt, tag='rpt') \ No newline at end of file diff --git a/sciapp/util/xlreport.py b/sciapp/util/xlreport.py new file mode 100644 index 00000000..18b3931c --- /dev/null +++ b/sciapp/util/xlreport.py @@ -0,0 +1,116 @@ +import openpyxl as pyxl +from openpyxl.utils.units import cm_to_EMU, EMU_to_pixels +from io import BytesIO +from openpyxl.drawing.image import Image +from PIL import Image as PImage +import numpy as np +import pandas as pd +from copy import copy + +if not '.rpt' in pyxl.reader.excel.SUPPORTED_FORMATS: + pyxl.reader.excel.SUPPORTED_FORMATS += ('.rpt',) + +def parse(wb): + rst, key = [], {} + for ws in wb.worksheets: + rst.append((ws.title, [])) + for row in ws.rows: + for cell in row: + if not isinstance(cell.value, str):continue + if cell.value[0]+cell.value[-1] != '{}': continue + cont = cell.value[1:-1].strip() + tp = cont.split(' ')[0] + cont = cont[len(tp):].strip() + note, value = 'no description', None + if '#' in cont: + note = cont.split('#')[-1].strip() + cont = cont[:cont.index('#')].strip() + if '=' in cont: + value = cont.split('=')[1].strip() + name = cont[:cont.index('=')].strip() + else: name = cont + + rst[-1][-1].append(((cell.row, cell.col_idx), + [tp, name, value, note])) + key[name] = [tp, name, value, note] + return rst, key + +def trans(img, W, H, margin, scale): + h, w = img.shape[:2] + h2, w2 = int(h/margin), int(w/margin) + if scale: + if W/H > w/h: w2 = int(W/H*h2) + if H/W > h/w: h2 = int(H/W*w2) + newshp = (h2, w2) if img.ndim==2 else (h2, w2, 3) + blank = np.ones(newshp, dtype=np.uint8) * 255 + blank[(h2-h)//2:(h2-h)//2+h, (w2-w)//2:(w2-w)//2+w] = img + return blank + +def add_image(wb, ws, pos, key, img): + if img is None: return + w, h, margin, scale = eval(key[2]) + img = trans(img, w, h, margin, scale==0) + img = PImage.fromarray(img) + image_file = BytesIO() + img.save(image_file, 'png') + ref = BytesIO(image_file.getvalue()) + image = Image(img) + image.ref = ref + image.height = EMU_to_pixels(cm_to_EMU(h)) + image.width = EMU_to_pixels(cm_to_EMU(w)) + wb[ws].add_image(image, wb[ws].cell(*pos).coordinate) + +def add_table(wb, ws, pos, key, data): + if data is None: return + vs = data.values + idx, cols = data.index, data.columns + dr, dc, ir, ic = 1, 1, 0, 0 + if key[2] != None: dr, dc, ir, ic = eval(key[2]) + for r in range(vs.shape[0]): + if ir!=0: wb[ws].cell(pos[0]+r*dr, pos[1]+ir, idx[r]) + for c in range(vs.shape[1]): + if ic!=0: wb[ws].cell(pos[0]+ic, pos[1]+c*dc, cols[c]) + for r in range(vs.shape[0]): + for c in range(vs.shape[1]): + wb[ws].cell(pos[0]+r*dr, pos[1]+c*dc, vs[r,c]) + +def fill_value(wb, infos, para): + for worksheet in infos: + ws, info = worksheet + for pos, key in info: + if not key[1] in para: continue + if key[0] in ('str', 'int', 'float', 'bool', 'txt', 'list', 'date'): + wb[ws].cell(pos[0], pos[1], para[key[1]]) + if key[0] == 'img': + add_image(wb, ws, pos, key, para[key[1]]) + if key[0] == 'tab': + add_table(wb, ws, pos, key, para[key[1]]) + +def repair(wb): + for ws in wb.worksheets: + for cr in ws.merged_cells: + ltc = ws.cell(cr.min_row, cr.min_col) + vb, hb = ltc.border.left, ltc.border.top + for r in range(cr.min_row, cr.max_row+1): + for c in range(cr.min_col, cr.max_col+1): + cur = copy(ws.cell(r, c).border) + cur.left, cur.right = copy(vb), copy(vb) + cur.top, cur.bottom = copy(hb), copy(hb) + ws.cell(r, c).border = cur + +if __name__ == '__main__': + rst = pd.read_csv('rst.csv') + img = np.arange(10000, dtype=np.uint8).reshape((100,100)) + data = {'Sample_ID':'Coins-0001', 'Operator_Name':'YX Dragon', 'Date':'2019-02-05', + 'Record':rst, 'Original_Image':img, 'Mask_Image':img} + + wb = pyxl.load_workbook('Coins Report.xlsx',) + repair(wb) + ws = wb.active + + + infos = parse(wb) + print(infos) + fill_value(wb, infos, data) + wb.save('new.xlsx') + diff --git a/sciwx/widgets/propertygrid.py b/sciwx/widgets/propertygrid.py new file mode 100644 index 00000000..60eb5f60 --- /dev/null +++ b/sciwx/widgets/propertygrid.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +import sys, time, math, os.path + +import wx, wx.adv +import wx.propgrid as wxpg + +from six import exec_ +_ = wx.GetTranslation + +data = [('Sheet1', [((4, 5), ['str', 'Sample_ID', '5', "image's name"]), ((4, 17), ['str', 'Operator_Name', None, 'your name']), ((4, 30), ['date', 'Date', None, 'today']), ((10, 1), ['img', 'Original_Image', '[8.16,5.76,0.9,0]', 'the original image']), ((10, 20), ['img', 'Mask_Image', '[8.16,5.76,0.9,0]', '']), ((28, 1), ['tab', 'Record', '[1,3,0,0]', 'records'])]), ('Sheet2', [((0,0), ['list', 'a', '[1,2,345]', 'nothing'])]), ('Sheet3', [])] +key = {'Sample_ID': ['str', 'Sample_ID', '5', "image's name"], 'Operator_Name': ['str', 'Operator_Name', None, 'your name'], 'Date': ['date', 'Date', None, 'today'], 'Original_Image': ['img', 'Original_Image', '[8.16,5.76,0.9,0]', 'the original image'], 'Mask_Image': ['img', 'Mask_Image', '[8.16,5.76,0.9,0]', ''], 'Record': ['tab', 'Record', '[1,3,0,0]', 'records']} + +class GridDialog( wx.Dialog ): + + def __init__( self, parent, title, tree, key): + wx.Dialog.__init__ (self, parent, -1, title, style = wx.DEFAULT_DIALOG_STYLE, size = wx.Size((300, 480))) + # wx.Panel.__init__(self, parent, wx.ID_ANY) + self.app = parent + + self.panel = panel = wx.Panel(self, wx.ID_ANY) + topsizer = wx.BoxSizer(wx.VERTICAL) + + # Difference between using PropertyGridManager vs PropertyGrid is that + # the manager supports multiple pages and a description box. + self.pg = pg = wxpg.PropertyGridManager(panel, + style=wxpg.PG_SPLITTER_AUTO_CENTER) + + # Show help as tooltips + pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS) + + pg.Bind( wxpg.EVT_PG_CHANGED, self.OnPropGridChange ) + pg.Bind( wxpg.EVT_PG_SELECTED, self.OnPropGridSelect ) + + pg.AddPage('Page 1') + self.key = key + for page in tree: + pg.Append(wxpg.PropertyCategory(page[0])) + for item in page[1]: + v = item[1] + if v[0] == 'int': pg.Append( wxpg.IntProperty(v[1], value=int(v[2]) or 0)) + if v[0] == 'float': pg.Append( wxpg.FloatProperty(v[1], value=float(v[2]) or 0)) + if v[0] == 'str': pg.Append( wxpg.StringProperty(v[1], value=v[2] or '')) + if v[0] == 'txt': pg.Append( wxpg.LongStringProperty(v[1], value=v[2] or '')) + if v[0] == 'bool': pg.Append( wxpg.BoolProperty(v[1])) + if v[0] == 'date': pg.Append( wxpg.DateProperty(v[1], value=wx.DateTime.Now())) + if v[0] == 'list': pg.Append( wxpg.EnumProperty(v[1], v[1], [i.strip() for i in v[2][1:-1].split(',')])) + if v[0] == 'img': pg.Append( wxpg.EnumProperty(v[1], v[1], self.app.img_names())) + if v[0] == 'tab': pg.Append( wxpg.EnumProperty(v[1], v[1], self.app.table_names())) + + topsizer.Add(pg, 1, wx.EXPAND) + self.txt_info = wx.TextCtrl( self, wx.ID_ANY, 'information', wx.DefaultPosition, wx.Size(80, 80), wx.TE_MULTILINE|wx.TRANSPARENT_WINDOW ) + topsizer.Add(self.txt_info, 0, wx.EXPAND|wx.ALL, 0) + rowsizer = wx.BoxSizer(wx.HORIZONTAL) + but = wx.Button(panel, wx.ID_OK, "OK") + rowsizer.Add(but,1) + #but.Bind( wx.EVT_BUTTON, self.OnGetPropertyValues ) + but = wx.Button(panel, wx.ID_CANCEL,"Cancel") + rowsizer.Add(but,1) + topsizer.Add(rowsizer,0,wx.EXPAND) + + panel.SetSizer(topsizer) + topsizer.SetSizeHints(panel) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(panel, 1, wx.EXPAND) + self.SetSizer(sizer) + self.SetAutoLayout(True) + + def GetValue(self): + return self.pg.GetPropertyValues(as_strings=True) + + def OnGetPropertyValues(self,event): + para = self.pg.GetPropertyValues(inc_attributes=True) + + def OnPropGridChange(self, event): + p = event.GetProperty() + if p: print('%s changed to "%s"\n' % (p.GetName(),p.GetValueAsString())) + + def OnPropGridSelect(self, event): + p = event.GetProperty() + if p: self.txt_info.SetValue('%s: %s'%(p.GetName(), self.key[p.GetName()][3])) + +if __name__ == '__main__': + app = wx.App(False) + frame = GridDialog(None, 'Property Grid', data, key) + rst = frame.ShowModal() == wx.ID_OK + app.MainLoop() \ No newline at end of file From 817e008f8743cbd0a4f2a6a319144f4755af92a0 Mon Sep 17 00:00:00 2001 From: Xin Bo Qi Date: Tue, 9 Mar 2021 14:59:29 +0800 Subject: [PATCH 7/8] fix bugs in threshold plugin --- imagepy/menus/Process/Threshold/threshold_plgs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imagepy/menus/Process/Threshold/threshold_plgs.py b/imagepy/menus/Process/Threshold/threshold_plgs.py index 2d46189a..063487ea 100644 --- a/imagepy/menus/Process/Threshold/threshold_plgs.py +++ b/imagepy/menus/Process/Threshold/threshold_plgs.py @@ -81,7 +81,7 @@ class Niblack(Filter): (float, 'k', (0, 1), 2, 'offset', '')] def run(self, ips, snap, img, para = None): - if para['size']%2==0: return IPy.alert('size must be Odd') + if para['size']%2==0: return self.app.alert('size must be Odd') img[:] = (snap>threshold_niblack(snap, para['size'], para['k']))*ips.range[1] class Sauvola(Filter): @@ -92,7 +92,7 @@ class Sauvola(Filter): (float, 'k', (0, 1), 2, 'offset', '')] def run(self, ips, snap, img, para = None): - if para['size']%2==0: return IPy.alert('size must be Odd') + if para['size']%2==0: return self.app.alert('size must be Odd') img[:] = (snap>threshold_sauvola(snap, para['size'], para['k']))*ips.range[1] plgs = [SimpleThreshold, Auto, '-', Local, Niblack, Sauvola, '-', Hysteresis] \ No newline at end of file From e13cd7d9b9360939dddbcc199fc49f568774d7bc Mon Sep 17 00:00:00 2001 From: Xin Bo Qi Date: Thu, 18 Mar 2021 17:17:59 +0800 Subject: [PATCH 8/8] fix the toolbar color with wx=4.1.1 --- sciwx/widgets/toolbar.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sciwx/widgets/toolbar.py b/sciwx/widgets/toolbar.py index 993fd7ee..41c84da5 100644 --- a/sciwx/widgets/toolbar.py +++ b/sciwx/widgets/toolbar.py @@ -23,9 +23,10 @@ def make_logo(obj): 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() + # img = bmp.ConvertToImage() + # img.Resize((20,20), (2,2)) + # return img.ConvertToBitmap() + return bmp class ToolBar(wx.Panel): def __init__(self, parent, vertical=False):