pyzemrpc 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. pyzemrpc-1.2.0.data/data/data/pyzemrpc/conf/config.yml +12 -0
  2. pyzemrpc-1.2.0.dist-info/LICENSE +21 -0
  3. pyzemrpc-1.2.0.dist-info/METADATA +124 -0
  4. pyzemrpc-1.2.0.dist-info/RECORD +42 -0
  5. pyzemrpc-1.2.0.dist-info/WHEEL +5 -0
  6. pyzemrpc-1.2.0.dist-info/entry_points.txt +7 -0
  7. pyzemrpc-1.2.0.dist-info/top_level.txt +1 -0
  8. pyzemrpc-1.2.0.dist-info/zip-safe +1 -0
  9. zemrpc/__commit__.py +1 -0
  10. zemrpc/__init__.py +74 -0
  11. zemrpc/__version__.py +10 -0
  12. zemrpc/gui/__init__.py +51 -0
  13. zemrpc/gui/style.qss +15 -0
  14. zemrpc/gui/ui_utils.py +53 -0
  15. zemrpc/gui/widgets/__init__.py +3 -0
  16. zemrpc/gui/widgets/nav_img_viewer.py +180 -0
  17. zemrpc/gui/widgets/zem_img_viewer.py +69 -0
  18. zemrpc/gui/zemrpc_main.py +50 -0
  19. zemrpc/gui/zemrpc_win.py +369 -0
  20. zemrpc/gui/zemrpc_win_ui.py +735 -0
  21. zemrpc/tools/__init__.py +3 -0
  22. zemrpc/tools/virtual_zem/__init__.py +3 -0
  23. zemrpc/tools/virtual_zem/__main__.py +14 -0
  24. zemrpc/tools/virtual_zem/jsonrpc_server.py +73 -0
  25. zemrpc/tools/virtual_zem/main.py +30 -0
  26. zemrpc/tools/virtual_zem/virtual_zem.py +1178 -0
  27. zemrpc/tools/virtual_zem/virtual_zem_mgr.py +147 -0
  28. zemrpc/tools/zemrpc_info.py +81 -0
  29. zemrpc/tools/zemrpc_show_img_meta.py +56 -0
  30. zemrpc/zem/__init__.py +3 -0
  31. zemrpc/zem/conn/__init__.py +3 -0
  32. zemrpc/zem/conn/img_receiver.py +90 -0
  33. zemrpc/zem/conn/protocol.py +789 -0
  34. zemrpc/zem/conn/rpc_protocol.py +116 -0
  35. zemrpc/zem/context.py +25 -0
  36. zemrpc/zem/ip_util.py +187 -0
  37. zemrpc/zem/models.py +363 -0
  38. zemrpc/zem/zem_api.py +372 -0
  39. zemrpc/zem/zem_image_meta.py +294 -0
  40. zemrpc/zem/zem_image_processor.py +126 -0
  41. zemrpc/zem/zem_rpc_device.py +1095 -0
  42. zemrpc/zem/zem_util.py +34 -0
