shinestacker 0.3.6__tar.gz → 0.4.0__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.

Files changed (133) hide show
  1. {shinestacker-0.3.6 → shinestacker-0.4.0}/CHANGELOG.md +19 -3
  2. shinestacker-0.4.0/LICENSE +165 -0
  3. {shinestacker-0.3.6 → shinestacker-0.4.0}/PKG-INFO +5 -4
  4. {shinestacker-0.3.6 → shinestacker-0.4.0}/README.md +4 -3
  5. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/alignment.md +4 -2
  6. shinestacker-0.4.0/src/shinestacker/_version.py +1 -0
  7. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/align.py +37 -20
  8. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/balance.py +2 -1
  9. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/base_stack_algo.py +2 -1
  10. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/multilayer.py +11 -8
  11. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/noise_detection.py +13 -7
  12. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/stack.py +5 -4
  13. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/stack_framework.py +12 -10
  14. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/main.py +1 -1
  15. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/config/config.py +1 -0
  16. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/config/constants.py +8 -1
  17. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/core/framework.py +15 -10
  18. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/action_config.py +11 -7
  19. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/gui_logging.py +8 -7
  20. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/gui_run.py +8 -8
  21. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/main_window.py +4 -4
  22. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/new_project.py +31 -17
  23. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/project_converter.py +0 -1
  24. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/select_path_widget.py +3 -1
  25. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/image_editor.py +1 -1
  26. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/image_editor_ui.py +2 -1
  27. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/image_viewer.py +104 -20
  28. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/io_gui_handler.py +17 -16
  29. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/io_manager.py +0 -1
  30. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/layer_collection.py +2 -1
  31. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker.egg-info/PKG-INFO +5 -4
  32. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker.egg-info/SOURCES.txt +0 -1
  33. shinestacker-0.3.6/.coverage +0 -0
  34. shinestacker-0.3.6/LICENSE +0 -1
  35. shinestacker-0.3.6/src/shinestacker/_version.py +0 -1
  36. {shinestacker-0.3.6 → shinestacker-0.4.0}/.coveragerc +0 -0
  37. {shinestacker-0.3.6 → shinestacker-0.4.0}/.flake8 +0 -0
  38. {shinestacker-0.3.6 → shinestacker-0.4.0}/.github/workflows/ci-multiplatform.yml +0 -0
  39. {shinestacker-0.3.6 → shinestacker-0.4.0}/.github/workflows/pylint.yml +0 -0
  40. {shinestacker-0.3.6 → shinestacker-0.4.0}/.github/workflows/pypi-publish.yml +0 -0
  41. {shinestacker-0.3.6 → shinestacker-0.4.0}/.github/workflows/release.yml +0 -0
  42. {shinestacker-0.3.6 → shinestacker-0.4.0}/.gitignore +0 -0
  43. {shinestacker-0.3.6 → shinestacker-0.4.0}/.pylintrc +0 -0
  44. {shinestacker-0.3.6 → shinestacker-0.4.0}/.readthedocs.yaml +0 -0
  45. {shinestacker-0.3.6 → shinestacker-0.4.0}/MANIFEST.in +0 -0
  46. {shinestacker-0.3.6 → shinestacker-0.4.0}/THIRD_PARTY_LICENSES.txt +0 -0
  47. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/api.md +0 -0
  48. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/balancing.md +0 -0
  49. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/conf.py +0 -0
  50. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/focus_stacking.md +0 -0
  51. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/gui.md +0 -0
  52. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/index.md +0 -0
  53. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/job.md +0 -0
  54. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/main.md +0 -0
  55. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/multilayer.md +0 -0
  56. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/noise.md +0 -0
  57. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/requirements.txt +0 -0
  58. {shinestacker-0.3.6 → shinestacker-0.4.0}/docs/vignetting.md +0 -0
  59. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/coffee.gif +0 -0
  60. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/coffee_stack.jpg +0 -0
  61. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/extreme-vignetting.jpg +0 -0
  62. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/flies.gif +0 -0
  63. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/flies_stack.jpg +0 -0
  64. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/flow-diagram.png +0 -0
  65. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/gui-finder.png +0 -0
  66. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/gui-project-new.png +0 -0
  67. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/gui-project-run.png +0 -0
  68. {shinestacker-0.3.6 → shinestacker-0.4.0}/img/gui-retouch.png +0 -0
  69. {shinestacker-0.3.6 → shinestacker-0.4.0}/pyproject.toml +0 -0
  70. {shinestacker-0.3.6 → shinestacker-0.4.0}/requirements.txt +0 -0
  71. {shinestacker-0.3.6 → shinestacker-0.4.0}/scripts/build_release.py +0 -0
  72. {shinestacker-0.3.6 → shinestacker-0.4.0}/scripts/validate-tomli.py +0 -0
  73. {shinestacker-0.3.6 → shinestacker-0.4.0}/setup.cfg +0 -0
  74. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/__init__.py +0 -0
  75. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/__init__.py +0 -0
  76. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/denoise.py +0 -0
  77. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/depth_map.py +0 -0
  78. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/exif.py +0 -0
  79. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/pyramid.py +0 -0
  80. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/sharpen.py +0 -0
  81. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/utils.py +0 -0
  82. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/vignetting.py +0 -0
  83. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/algorithms/white_balance.py +0 -0
  84. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/__init__.py +0 -0
  85. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/about_dialog.py +0 -0
  86. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/app_config.py +0 -0
  87. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/gui_utils.py +0 -0
  88. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/help_menu.py +0 -0
  89. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/open_frames.py +0 -0
  90. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/project.py +0 -0
  91. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/app/retouch.py +0 -0
  92. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/config/__init__.py +0 -0
  93. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/config/gui_constants.py +0 -0
  94. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/core/__init__.py +0 -0
  95. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/core/colors.py +0 -0
  96. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/core/core_utils.py +0 -0
  97. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/core/exceptions.py +0 -0
  98. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/core/logging.py +0 -0
  99. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/__init__.py +0 -0
  100. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/actions_window.py +0 -0
  101. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/colors.py +0 -0
  102. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/gui_images.py +0 -0
  103. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  104. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  105. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  106. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  107. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  108. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  109. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  110. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  111. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/project_editor.py +0 -0
  112. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/gui/project_model.py +0 -0
  113. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/__init__.py +0 -0
  114. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/base_filter.py +0 -0
  115. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/brush.py +0 -0
  116. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
  117. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/brush_preview.py +0 -0
  118. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/brush_tool.py +0 -0
  119. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/denoise_filter.py +0 -0
  120. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/display_manager.py +0 -0
  121. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/exif_data.py +0 -0
  122. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/file_loader.py +0 -0
  123. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/filter_manager.py +0 -0
  124. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/icon_container.py +0 -0
  125. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/image_filters.py +0 -0
  126. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/shortcuts_help.py +0 -0
  127. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/undo_manager.py +0 -0
  128. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
  129. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
  130. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  131. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
  132. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker.egg-info/requires.txt +0 -0
  133. {shinestacker-0.3.6 → shinestacker-0.4.0}/src/shinestacker.egg-info/top_level.txt +0 -0
