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.
Files changed (51) hide show
  1. openfocus-1.28/LICENSE +21 -0
  2. openfocus-1.28/PKG-INFO +210 -0
  3. openfocus-1.28/README.md +177 -0
  4. openfocus-1.28/constants.py +43 -0
  5. openfocus-1.28/core/__init__.py +30 -0
  6. openfocus-1.28/core/app.py +162 -0
  7. openfocus-1.28/core/cli.py +306 -0
  8. openfocus-1.28/core/image_loader.py +500 -0
  9. openfocus-1.28/core/models/__init__.py +18 -0
  10. openfocus-1.28/core/models/stackmffv4_network.py +630 -0
  11. openfocus-1.28/core/multi_focus_fusion.py +955 -0
  12. openfocus-1.28/core/registration.py +1152 -0
  13. openfocus-1.28/core/workers.py +894 -0
  14. openfocus-1.28/fusion_methods/__init__.py +14 -0
  15. openfocus-1.28/fusion_methods/dct.py +220 -0
  16. openfocus-1.28/fusion_methods/dtcwt.py +211 -0
  17. openfocus-1.28/fusion_methods/gff.py +264 -0
  18. openfocus-1.28/fusion_methods/gfg_fgf.py +362 -0
  19. openfocus-1.28/fusion_methods/stackmffv4.py +288 -0
  20. openfocus-1.28/openfocus.egg-info/PKG-INFO +210 -0
  21. openfocus-1.28/openfocus.egg-info/SOURCES.txt +49 -0
  22. openfocus-1.28/openfocus.egg-info/dependency_links.txt +1 -0
  23. openfocus-1.28/openfocus.egg-info/entry_points.txt +2 -0
  24. openfocus-1.28/openfocus.egg-info/requires.txt +12 -0
  25. openfocus-1.28/openfocus.egg-info/top_level.txt +5 -0
  26. openfocus-1.28/pyproject.toml +53 -0
  27. openfocus-1.28/setup.cfg +4 -0
  28. openfocus-1.28/tests/test_16bit_and_cache.py +311 -0
  29. openfocus-1.28/tests/test_cli.py +130 -0
  30. openfocus-1.28/tests/test_fusion_methods.py +207 -0
  31. openfocus-1.28/tests/test_gui_units.py +738 -0
  32. openfocus-1.28/tests/test_i18n_placeholders.py +28 -0
  33. openfocus-1.28/tests/test_label_range.py +46 -0
  34. openfocus-1.28/tests/test_project_file.py +149 -0
  35. openfocus-1.28/tests/test_registration.py +111 -0
  36. openfocus-1.28/tests/test_stackmffv4.py +28 -0
  37. openfocus-1.28/tests/test_updater.py +287 -0
  38. openfocus-1.28/tests/test_v116_units.py +107 -0
  39. openfocus-1.28/utils/__init__.py +81 -0
  40. openfocus-1.28/utils/align_cache.py +102 -0
  41. openfocus-1.28/utils/image_utils.py +176 -0
  42. openfocus-1.28/utils/logging_setup.py +63 -0
  43. openfocus-1.28/utils/platform_utils.py +108 -0
  44. openfocus-1.28/utils/project_file.py +220 -0
  45. openfocus-1.28/utils/self_update.py +290 -0
  46. openfocus-1.28/utils/settings_store.py +246 -0
  47. openfocus-1.28/utils/ui_utils.py +151 -0
  48. openfocus-1.28/utils/updater.py +177 -0
  49. openfocus-1.28/utils/validators.py +224 -0
  50. openfocus-1.28/weights/__init__.py +1 -0
  51. 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.
@@ -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
+ [![Star History Chart](https://api.star-history.com/svg?repos=Xinzhe99/OpenFocus&type=Date)](https://star-history.com/#Xinzhe99/OpenFocus&Date)
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
@@ -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
+ [![Star History Chart](https://api.star-history.com/svg?repos=Xinzhe99/OpenFocus&type=Date)](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)