@@ -0,0 +1,12 @@
1
+ pyzemrpc:
2
+ version: 1.0.0
3
+ output_dir: ./data/appout
4
+
5
+ virtual_zem:
6
+ img_dtype: np.uint16
7
+ simu_timeout: 0 # 随机数小于该值模拟超时
8
+ simu_busy: 0 # 随机数小于该值模拟设备忙
9
+ replay_zem:
10
+ enable: False
11
+ # cap_roi_task: D:/tmp/2025/x20250613_diatom/replay_zem-tasks/cap_roi-20250714_120555
12
+ cap_roi_task: E:/days/2025/x20250613_diatom/replay_zem-tasks/cap_roi-20250715_093056
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 ZEPTOOLS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.1
2
+ Name: pyzemrpc
3
+ Version: 1.2.0
4
+ Summary: zeptools zem remote api.
5
+ Home-page: https://gitee.com/zeyou-anhui/pyzemrpc.git
6
+ Author: tribf
7
+ Author-email: tribf@tribf.com
8
+ License: MIT
9
+ Keywords: SEM,Calibration,Utilities
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Operating System :: POSIX
23
+ Classifier: Operating System :: Unix
24
+ Classifier: Operating System :: MacOS
25
+ Requires-Python: >=3.11
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: jgb >=0.2.1
29
+ Requires-Dist: PySide6 >=6.7.2
30
+ Requires-Dist: QtPy >=2.4.1
31
+ Requires-Dist: opencv-contrib-python >=4.10.0.84
32
+ Requires-Dist: opencv-python >=4.10.0.84
33
+ Requires-Dist: tenacity >=8.5.0
34
+ Requires-Dist: loguru >=0.7.2
35
+ Requires-Dist: pydantic >=2.8.2
36
+ Requires-Dist: pyqtgraph >=0.13.7
37
+ Requires-Dist: anytree >=2.12.0
38
+ Requires-Dist: blinker >=1.9.0
39
+ Requires-Dist: requests >=2.32.3
40
+
41
+ ![pyzemrpc Banner](./doc/pyzemrpc_banner.png)
42
+
43
+ # pyzemrpc
44
+
45
+ **ZEM远程控制接口(python版本)**
46
+
47
+ `pyzemrpc` 是安徽泽攸深科(ZEPTOOLS)扫描电镜(SEM)的Python远程控制接口与工具集。它提供了一套Python API,允许开发者通过代码控制SEM设备,以及一个图形用户界面(GUI)用于交互式操作。
48
+
49
+ ---
50
+
51
+ ## ✨ 特性 (Features)
52
+
53
+ * **远程控制**: 支持对ZEM系列扫描电镜的远程操作与监控。
54
+ * **Python API**: 提供友好的Python接口,便于集成到自动化脚本或第三方软件中。
55
+ * **图形界面**: 内置 `pyzemrpc` GUI 工具,开箱即用。
56
+ * **配置灵活**: 基于 YAML 和 数据库 的灵活配置管理。
57
+
58
+ ## 🚀 安装 (Installation)
59
+
60
+ ### 依赖环境
61
+ * Python 3.10+
62
+ * Windows / Linux / MacOS
63
+
64
+ ### 安装步骤
65
+
66
+ 1. 克隆仓库:
67
+ ```bash
68
+ git clone https://gitee.com/zeyou-anhui/pyzemrpc.git
69
+ cd pyzemrpc
70
+ ```
71
+
72
+ 2. 安装依赖并安装包:
73
+ ```bash
74
+ pip install .
75
+ ```
76
+
77
+ 或者使用开发模式安装:
78
+ ```bash
79
+ pip install -e .
80
+ ```
81
+
82
+ 3. 或者使用pypi安装
83
+ ```bash
84
+ pip install pyzemrpc
85
+ ```
86
+
87
+
88
+
89
+ ## 🖥️ 使用 (Usage)
90
+
91
+ ### 启动 GUI
92
+ 安装完成后,可以直接在终端运行以下命令启动图形界面:
93
+ ```bash
94
+ pyzemrpc
95
+ ```
96
+
97
+ ### 命令行工具
98
+ 查看相关信息:
99
+ ```bash
100
+ pyzemrpc_info
101
+ ```
102
+
103
+ ### Python 开发
104
+ 在您的 Python 脚本中导入 `zemrpc`:
105
+
106
+ ```python
107
+ import zemrpc
108
+ from zemrpc import app_cfg
109
+
110
+ # 示例代码 (具体API参考文档)
111
+ print(f"pyzemrpc version: {app_cfg.version}")
112
+ ```
113
+
114
+ ## 📄 文档 (Documentation)
115
+ 更多详细文档和配置说明,请查阅 [doc](./doc) 目录。
116
+
117
+ ## 🤝 贡献 (Contributing)
118
+ 欢迎提交 Issue 或 Pull Request 来改进本项目。
119
+
120
+ ## 📃 许可证 (License)
121
+ 本项目采用 [MIT License](LICENSE) 开源。
122
+
123
+ ---
124
+ Copyright © 2024-2026 ZEPTOOLS
@@ -0,0 +1,42 @@
1
+ pyzemrpc-1.2.0.data/data/data/pyzemrpc/conf/config.yml,sha256=rZcUJMNMb0Yb60UyG_hNj7SLotFvOesjTUI6H_y3yIo,466
2
+ zemrpc/__commit__.py,sha256=HgWFXXnSfqK75E5edkqM1NWaCqEjWyhCAvVOmPIvZHU,55
3
+ zemrpc/__init__.py,sha256=MZKx7AtlnYO53wcdsZUGVawowVWT-EhvvYKkeXDHP_Y,2128
4
+ zemrpc/__version__.py,sha256=Hjc7T6q7mvv6SehLwZF6goeyQ-ZIsaJ2eMb0eYzZgA8,361
5
+ zemrpc/gui/__init__.py,sha256=i3OYuJ6cY6JR8Helt-KdJtN7ZOxM4hZaS_Li_-AOHDY,2112
6
+ zemrpc/gui/style.qss,sha256=jIKkAuO2ZuIEk23-spCT_wth-nfcJaoMkWXDD_HmFfM,250
7
+ zemrpc/gui/ui_utils.py,sha256=wmacUOny7YXcfpSTdpoTBoevVdlsEwoa1Da7fILdHH0,1325
8
+ zemrpc/gui/zemrpc_main.py,sha256=qrH51YLnCkSGeopKAw3PyAAYV-lnFw5g2souEk4_NDU,1263
9
+ zemrpc/gui/zemrpc_win.py,sha256=j80_1XOVVbqqWzXTPfTQPooyrQeFJvd7kXSveUq_avs,14688
10
+ zemrpc/gui/zemrpc_win_ui.py,sha256=mXimvUo2kJlE9bPIt4YD-_v1TmKcWt7_zBWORfDeZLc,34597
11
+ zemrpc/gui/widgets/__init__.py,sha256=V-xn7sDg8Mv03JYDkrF2uq8nn4VUyRl8ia4xtf8hFfs,74
12
+ zemrpc/gui/widgets/nav_img_viewer.py,sha256=peFxod7F0pVt0qFhjrAzIxbMpumjdVG9-8Xl4PqvNHk,7483
13
+ zemrpc/gui/widgets/zem_img_viewer.py,sha256=nA55DEF2w53g3EuPj3joTgw3M5m1-hiJ7qoai8H41n0,2544
14
+ zemrpc/tools/__init__.py,sha256=V-xn7sDg8Mv03JYDkrF2uq8nn4VUyRl8ia4xtf8hFfs,74
15
+ zemrpc/tools/zemrpc_info.py,sha256=GDURnPhySVuMjI0TFgSAVFhTzQPlCVb7XqUzK_hy-WQ,2126
16
+ zemrpc/tools/zemrpc_show_img_meta.py,sha256=nJr3ICysVGR8qOBuV_ExNZMn6wi1WiEIdj-bd1QiyGM,2250
17
+ zemrpc/tools/virtual_zem/__init__.py,sha256=V-xn7sDg8Mv03JYDkrF2uq8nn4VUyRl8ia4xtf8hFfs,74
18
+ zemrpc/tools/virtual_zem/__main__.py,sha256=kRcQNwspF_jB7seZ8tOdR-zZ2raz-1C8H4I6TY_uMpg,267
19
+ zemrpc/tools/virtual_zem/jsonrpc_server.py,sha256=0NDyfqmK7MqHfvmbtfZbrRfovkqGLuDxutcauLa0eYE,1772
20
+ zemrpc/tools/virtual_zem/main.py,sha256=1mcCHPpoEa_dp9p1ov8FIRKChISoDXWF5G7G4FAlg4E,862
21
+ zemrpc/tools/virtual_zem/virtual_zem.py,sha256=iTJPuCqRPNUm-baFpvKmpBiF2HJ4SFe2n9C5i84GOHk,42702
22
+ zemrpc/tools/virtual_zem/virtual_zem_mgr.py,sha256=O4Js0twqwC_ppaQoQbjm97rg2bt6xrPoCDLhyU3in6Q,4876
23
+ zemrpc/zem/__init__.py,sha256=V-xn7sDg8Mv03JYDkrF2uq8nn4VUyRl8ia4xtf8hFfs,74
24
+ zemrpc/zem/context.py,sha256=fNOdidGzNxmHMvo1RcKVpHLlAfQxruXNGgc4XUYSQWQ,533
25
+ zemrpc/zem/ip_util.py,sha256=08pnL-G31XMg9iIoAiJwrE9Wa4_IQt-8HnWcv08xT44,5778
26
+ zemrpc/zem/models.py,sha256=XC5hezD6fbsqTO3albvoKxoRrKCqctSCzh4Gsdo430Q,10207
27
+ zemrpc/zem/zem_api.py,sha256=hVpjJnBjbk7KV8UaqiHEdcaFzNnXnIsc5yLKgXoTrTo,13628
28
+ zemrpc/zem/zem_image_meta.py,sha256=OhMakNt2V8FuhV3Ln-Wu_IrHaEo9g4vF4-RajfO57xY,10000
29
+ zemrpc/zem/zem_image_processor.py,sha256=iMl2Sm3lNh03LfbSV5ZI8LSUGxyPdHLJdG-QHMJmY6s,3566
30
+ zemrpc/zem/zem_rpc_device.py,sha256=_P40oqxsKbk4CbM-xXuSVXgxhjImqz9nDG508t0fDy0,36611
31
+ zemrpc/zem/zem_util.py,sha256=FYLMI4iv8nBLlAWK8Mh3ORpcJyKMYggSp3MX_-QZ_cs,962
32
+ zemrpc/zem/conn/__init__.py,sha256=V-xn7sDg8Mv03JYDkrF2uq8nn4VUyRl8ia4xtf8hFfs,74
33
+ zemrpc/zem/conn/img_receiver.py,sha256=vklffj7PSMO7dCCMMic0FEoj1TA4-JUMNEhoYQtgLsc,2970
34
+ zemrpc/zem/conn/protocol.py,sha256=lnKBhsj90c6f_Thkq2Q5QudQk9qK44fHO_-5vjs-cdI,33243
35
+ zemrpc/zem/conn/rpc_protocol.py,sha256=eFVetVBgglhiz_AXuiYuQx7Pisc3dNwNW-CUfBVvKco,3064
36
+ pyzemrpc-1.2.0.dist-info/LICENSE,sha256=SPviBjCp6i_RvxZabbX8HJvHY1eDYhuqVrZBnKG60Vw,1091
37
+ pyzemrpc-1.2.0.dist-info/METADATA,sha256=VQ5C0GFNvg-RvlDPekr6Yx36cRkiZOch3cwyApPDG98,3428
38
+ pyzemrpc-1.2.0.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
39
+ pyzemrpc-1.2.0.dist-info/entry_points.txt,sha256=mLV5blOYbGlLtniosy4TeOc-qsC2NMJfwg0yLLKLwRc,240
40
+ pyzemrpc-1.2.0.dist-info/top_level.txt,sha256=KsBHoGQ_4r9fbPw7stFSUQQ5F8-7Wb5psfq2pXNEflo,7
41
+ pyzemrpc-1.2.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
42
+ pyzemrpc-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,7 @@
1
+ [console_scripts]
2
+ pyzemrpc_info = zemrpc.tools.zemrpc_info:main
3
+ pyzemrpc_show_img_meta = zemrpc.tools.zemrpc_show_img_meta:main
4
+ pyzemrpc_virtual_zem = zemrpc.tools.virtual_zem.main:main
5
+
6
+ [gui_scripts]
7
+ pyzemrpc = zemrpc.gui.zemrpc_main:main
@@ -0,0 +1 @@
1
+ zemrpc
@@ -0,0 +1 @@
1
+
zemrpc/__commit__.py ADDED
@@ -0,0 +1 @@
1
+ GIT_INFO = "22d6e40ee5df4f9b9ca3551ef348dce6b230e808"
zemrpc/__init__.py ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # tr!bf tribf@tribf.com
4
+
5
+ import sys
6
+ import addict
7
+ from pathlib import Path
8
+
9
+ from jgb.util import app_util, db_util, addict_util, file_util
10
+
11
+ from zemrpc.__commit__ import GIT_INFO
12
+ from zemrpc.__version__ import __title__, __version__
13
+
14
+
15
+ class CfgDb:
16
+ def __init__(self, app_name, cfg: addict.Dict, **kwargs):
17
+ self.cfg = cfg
18
+ if (db_file := kwargs.get("db_file", None)) is None:
19
+ db_file = app_util.resolve_app_home(app_name, None, None) / "cfg.db"
20
+ self.kv_db = db_util.KvDataBase(db_file)
21
+ self.update()
22
+
23
+ def update(self):
24
+ for k in self.kv_db.db.keys():
25
+ if not k.startswith("cfg."):
26
+ continue
27
+ addict_util.update_addict(self.cfg, k[4:], self.kv_db.get_pkl(k))
28
+
29
+
30
+ class AppConfig(app_util.BaseAppConfig):
31
+ def __init__(self):
32
+ src_home = Path(__file__).resolve().parents[1]
33
+ super().__init__(src_home, app_name=__title__, pkg_name="zemrpc", author="Zeptools", version=__version__)
34
+
35
+ def after_init(self):
36
+ if cfg_db := self.cfg.get("cfg_db", None):
37
+ db_file = self.app_home / cfg_db
38
+ else:
39
+ db_file = None
40
+
41
+ self.cfg_db = CfgDb(self.app_name, self.cfg, db_file=db_file)
42
+
43
+ def init_cfg(self):
44
+ assert self.run_type != app_util.AppRunType.DEV
45
+ in_dir = Path(sys.prefix) / f"data/{__title__}"
46
+ files = [
47
+ (in_dir / "conf/config.yml", self.app_home / "conf/config.yml"),
48
+ ]
49
+
50
+ for src, dst in files:
51
+ if dst.exists():
52
+ continue
53
+
54
+ print(f"copy {src} -> {dst}")
55
+ file_util.copy(src, dst)
56
+
57
+ @property
58
+ def outdir(self) -> Path:
59
+ return self.app_home / self.cfg.output_dir
60
+
61
+ @property
62
+ def log_dir(self) -> Path:
63
+ return self.outdir / "logs"
64
+
65
+ @property
66
+ def about(self):
67
+ s = [
68
+ f"<p>版本: {self.version}</p>",
69
+ f"<p>哈希: {GIT_INFO}</p>",
70
+ ]
71
+ return "".join(s)
72
+
73
+
74
+ app_cfg = AppConfig()
zemrpc/__version__.py ADDED
@@ -0,0 +1,10 @@
1
+ __title__ = "pyzemrpc"
2
+ __version__ = "1.2.0"
3
+ __description__ = "zeptools zem remote api."
4
+ __keywords__ = "SEM,Calibration,Utilities"
5
+ __url__ = "https://gitee.com/zeyou-anhui/pyzemrpc.git"
6
+ __author__ = "tribf"
7
+ __author_email__ = "tribf@tribf.com"
8
+ __license__ = "MIT"
9
+ __copyright__ = "Copyright 2024-2026 ZEPTOOLS"
10
+ __cake__ = "\u2728 \U0001f370 \u2728"
zemrpc/gui/__init__.py ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # tr!bf tribf@tribf.com
4
+ import sys
5
+ from loguru import logger
6
+ from qtpy import QtCore, QtWidgets
7
+ import traceback
8
+
9
+
10
+ def show_exception_box(log_msg):
11
+ """Checks if a QApplication instance is available and shows a messagebox with the exception message.
12
+ If unavailable (non-console application), log an additional notice.
13
+ """
14
+ if QtWidgets.QApplication.instance() is not None:
15
+ errorbox = QtWidgets.QMessageBox()
16
+ errorbox.setText("Oops. An unexpected error occured:\n{0}".format(log_msg))
17
+ errorbox.exec_()
18
+ else:
19
+ logger.debug("No QApplication instance available.")
20
+
21
+
22
+ class UncaughtHook(QtCore.QObject):
23
+ _exception_caught = QtCore.Signal(object)
24
+
25
+ def __init__(self, *args, **kwargs):
26
+ super(UncaughtHook, self).__init__(*args, **kwargs)
27
+ sys.excepthook = self.exception_hook
28
+ # connect signal to execute the message box function always on main thread
29
+ self._exception_caught.connect(show_exception_box)
30
+
31
+ def exception_hook(self, exc_type, exc_value, exc_traceback):
32
+ """Function handling uncaught exceptions.
33
+ It is triggered each time an uncaught exception occurs.
34
+ """
35
+ if issubclass(exc_type, KeyboardInterrupt):
36
+ # ignore keyboard interrupt to support console applications
37
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
38
+ else:
39
+ log_msg = "\n".join(["".join(traceback.format_tb(exc_traceback)), "{0}: {1}".format(exc_type.__name__, exc_value)])
40
+
41
+ exc_info = (exc_type, exc_value, exc_traceback)
42
+ # log.critical("Uncaught exception:\n {0}".format(log_msg), exc_info=exc_info)
43
+ # logger.warning("Uncaught exception:\n {0}".format(log_msg), exc_info=exc_info)
44
+ logger.exception("Uncaught exception:\n {0}".format(log_msg))
45
+
46
+ # trigger message box show
47
+ self._exception_caught.emit(log_msg)
48
+
49
+
50
+ # create a global instance of our class to register the hook
51
+ qt_exception_hook = UncaughtHook()
zemrpc/gui/style.qss ADDED
@@ -0,0 +1,15 @@
1
+ QMenu {
2
+ color: #111;
3
+ background-color: #f4f4f4;
4
+ border: 1px solid #c7c7c7;
5
+ }
6
+
7
+ QMenu::item {
8
+ padding: 4px 16px;
9
+ background: transparent;
10
+ }
11
+
12
+ QMenu::item:selected {
13
+ background-color: #2d6cdf;
14
+ color: #ffffff;
15
+ }
zemrpc/gui/ui_utils.py ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # tr!bf tribf@tribf.com
4
+
5
+ from typing import Dict
6
+
7
+ cbox_style = "QComboBox QAbstractItemView::item { min-height: 15px; min-width: 50px; }QListView::item:selected { color: black; background-color: lightgray}"
8
+
9
+
10
+ def get_api_cmds():
11
+ from zemrpc.zem.conn.protocol import EnumCmd
12
+
13
+ cbox_cmds = {} # type: Dict[str, EnumCmd]
14
+ for c in EnumCmd:
15
+ if c in {EnumCmd.InvalidCmd, EnumCmd.Handshake, EnumCmd.HandshakeResp}:
16
+ continue
17
+ cbox_cmds[c.name_cn] = c
18
+ return cbox_cmds
19
+
20
+
21
+ def parse_mag_str(mag_str):
22
+ tbl = {"k": 1000, "w": 10000, "x": 1}
23
+ return int(mag_str[:-1]) * tbl[mag_str[-1]]
24
+
25
+
26
+ def stage_pos_str(x: float, y: float):
27
+ return f"{x:.3f},{y:.3f}"
28
+
29
+
30
+ def parse_stage_pos(pos_str: str):
31
+ return list(map(float, pos_str.replace(",", ",").split(",")))
32
+
33
+
34
+ def get_snap_size():
35
+ from zemrpc.zem.models import EnumImageSize
36
+
37
+ sizes = {}
38
+ for s in EnumImageSize:
39
+ if s == EnumImageSize.Unk:
40
+ continue
41
+ sizes[s.name_cn] = s
42
+ return sizes
43
+
44
+
45
+ def get_beam_size():
46
+ from zemrpc.zem.models import EnumBeamSize
47
+
48
+ sizes = []
49
+ for s in EnumBeamSize:
50
+ if s == EnumBeamSize.Unk:
51
+ continue
52
+ sizes.append(s)
53
+ return sizes
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # tr!bf tribf@tribf.com
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # tr!bf tribf@tribf.com
4
+
5
+ import math
6
+ import functools
7
+ import numpy as np
8
+ from qtpy.QtGui import QCursor, QIcon
9
+ from qtpy.QtWidgets import QMenu, QToolButton
10
+ from matplotlib.patches import Circle, Rectangle
11
+ from matplotlib.backend_bases import MouseButton, MouseEvent, PickEvent
12
+ from loguru import logger
13
+
14
+ from jgb.gui.qt import qt_util
15
+ from jgb.gui.qt.widgets.mpl_qpanel import QMplPanelWithToolbar
16
+
17
+ from zemrpc.zem.models import FieldWithLoc, NavImage
18
+ from zemrpc.zem.zem_api import zem_api
19
+
20
+
21
+ class NavCircleMarker:
22
+ def __init__(self):
23
+ self.gid_nav_circle_mark = "nav_circle_mark"
24
+ self.gid_nav_circle_mark_c = "nav_circle_mark_c"
25
+ self.gid_nav_circle_mark_r = "nav_circle_mark_r"
26
+ self.center = (0, 0)
27
+ self.radius = 500
28
+
29
+
30
+ class NavImgViewer:
31
+ def __init__(self, panel):
32
+ self.widget, self.layout = qt_util.init_panel_with_widget(panel, QMplPanelWithToolbar)
33
+ self.canvas = self.widget.plot_canvas
34
+
35
+ self.btn = QToolButton(self.widget.navi_toolbar)
36
+ self.btn.setIcon(QIcon(":/pyzemrpc/images/clear.png"))
37
+ self.btn.clicked.connect(self._slot_clear_mark)
38
+ self.widget.navi_toolbar.addWidget(self.btn)
39
+
40
+ self._picked_gid = None # type: str
41
+ self.gid_nav_img = "nav_img"
42
+ self.gid_nav_cmarker = "nav_center_marker"
43
+
44
+ self.gid_stage_pos_xy = "stage_pos_xy"
45
+ self.gid_stage_rect = "stage_pos_scan_field"
46
+
47
+ self.gid_nav_poit_mark = "nav_poit_mark-"
48
+ self.nav_poit_marks = []
49
+
50
+ self.nav_circle_marker = NavCircleMarker()
51
+
52
+ self.canvas.init_ss(self)
53
+
54
+ def update_nav_img(self, nav_img: NavImage):
55
+ if nav_img is None or nav_img.img is None:
56
+ self.canvas.clear_ui(self.gid_nav_img)
57
+ self.canvas.clear_ui("stage_pos")
58
+ self.canvas.draw()
59
+ return
60
+
61
+ x_min, y_min, x_max, y_max = nav_img.bounds
62
+ self.canvas.update_ui(self.gid_nav_img, self.canvas.axes.imshow(nav_img.img, extent=(x_max, x_min, y_max, y_min)))
63
+ self.canvas.set_lims((x_max, x_min), (y_max, y_min))
64
+ self.canvas.update_ui(self.gid_nav_cmarker, self.canvas.axes.scatter(0, 0, c="green", marker="+", zorder=10))
65
+ self.canvas.draw()
66
+
67
+ self.widget.navi_toolbar.update()
68
+ self.widget.navi_toolbar.push_current()
69
+
70
+ def on_click(self, event: MouseEvent):
71
+ if event.xdata is None or event.ydata is None:
72
+ return
73
+
74
+ if not self.canvas.has_ui(self.gid_nav_img):
75
+ return
76
+
77
+ if event.button == MouseButton.RIGHT:
78
+ self._popup_menu(
79
+ [
80
+ ("移动至此", functools.partial(self._slot_goto, (event.xdata, event.ydata))),
81
+ ("添加标记", functools.partial(self._slot_mark, (event.xdata, event.ydata))),
82
+ ]
83
+ )
84
+
85
+ def _popup_menu(self, items):
86
+ menu = QMenu(self.canvas)
87
+ for label, callback in items:
88
+ action = menu.addAction(label)
89
+ action.triggered.connect(callback)
90
+ menu.exec(QCursor.pos())
91
+
92
+ def _slot_goto(self, xy, checked: bool = False):
93
+ zem_api.stage_xy_goto(xy)
94
+
95
+ def _slot_mark(self, xy, checked: bool = False):
96
+ gid = f"{self.gid_nav_poit_mark}{len(self.nav_poit_marks)}"
97
+ self.canvas.update_ui(gid, self.canvas.axes.scatter(xy[0], xy[1], s=20, marker="*", c="blue", picker=True, zorder=10))
98
+ self.nav_poit_marks.append(xy)
99
+ self.canvas.draw()
100
+
101
+ def _update_nav_point_mark(self, gid: str, x, y):
102
+ idx = int(gid.split("-")[-1])
103
+ self.nav_poit_marks[idx] = (x, y)
104
+ self.canvas.ui_items[gid].set_offsets(np.array((x, y), dtype=np.float32))
105
+ self.canvas.draw()
106
+
107
+ def _slot_clear_mark(self, checked: bool = False):
108
+ self.canvas.clear_uis(self.gid_nav_poit_mark)
109
+ self.nav_poit_marks.clear()
110
+
111
+ self.canvas.clear_uis(self.nav_circle_marker.gid_nav_circle_mark)
112
+ self.canvas.draw()
113
+
114
+ def on_release(self, event: MouseEvent):
115
+ self._picked_gid = None
116
+
117
+ def on_pick(self, event: PickEvent):
118
+ if event.mouseevent.button != 1:
119
+ return
120
+ gid = event.artist.get_gid()
121
+ logger.debug("pick: index={} gid={}".format(event.ind, gid))
122
+ self._picked_gid = gid
123
+
124
+ def on_move(self, event: MouseEvent):
125
+ if event.xdata is None or event.ydata is None or self._picked_gid is None:
126
+ return
127
+
128
+ x, y = event.xdata, event.ydata
129
+
130
+ if self._picked_gid.startswith(self.gid_nav_poit_mark):
131
+ self._update_nav_point_mark(self._picked_gid, x, y)
132
+
133
+ match self._picked_gid:
134
+ case self.nav_circle_marker.gid_nav_circle_mark_c:
135
+ self._update_nav_circle_mark_c(x, y)
136
+ case self.nav_circle_marker.gid_nav_circle_mark_r:
137
+ self._update_nav_circle_mark_r(x, y)
138
+
139
+ def update_field_and_loc(self, field_with_loc: FieldWithLoc):
140
+ x, y = field_with_loc.pos.x, field_with_loc.pos.y
141
+ scan_um = field_with_loc.scan_info.scan_um
142
+
143
+ if self.canvas.has_ui(self.gid_stage_pos_xy):
144
+ w_pos = self.canvas.ui_items[self.gid_stage_pos_xy]
145
+ w_pos.set_offsets((x, y))
146
+
147
+ w_rect = self.canvas.ui_items[self.gid_stage_rect] # type: Rectangle
148
+ w_rect.xy = (x - scan_um / 2, y - scan_um / 2)
149
+ w_rect.set_width(scan_um)
150
+ w_rect.set_height(scan_um)
151
+ else:
152
+ rect = Rectangle((x - scan_um / 2, y - scan_um / 2), scan_um, scan_um, facecolor="None", edgecolor="red", linewidth=1, zorder=10)
153
+ self.canvas.update_ui(self.gid_stage_rect, rect)
154
+ self.canvas.update_ui(self.gid_stage_pos_xy, self.canvas.axes.scatter(x, y, c="green", marker="+", zorder=10))
155
+ self.canvas.draw()
156
+
157
+ def add_circle_mark(self, center, radius):
158
+ ncm = self.nav_circle_marker
159
+ ncm.center = center
160
+ ncm.radius = radius
161
+ self.canvas.update_ui(ncm.gid_nav_circle_mark, Circle(center, radius=radius, facecolor="None", edgecolor="blue", label="circle roi", linestyle="--", linewidth=2, zorder=10))
162
+ self.canvas.update_ui(ncm.gid_nav_circle_mark_c, self.canvas.axes.scatter(center[0], center[1], s=20, marker="+", c="blue", picker=True, zorder=10))
163
+ self.canvas.update_ui(ncm.gid_nav_circle_mark_r, self.canvas.axes.scatter(center[0] + radius, center[1], s=20, marker="x", c="red", picker=True, zorder=10))
164
+ self.canvas.draw()
165
+
166
+ def _update_nav_circle_mark_c(self, x, y):
167
+ self.nav_circle_marker.center = (x, y)
168
+ self._update_nav_circle_mark()
169
+
170
+ def _update_nav_circle_mark_r(self, x, y):
171
+ self.nav_circle_marker.radius = math.sqrt((x - self.nav_circle_marker.center[0]) ** 2 + (y - self.nav_circle_marker.center[1]) ** 2)
172
+ self._update_nav_circle_mark()
173
+
174
+ def _update_nav_circle_mark(self):
175
+ ncm = self.nav_circle_marker
176
+ self.canvas.ui_items[ncm.gid_nav_circle_mark].center = ncm.center
177
+ self.canvas.ui_items[ncm.gid_nav_circle_mark].radius = ncm.radius
178
+ self.canvas.ui_items[ncm.gid_nav_circle_mark_c].set_offsets(ncm.center)
179
+ self.canvas.ui_items[ncm.gid_nav_circle_mark_r].set_offsets((ncm.center[0] + ncm.radius, ncm.center[1]))
180
+ self.canvas.draw()