openfocus 1.28__tar.gz
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.
- openfocus-1.28/LICENSE +21 -0
- openfocus-1.28/PKG-INFO +210 -0
- openfocus-1.28/README.md +177 -0
- openfocus-1.28/constants.py +43 -0
- openfocus-1.28/core/__init__.py +30 -0
- openfocus-1.28/core/app.py +162 -0
- openfocus-1.28/core/cli.py +306 -0
- openfocus-1.28/core/image_loader.py +500 -0
- openfocus-1.28/core/models/__init__.py +18 -0
- openfocus-1.28/core/models/stackmffv4_network.py +630 -0
- openfocus-1.28/core/multi_focus_fusion.py +955 -0
- openfocus-1.28/core/registration.py +1152 -0
- openfocus-1.28/core/workers.py +894 -0
- openfocus-1.28/fusion_methods/__init__.py +14 -0
- openfocus-1.28/fusion_methods/dct.py +220 -0
- openfocus-1.28/fusion_methods/dtcwt.py +211 -0
- openfocus-1.28/fusion_methods/gff.py +264 -0
- openfocus-1.28/fusion_methods/gfg_fgf.py +362 -0
- openfocus-1.28/fusion_methods/stackmffv4.py +288 -0
- openfocus-1.28/openfocus.egg-info/PKG-INFO +210 -0
- openfocus-1.28/openfocus.egg-info/SOURCES.txt +49 -0
- openfocus-1.28/openfocus.egg-info/dependency_links.txt +1 -0
- openfocus-1.28/openfocus.egg-info/entry_points.txt +2 -0
- openfocus-1.28/openfocus.egg-info/requires.txt +12 -0
- openfocus-1.28/openfocus.egg-info/top_level.txt +5 -0
- openfocus-1.28/pyproject.toml +53 -0
- openfocus-1.28/setup.cfg +4 -0
- openfocus-1.28/tests/test_16bit_and_cache.py +311 -0
- openfocus-1.28/tests/test_cli.py +130 -0
- openfocus-1.28/tests/test_fusion_methods.py +207 -0
- openfocus-1.28/tests/test_gui_units.py +738 -0
- openfocus-1.28/tests/test_i18n_placeholders.py +28 -0
- openfocus-1.28/tests/test_label_range.py +46 -0
- openfocus-1.28/tests/test_project_file.py +149 -0
- openfocus-1.28/tests/test_registration.py +111 -0
- openfocus-1.28/tests/test_stackmffv4.py +28 -0
- openfocus-1.28/tests/test_updater.py +287 -0
- openfocus-1.28/tests/test_v116_units.py +107 -0
- openfocus-1.28/utils/__init__.py +81 -0
- openfocus-1.28/utils/align_cache.py +102 -0
- openfocus-1.28/utils/image_utils.py +176 -0
- openfocus-1.28/utils/logging_setup.py +63 -0
- openfocus-1.28/utils/platform_utils.py +108 -0
- openfocus-1.28/utils/project_file.py +220 -0
- openfocus-1.28/utils/self_update.py +290 -0
- openfocus-1.28/utils/settings_store.py +246 -0
- openfocus-1.28/utils/ui_utils.py +151 -0
- openfocus-1.28/utils/updater.py +177 -0
- openfocus-1.28/utils/validators.py +224 -0
- openfocus-1.28/weights/__init__.py +1 -0
- openfocus-1.28/weights/stackmffv4.pth +0 -0
openfocus-1.28/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 OpenFocus Contributors
|
|
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.
|
openfocus-1.28/PKG-INFO
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openfocus
|
|
3
|
+
Version: 1.28
|
|
4
|
+
Summary: Multi-focus image fusion from the command line: registration (ECC / homography) plus five fusion algorithms in one pip-installable tool
|
|
5
|
+
Author-email: Xinzhe Xie <xiexinzheyyy@163.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Xinzhe99/OpenFocus
|
|
8
|
+
Project-URL: Documentation, https://github.com/Xinzhe99/OpenFocus#command-line-usage
|
|
9
|
+
Project-URL: Changelog, https://github.com/Xinzhe99/OpenFocus/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: image-fusion,focus-stacking,multifocus,microscopy,photography
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numpy
|
|
23
|
+
Requires-Dist: opencv-python-headless
|
|
24
|
+
Requires-Dist: scipy
|
|
25
|
+
Requires-Dist: dtcwt
|
|
26
|
+
Requires-Dist: imageio
|
|
27
|
+
Provides-Extra: ai
|
|
28
|
+
Requires-Dist: torch; extra == "ai"
|
|
29
|
+
Requires-Dist: torchvision; extra == "ai"
|
|
30
|
+
Provides-Extra: heic
|
|
31
|
+
Requires-Dist: pillow-heif; extra == "heic"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# <img src="assets/OpenFocus.png" alt="OpenFocus Logo" width="120"> OpenFocus
|
|
35
|
+
|
|
36
|
+
OpenFocus delivers focus stacking quality that rivals commercial-grade software, while staying fully open source and easy to extend.
|
|
37
|
+
|
|
38
|
+
<p align="left">
|
|
39
|
+
<a href="https://www.python.org/downloads/release/python-3100/"><img src="https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white" alt="Python 3.10+" /></a>
|
|
40
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?logo=open-source-initiative&logoColor=white" alt="License: MIT" /></a>
|
|
41
|
+
<a href="https://github.com/Xinzhe99/OpenFocus"><img src="https://img.shields.io/badge/GitHub-Repository-181717?logo=github&logoColor=white" alt="GitHub Repository" /></a>
|
|
42
|
+
<a href="https://github.com/Xinzhe99/OpenFocus/releases"><img src="https://img.shields.io/badge/Windows%20%7C%20macOS-Download-0078D7?logo=github&logoColor=white" alt="Download" /></a>
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
## 📢 News
|
|
46
|
+
|
|
47
|
+
> [!NOTE]
|
|
48
|
+
> 🎉 **2026.09.29**: **v1.27** — **16-bit stacks no longer fuse to a black or blown-out image** (bit depth is now carried from loading through fusion to export), **Help → Check for Updates → "Update and Restart" works again** (it had been failing silently, and now also handles installs under Program Files), tall/wide stacks stop crashing tiled fusion, cancelling a large render takes effect immediately, and closing the window mid-job asks before discarding the result. Also fixed this round: quick-preview and ROI renders no longer poison the saved alignment, Compare All can be cancelled, deleting a frame keeps its filename, downsampling stops compounding below the percentage you chose, batch jobs check their output folder before they start, `.ofproj` projects reopen after the images move, and the sharpness curve is readable in the dark theme.
|
|
49
|
+
|
|
50
|
+
> 🎉 **2026.09.29**: **v1.26** — fixed the **"I/O operation on closed file"** render crash in windowed builds and a stale embedded version number that kept offering updates users already had.
|
|
51
|
+
|
|
52
|
+
> 🎉 **2026.09.28**: **v1.25 / v1.24 / v1.22** — portable mode (settings travel with the app), one-click in-app update with progress, EXIF preservation, HEIC/HEIF input, and render-failure tracebacks in the log folder.
|
|
53
|
+
|
|
54
|
+
> 🎉 **2026.09.28**: **v1.21** — project files (.ofproj), built-in demo stack, per-frame sharpness curve, Japanese + Spanish interfaces.
|
|
55
|
+
|
|
56
|
+
**Earlier milestones**: v1.9–v1.20 brought the Wipe compare view, dark/light themes, Compare-All-Methods rendering, quick preview, cancellable background renders, 16-bit pipeline, registration disk cache, batch CLI, WebP export, Windows installer + macOS DMG, and CI test automation. Full history in the [Changelog](./CHANGELOG.md).
|
|
57
|
+
|
|
58
|
+
<a id="download"></a>
|
|
59
|
+
## ⬇️ Download & Install
|
|
60
|
+
|
|
61
|
+
No setup needed — grab a build from the [Releases](https://github.com/Xinzhe99/OpenFocus/releases) page:
|
|
62
|
+
|
|
63
|
+
| Platform | File | Type |
|
|
64
|
+
|----------|------|------|
|
|
65
|
+
| Windows 10/11 (64-bit) | `OpenFocus-*-setup.exe` | **Installer** (recommended) |
|
|
66
|
+
| Windows 10/11 (64-bit) | `OpenFocus-*-windows-x64.zip` | Portable (unzip and run) |
|
|
67
|
+
| macOS Apple Silicon (M1–M4) | `OpenFocus-*-macos.dmg` | Disk image (drag to Applications) |
|
|
68
|
+
| macOS Apple Silicon (M1–M4) | `OpenFocus-*-macos-arm64.zip` | Portable (unzip) |
|
|
69
|
+
|
|
70
|
+
The app is unsigned, so the first launch may show a security prompt:
|
|
71
|
+
- **Windows**: SmartScreen → "More info" → "Run anyway". The first start may take 30–60 s.
|
|
72
|
+
- **macOS**: right-click → Open, or run `xattr -cr /Applications/OpenFocus.app` in Terminal.
|
|
73
|
+
|
|
74
|
+
Full details, including the portable mode and the log folder, are in the [User Manual](./docs/USER_MANUAL_EN.md).
|
|
75
|
+
|
|
76
|
+
<a id="command-line-usage"></a>
|
|
77
|
+
## 💻 Command Line Usage
|
|
78
|
+
Beyond the GUI, OpenFocus can run headless — handy for batch scripts and CI pipelines:
|
|
79
|
+
```bash
|
|
80
|
+
# Fuse a folder of images with guided filter, no registration
|
|
81
|
+
python main.py --input ./stack_folder --output ./result/fused.png
|
|
82
|
+
|
|
83
|
+
# DTCWT with ECC registration, 8 threads
|
|
84
|
+
python main.py -i ./stack_folder -o ./result/fused.png -m dtcwt -a ecc -t 8
|
|
85
|
+
|
|
86
|
+
# StackMFF-V4 forced to CPU with custom tile size
|
|
87
|
+
python main.py -i ./stack_folder -o ./result/fused.png -m stackmffv4 --cpu --tile-size 512
|
|
88
|
+
|
|
89
|
+
# Explicit file list instead of a folder; video files also work as input
|
|
90
|
+
python main.py -i img1.jpg img2.jpg img3.jpg -o fused.png -m gfgfgf
|
|
91
|
+
|
|
92
|
+
# Batch mode: fuse every folder into one output directory
|
|
93
|
+
python main.py --input ./stackA ./stackB --output-dir ./results
|
|
94
|
+
```
|
|
95
|
+
Exit codes: `0` success, `1` processing error, `2` usage error. Run `python main.py --help` for all options.
|
|
96
|
+
|
|
97
|
+
## 📖 Documentation
|
|
98
|
+
- [**User Manual (English)**](./docs/USER_MANUAL_EN.md) — full feature guide: interface, workflows, wipe compare, settings, batch, CLI, troubleshooting
|
|
99
|
+
- [**用户手册(中文)**](./docs/USER_MANUAL_ZH.md) — 完整中文功能手册
|
|
100
|
+
- [**Changelog**](./CHANGELOG.md) — release history and notable changes
|
|
101
|
+
- [**Build Commands**](./docs/BUILD_COMMANDS.md) — packaging from source with PyInstaller
|
|
102
|
+
|
|
103
|
+
<a id="building-from-source"></a>
|
|
104
|
+
## 🛠️ Building from Source
|
|
105
|
+
|
|
106
|
+
For contributors and platforms without pre-built packages:
|
|
107
|
+
```bash
|
|
108
|
+
conda create -n openfocus python=3.10
|
|
109
|
+
conda activate openfocus
|
|
110
|
+
pip install -r requirements.txt
|
|
111
|
+
python main.py
|
|
112
|
+
```
|
|
113
|
+
Packaging with PyInstaller: see [docs/BUILD_COMMANDS.md](./docs/BUILD_COMMANDS.md).
|
|
114
|
+
|
|
115
|
+
## Table of Contents
|
|
116
|
+
- [⬇️ Download & Install](#download)
|
|
117
|
+
- [💻 Command Line Usage](#command-line-usage)
|
|
118
|
+
- [📖 Documentation](#documentation)
|
|
119
|
+
- [🛠️ Building from Source](#building-from-source)
|
|
120
|
+
- [🔭 Overview](#overview)
|
|
121
|
+
- [✨ Highlights](#highlights)
|
|
122
|
+
- [🧪 Algorithms](#algorithms)
|
|
123
|
+
- [📚 References](#references)
|
|
124
|
+
- [🤝 Contribution](#contribution)
|
|
125
|
+
- [📄 License](#license)
|
|
126
|
+
- [⭐ Star History](#star-history)
|
|
127
|
+
|
|
128
|
+
<a id="overview"></a>
|
|
129
|
+
## 🔭 Overview
|
|
130
|
+
OpenFocus is a PyQt6-based multi-focus registration and fusion workstation that delivers commercial-grade alignment and blending results. The project is fully open source (MIT License) and runs on CPU by default with optional GPU acceleration for the StackMFF V4 neural model.
|
|
131
|
+
|
|
132
|
+
<p align="center">
|
|
133
|
+
<img src="assets/ui.jpg" alt="OpenFocus UI" width="720">
|
|
134
|
+
</p>
|
|
135
|
+
|
|
136
|
+
<a id="highlights"></a>
|
|
137
|
+
## ✨ Highlights
|
|
138
|
+
- **Beginner-Friendly**: Plug-and-play workflows with unapologetically simple, guided operations.
|
|
139
|
+
- **Flexible Processing Flows**: Run fusion-only, registration-only, or combined registration + fusion pipelines depending on your workload.
|
|
140
|
+
- **Wipe Compare View**: Overlay any source frame and any result in one frame with a draggable divider and shared zoom — spot alignment errors at a glance.
|
|
141
|
+
- **Batch Automation**: Kick off batch jobs across multiple folders with live progress, cancellation, and automatic output organization.
|
|
142
|
+
- **Headless CLI**: Full pipeline from the command line with script-friendly exit codes.
|
|
143
|
+
- **Annotation & Export Toolkit**: Overlay labels, export GIF animations, drag results straight out of the app, and save stacks in JPG/PNG/BMP/TIFF.
|
|
144
|
+
- **AI-Assisted Fusion**: Ship with StackMFF V4 to unlock deep-learning-quality fusion alongside classic signal-processing methods.
|
|
145
|
+
- **Remembers You**: Settings, language, GPU preference and recently opened stacks persist across sessions; bilingual UI throughout.
|
|
146
|
+
|
|
147
|
+
<a id="fusion--registration-methods"></a>
|
|
148
|
+
## 🧪 Algorithms
|
|
149
|
+
### Fusion Algorithms
|
|
150
|
+
|
|
151
|
+
- **Guided Filter**: Fast edge-preserving fusion that enhances contrast while suppressing noise.
|
|
152
|
+
- **DCT Multi-Focus Fusion**: Frequency-domain technique optimized for crisp detail recovery.
|
|
153
|
+
- **Dual-Tree Complex Wavelet Transform (DTCWT)**: Multi-scale representation that preserves fine texture structures.
|
|
154
|
+
- **GFG-FGF**: GFG-FGF is based on a generalized four-neighborhood Gaussian gradient (GFG) operator combined with a fast guided filter (FGF).
|
|
155
|
+
- **StackMFF V4**: Pretrained deep model delivering state-of-the-art focus stacking quality.
|
|
156
|
+
|
|
157
|
+
### Registration Algorithms
|
|
158
|
+
- **Homography**: Performs feature-based projective alignment using keypoint matching and RANSAC to handle global perspective transformations.
|
|
159
|
+
- **ECC**: Performs intensity-based alignment by maximizing the enhanced correlation coefficient for precise, sub-pixel registration.
|
|
160
|
+
|
|
161
|
+
> **License Notice:** Every fusion/registration algorithm included comes from open-source research implementations. When using or redistributing them, please follow each algorithm’s original license terms in addition to the OpenFocus MIT license.
|
|
162
|
+
|
|
163
|
+
<a id="references"></a>
|
|
164
|
+
## 📚 References
|
|
165
|
+
- M. B. A. Haghighat, A. Aghagolzadeh, and H. Seyedarabi, "Multi-focus image fusion for visual sensor networks in DCT domain," *Computers & Electrical Engineering*, vol. 37, no. 5, pp. 789-797, 2011.
|
|
166
|
+
- J. J. Lewis, R. J. O'Callaghan, S. G. Nikolov, D. R. Bull, and N. Canagarajah, "Pixel- and region-based image fusion with complex wavelets," *Information Fusion*, vol. 8, no. 2, pp. 119-130, 2007.
|
|
167
|
+
- S. Li, X. Kang, and J. Hu, "Image fusion with guided filtering," *IEEE Transactions on Image Processing*, vol. 22, no. 7, pp. 2864-2875, 2013.
|
|
168
|
+
- 付宏语, 巩岩, 汪路涵, 等. 多聚焦显微图像融合算法[J]. Laser & Optoelectronics Progress, 2024, 61(6): 0618022-0618022-9.
|
|
169
|
+
|
|
170
|
+
<a id="contribution"></a>
|
|
171
|
+
## 🤝 Contribution
|
|
172
|
+
We welcome community contributions of all kinds:
|
|
173
|
+
1. **Issues**: Report bugs, request features, or propose UX enhancements.
|
|
174
|
+
2. **Algorithm & Performance Work**: Share new fusion/registration ideas, optimizations.
|
|
175
|
+
|
|
176
|
+
> Bug reports or suggestions? Please open an issue so we can follow up quickly.
|
|
177
|
+
|
|
178
|
+
<a id="license"></a>
|
|
179
|
+
|
|
180
|
+
## 📄 License
|
|
181
|
+
This project is released under the [MIT License](./LICENSE). Feel free to use, modify, and distribute within the terms of the license.
|
|
182
|
+
|
|
183
|
+
If you publish images created with OpenFocus, please consider adding a note such as:
|
|
184
|
+
|
|
185
|
+
Created with OpenFocus – https://github.com/Xinzhe99/OpenFocus
|
|
186
|
+
|
|
187
|
+
This is not mandatory, but highly appreciated.
|
|
188
|
+
|
|
189
|
+
<p align="center" style="font-size:1.25rem; font-weight:600;">
|
|
190
|
+
If OpenFocus helps you, please consider leaving a ⭐ on the repository!
|
|
191
|
+
</p>
|
|
192
|
+
|
|
193
|
+
<a id="star-history"></a>
|
|
194
|
+
## ⭐ Star History
|
|
195
|
+
|
|
196
|
+
[](https://star-history.com/#Xinzhe99/OpenFocus&Date)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
openfocus-1.28/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# <img src="assets/OpenFocus.png" alt="OpenFocus Logo" width="120"> OpenFocus
|
|
2
|
+
|
|
3
|
+
OpenFocus delivers focus stacking quality that rivals commercial-grade software, while staying fully open source and easy to extend.
|
|
4
|
+
|
|
5
|
+
<p align="left">
|
|
6
|
+
<a href="https://www.python.org/downloads/release/python-3100/"><img src="https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white" alt="Python 3.10+" /></a>
|
|
7
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?logo=open-source-initiative&logoColor=white" alt="License: MIT" /></a>
|
|
8
|
+
<a href="https://github.com/Xinzhe99/OpenFocus"><img src="https://img.shields.io/badge/GitHub-Repository-181717?logo=github&logoColor=white" alt="GitHub Repository" /></a>
|
|
9
|
+
<a href="https://github.com/Xinzhe99/OpenFocus/releases"><img src="https://img.shields.io/badge/Windows%20%7C%20macOS-Download-0078D7?logo=github&logoColor=white" alt="Download" /></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
## 📢 News
|
|
13
|
+
|
|
14
|
+
> [!NOTE]
|
|
15
|
+
> 🎉 **2026.09.29**: **v1.27** — **16-bit stacks no longer fuse to a black or blown-out image** (bit depth is now carried from loading through fusion to export), **Help → Check for Updates → "Update and Restart" works again** (it had been failing silently, and now also handles installs under Program Files), tall/wide stacks stop crashing tiled fusion, cancelling a large render takes effect immediately, and closing the window mid-job asks before discarding the result. Also fixed this round: quick-preview and ROI renders no longer poison the saved alignment, Compare All can be cancelled, deleting a frame keeps its filename, downsampling stops compounding below the percentage you chose, batch jobs check their output folder before they start, `.ofproj` projects reopen after the images move, and the sharpness curve is readable in the dark theme.
|
|
16
|
+
|
|
17
|
+
> 🎉 **2026.09.29**: **v1.26** — fixed the **"I/O operation on closed file"** render crash in windowed builds and a stale embedded version number that kept offering updates users already had.
|
|
18
|
+
|
|
19
|
+
> 🎉 **2026.09.28**: **v1.25 / v1.24 / v1.22** — portable mode (settings travel with the app), one-click in-app update with progress, EXIF preservation, HEIC/HEIF input, and render-failure tracebacks in the log folder.
|
|
20
|
+
|
|
21
|
+
> 🎉 **2026.09.28**: **v1.21** — project files (.ofproj), built-in demo stack, per-frame sharpness curve, Japanese + Spanish interfaces.
|
|
22
|
+
|
|
23
|
+
**Earlier milestones**: v1.9–v1.20 brought the Wipe compare view, dark/light themes, Compare-All-Methods rendering, quick preview, cancellable background renders, 16-bit pipeline, registration disk cache, batch CLI, WebP export, Windows installer + macOS DMG, and CI test automation. Full history in the [Changelog](./CHANGELOG.md).
|
|
24
|
+
|
|
25
|
+
<a id="download"></a>
|
|
26
|
+
## ⬇️ Download & Install
|
|
27
|
+
|
|
28
|
+
No setup needed — grab a build from the [Releases](https://github.com/Xinzhe99/OpenFocus/releases) page:
|
|
29
|
+
|
|
30
|
+
| Platform | File | Type |
|
|
31
|
+
|----------|------|------|
|
|
32
|
+
| Windows 10/11 (64-bit) | `OpenFocus-*-setup.exe` | **Installer** (recommended) |
|
|
33
|
+
| Windows 10/11 (64-bit) | `OpenFocus-*-windows-x64.zip` | Portable (unzip and run) |
|
|
34
|
+
| macOS Apple Silicon (M1–M4) | `OpenFocus-*-macos.dmg` | Disk image (drag to Applications) |
|
|
35
|
+
| macOS Apple Silicon (M1–M4) | `OpenFocus-*-macos-arm64.zip` | Portable (unzip) |
|
|
36
|
+
|
|
37
|
+
The app is unsigned, so the first launch may show a security prompt:
|
|
38
|
+
- **Windows**: SmartScreen → "More info" → "Run anyway". The first start may take 30–60 s.
|
|
39
|
+
- **macOS**: right-click → Open, or run `xattr -cr /Applications/OpenFocus.app` in Terminal.
|
|
40
|
+
|
|
41
|
+
Full details, including the portable mode and the log folder, are in the [User Manual](./docs/USER_MANUAL_EN.md).
|
|
42
|
+
|
|
43
|
+
<a id="command-line-usage"></a>
|
|
44
|
+
## 💻 Command Line Usage
|
|
45
|
+
Beyond the GUI, OpenFocus can run headless — handy for batch scripts and CI pipelines:
|
|
46
|
+
```bash
|
|
47
|
+
# Fuse a folder of images with guided filter, no registration
|
|
48
|
+
python main.py --input ./stack_folder --output ./result/fused.png
|
|
49
|
+
|
|
50
|
+
# DTCWT with ECC registration, 8 threads
|
|
51
|
+
python main.py -i ./stack_folder -o ./result/fused.png -m dtcwt -a ecc -t 8
|
|
52
|
+
|
|
53
|
+
# StackMFF-V4 forced to CPU with custom tile size
|
|
54
|
+
python main.py -i ./stack_folder -o ./result/fused.png -m stackmffv4 --cpu --tile-size 512
|
|
55
|
+
|
|
56
|
+
# Explicit file list instead of a folder; video files also work as input
|
|
57
|
+
python main.py -i img1.jpg img2.jpg img3.jpg -o fused.png -m gfgfgf
|
|
58
|
+
|
|
59
|
+
# Batch mode: fuse every folder into one output directory
|
|
60
|
+
python main.py --input ./stackA ./stackB --output-dir ./results
|
|
61
|
+
```
|
|
62
|
+
Exit codes: `0` success, `1` processing error, `2` usage error. Run `python main.py --help` for all options.
|
|
63
|
+
|
|
64
|
+
## 📖 Documentation
|
|
65
|
+
- [**User Manual (English)**](./docs/USER_MANUAL_EN.md) — full feature guide: interface, workflows, wipe compare, settings, batch, CLI, troubleshooting
|
|
66
|
+
- [**用户手册(中文)**](./docs/USER_MANUAL_ZH.md) — 完整中文功能手册
|
|
67
|
+
- [**Changelog**](./CHANGELOG.md) — release history and notable changes
|
|
68
|
+
- [**Build Commands**](./docs/BUILD_COMMANDS.md) — packaging from source with PyInstaller
|
|
69
|
+
|
|
70
|
+
<a id="building-from-source"></a>
|
|
71
|
+
## 🛠️ Building from Source
|
|
72
|
+
|
|
73
|
+
For contributors and platforms without pre-built packages:
|
|
74
|
+
```bash
|
|
75
|
+
conda create -n openfocus python=3.10
|
|
76
|
+
conda activate openfocus
|
|
77
|
+
pip install -r requirements.txt
|
|
78
|
+
python main.py
|
|
79
|
+
```
|
|
80
|
+
Packaging with PyInstaller: see [docs/BUILD_COMMANDS.md](./docs/BUILD_COMMANDS.md).
|
|
81
|
+
|
|
82
|
+
## Table of Contents
|
|
83
|
+
- [⬇️ Download & Install](#download)
|
|
84
|
+
- [💻 Command Line Usage](#command-line-usage)
|
|
85
|
+
- [📖 Documentation](#documentation)
|
|
86
|
+
- [🛠️ Building from Source](#building-from-source)
|
|
87
|
+
- [🔭 Overview](#overview)
|
|
88
|
+
- [✨ Highlights](#highlights)
|
|
89
|
+
- [🧪 Algorithms](#algorithms)
|
|
90
|
+
- [📚 References](#references)
|
|
91
|
+
- [🤝 Contribution](#contribution)
|
|
92
|
+
- [📄 License](#license)
|
|
93
|
+
- [⭐ Star History](#star-history)
|
|
94
|
+
|
|
95
|
+
<a id="overview"></a>
|
|
96
|
+
## 🔭 Overview
|
|
97
|
+
OpenFocus is a PyQt6-based multi-focus registration and fusion workstation that delivers commercial-grade alignment and blending results. The project is fully open source (MIT License) and runs on CPU by default with optional GPU acceleration for the StackMFF V4 neural model.
|
|
98
|
+
|
|
99
|
+
<p align="center">
|
|
100
|
+
<img src="assets/ui.jpg" alt="OpenFocus UI" width="720">
|
|
101
|
+
</p>
|
|
102
|
+
|
|
103
|
+
<a id="highlights"></a>
|
|
104
|
+
## ✨ Highlights
|
|
105
|
+
- **Beginner-Friendly**: Plug-and-play workflows with unapologetically simple, guided operations.
|
|
106
|
+
- **Flexible Processing Flows**: Run fusion-only, registration-only, or combined registration + fusion pipelines depending on your workload.
|
|
107
|
+
- **Wipe Compare View**: Overlay any source frame and any result in one frame with a draggable divider and shared zoom — spot alignment errors at a glance.
|
|
108
|
+
- **Batch Automation**: Kick off batch jobs across multiple folders with live progress, cancellation, and automatic output organization.
|
|
109
|
+
- **Headless CLI**: Full pipeline from the command line with script-friendly exit codes.
|
|
110
|
+
- **Annotation & Export Toolkit**: Overlay labels, export GIF animations, drag results straight out of the app, and save stacks in JPG/PNG/BMP/TIFF.
|
|
111
|
+
- **AI-Assisted Fusion**: Ship with StackMFF V4 to unlock deep-learning-quality fusion alongside classic signal-processing methods.
|
|
112
|
+
- **Remembers You**: Settings, language, GPU preference and recently opened stacks persist across sessions; bilingual UI throughout.
|
|
113
|
+
|
|
114
|
+
<a id="fusion--registration-methods"></a>
|
|
115
|
+
## 🧪 Algorithms
|
|
116
|
+
### Fusion Algorithms
|
|
117
|
+
|
|
118
|
+
- **Guided Filter**: Fast edge-preserving fusion that enhances contrast while suppressing noise.
|
|
119
|
+
- **DCT Multi-Focus Fusion**: Frequency-domain technique optimized for crisp detail recovery.
|
|
120
|
+
- **Dual-Tree Complex Wavelet Transform (DTCWT)**: Multi-scale representation that preserves fine texture structures.
|
|
121
|
+
- **GFG-FGF**: GFG-FGF is based on a generalized four-neighborhood Gaussian gradient (GFG) operator combined with a fast guided filter (FGF).
|
|
122
|
+
- **StackMFF V4**: Pretrained deep model delivering state-of-the-art focus stacking quality.
|
|
123
|
+
|
|
124
|
+
### Registration Algorithms
|
|
125
|
+
- **Homography**: Performs feature-based projective alignment using keypoint matching and RANSAC to handle global perspective transformations.
|
|
126
|
+
- **ECC**: Performs intensity-based alignment by maximizing the enhanced correlation coefficient for precise, sub-pixel registration.
|
|
127
|
+
|
|
128
|
+
> **License Notice:** Every fusion/registration algorithm included comes from open-source research implementations. When using or redistributing them, please follow each algorithm’s original license terms in addition to the OpenFocus MIT license.
|
|
129
|
+
|
|
130
|
+
<a id="references"></a>
|
|
131
|
+
## 📚 References
|
|
132
|
+
- M. B. A. Haghighat, A. Aghagolzadeh, and H. Seyedarabi, "Multi-focus image fusion for visual sensor networks in DCT domain," *Computers & Electrical Engineering*, vol. 37, no. 5, pp. 789-797, 2011.
|
|
133
|
+
- J. J. Lewis, R. J. O'Callaghan, S. G. Nikolov, D. R. Bull, and N. Canagarajah, "Pixel- and region-based image fusion with complex wavelets," *Information Fusion*, vol. 8, no. 2, pp. 119-130, 2007.
|
|
134
|
+
- S. Li, X. Kang, and J. Hu, "Image fusion with guided filtering," *IEEE Transactions on Image Processing*, vol. 22, no. 7, pp. 2864-2875, 2013.
|
|
135
|
+
- 付宏语, 巩岩, 汪路涵, 等. 多聚焦显微图像融合算法[J]. Laser & Optoelectronics Progress, 2024, 61(6): 0618022-0618022-9.
|
|
136
|
+
|
|
137
|
+
<a id="contribution"></a>
|
|
138
|
+
## 🤝 Contribution
|
|
139
|
+
We welcome community contributions of all kinds:
|
|
140
|
+
1. **Issues**: Report bugs, request features, or propose UX enhancements.
|
|
141
|
+
2. **Algorithm & Performance Work**: Share new fusion/registration ideas, optimizations.
|
|
142
|
+
|
|
143
|
+
> Bug reports or suggestions? Please open an issue so we can follow up quickly.
|
|
144
|
+
|
|
145
|
+
<a id="license"></a>
|
|
146
|
+
|
|
147
|
+
## 📄 License
|
|
148
|
+
This project is released under the [MIT License](./LICENSE). Feel free to use, modify, and distribute within the terms of the license.
|
|
149
|
+
|
|
150
|
+
If you publish images created with OpenFocus, please consider adding a note such as:
|
|
151
|
+
|
|
152
|
+
Created with OpenFocus – https://github.com/Xinzhe99/OpenFocus
|
|
153
|
+
|
|
154
|
+
This is not mandatory, but highly appreciated.
|
|
155
|
+
|
|
156
|
+
<p align="center" style="font-size:1.25rem; font-weight:600;">
|
|
157
|
+
If OpenFocus helps you, please consider leaving a ⭐ on the repository!
|
|
158
|
+
</p>
|
|
159
|
+
|
|
160
|
+
<a id="star-history"></a>
|
|
161
|
+
## ⭐ Star History
|
|
162
|
+
|
|
163
|
+
[](https://star-history.com/#Xinzhe99/OpenFocus&Date)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
APP_VERSION = "1.28"
|
|
2
|
+
|
|
3
|
+
WINDOW_WIDTH = 1600
|
|
4
|
+
WINDOW_HEIGHT = 950
|
|
5
|
+
|
|
6
|
+
TILE_BLOCK_SIZE = 1024
|
|
7
|
+
TILE_OVERLAP = 256
|
|
8
|
+
TILE_THRESHOLD = 2048
|
|
9
|
+
TILE_BLOCK_MIN = 64
|
|
10
|
+
TILE_BLOCK_MAX = 16384
|
|
11
|
+
TILE_OVERLAP_MAX = 4096
|
|
12
|
+
TILE_THRESHOLD_MIN = 256
|
|
13
|
+
TILE_THRESHOLD_MAX = 131072
|
|
14
|
+
|
|
15
|
+
REG_DOWNSCALE_WIDTH = 1024
|
|
16
|
+
HOMOGRAPHY_DOWNSCALE_WIDTH = 1600
|
|
17
|
+
ECC_DOWNSCALE_WIDTH = 1000
|
|
18
|
+
|
|
19
|
+
STATUS_UPDATE_INTERVAL_MS = 2000
|
|
20
|
+
|
|
21
|
+
BYTES_TO_GB = 1024 ** 3
|
|
22
|
+
|
|
23
|
+
FEATURE_MAX_KEYPOINTS = 200
|
|
24
|
+
FEATURE_MATCH_THRESHOLD = 0.01
|
|
25
|
+
FEATURE_RANSAC_REJECTS = 30
|
|
26
|
+
FEATURE_RANSAC_MAX_ITER = 3
|
|
27
|
+
|
|
28
|
+
MAGNIFIER_SIZE = 320
|
|
29
|
+
ANIMATION_DURATION_MS = 400
|
|
30
|
+
|
|
31
|
+
DEFAULT_THREAD_COUNT = 4
|
|
32
|
+
MIN_THREAD_COUNT = 1
|
|
33
|
+
MAX_THREAD_COUNT = 16
|
|
34
|
+
|
|
35
|
+
STACKMFFV4_BATCH_SIZE = 2
|
|
36
|
+
STACKMFFV4_BATCH_SIZE_MIN = 1
|
|
37
|
+
STACKMFFV4_BATCH_SIZE_MAX = 16
|
|
38
|
+
|
|
39
|
+
KERNEL_SIZE_MIN = 3
|
|
40
|
+
KERNEL_SIZE_MAX = 51
|
|
41
|
+
KERNEL_SIZE_DEFAULT_DCT = 7
|
|
42
|
+
KERNEL_SIZE_DEFAULT_GFF = 31
|
|
43
|
+
KERNEL_SIZE_DEFAULT_GFG = 7
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core modules for OpenFocus.
|
|
3
|
+
|
|
4
|
+
Modules:
|
|
5
|
+
- image_loader: Image stack loading and processing
|
|
6
|
+
- registration: Image registration algorithms
|
|
7
|
+
- multi_focus_fusion: Multi-focus image fusion orchestrator
|
|
8
|
+
- workers: Background thread workers for rendering
|
|
9
|
+
- models: Neural network models (StackMFF-V4)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from core.image_loader import ImageStackLoader
|
|
13
|
+
from core.multi_focus_fusion import MultiFocusFusion, is_stackmffv4_available
|
|
14
|
+
|
|
15
|
+
try: # GUI stack; workers (and app below) need PyQt6, the headless CLI
|
|
16
|
+
# package installs without it.
|
|
17
|
+
from core.workers import RenderWorker, ROIAlignmentWorker
|
|
18
|
+
from core.app import OpenFocusApplication, process_command_line_args
|
|
19
|
+
_HAS_QT = True
|
|
20
|
+
except ImportError: # pragma: no cover - headless openfocus package
|
|
21
|
+
RenderWorker = ROIAlignmentWorker = None
|
|
22
|
+
OpenFocusApplication = process_command_line_args = None
|
|
23
|
+
_HAS_QT = False
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'ImageStackLoader',
|
|
27
|
+
'MultiFocusFusion',
|
|
28
|
+
'is_stackmffv4_available',
|
|
29
|
+
'RenderWorker',
|
|
30
|
+
]
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cross-platform file drag-and-drop support for OpenFocus.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
- macOS: Dock drag-and-drop via QFileOpenEvent
|
|
6
|
+
- Linux: Desktop file drag-and-drop via command-line arguments
|
|
7
|
+
- Windows: EXE/shortcut drag-and-drop via command-line arguments
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
from urllib.parse import unquote
|
|
14
|
+
|
|
15
|
+
from PyQt6.QtWidgets import QApplication
|
|
16
|
+
from PyQt6.QtGui import QFileOpenEvent
|
|
17
|
+
from PyQt6.QtCore import QTimer, QUrl
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PlatformFileHandler:
|
|
21
|
+
"""Normalizes and classifies file paths from different platform sources."""
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def normalize(path: str) -> str:
|
|
25
|
+
"""Turn a file:// URI (or a mac-style /// path) into a local path.
|
|
26
|
+
|
|
27
|
+
QUrl.toLocalFile() does the platform-correct job: hand-stripping the
|
|
28
|
+
scheme leaves the URI's leading slash on Windows, so 'file:///C:/stack'
|
|
29
|
+
became '/C:/stack' and the file was never found. Percent-decoding also
|
|
30
|
+
belongs to URI syntax only — a plain command-line path may contain a
|
|
31
|
+
literal '%', so it must not be unquoted here.
|
|
32
|
+
"""
|
|
33
|
+
if not path:
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
if path.startswith('file://'):
|
|
37
|
+
local = QUrl(path).toLocalFile()
|
|
38
|
+
if local:
|
|
39
|
+
return local
|
|
40
|
+
return '/' + unquote(path[7:]).lstrip('/') # malformed URI fallback
|
|
41
|
+
|
|
42
|
+
if path.startswith('///'):
|
|
43
|
+
# Extra leading slashes in front of an absolute POSIX path
|
|
44
|
+
return '/' + unquote(path).lstrip('/')
|
|
45
|
+
|
|
46
|
+
return path
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def is_video(path: str) -> bool:
|
|
50
|
+
from core.image_loader import ImageStackLoader
|
|
51
|
+
return os.path.splitext(path)[1].lower() in ImageStackLoader.SUPPORTED_VIDEO_FORMATS
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def is_image(path: str) -> bool:
|
|
55
|
+
from core.image_loader import ImageStackLoader
|
|
56
|
+
return os.path.splitext(path)[1].lower() in ImageStackLoader.SUPPORTED_FORMATS
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def classify(paths: List[str]) -> dict:
|
|
60
|
+
result = {'dirs': [], 'videos': [], 'images': [], 'other': []}
|
|
61
|
+
for p in paths:
|
|
62
|
+
normalized = PlatformFileHandler.normalize(p)
|
|
63
|
+
if not os.path.exists(normalized):
|
|
64
|
+
continue
|
|
65
|
+
if os.path.isdir(normalized):
|
|
66
|
+
result['dirs'].append(normalized)
|
|
67
|
+
elif PlatformFileHandler.is_video(normalized):
|
|
68
|
+
result['videos'].append(normalized)
|
|
69
|
+
elif PlatformFileHandler.is_image(normalized):
|
|
70
|
+
result['images'].append(normalized)
|
|
71
|
+
else:
|
|
72
|
+
result['other'].append(normalized)
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class OpenFocusApplication(QApplication):
|
|
77
|
+
"""QApplication subclass handling file open events from platform sources."""
|
|
78
|
+
|
|
79
|
+
def __init__(self, *args, **kwargs):
|
|
80
|
+
super().__init__(*args, **kwargs)
|
|
81
|
+
self.main_window = None
|
|
82
|
+
self._pending: List[str] = []
|
|
83
|
+
self._ready = False
|
|
84
|
+
|
|
85
|
+
def set_main_window(self, window):
|
|
86
|
+
self.main_window = window
|
|
87
|
+
self._ready = True
|
|
88
|
+
if self._pending:
|
|
89
|
+
pending = self._pending.copy()
|
|
90
|
+
self._pending.clear()
|
|
91
|
+
QTimer.singleShot(100, lambda: self._process(pending))
|
|
92
|
+
|
|
93
|
+
def event(self, event) -> bool:
|
|
94
|
+
if event.type() == QFileOpenEvent.Type:
|
|
95
|
+
path = QFileOpenEvent(event).file()
|
|
96
|
+
if path:
|
|
97
|
+
if self._ready and self.main_window:
|
|
98
|
+
self._handle(path)
|
|
99
|
+
else:
|
|
100
|
+
self._pending.append(path)
|
|
101
|
+
return True
|
|
102
|
+
return super().event(event)
|
|
103
|
+
|
|
104
|
+
def _handle(self, path: str):
|
|
105
|
+
normalized = PlatformFileHandler.normalize(path)
|
|
106
|
+
if normalized:
|
|
107
|
+
self._process([normalized])
|
|
108
|
+
|
|
109
|
+
def _process(self, paths: List[str]):
|
|
110
|
+
if not self._ready or not self.main_window:
|
|
111
|
+
return
|
|
112
|
+
if not hasattr(self.main_window, 'source_manager'):
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
src = self.main_window.source_manager
|
|
116
|
+
c = PlatformFileHandler.classify(paths)
|
|
117
|
+
files = c['videos'] + c['images'] + c['other']
|
|
118
|
+
|
|
119
|
+
if len(c['dirs']) == 1 and not files:
|
|
120
|
+
src.load_image_stack_from_icon(c['dirs'][0])
|
|
121
|
+
elif c['dirs']:
|
|
122
|
+
src.load_multiple_folders_from_icon(c['dirs'])
|
|
123
|
+
elif len(c['videos']) == 1 and not c['images']:
|
|
124
|
+
src.load_video_stack_from_icon(c['videos'][0])
|
|
125
|
+
elif c['images']:
|
|
126
|
+
src.load_image_files_from_icon(c['images'])
|
|
127
|
+
else:
|
|
128
|
+
from utils import show_warning_box
|
|
129
|
+
from locales import trans
|
|
130
|
+
show_warning_box(
|
|
131
|
+
self.main_window,
|
|
132
|
+
trans.t("msg_no_valid_files_title"),
|
|
133
|
+
trans.t("msg_no_valid_files_text"),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def process_command_line_args(main_window, args: List[str]):
|
|
138
|
+
"""Handle files passed via command-line (drag-to-EXE, desktop file %U, etc.)."""
|
|
139
|
+
if not args:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
file_paths = [a for a in args if not a.startswith('-')]
|
|
143
|
+
if not file_paths:
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
def _delayed():
|
|
147
|
+
if not hasattr(main_window, 'source_manager'):
|
|
148
|
+
return
|
|
149
|
+
c = PlatformFileHandler.classify(file_paths)
|
|
150
|
+
src = main_window.source_manager
|
|
151
|
+
files = c['videos'] + c['images'] + c['other']
|
|
152
|
+
|
|
153
|
+
if len(c['dirs']) == 1 and not files:
|
|
154
|
+
src.load_image_stack_from_icon(c['dirs'][0])
|
|
155
|
+
elif c['dirs']:
|
|
156
|
+
src.load_multiple_folders_from_icon(c['dirs'])
|
|
157
|
+
elif len(c['videos']) == 1 and not c['images']:
|
|
158
|
+
src.load_video_stack_from_icon(c['videos'][0])
|
|
159
|
+
elif c['images']:
|
|
160
|
+
src.load_image_files_from_icon(c['images'])
|
|
161
|
+
|
|
162
|
+
QTimer.singleShot(100, _delayed)
|