@@ -4,14 +4,30 @@ 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**
7
+ ## [v0.4.0] - 2025-08-19
8
+ **Support touchpad navigation**
9
+
10
+ ### Changes
11
+
12
+ * implemented touchpad image navigation (pan, zoom with pinch)
13
+ * alignment robustness: retry without subsampling if number of bood matches is below a threshold parameter
14
+ * added more robust path management in retouch area
15
+ * added frame count display in "New Project" dialog
16
+ * more unifrom color code in GUI run log
17
+ * code clanup, removed remnants of obsolete code
18
+ * various fixes
19
+
20
+ ---
21
+
22
+ ## [v0.3.6] - 2025-08-18
23
+ **Bug fixes**
9
24
 
10
25
  ### Changes
11
26
 
12
27
  * fixed a bug that prevented a complete clean up when "New Project" action is called
13
28
  * fixed the management of project file path while loading and saving
14
- * removed duplicated code, code clean up
29
+ * removed duplicated code
30
+ * some code clean up
15
31
 
16
32
  ---
17
33
 
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 0.3.6
3
+ Version: 0.4.0
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -37,10 +37,10 @@ Dynamic: license-file
37
37
  [![PyPI version](https://img.shields.io/pypi/v/shinestacker?color=success)](https://pypi.org/project/shinestacker/)
38
38
  [![Python Versions](https://img.shields.io/pypi/pyversions/shinestacker)](https://pypi.org/project/shinestacker/)
39
39
  [![Qt Versions](https://img.shields.io/badge/Qt-6-blue.svg?&logo=Qt&logoWidth=18&logoColor=white)](https://www.qt.io/qt-for-python)
40
- [![pylint](https://img.shields.io/badge/PyLint-9.98-yellow?logo=python&logoColor=white)](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
40
+ [![pylint](https://img.shields.io/badge/PyLint-10.00-brightgreen?logo=python&logoColor=white)](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
41
41
  [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
42
42
  [![Documentation Status](https://readthedocs.org/projects/shinestacker/badge/?version=latest)](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
43
-
43
+ [![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
44
44
 
45
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">
46
46
 
@@ -83,7 +83,8 @@ Pyramid methods in image processing
83
83
 
84
84
  # License
85
85
 
86
- The software is provided as is under the [GNU Lesser General Public License v3.0](https://choosealicense.com/licenses/lgpl-3.0/).
86
+ <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
87
+ The software is provided as is under the [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html). See [LICENSE](https://github.com/lucalista/shinestacker/blob/main/LICENSE) for details.
87
88
 
88
89
  # Attribution request
89
90
  📸 If you publish images created with Shine Stacker, please consider adding a note such as:
@@ -6,10 +6,10 @@
6
6
  [![PyPI version](https://img.shields.io/pypi/v/shinestacker?color=success)](https://pypi.org/project/shinestacker/)
7
7
  [![Python Versions](https://img.shields.io/pypi/pyversions/shinestacker)](https://pypi.org/project/shinestacker/)
8
8
  [![Qt Versions](https://img.shields.io/badge/Qt-6-blue.svg?&logo=Qt&logoWidth=18&logoColor=white)](https://www.qt.io/qt-for-python)
9
- [![pylint](https://img.shields.io/badge/PyLint-9.98-yellow?logo=python&logoColor=white)](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
9
+ [![pylint](https://img.shields.io/badge/PyLint-10.00-brightgreen?logo=python&logoColor=white)](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
10
10
  [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
11
11
  [![Documentation Status](https://readthedocs.org/projects/shinestacker/badge/?version=latest)](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
12
-
12
+ [![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
13
13
 
14
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">
15
15
 
@@ -52,7 +52,8 @@ Pyramid methods in image processing
52
52
 
53
53
  # License
54
54
 
55
- The software is provided as is under the [GNU Lesser General Public License v3.0](https://choosealicense.com/licenses/lgpl-3.0/).
55
+ <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
56
+ The software is provided as is under the [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html). See [LICENSE](https://github.com/lucalista/shinestacker/blob/main/LICENSE) for details.
56
57
 
57
58
  # Attribution request
58
59
  📸 If you publish images created with Shine Stacker, please consider adding a note such as:
@@ -66,7 +66,8 @@ alignment_config = {
66
66
  'border_value': (0, 0, 0, 0),
67
67
  'border_blur': 50,
68
68
  'subsample': 1,
69
- 'fast_subsampling': False
69
+ 'fast_subsampling': False,
70
+ 'min_good_matches': 100
70
71
  }
71
72
  ```
72
73
  * ```transform``` (optional, default: ```ALIGN_RIGID```): the transformation applied to register images. Possible values are:
@@ -80,7 +81,8 @@ alignment_config = {
80
81
  * ```align_confidence``` (optional, default: 99.9): alignment algorithm confidence (%). Used only if ```transform=ALIGN_RIGID```.
81
82
  * ```max_iters``` (optional, default: 2000): maximum number of iterations. Used only if ```transform=ALIGN_HOMOGRAPHY```.
82
83
  * ```subsample``` (optional, default: 1): subsample image for faster alignment. Faster, but alignment could be less accurate. It can save time, in particular for large images.
83
- * ```fast_subsampling``` (optiona, default: ```False```): perform fast image subsampling without interpolation. Used if ```subsample``` is set to ```True```.
84
+ * ```fast_subsampling``` (optional, default: ```False```): perform fast image subsampling without interpolation. Used if ```subsample``` is set to ```True```.
85
+ * ```min_good_matches``` (optional, default: 100): if ```subsample```>1 and the number of good matches is below ```min_good_matches```, the alignment is retried without subsampling. This improbes robustness in case a too large subsampling factor is specified.
84
86
  * ```border_mode``` (optional, default: ```BORDER_REPLICATE_BLUR```): border mode. See [Adding borders to your images](https://docs.opencv.org/3.4/dc/da3/tutorial_copyMakeBorder.html) for more details. Possible values are:
85
87
  * ```BORDER_CONSTANT```: pad the image with a constant value. The border value is specified with the parameter ```border_value```.
86
88
  * ```BORDER_REPLICATE```: the rows and columns at the very edge of the original are replicated to the extra border.
@@ -0,0 +1 @@
1
+ __version__ = '0.4.0'
@@ -5,6 +5,7 @@ import matplotlib.pyplot as plt
5
5
  import cv2
6
6
  from .. config.constants import constants
7
7
  from .. core.exceptions import AlignmentError, InvalidOptionError
8
+ from .. core.colors import color_str
8
9
  from .utils import img_8bit, img_bw_8bit, save_plot
9
10
  from .utils import get_img_metadata, validate_image
10
11
  from .stack_framework import SubAction
@@ -33,7 +34,8 @@ _DEFAULT_ALIGNMENT_CONFIG = {
33
34
  'border_value': constants.DEFAULT_BORDER_VALUE,
34
35
  'border_blur': constants.DEFAULT_BORDER_BLUR,
35
36
  'subsample': constants.DEFAULT_ALIGN_SUBSAMPLE,
36
- 'fast_subsampling': constants.DEFAULT_ALIGN_FAST_SUBSAMPLING
37
+ 'fast_subsampling': constants.DEFAULT_ALIGN_FAST_SUBSAMPLING,
38
+ 'min_good_matches': constants.DEFAULT_ALIGN_MIN_GOOD_MATCHES
37
39
  }
38
40
 
39
41
 
@@ -164,21 +166,31 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
164
166
  if callbacks and 'message' in callbacks:
165
167
  callbacks['message']()
166
168
  subsample = alignment_config['subsample']
167
- if subsample > 1:
168
- if alignment_config['fast_subsampling']:
169
- img_0_sub, img_1_sub = img_0[::subsample, ::subsample], img_1[::subsample, ::subsample]
169
+ min_good_matches = alignment_config['min_good_matches']
170
+ while True:
171
+ if subsample > 1:
172
+ if alignment_config['fast_subsampling']:
173
+ img_0_sub, img_1_sub = \
174
+ img_0[::subsample, ::subsample], img_1[::subsample, ::subsample]
175
+ else:
176
+ img_0_sub = cv2.resize(img_0, (0, 0),
177
+ fx=1 / subsample, fy=1 / subsample,
178
+ interpolation=cv2.INTER_AREA)
179
+ img_1_sub = cv2.resize(img_1, (0, 0),
180
+ fx=1 / subsample, fy=1 / subsample,
181
+ interpolation=cv2.INTER_AREA)
170
182
  else:
171
- img_0_sub = cv2.resize(img_0, (0, 0),
172
- fx=1 / subsample, fy=1 / subsample,
173
- interpolation=cv2.INTER_AREA)
174
- img_1_sub = cv2.resize(img_1, (0, 0),
175
- fx=1 / subsample, fy=1 / subsample,
176
- interpolation=cv2.INTER_AREA)
177
- else:
178
- img_0_sub, img_1_sub = img_0, img_1
179
- kp_0, kp_1, good_matches = detect_and_compute(img_0_sub, img_1_sub,
180
- feature_config, matching_config)
181
- n_good_matches = len(good_matches)
183
+ img_0_sub, img_1_sub = img_0, img_1
184
+ kp_0, kp_1, good_matches = detect_and_compute(img_0_sub, img_1_sub,
185
+ feature_config, matching_config)
186
+ n_good_matches = len(good_matches)
187
+ if n_good_matches > min_good_matches or subsample == 1:
188
+ break
189
+ subsample = 1
190
+ if callbacks and 'warning' in callbacks:
191
+ callbacks['warning'](
192
+ f"only {n_good_matches} < {min_good_matches} matches found, "
193
+ "retrying without subsampling")
182
194
  if callbacks and 'matches_message' in callbacks:
183
195
  callbacks['matches_message'](n_good_matches)
184
196
  img_warp = None
@@ -277,14 +289,19 @@ class AlignFrames(SubAction):
277
289
  img_ref = self.process.img_ref(ref_idx)
278
290
  return self.align_images(idx, img_ref, img_0)
279
291
 
292
+ def sub_msg(self, msg, color=constants.LOG_COLOR_LEVEL_3):
293
+ self.process.sub_message_r(color_str(msg, color))
294
+
280
295
  def align_images(self, idx, img_1, img_0):
281
296
  idx_str = f"{idx:04d}"
282
297
  callbacks = {
283
- 'message': lambda: self.process.sub_message_r(': find matches'),
284
- 'matches_message': lambda n: self.process.sub_message_r(f": matches: {n}"),
285
- 'align_message': lambda: self.process.sub_message_r(': align images'),
286
- 'ecc_message': lambda: self.process.sub_message_r(": ecc refinement"),
287
- 'blur_message': lambda: self.process.sub_message_r(': blur borders'),
298
+ 'message': lambda: self.sub_msg(': find matches'),
299
+ 'matches_message': lambda n: self.sub_msg(f": good matches: {n}"),
300
+ 'align_message': lambda: self.sub_msg(': align images'),
301
+ 'ecc_message': lambda: self.sub_msg(": ecc refinement"),
302
+ 'blur_message': lambda: self.sub_msg(': blur borders'),
303
+ 'warning': lambda msg: self.sub_msg(
304
+ f': {msg}', constants.LOG_COLOR_ALERT),
288
305
  'save_plot': lambda plot_path: self.process.callback(
289
306
  'save_plot', self.process.id,
290
307
  f"{self.process.name}: matches\nframe {idx_str}", plot_path)
@@ -6,6 +6,7 @@ from scipy.optimize import bisect
6
6
  from scipy.interpolate import interp1d
7
7
  from .. config.constants import constants
8
8
  from .. core.exceptions import InvalidOptionError
9
+ from .. core.colors import color_str
9
10
  from .utils import read_img, save_plot
10
11
  from .stack_framework import SubAction
11
12
 
@@ -408,6 +409,6 @@ class BalanceFrames(SubAction):
408
409
 
409
410
  def run_frame(self, idx, _ref_idx, image):
410
411
  if idx != self.process.ref_idx:
411
- self.process.sub_message_r(': balance image')
412
+ self.process.sub_message_r(color_str(': balance image', constants.LOG_COLOR_LEVEL_3))
412
413
  image = self.correction.apply_correction(idx, image)
413
414
  return image
@@ -2,6 +2,7 @@
2
2
  import numpy as np
3
3
  from .. core.exceptions import InvalidOptionError, ImageLoadError
4
4
  from .. config.constants import constants
5
+ from .. core.colors import color_str
5
6
  from .utils import read_img, get_img_metadata, validate_image
6
7
 
7
8
 
@@ -27,7 +28,7 @@ class BaseStackAlgo:
27
28
  return self._steps_per_frame
28
29
 
29
30
  def print_message(self, msg):
30
- self.process.sub_message_r(msg)
31
+ self.process.sub_message_r(color_str(msg, constants.LOG_COLOR_LEVEL_3))
31
32
 
32
33
  def read_image_and_update_metadata(self, img_path, metadata):
33
34
  img = read_img(img_path)
@@ -176,7 +176,9 @@ class MultiLayer(JobBase, FrameMultiDirectory):
176
176
  else:
177
177
  raise RuntimeError("input_path option must contain a path or an array of paths")
178
178
  if len(paths) == 0:
179
- self.print_message(color_str("no input paths specified", "red"), level=logging.WARNING)
179
+ self.print_message(color_str("no input paths specified",
180
+ constants.LOG_COLOR_LEVEL_ALERT),
181
+ level=logging.WARNING)
180
182
  return
181
183
  files = self.folder_filelist()
182
184
  if len(files) == 0:
@@ -184,22 +186,23 @@ class MultiLayer(JobBase, FrameMultiDirectory):
184
186
  color_str(f"no input in {len(paths)} specified path" +
185
187
  ('s' if len(paths) > 1 else '') + ": "
186
188
  ", ".join([f"'{p}'" for p in paths]),
187
- "red"),
189
+ constants.LOG_COLOR_LEVEL_ALERT),
188
190
  level=logging.WARNING)
189
191
  return
190
- self.print_message(color_str("merging frames in " + self.folder_list_str(), "blue"))
192
+ self.print_message(color_str("merging frames in " + self.folder_list_str(),
193
+ constants.LOG_COLOR_LEVEL_2))
191
194
  input_files = [f"{self.working_path}/{f}" for f in files]
192
195
  self.print_message(
193
- color_str("frames: " + ", ".join([i.split("/")[-1] for i in files]), "blue"))
194
- self.print_message(
195
- color_str("reading files", "blue"))
196
+ color_str("frames: " + ", ".join([i.split("/")[-1] for i in files]),
197
+ constants.LOG_COLOR_LEVEL_2))
198
+ self.print_message(color_str("reading files", constants.LOG_COLOR_LEVEL_2))
196
199
  filename = ".".join(files[0].split("/")[-1].split(".")[:-1])
197
200
  output_file = f"{self.working_path}/{self.output_path}/{filename}.tif"
198
201
  callbacks = {
199
202
  'exif_msg': lambda path: self.print_message(
200
- color_str(f"copying exif data from path: {path}", "blue")),
203
+ color_str(f"copying exif data from path: {path}", constants.LOG_COLOR_LEVEL_2)),
201
204
  'write_msg': lambda path: self.print_message(
202
- color_str(f"writing multilayer tiff file: {path}", "blue"))
205
+ color_str(f"writing multilayer tiff file: {path}", constants.LOG_COLOR_LEVEL_2))
203
206
  }
204
207
  write_multilayer_tiff(input_files, output_file, labels=None, exif_path=self.exif_path,
205
208
  callbacks=callbacks)
@@ -73,7 +73,8 @@ class NoiseDetection(JobBase, FrameMultiDirectory):
73
73
 
74
74
  def run_core(self):
75
75
  self.print_message(color_str(
76
- f"map noisy pixels from frames in {self.folder_list_str()}", "blue"
76
+ f"map noisy pixels from frames in {self.folder_list_str()}",
77
+ constants.LOG_COLOR_LEVEL_2
77
78
  ))
78
79
  files = self.folder_filelist()
79
80
  in_paths = [self.working_path + "/" + f for f in files]
@@ -89,7 +90,7 @@ class NoiseDetection(JobBase, FrameMultiDirectory):
89
90
  mean_img = mean_image(
90
91
  file_paths=in_paths, max_frames=self.max_frames,
91
92
  message_callback=lambda path: self.print_message_r(
92
- color_str(f"reading frame: {path.split('/')[-1]}", "blue")
93
+ color_str(f"reading frame: {path.split('/')[-1]}", constants.LOG_COLOR_LEVEL_2)
93
94
  ),
94
95
  progress_callback=progress_callback)
95
96
  if not config.DISABLE_TQDM:
@@ -103,13 +104,16 @@ class NoiseDetection(JobBase, FrameMultiDirectory):
103
104
  hot_rgb = cv2.bitwise_or(hot_px[0], cv2.bitwise_or(hot_px[1], hot_px[2]))
104
105
  msg = []
105
106
  for ch, hot in zip(['rgb', *constants.RGB_LABELS], [hot_rgb] + hot_px):
106
- msg.append(f"{ch}: {np.count_nonzero(hot > 0)}")
107
- self.print_message("hot pixels: " + ", ".join(msg))
107
+ hpx = color_str(f"{ch}: {np.count_nonzero(hot > 0)}",
108
+ {'rgb': 'black', 'r': 'red', 'g': 'green', 'b': 'blue'}[ch])
109
+ msg.append(hpx)
110
+ self.print_message(color_str("hot pixels: " + ", ".join(msg), constants.LOG_COLOR_LEVEL_2))
108
111
  path = "/".join(self.file_name.split("/")[:-1])
109
112
  if not os.path.exists(f"{self.working_path}/{path}"):
110
113
  self.print_message(f"create directory: {path}")
111
114
  os.mkdir(f"{self.working_path}/{path}")
112
- self.print_message(f"writing hot pixels map file: {self.file_name}")
115
+ self.print_message(color_str(f"writing hot pixels map file: {self.file_name}",
116
+ constants.LOG_COLOR_LEVEL_2))
113
117
  cv2.imwrite(f"{self.working_path}/{self.file_name}", hot_rgb)
114
118
  plot_range = self.plot_range
115
119
  min_th, max_th = min(self.channel_thresholds), max(self.channel_thresholds)
@@ -155,7 +159,9 @@ class MaskNoise(SubAction):
155
159
  self.process = process
156
160
  path = f"{process.working_path}/{self.noise_mask}"
157
161
  if os.path.exists(path):
158
- self.process.sub_message_r(f': reading noisy pixel mask file: {self.noise_mask}')
162
+ self.process.sub_message_r(color_str(
163
+ f': reading noisy pixel mask file: {self.noise_mask}',
164
+ constants.LOG_COLOR_LEVEL_3))
159
165
  self.noise_mask_img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
160
166
  if self.noise_mask_img is None:
161
167
  raise ImageLoadError(path, f"failed to load image file {self.noise_mask}.")
@@ -163,7 +169,7 @@ class MaskNoise(SubAction):
163
169
  raise ImageLoadError(path, "file not found.")
164
170
 
165
171
  def run_frame(self, _idx, _ref_idx, image):
166
- self.process.sub_message_r(': mask noisy pixels')
172
+ self.process.sub_message_r(color_str(': mask noisy pixels', constants.LOG_COLOR_LEVEL_3))
167
173
  if len(image.shape) == 3:
168
174
  corrected = image.copy()
169
175
  for c in range(3):
@@ -3,6 +3,7 @@ import os
3
3
  import numpy as np
4
4
  from .. config.constants import constants
5
5
  from .. core.framework import JobBase
6
+ from .. core.colors import color_str
6
7
  from .. core.exceptions import InvalidOptionError
7
8
  from .utils import write_img
8
9
  from .stack_framework import FrameDirectory, ActionList
@@ -23,7 +24,7 @@ class FocusStackBase(JobBase, FrameDirectory):
23
24
  self.frame_count = -1
24
25
 
25
26
  def focus_stack(self, filenames):
26
- self.sub_message_r(': reading input files')
27
+ self.sub_message_r(color_str(': reading input files', constants.LOG_COLOR_LEVEL_3))
27
28
  img_files = sorted([os.path.join(self.input_full_path, name) for name in filenames])
28
29
  stacked_img = self.stack_algo.focus_stack(img_files)
29
30
  in_filename = filenames[0].split(".")
@@ -42,7 +43,7 @@ class FocusStackBase(JobBase, FrameDirectory):
42
43
  copy_exif_from_file_to_file(exif_filename, out_filename)
43
44
  self.sub_message_r(' ' * 60)
44
45
  if self.plot_stack:
45
- idx_str = f"{self.frame_count:04d}" if self.frame_count >= 0 else ''
46
+ idx_str = f"{self.frame_count + 1:04d}" if self.frame_count >= 0 else ''
46
47
  name = f"{self.name}: {self.stack_algo.name()}"
47
48
  if idx_str != '':
48
49
  name += f"\nbunch: {idx_str}"
@@ -91,9 +92,9 @@ class FocusStackBunch(ActionList, FocusStackBase):
91
92
  ActionList.end(self)
92
93
 
93
94
  def run_step(self):
94
- self.print_message_r(f"fusing bunch: {self.count}")
95
+ self.print_message_r(color_str(f"fusing bunch: {self.count + 1}/{self.counts}",
96
+ constants.LOG_COLOR_LEVEL_2))
95
97
  self.focus_stack(self._chunks[self.count - 1])
96
- self.callback('after_step', self.id, self.name, self.count)
97
98
 
98
99
 
99
100
  class FocusStack(FocusStackBase):
@@ -52,8 +52,7 @@ class FramePaths:
52
52
  self.filenames = self.folder_filelist()
53
53
  file_list = self.input_full_path.replace(self.working_path, '').lstrip('/')
54
54
  self.print_message(color_str(f": {len(self.filenames)} files in folder: {file_list}",
55
- 'blue'))
56
- self.print_message(color_str("focus stacking", 'blue'))
55
+ constants.LOG_COLOR_LEVEL_2))
57
56
 
58
57
  def init(self, job):
59
58
  if self.working_path == '':
@@ -213,14 +212,14 @@ class FramesRefActions(ActionList, FrameDirectory):
213
212
  pass
214
213
 
215
214
  def run_step(self):
216
- if self.count == 1:
215
+ if self.count == 0:
217
216
  self._idx = self.ref_idx if self.step_process else 0
218
217
  self._ref_idx = self.ref_idx
219
218
  self._idx_step = +1
220
219
  ll = len(self.filenames)
221
220
  self.print_message_r(
222
- color_str(f"step {self.count}/{ll}: process file: {self.filenames[self._idx]}, "
223
- f"reference: {self.filenames[self._ref_idx]}", "blue"))
221
+ color_str(f"step {self.count + 1}/{ll}: process file: {self.filenames[self._idx]}, "
222
+ f"reference: {self.filenames[self._ref_idx]}", constants.LOG_COLOR_LEVEL_2))
224
223
  self.run_frame(self._idx, self._ref_idx)
225
224
  if self._idx < ll:
226
225
  if self.step_process:
@@ -269,7 +268,7 @@ class CombinedActions(FramesRefActions):
269
268
 
270
269
  def run_frame(self, idx, ref_idx):
271
270
  filename = self.filenames[idx]
272
- self.sub_message_r(': read input image')
271
+ self.sub_message_r(color_str(': read input image', constants.LOG_COLOR_LEVEL_3))
273
272
  img = read_img(f"{self.input_full_path}/{filename}")
274
273
  if self.dtype is not None and img.dtype != self.dtype:
275
274
  raise BitDepthError(self.dtype, img.dtype, )
@@ -278,7 +277,8 @@ class CombinedActions(FramesRefActions):
278
277
  if img is None:
279
278
  raise RuntimeError(f"Invalid file: {self.input_full_path}/{filename}")
280
279
  if len(self._actions) == 0:
281
- self.sub_message(color_str(": no actions specified.", "red"), level=logging.WARNING)
280
+ self.sub_message(color_str(": no actions specified.", constants.LOG_COLOR_ALERT),
281
+ level=logging.WARNING)
282
282
  for a in self._actions:
283
283
  if not a.enabled:
284
284
  self.get_logger().warning(color_str(f"{self.base_message}: sub-action disabled",
@@ -287,12 +287,14 @@ class CombinedActions(FramesRefActions):
287
287
  if self.callback('check_running', self.id, self.name) is False:
288
288
  raise RunStopException(self.name)
289
289
  img = a.run_frame(idx, ref_idx, img)
290
- self.sub_message_r(': write output image')
290
+ self.sub_message_r(color_str(': write output image', constants.LOG_COLOR_LEVEL_3))
291
291
  if img is not None:
292
292
  write_img(self.output_dir + "/" + filename, img)
293
293
  else:
294
- self.print_message("No output file resulted from processing input file: "
295
- f"{self.input_full_path}/{filename}", level=logging.WARNING)
294
+ self.print_message(color_str(
295
+ "No output file resulted from processing input file: "
296
+ f"{self.input_full_path}/{filename}",
297
+ constants.LOG_COLOR_ALERT), level=logging.WARNING)
296
298
 
297
299
  def end(self):
298
300
  for a in self._actions:
@@ -89,7 +89,7 @@ class MainApp(QMainWindow):
89
89
  if isinstance(filename, list):
90
90
  open_frames(self.retouch_window, None, ";".join(filename))
91
91
  else:
92
- self.retouch_window.open_file(filename)
92
+ self.retouch_window.io_gui_handler.open_file(filename)
93
93
 
94
94
 
95
95
  class Application(QApplication):
@@ -24,6 +24,7 @@ class _ConfigBase:
24
24
  raise AttributeError("Can't change config after initialization")
25
25
  super().__setattr__(name, value)
26
26
 
27
+
27
28
  class _Config(_ConfigBase):
28
29
 
29
30
  def __new__(cls):