shinestacker 0.3.4__tar.gz → 0.3.6__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.
Potentially problematic release.
This version of shinestacker might be problematic. Click here for more details.
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.coverage +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/CHANGELOG.md +22 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/PKG-INFO +10 -8
- {shinestacker-0.3.4 → shinestacker-0.3.6}/README.md +9 -7
- shinestacker-0.3.6/THIRD_PARTY_LICENSES.txt +80 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/main.md +2 -5
- shinestacker-0.3.6/img/gui-project-new.png +0 -0
- shinestacker-0.3.6/src/shinestacker/_version.py +1 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/base_stack_algo.py +1 -2
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/noise_detection.py +0 -3
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/pyramid.py +7 -4
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/stack.py +1 -2
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/stack_framework.py +8 -2
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/vignetting.py +5 -4
- shinestacker-0.3.6/src/shinestacker/app/app_config.py +22 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/help_menu.py +1 -1
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/config/config.py +21 -16
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/action_config.py +10 -35
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/actions_window.py +22 -54
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/new_project.py +5 -22
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/project_editor.py +49 -20
- shinestacker-0.3.6/src/shinestacker/gui/select_path_widget.py +30 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/base_filter.py +12 -1
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/denoise_filter.py +4 -10
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/exif_data.py +3 -9
- shinestacker-0.3.6/src/shinestacker/retouch/icon_container.py +19 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/shortcuts_help.py +2 -13
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/unsharp_mask_filter.py +3 -10
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/white_balance_filter.py +5 -13
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/PKG-INFO +10 -8
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/SOURCES.txt +3 -3
- shinestacker-0.3.4/img/coins.gif +0 -0
- shinestacker-0.3.4/img/coins_stack.jpg +0 -0
- shinestacker-0.3.4/img/gui-project-new.png +0 -0
- shinestacker-0.3.4/src/shinestacker/_version.py +0 -1
- shinestacker-0.3.4/src/shinestacker/algorithms/core_utils.py +0 -22
- shinestacker-0.3.4/src/shinestacker/app/app_config.py +0 -40
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.coveragerc +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.flake8 +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.github/workflows/pylint.yml +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.github/workflows/release.yml +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.gitignore +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.pylintrc +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/.readthedocs.yaml +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/LICENSE +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/MANIFEST.in +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/alignment.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/api.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/balancing.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/conf.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/focus_stacking.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/gui.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/index.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/job.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/multilayer.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/noise.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/requirements.txt +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/vignetting.md +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/coffee.gif +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/coffee_stack.jpg +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/flies.gif +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/flies_stack.jpg +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/flow-diagram.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/gui-finder.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/gui-project-run.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/img/gui-retouch.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/pyproject.toml +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/requirements.txt +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/scripts/build_release.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/scripts/validate-tomli.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/setup.cfg +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/__init__.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/utils.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/main.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/project.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/retouch.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/config/gui_constants.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/gui_run.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/main_window.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/brush_preview.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/display_manager.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/image_editor.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/image_editor_ui.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/image_filters.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/image_viewer.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/io_gui_handler.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/top_level.txt +0 -0
|
Binary file
|
|
@@ -4,6 +4,28 @@ This page reports the main releases only and the main changes therein.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [v-.-.-] - 2025-08-17
|
|
8
|
+
**Unreleased changes, for next release round**
|
|
9
|
+
|
|
10
|
+
### Changes
|
|
11
|
+
|
|
12
|
+
* fixed a bug that prevented a complete clean up when "New Project" action is called
|
|
13
|
+
* fixed the management of project file path while loading and saving
|
|
14
|
+
* removed duplicated code, code clean up
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## [v0.3.5] - 2025-08-17
|
|
19
|
+
**Bug fixes**
|
|
20
|
+
|
|
21
|
+
### Changes
|
|
22
|
+
|
|
23
|
+
* fixed a bug that prevented to add sub-actions
|
|
24
|
+
* vignetting constrains model parameter in order to prevent searching for dark areas at the center of the image instead of at periphery
|
|
25
|
+
* updated sample images and documentation
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
7
29
|
## [v0.3.4] - 2025-08-16
|
|
8
30
|
**Code consolidation and fixes**
|
|
9
31
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shinestacker
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: ShineStacker
|
|
5
5
|
Author-email: Luca Lista <luka.lista@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0
|
|
@@ -37,25 +37,22 @@ Dynamic: license-file
|
|
|
37
37
|
[](https://pypi.org/project/shinestacker/)
|
|
38
38
|
[](https://pypi.org/project/shinestacker/)
|
|
39
39
|
[](https://www.qt.io/qt-for-python)
|
|
40
|
-
[](https://codecov.io/github/lucalista/shinestacker)
|
|
41
40
|
[](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
|
|
41
|
+
[](https://codecov.io/github/lucalista/shinestacker)
|
|
42
42
|
[](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
|
|
46
45
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies_stack.jpg' width="400" referrerpolicy="no-referrer">
|
|
47
46
|
|
|
48
47
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee_stack.jpg' width="400" referrerpolicy="no-referrer">
|
|
49
48
|
|
|
50
|
-
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins_stack.jpg' width="400" referrerpolicy="no-referrer">
|
|
51
49
|
> **Focus stacking** for microscopy, macro photography, and computational imaging
|
|
52
50
|
|
|
53
51
|
## Key Features
|
|
54
52
|
- 🚀 **Batch Processing**: Align, balance, and stack hundreds of images
|
|
55
|
-
- 🎨 **Hybrid Workflows**: Combine Python scripting with GUI refinement
|
|
56
53
|
- 🧩 **Modular Architecture**: Mix-and-match processing modules
|
|
57
54
|
- 🖌️ **Retouch Editing**: Final interactive retouch of stacked image from individual frames
|
|
58
|
-
- 📊 **Jupyter Integration**:
|
|
55
|
+
- 📊 **Jupyter Integration**: Image processing python notebooks
|
|
59
56
|
|
|
60
57
|
## Interactive GUI
|
|
61
58
|
|
|
@@ -76,16 +73,21 @@ The GUI has two main working areas:
|
|
|
76
73
|
|
|
77
74
|
# Credits
|
|
78
75
|
|
|
79
|
-
The
|
|
76
|
+
The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
|
|
80
77
|
|
|
81
78
|
# Resources
|
|
82
79
|
|
|
83
80
|
* [Pyramid Methods in Image Processing](https://www.researchgate.net/publication/246727904_Pyramid_Methods_in_Image_Processing), E. H. Adelson, C. H. Anderson, J. R. Bergen, P. J. Burt, J. M. Ogden, RCA Engineer, 29-6, Nov/Dec 1984
|
|
84
81
|
Pyramid methods in image processing
|
|
85
82
|
* [A Multi-focus Image Fusion Method Based on Laplacian Pyramid](http://www.jcomputers.us/vol6/jcp0612-07.pdf), Wencheng Wang, Faliang Chang, Journal of Computers 6 (12), 2559, December 2011
|
|
86
|
-
* Another [original implementation on GitHub](https://github.com/bznick98/Focus_Stacking) by Zongnan Bao
|
|
87
83
|
|
|
88
84
|
# License
|
|
89
85
|
|
|
90
86
|
The software is provided as is under the [GNU Lesser General Public License v3.0](https://choosealicense.com/licenses/lgpl-3.0/).
|
|
91
87
|
|
|
88
|
+
# Attribution request
|
|
89
|
+
📸 If you publish images created with Shine Stacker, please consider adding a note such as:
|
|
90
|
+
|
|
91
|
+
*Created with Shine Stacker – https://github.com/lucalista/shinestacker*
|
|
92
|
+
|
|
93
|
+
This is not mandatory, but highly appreciated.
|
|
@@ -6,25 +6,22 @@
|
|
|
6
6
|
[](https://pypi.org/project/shinestacker/)
|
|
7
7
|
[](https://pypi.org/project/shinestacker/)
|
|
8
8
|
[](https://www.qt.io/qt-for-python)
|
|
9
|
-
[](https://codecov.io/github/lucalista/shinestacker)
|
|
10
9
|
[](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
|
|
10
|
+
[](https://codecov.io/github/lucalista/shinestacker)
|
|
11
11
|
[](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies_stack.jpg' width="400" referrerpolicy="no-referrer">
|
|
16
15
|
|
|
17
16
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee_stack.jpg' width="400" referrerpolicy="no-referrer">
|
|
18
17
|
|
|
19
|
-
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins_stack.jpg' width="400" referrerpolicy="no-referrer">
|
|
20
18
|
> **Focus stacking** for microscopy, macro photography, and computational imaging
|
|
21
19
|
|
|
22
20
|
## Key Features
|
|
23
21
|
- 🚀 **Batch Processing**: Align, balance, and stack hundreds of images
|
|
24
|
-
- 🎨 **Hybrid Workflows**: Combine Python scripting with GUI refinement
|
|
25
22
|
- 🧩 **Modular Architecture**: Mix-and-match processing modules
|
|
26
23
|
- 🖌️ **Retouch Editing**: Final interactive retouch of stacked image from individual frames
|
|
27
|
-
- 📊 **Jupyter Integration**:
|
|
24
|
+
- 📊 **Jupyter Integration**: Image processing python notebooks
|
|
28
25
|
|
|
29
26
|
## Interactive GUI
|
|
30
27
|
|
|
@@ -45,16 +42,21 @@ The GUI has two main working areas:
|
|
|
45
42
|
|
|
46
43
|
# Credits
|
|
47
44
|
|
|
48
|
-
The
|
|
45
|
+
The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
|
|
49
46
|
|
|
50
47
|
# Resources
|
|
51
48
|
|
|
52
49
|
* [Pyramid Methods in Image Processing](https://www.researchgate.net/publication/246727904_Pyramid_Methods_in_Image_Processing), E. H. Adelson, C. H. Anderson, J. R. Bergen, P. J. Burt, J. M. Ogden, RCA Engineer, 29-6, Nov/Dec 1984
|
|
53
50
|
Pyramid methods in image processing
|
|
54
51
|
* [A Multi-focus Image Fusion Method Based on Laplacian Pyramid](http://www.jcomputers.us/vol6/jcp0612-07.pdf), Wencheng Wang, Faliang Chang, Journal of Computers 6 (12), 2559, December 2011
|
|
55
|
-
* Another [original implementation on GitHub](https://github.com/bznick98/Focus_Stacking) by Zongnan Bao
|
|
56
52
|
|
|
57
53
|
# License
|
|
58
54
|
|
|
59
55
|
The software is provided as is under the [GNU Lesser General Public License v3.0](https://choosealicense.com/licenses/lgpl-3.0/).
|
|
60
56
|
|
|
57
|
+
# Attribution request
|
|
58
|
+
📸 If you publish images created with Shine Stacker, please consider adding a note such as:
|
|
59
|
+
|
|
60
|
+
*Created with Shine Stacker – https://github.com/lucalista/shinestacker*
|
|
61
|
+
|
|
62
|
+
This is not mandatory, but highly appreciated.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
THIRD-PARTY LICENSES
|
|
2
|
+
====================
|
|
3
|
+
|
|
4
|
+
This application bundles several third-party Python packages.
|
|
5
|
+
Each package is copyrighted by its respective authors and distributed under the following licenses.
|
|
6
|
+
Full license texts are reproduced below when required.
|
|
7
|
+
|
|
8
|
+
-------------------------------------------------------------------------------
|
|
9
|
+
matplotlib
|
|
10
|
+
License: PSF license + BSD style
|
|
11
|
+
https://matplotlib.org/stable/users/project/license.html
|
|
12
|
+
|
|
13
|
+
-------------------------------------------------------------------------------
|
|
14
|
+
imagecodecs
|
|
15
|
+
License: BSD-3-Clause
|
|
16
|
+
https://pypi.org/project/imagecodecs/
|
|
17
|
+
|
|
18
|
+
-------------------------------------------------------------------------------
|
|
19
|
+
jsonpickle
|
|
20
|
+
License: BSD-3-Clause
|
|
21
|
+
https://github.com/jsonpickle/jsonpickle
|
|
22
|
+
|
|
23
|
+
-------------------------------------------------------------------------------
|
|
24
|
+
numpy
|
|
25
|
+
License: BSD-3-Clause
|
|
26
|
+
https://numpy.org/
|
|
27
|
+
|
|
28
|
+
-------------------------------------------------------------------------------
|
|
29
|
+
opencv-python
|
|
30
|
+
License: Apache License 2.0
|
|
31
|
+
https://github.com/opencv/opencv-python
|
|
32
|
+
|
|
33
|
+
-------------------------------------------------------------------------------
|
|
34
|
+
pillow
|
|
35
|
+
License: Historical PIL Software License (similar to MIT)
|
|
36
|
+
https://python-pillow.org
|
|
37
|
+
|
|
38
|
+
-------------------------------------------------------------------------------
|
|
39
|
+
psdtags
|
|
40
|
+
License: MIT
|
|
41
|
+
https://pypi.org/project/psdtags/
|
|
42
|
+
|
|
43
|
+
-------------------------------------------------------------------------------
|
|
44
|
+
PySide6
|
|
45
|
+
License: GNU Lesser General Public License v3.0 (LGPL-3.0)
|
|
46
|
+
or commercial license from The Qt Company
|
|
47
|
+
https://doc.qt.io/qtforpython/
|
|
48
|
+
|
|
49
|
+
Full license text follows:
|
|
50
|
+
|
|
51
|
+
--- BEGIN LGPL-3.0 LICENSE ---
|
|
52
|
+
<qui incolla il testo completo della LGPL-3.0>
|
|
53
|
+
--- END LGPL-3.0 LICENSE ---
|
|
54
|
+
|
|
55
|
+
-------------------------------------------------------------------------------
|
|
56
|
+
scipy
|
|
57
|
+
License: BSD-3-Clause
|
|
58
|
+
https://scipy.org/
|
|
59
|
+
|
|
60
|
+
-------------------------------------------------------------------------------
|
|
61
|
+
tifffile
|
|
62
|
+
License: BSD-3-Clause
|
|
63
|
+
https://www.lfd.uci.edu/~gohlke/
|
|
64
|
+
|
|
65
|
+
-------------------------------------------------------------------------------
|
|
66
|
+
tqdm
|
|
67
|
+
License: Mozilla Public License 2.0 (MPL-2.0)
|
|
68
|
+
https://github.com/tqdm/tqdm
|
|
69
|
+
|
|
70
|
+
Full license text follows:
|
|
71
|
+
|
|
72
|
+
--- BEGIN MPL-2.0 LICENSE ---
|
|
73
|
+
<qui incolla il testo completo della MPL-2.0>
|
|
74
|
+
--- END MPL-2.0 LICENSE ---
|
|
75
|
+
|
|
76
|
+
-------------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
NOTE:
|
|
79
|
+
This file is provided for license compliance and attribution purposes.
|
|
80
|
+
Your application code is licensed separately under the GNU Lesser General Public License v3.0.
|
|
@@ -6,16 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee_stack.jpg' width="400" referrerpolicy="no-referrer">
|
|
8
8
|
|
|
9
|
-
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins_stack.jpg' width="400" referrerpolicy="no-referrer">
|
|
10
|
-
|
|
11
9
|
> **Focus stacking** for microscopy, macro photography, and computational imaging
|
|
12
10
|
|
|
13
11
|
## Key Features
|
|
14
12
|
- 🚀 **Batch Processing**: Align, balance, and stack hundreds of images
|
|
15
|
-
- 🎨 **Hybrid Workflows**: Combine Python scripting with GUI refinement
|
|
16
13
|
- 🧩 **Modular Architecture**: Mix-and-match processing modules
|
|
17
|
-
- 🖌️ **
|
|
18
|
-
- 📊 **Jupyter Integration**:
|
|
14
|
+
- 🖌️ **Retouch Editing**: Final interactive retouch of stacked image from individual frames
|
|
15
|
+
- 📊 **Jupyter Integration**: Image processing python notebooks
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
## Quick start
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.3.6'
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0602, R0903
|
|
2
2
|
import numpy as np
|
|
3
|
-
from .. core.colors import color_str
|
|
4
3
|
from .. core.exceptions import InvalidOptionError, ImageLoadError
|
|
5
4
|
from .. config.constants import constants
|
|
6
5
|
from .utils import read_img, get_img_metadata, validate_image
|
|
@@ -28,7 +27,7 @@ class BaseStackAlgo:
|
|
|
28
27
|
return self._steps_per_frame
|
|
29
28
|
|
|
30
29
|
def print_message(self, msg):
|
|
31
|
-
self.process.sub_message_r(
|
|
30
|
+
self.process.sub_message_r(msg)
|
|
32
31
|
|
|
33
32
|
def read_image_and_update_metadata(self, img_path, metadata):
|
|
34
33
|
img = read_img(img_path)
|
|
@@ -162,9 +162,6 @@ class MaskNoise(SubAction):
|
|
|
162
162
|
else:
|
|
163
163
|
raise ImageLoadError(path, "file not found.")
|
|
164
164
|
|
|
165
|
-
def end(self):
|
|
166
|
-
pass
|
|
167
|
-
|
|
168
165
|
def run_frame(self, _idx, _ref_idx, image):
|
|
169
166
|
self.process.sub_message_r(': mask noisy pixels')
|
|
170
167
|
if len(image.shape) == 3:
|
|
@@ -12,7 +12,7 @@ class PyramidBase(BaseStackAlgo):
|
|
|
12
12
|
kernel_size=constants.DEFAULT_PY_KERNEL_SIZE,
|
|
13
13
|
gen_kernel=constants.DEFAULT_PY_GEN_KERNEL,
|
|
14
14
|
float_type=constants.DEFAULT_PY_FLOAT):
|
|
15
|
-
super().__init__("pyramid",
|
|
15
|
+
super().__init__("pyramid", 2, float_type)
|
|
16
16
|
self.min_size = min_size
|
|
17
17
|
self.kernel_size = kernel_size
|
|
18
18
|
self.pad_amount = (kernel_size - 1) // 2
|
|
@@ -151,11 +151,11 @@ class PyramidStack(PyramidBase):
|
|
|
151
151
|
metadata = None
|
|
152
152
|
all_laplacians = []
|
|
153
153
|
levels = None
|
|
154
|
+
n = len(filenames)
|
|
154
155
|
for i, img_path in enumerate(filenames):
|
|
155
156
|
self.print_message(f": validating file {img_path.split('/')[-1]}")
|
|
156
157
|
|
|
157
158
|
img, metadata, updated = self.read_image_and_update_metadata(img_path, metadata)
|
|
158
|
-
|
|
159
159
|
if updated:
|
|
160
160
|
self.dtype = metadata[1]
|
|
161
161
|
self.num_pixel_values = constants.NUM_UINT8 \
|
|
@@ -163,14 +163,17 @@ class PyramidStack(PyramidBase):
|
|
|
163
163
|
self.max_pixel_value = constants.MAX_UINT8 \
|
|
164
164
|
if self.dtype == np.uint8 else constants.MAX_UINT16
|
|
165
165
|
levels = int(np.log2(min(img.shape[:2]) / self.min_size))
|
|
166
|
-
|
|
167
166
|
if self.do_step_callback:
|
|
168
167
|
self.process.callback('after_step', self.process.id, self.process.name, i)
|
|
169
168
|
if self.process.callback('check_running', self.process.id, self.process.name) is False:
|
|
170
169
|
raise RunStopException(self.name)
|
|
171
|
-
for img_path in filenames:
|
|
170
|
+
for i, img_path in enumerate(filenames):
|
|
172
171
|
self.print_message(f": processing file {img_path.split('/')[-1]}")
|
|
173
172
|
img = read_img(img_path)
|
|
174
173
|
all_laplacians.append(self.process_single_image(img, levels))
|
|
174
|
+
if self.do_step_callback:
|
|
175
|
+
self.process.callback('after_step', self.process.id, self.process.name, i + n)
|
|
176
|
+
if self.process.callback('check_running', self.process.id, self.process.name) is False:
|
|
177
|
+
raise RunStopException(self.name)
|
|
175
178
|
stacked_image = self.collapse(self.fuse_pyramids(all_laplacians))
|
|
176
179
|
return stacked_image.astype(self.dtype)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import os
|
|
3
3
|
import numpy as np
|
|
4
4
|
from .. config.constants import constants
|
|
5
|
-
from .. core.colors import color_str
|
|
6
5
|
from .. core.framework import JobBase
|
|
7
6
|
from .. core.exceptions import InvalidOptionError
|
|
8
7
|
from .utils import write_img
|
|
@@ -92,7 +91,7 @@ class FocusStackBunch(ActionList, FocusStackBase):
|
|
|
92
91
|
ActionList.end(self)
|
|
93
92
|
|
|
94
93
|
def run_step(self):
|
|
95
|
-
self.print_message_r(
|
|
94
|
+
self.print_message_r(f"fusing bunch: {self.count}")
|
|
96
95
|
self.focus_stack(self._chunks[self.count - 1])
|
|
97
96
|
self.callback('after_step', self.id, self.name, self.count)
|
|
98
97
|
|
|
@@ -237,6 +237,12 @@ class SubAction:
|
|
|
237
237
|
def __init__(self, enabled=True):
|
|
238
238
|
self.enabled = enabled
|
|
239
239
|
|
|
240
|
+
def begin(self, process):
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
def end(self):
|
|
244
|
+
pass
|
|
245
|
+
|
|
240
246
|
|
|
241
247
|
class CombinedActions(FramesRefActions):
|
|
242
248
|
def __init__(self, name, actions=[], enabled=True, **kwargs):
|
|
@@ -255,10 +261,10 @@ class CombinedActions(FramesRefActions):
|
|
|
255
261
|
filename = self.filenames[idx]
|
|
256
262
|
img = read_img((self.output_dir
|
|
257
263
|
if self.step_process else self.input_full_path) + f"/{filename}")
|
|
258
|
-
self.dtype = img.dtype
|
|
259
|
-
self.shape = img.shape
|
|
260
264
|
if img is None:
|
|
261
265
|
raise RuntimeError(f"Invalid file: {self.input_full_path}/{filename}")
|
|
266
|
+
self.dtype = img.dtype
|
|
267
|
+
self.shape = img.shape
|
|
262
268
|
return img
|
|
263
269
|
|
|
264
270
|
def run_frame(self, idx, ref_idx):
|
|
@@ -59,7 +59,8 @@ class Vignetting(SubAction):
|
|
|
59
59
|
i_valid, r_valid = intensities[valid_mask], radii[valid_mask]
|
|
60
60
|
try:
|
|
61
61
|
res = curve_fit(Vignetting.sigmoid, r_valid, i_valid,
|
|
62
|
-
p0=[np.max(i_valid), 0.01, np.median(r_valid)]
|
|
62
|
+
p0=[np.max(i_valid), 0.01, np.median(r_valid)],
|
|
63
|
+
bounds=([0, 0, 0], ['inf', 'inf', 'inf']))[0]
|
|
63
64
|
except Exception:
|
|
64
65
|
self.process.sub_message(
|
|
65
66
|
color_str(": could not find vignetting model", "red"),
|
|
@@ -83,7 +84,7 @@ class Vignetting(SubAction):
|
|
|
83
84
|
if image.dtype == np.uint8 else 65535).astype(image.dtype)
|
|
84
85
|
|
|
85
86
|
def run_frame(self, idx, _ref_idx, img_0):
|
|
86
|
-
self.process.sub_message_r(
|
|
87
|
+
self.process.sub_message_r(": compute vignetting")
|
|
87
88
|
img = cv2.cvtColor(img_8bit(img_0), cv2.COLOR_BGR2GRAY)
|
|
88
89
|
radii, intensities = self.radial_mean_intensity(img)
|
|
89
90
|
pars = self.fit_sigmoid(radii, intensities)
|
|
@@ -92,7 +93,7 @@ class Vignetting(SubAction):
|
|
|
92
93
|
self.v0 = Vignetting.sigmoid(0, *pars)
|
|
93
94
|
i0_fit, k_fit, r0_fit = pars
|
|
94
95
|
self.process.sub_message(
|
|
95
|
-
f":
|
|
96
|
+
f": vignetting model parameters: i0={i0_fit:.4f}, k={k_fit:.4f}, r0={r0_fit:.4f}",
|
|
96
97
|
level=logging.DEBUG)
|
|
97
98
|
if self.plot_correction:
|
|
98
99
|
plt.figure(figsize=(10, 5))
|
|
@@ -116,7 +117,7 @@ class Vignetting(SubAction):
|
|
|
116
117
|
self.corrections[i][idx] = fsolve(lambda x: Vignetting.sigmoid(x, *pars) /
|
|
117
118
|
self.v0 - p, r0_fit)[0]
|
|
118
119
|
if self.apply_correction:
|
|
119
|
-
self.process.sub_message_r(
|
|
120
|
+
self.process.sub_message_r(": correct vignetting")
|
|
120
121
|
return self.correct_vignetting(img_0, pars)
|
|
121
122
|
return img_0
|
|
122
123
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, C0103, W0201
|
|
2
|
+
from .. config.config import _ConfigBase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class _AppConfig(_ConfigBase):
|
|
6
|
+
def __new__(cls):
|
|
7
|
+
return _ConfigBase.__new__(cls)
|
|
8
|
+
|
|
9
|
+
def _init_defaults(self):
|
|
10
|
+
self._DONT_USE_NATIVE_MENU = True
|
|
11
|
+
self._COMBINED_APP = False
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def DONT_USE_NATIVE_MENU(self):
|
|
15
|
+
return self._DONT_USE_NATIVE_MENU
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def COMBINED_APP(self):
|
|
19
|
+
return self._COMBINED_APP
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
app_config = _AppConfig()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, C0103, R0903, W0718, W0104, W0201, E0602
|
|
2
|
-
class
|
|
2
|
+
class _ConfigBase:
|
|
3
3
|
_initialized = False
|
|
4
4
|
_instance = None
|
|
5
5
|
|
|
@@ -9,16 +9,6 @@ class _Config:
|
|
|
9
9
|
cls._instance._init_defaults()
|
|
10
10
|
return cls._instance
|
|
11
11
|
|
|
12
|
-
def _init_defaults(self):
|
|
13
|
-
self._DISABLE_TQDM = False
|
|
14
|
-
self._COMBINED_APP = False
|
|
15
|
-
self._DONT_USE_NATIVE_MENU = True
|
|
16
|
-
try:
|
|
17
|
-
__IPYTHON__ # noqa
|
|
18
|
-
self._JUPYTER_NOTEBOOK = True
|
|
19
|
-
except Exception:
|
|
20
|
-
self._JUPYTER_NOTEBOOK = False
|
|
21
|
-
|
|
22
12
|
def init(self, **kwargs):
|
|
23
13
|
if self._initialized:
|
|
24
14
|
raise RuntimeError("Config already initialized")
|
|
@@ -29,6 +19,26 @@ class _Config:
|
|
|
29
19
|
raise AttributeError(f"Invalid config key: {k}")
|
|
30
20
|
self._initialized = True
|
|
31
21
|
|
|
22
|
+
def __setattr__(self, name, value):
|
|
23
|
+
if self._initialized and name.startswith('_'):
|
|
24
|
+
raise AttributeError("Can't change config after initialization")
|
|
25
|
+
super().__setattr__(name, value)
|
|
26
|
+
|
|
27
|
+
class _Config(_ConfigBase):
|
|
28
|
+
|
|
29
|
+
def __new__(cls):
|
|
30
|
+
return _ConfigBase.__new__(cls)
|
|
31
|
+
|
|
32
|
+
def _init_defaults(self):
|
|
33
|
+
self._DISABLE_TQDM = False
|
|
34
|
+
self._COMBINED_APP = False
|
|
35
|
+
self._DONT_USE_NATIVE_MENU = True
|
|
36
|
+
try:
|
|
37
|
+
__IPYTHON__ # noqa
|
|
38
|
+
self._JUPYTER_NOTEBOOK = True
|
|
39
|
+
except Exception:
|
|
40
|
+
self._JUPYTER_NOTEBOOK = False
|
|
41
|
+
|
|
32
42
|
@property
|
|
33
43
|
def DISABLE_TQDM(self):
|
|
34
44
|
return self._DISABLE_TQDM
|
|
@@ -45,10 +55,5 @@ class _Config:
|
|
|
45
55
|
def COMBINED_APP(self):
|
|
46
56
|
return self._COMBINED_APP
|
|
47
57
|
|
|
48
|
-
def __setattr__(self, name, value):
|
|
49
|
-
if self._initialized and name.startswith('_'):
|
|
50
|
-
raise AttributeError("Can't change config after initialization")
|
|
51
|
-
super().__setattr__(name, value)
|
|
52
|
-
|
|
53
58
|
|
|
54
59
|
config = _Config()
|
|
@@ -10,8 +10,10 @@ from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QFileDialog, Q
|
|
|
10
10
|
QAbstractItemView, QListView)
|
|
11
11
|
from PySide6.QtCore import Qt, QTimer
|
|
12
12
|
from .. config.constants import constants
|
|
13
|
-
from .project_model import ActionConfig
|
|
14
13
|
from .. algorithms.align import validate_align_config
|
|
14
|
+
from .project_model import ActionConfig
|
|
15
|
+
from .select_path_widget import (create_select_file_paths_widget, create_layout_widget_no_margins,
|
|
16
|
+
create_layout_widget_and_connect)
|
|
15
17
|
|
|
16
18
|
FIELD_TEXT = 'text'
|
|
17
19
|
FIELD_ABS_PATH = 'abs_path'
|
|
@@ -200,25 +202,11 @@ class FieldBuilder:
|
|
|
200
202
|
return edit
|
|
201
203
|
|
|
202
204
|
def create_abs_path_field(self, tag, **kwargs):
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def browse():
|
|
209
|
-
path = QFileDialog.getExistingDirectory(None, f"Select {tag.replace('_', ' ')}")
|
|
210
|
-
if path:
|
|
211
|
-
edit.setText(path)
|
|
212
|
-
button.clicked.connect(browse)
|
|
213
|
-
button.setAutoDefault(False)
|
|
214
|
-
layout = QHBoxLayout()
|
|
215
|
-
layout.addWidget(edit)
|
|
216
|
-
layout.addWidget(button)
|
|
217
|
-
layout.setContentsMargins(0, 0, 0, 0)
|
|
218
|
-
container = QWidget()
|
|
219
|
-
container.setLayout(layout)
|
|
220
|
-
container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
221
|
-
return container
|
|
205
|
+
return create_select_file_paths_widget(
|
|
206
|
+
self.action.params.get(tag, ''),
|
|
207
|
+
kwargs.get('placeholder', ''),
|
|
208
|
+
tag.replace('_', ' ')
|
|
209
|
+
)
|
|
222
210
|
|
|
223
211
|
def create_rel_path_field(self, tag, **kwargs):
|
|
224
212
|
value = self.action.params.get(tag, kwargs.get('default', ''))
|
|
@@ -326,17 +314,8 @@ class FieldBuilder:
|
|
|
326
314
|
except ValueError as e:
|
|
327
315
|
traceback.print_tb(e.__traceback__)
|
|
328
316
|
QMessageBox.warning(None, "Error", "Could not compute relative path")
|
|
317
|
+
return create_layout_widget_and_connect(button, edit, browse)
|
|
329
318
|
|
|
330
|
-
button.clicked.connect(browse)
|
|
331
|
-
button.setAutoDefault(False)
|
|
332
|
-
layout = QHBoxLayout()
|
|
333
|
-
layout.addWidget(edit)
|
|
334
|
-
layout.addWidget(button)
|
|
335
|
-
layout.setContentsMargins(0, 0, 0, 0)
|
|
336
|
-
container = QWidget()
|
|
337
|
-
container.setLayout(layout)
|
|
338
|
-
container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
339
|
-
return container
|
|
340
319
|
|
|
341
320
|
def create_float_field(self, tag, default=0.0, min_val=0.0, max_val=1.0,
|
|
342
321
|
step=0.1, decimals=2):
|
|
@@ -369,11 +348,7 @@ class FieldBuilder:
|
|
|
369
348
|
layout.addWidget(label)
|
|
370
349
|
layout.addWidget(spin)
|
|
371
350
|
layout.setStretch(layout.count() - 1, 1)
|
|
372
|
-
layout
|
|
373
|
-
container = QWidget()
|
|
374
|
-
container.setLayout(layout)
|
|
375
|
-
container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
376
|
-
return container
|
|
351
|
+
return create_layout_widget_no_margins(layout)
|
|
377
352
|
|
|
378
353
|
def create_combo_field(self, tag, options=None, default=None, **kwargs):
|
|
379
354
|
options = options or []
|