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.
- pyzemrpc-1.2.0.data/data/data/pyzemrpc/conf/config.yml +12 -0
- pyzemrpc-1.2.0.dist-info/LICENSE +21 -0
- pyzemrpc-1.2.0.dist-info/METADATA +124 -0
- pyzemrpc-1.2.0.dist-info/RECORD +42 -0
- pyzemrpc-1.2.0.dist-info/WHEEL +5 -0
- pyzemrpc-1.2.0.dist-info/entry_points.txt +7 -0
- pyzemrpc-1.2.0.dist-info/top_level.txt +1 -0
- pyzemrpc-1.2.0.dist-info/zip-safe +1 -0
- zemrpc/__commit__.py +1 -0
- zemrpc/__init__.py +74 -0
- zemrpc/__version__.py +10 -0
- zemrpc/gui/__init__.py +51 -0
- zemrpc/gui/style.qss +15 -0
- zemrpc/gui/ui_utils.py +53 -0
- zemrpc/gui/widgets/__init__.py +3 -0
- zemrpc/gui/widgets/nav_img_viewer.py +180 -0
- zemrpc/gui/widgets/zem_img_viewer.py +69 -0
- zemrpc/gui/zemrpc_main.py +50 -0
- zemrpc/gui/zemrpc_win.py +369 -0
- zemrpc/gui/zemrpc_win_ui.py +735 -0
- zemrpc/tools/__init__.py +3 -0
- zemrpc/tools/virtual_zem/__init__.py +3 -0
- zemrpc/tools/virtual_zem/__main__.py +14 -0
- zemrpc/tools/virtual_zem/jsonrpc_server.py +73 -0
- zemrpc/tools/virtual_zem/main.py +30 -0
- zemrpc/tools/virtual_zem/virtual_zem.py +1178 -0
- zemrpc/tools/virtual_zem/virtual_zem_mgr.py +147 -0
- zemrpc/tools/zemrpc_info.py +81 -0
- zemrpc/tools/zemrpc_show_img_meta.py +56 -0
- zemrpc/zem/__init__.py +3 -0
- zemrpc/zem/conn/__init__.py +3 -0
- zemrpc/zem/conn/img_receiver.py +90 -0
- zemrpc/zem/conn/protocol.py +789 -0
- zemrpc/zem/conn/rpc_protocol.py +116 -0
- zemrpc/zem/context.py +25 -0
- zemrpc/zem/ip_util.py +187 -0
- zemrpc/zem/models.py +363 -0
- zemrpc/zem/zem_api.py +372 -0
- zemrpc/zem/zem_image_meta.py +294 -0
- zemrpc/zem/zem_image_processor.py +126 -0
- zemrpc/zem/zem_rpc_device.py +1095 -0
- 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
|
+

|
|
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 @@
|
|
|
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
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,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()
|