setiastrosuitepro 1.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of setiastrosuitepro might be problematic. Click here for more details.

Files changed (174) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/saspro/__init__.py +20 -0
  3. setiastro/saspro/__main__.py +784 -0
  4. setiastro/saspro/_generated/__init__.py +7 -0
  5. setiastro/saspro/_generated/build_info.py +2 -0
  6. setiastro/saspro/abe.py +1295 -0
  7. setiastro/saspro/abe_preset.py +196 -0
  8. setiastro/saspro/aberration_ai.py +694 -0
  9. setiastro/saspro/aberration_ai_preset.py +224 -0
  10. setiastro/saspro/accel_installer.py +218 -0
  11. setiastro/saspro/accel_workers.py +30 -0
  12. setiastro/saspro/add_stars.py +621 -0
  13. setiastro/saspro/astrobin_exporter.py +1007 -0
  14. setiastro/saspro/astrospike.py +153 -0
  15. setiastro/saspro/astrospike_python.py +1839 -0
  16. setiastro/saspro/autostretch.py +196 -0
  17. setiastro/saspro/backgroundneutral.py +560 -0
  18. setiastro/saspro/batch_convert.py +325 -0
  19. setiastro/saspro/batch_renamer.py +519 -0
  20. setiastro/saspro/blemish_blaster.py +488 -0
  21. setiastro/saspro/blink_comparator_pro.py +2923 -0
  22. setiastro/saspro/bundles.py +61 -0
  23. setiastro/saspro/bundles_dock.py +114 -0
  24. setiastro/saspro/cheat_sheet.py +168 -0
  25. setiastro/saspro/clahe.py +342 -0
  26. setiastro/saspro/comet_stacking.py +1377 -0
  27. setiastro/saspro/config.py +38 -0
  28. setiastro/saspro/config_bootstrap.py +40 -0
  29. setiastro/saspro/config_manager.py +316 -0
  30. setiastro/saspro/continuum_subtract.py +1617 -0
  31. setiastro/saspro/convo.py +1397 -0
  32. setiastro/saspro/convo_preset.py +414 -0
  33. setiastro/saspro/copyastro.py +187 -0
  34. setiastro/saspro/cosmicclarity.py +1564 -0
  35. setiastro/saspro/cosmicclarity_preset.py +407 -0
  36. setiastro/saspro/crop_dialog_pro.py +948 -0
  37. setiastro/saspro/crop_preset.py +189 -0
  38. setiastro/saspro/curve_editor_pro.py +2544 -0
  39. setiastro/saspro/curves_preset.py +375 -0
  40. setiastro/saspro/debayer.py +670 -0
  41. setiastro/saspro/debug_utils.py +29 -0
  42. setiastro/saspro/dnd_mime.py +35 -0
  43. setiastro/saspro/doc_manager.py +2634 -0
  44. setiastro/saspro/exoplanet_detector.py +2166 -0
  45. setiastro/saspro/file_utils.py +284 -0
  46. setiastro/saspro/fitsmodifier.py +744 -0
  47. setiastro/saspro/free_torch_memory.py +48 -0
  48. setiastro/saspro/frequency_separation.py +1343 -0
  49. setiastro/saspro/function_bundle.py +1594 -0
  50. setiastro/saspro/ghs_dialog_pro.py +660 -0
  51. setiastro/saspro/ghs_preset.py +284 -0
  52. setiastro/saspro/graxpert.py +634 -0
  53. setiastro/saspro/graxpert_preset.py +287 -0
  54. setiastro/saspro/gui/__init__.py +0 -0
  55. setiastro/saspro/gui/main_window.py +8494 -0
  56. setiastro/saspro/gui/mixins/__init__.py +33 -0
  57. setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
  58. setiastro/saspro/gui/mixins/file_mixin.py +445 -0
  59. setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
  60. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  61. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  62. setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
  63. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  64. setiastro/saspro/gui/mixins/toolbar_mixin.py +1324 -0
  65. setiastro/saspro/gui/mixins/update_mixin.py +309 -0
  66. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  67. setiastro/saspro/halobgon.py +462 -0
  68. setiastro/saspro/header_viewer.py +445 -0
  69. setiastro/saspro/headless_utils.py +88 -0
  70. setiastro/saspro/histogram.py +753 -0
  71. setiastro/saspro/history_explorer.py +939 -0
  72. setiastro/saspro/image_combine.py +414 -0
  73. setiastro/saspro/image_peeker_pro.py +1596 -0
  74. setiastro/saspro/imageops/__init__.py +37 -0
  75. setiastro/saspro/imageops/mdi_snap.py +292 -0
  76. setiastro/saspro/imageops/scnr.py +36 -0
  77. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  78. setiastro/saspro/imageops/stretch.py +244 -0
  79. setiastro/saspro/isophote.py +1179 -0
  80. setiastro/saspro/layers.py +208 -0
  81. setiastro/saspro/layers_dock.py +714 -0
  82. setiastro/saspro/lazy_imports.py +193 -0
  83. setiastro/saspro/legacy/__init__.py +2 -0
  84. setiastro/saspro/legacy/image_manager.py +2226 -0
  85. setiastro/saspro/legacy/numba_utils.py +3659 -0
  86. setiastro/saspro/legacy/xisf.py +1071 -0
  87. setiastro/saspro/linear_fit.py +534 -0
  88. setiastro/saspro/live_stacking.py +1830 -0
  89. setiastro/saspro/log_bus.py +5 -0
  90. setiastro/saspro/logging_config.py +460 -0
  91. setiastro/saspro/luminancerecombine.py +309 -0
  92. setiastro/saspro/main_helpers.py +201 -0
  93. setiastro/saspro/mask_creation.py +928 -0
  94. setiastro/saspro/masks_core.py +56 -0
  95. setiastro/saspro/mdi_widgets.py +353 -0
  96. setiastro/saspro/memory_utils.py +666 -0
  97. setiastro/saspro/metadata_patcher.py +75 -0
  98. setiastro/saspro/mfdeconv.py +3826 -0
  99. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  100. setiastro/saspro/mfdeconvcudnn.py +3263 -0
  101. setiastro/saspro/mfdeconvsport.py +2382 -0
  102. setiastro/saspro/minorbodycatalog.py +567 -0
  103. setiastro/saspro/morphology.py +382 -0
  104. setiastro/saspro/multiscale_decomp.py +1290 -0
  105. setiastro/saspro/nbtorgb_stars.py +531 -0
  106. setiastro/saspro/numba_utils.py +3044 -0
  107. setiastro/saspro/numba_warmup.py +141 -0
  108. setiastro/saspro/ops/__init__.py +9 -0
  109. setiastro/saspro/ops/command_help_dialog.py +623 -0
  110. setiastro/saspro/ops/command_runner.py +217 -0
  111. setiastro/saspro/ops/commands.py +1594 -0
  112. setiastro/saspro/ops/script_editor.py +1102 -0
  113. setiastro/saspro/ops/scripts.py +1413 -0
  114. setiastro/saspro/ops/settings.py +560 -0
  115. setiastro/saspro/parallel_utils.py +554 -0
  116. setiastro/saspro/pedestal.py +121 -0
  117. setiastro/saspro/perfect_palette_picker.py +1053 -0
  118. setiastro/saspro/pipeline.py +110 -0
  119. setiastro/saspro/pixelmath.py +1600 -0
  120. setiastro/saspro/plate_solver.py +2435 -0
  121. setiastro/saspro/project_io.py +797 -0
  122. setiastro/saspro/psf_utils.py +136 -0
  123. setiastro/saspro/psf_viewer.py +549 -0
  124. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  125. setiastro/saspro/remove_green.py +314 -0
  126. setiastro/saspro/remove_stars.py +1625 -0
  127. setiastro/saspro/remove_stars_preset.py +404 -0
  128. setiastro/saspro/resources.py +472 -0
  129. setiastro/saspro/rgb_combination.py +207 -0
  130. setiastro/saspro/rgb_extract.py +19 -0
  131. setiastro/saspro/rgbalign.py +723 -0
  132. setiastro/saspro/runtime_imports.py +7 -0
  133. setiastro/saspro/runtime_torch.py +754 -0
  134. setiastro/saspro/save_options.py +72 -0
  135. setiastro/saspro/selective_color.py +1552 -0
  136. setiastro/saspro/sfcc.py +1425 -0
  137. setiastro/saspro/shortcuts.py +2807 -0
  138. setiastro/saspro/signature_insert.py +1099 -0
  139. setiastro/saspro/stacking_suite.py +17712 -0
  140. setiastro/saspro/star_alignment.py +7420 -0
  141. setiastro/saspro/star_alignment_preset.py +329 -0
  142. setiastro/saspro/star_metrics.py +49 -0
  143. setiastro/saspro/star_spikes.py +681 -0
  144. setiastro/saspro/star_stretch.py +470 -0
  145. setiastro/saspro/stat_stretch.py +502 -0
  146. setiastro/saspro/status_log_dock.py +78 -0
  147. setiastro/saspro/subwindow.py +3267 -0
  148. setiastro/saspro/supernovaasteroidhunter.py +1712 -0
  149. setiastro/saspro/swap_manager.py +99 -0
  150. setiastro/saspro/torch_backend.py +89 -0
  151. setiastro/saspro/torch_rejection.py +434 -0
  152. setiastro/saspro/view_bundle.py +1555 -0
  153. setiastro/saspro/wavescale_hdr.py +624 -0
  154. setiastro/saspro/wavescale_hdr_preset.py +100 -0
  155. setiastro/saspro/wavescalede.py +657 -0
  156. setiastro/saspro/wavescalede_preset.py +228 -0
  157. setiastro/saspro/wcs_update.py +374 -0
  158. setiastro/saspro/whitebalance.py +456 -0
  159. setiastro/saspro/widgets/__init__.py +48 -0
  160. setiastro/saspro/widgets/common_utilities.py +305 -0
  161. setiastro/saspro/widgets/graphics_views.py +122 -0
  162. setiastro/saspro/widgets/image_utils.py +518 -0
  163. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  164. setiastro/saspro/widgets/spinboxes.py +275 -0
  165. setiastro/saspro/widgets/themed_buttons.py +13 -0
  166. setiastro/saspro/widgets/wavelet_utils.py +299 -0
  167. setiastro/saspro/window_shelf.py +185 -0
  168. setiastro/saspro/xisf.py +1123 -0
  169. setiastrosuitepro-1.6.0.dist-info/METADATA +266 -0
  170. setiastrosuitepro-1.6.0.dist-info/RECORD +174 -0
  171. setiastrosuitepro-1.6.0.dist-info/WHEEL +4 -0
  172. setiastrosuitepro-1.6.0.dist-info/entry_points.txt +6 -0
  173. setiastrosuitepro-1.6.0.dist-info/licenses/LICENSE +674 -0
  174. setiastrosuitepro-1.6.0.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,472 @@
1
+ # pro/resources.py
2
+ """
3
+ Centralized resource paths for Seti Astro Suite Pro.
4
+
5
+ This module provides a single source of truth for all icon and resource paths,
6
+ handling both PyInstaller frozen builds and development environments.
7
+
8
+ Usage:
9
+ from setiastro.saspro.resources import Icons, Resources
10
+
11
+ icon = QIcon(Icons.WRENCH)
12
+ data_path = Resources.SASP_DATA
13
+ """
14
+ from __future__ import annotations
15
+ import os
16
+ import sys
17
+ from functools import lru_cache
18
+
19
+
20
+ def _get_base_path() -> str:
21
+ """Get base path for resources (PyInstaller, installed package, or development)."""
22
+ if hasattr(sys, '_MEIPASS'):
23
+ return sys._MEIPASS
24
+
25
+ # First, check if we're running from source (setiastrosuitepro.py exists)
26
+ # This takes priority when running the script directly from source
27
+ try:
28
+ # Check if we can find it via __main__ module (most reliable)
29
+ if '__main__' in sys.modules:
30
+ main_module = sys.modules['__main__']
31
+ if hasattr(main_module, '__file__') and main_module.__file__:
32
+ main_file = main_module.__file__
33
+ main_dir = os.path.dirname(os.path.abspath(main_file))
34
+
35
+ # Case 1: Running setiastrosuitepro.py directly
36
+ if os.path.basename(main_file) == 'setiastrosuitepro.py':
37
+ images_dir = os.path.join(main_dir, 'images')
38
+ if os.path.exists(images_dir):
39
+ return main_dir
40
+
41
+ # Case 2: Running as module (python -m setiastro.saspro)
42
+ # __main__.py is at src/setiastro/saspro/__main__.py
43
+ # Need to go up 4 levels to reach project root
44
+ if os.path.basename(main_file) == '__main__.py':
45
+ # Walk up from __main__.py to find project root with images/
46
+ search_dir = main_dir
47
+ for _ in range(6): # Don't go too far up
48
+ images_dir = os.path.join(search_dir, 'images')
49
+ if os.path.exists(images_dir):
50
+ return search_dir
51
+ parent = os.path.dirname(search_dir)
52
+ if parent == search_dir: # Reached filesystem root
53
+ break
54
+ search_dir = parent
55
+
56
+ # Check current working directory for setiastrosuitepro.py
57
+ # This handles the case where user runs: python setiastrosuitepro.py
58
+ cwd = os.getcwd()
59
+ main_script = os.path.join(cwd, 'setiastrosuitepro.py')
60
+ if os.path.exists(main_script):
61
+ images_dir = os.path.join(cwd, 'images')
62
+ if os.path.exists(images_dir):
63
+ return cwd
64
+
65
+ # Also check parent directories (in case we're in a subdirectory)
66
+ # Walk up from current file location looking for images/ directory
67
+ current_file = os.path.abspath(__file__)
68
+ search_dir = os.path.dirname(current_file)
69
+ for _ in range(6): # Don't go too far up
70
+ images_dir = os.path.join(search_dir, 'images')
71
+ if os.path.exists(images_dir):
72
+ return search_dir
73
+ parent = os.path.dirname(search_dir)
74
+ if parent == search_dir: # Reached filesystem root
75
+ break
76
+ search_dir = parent
77
+ except Exception:
78
+ pass
79
+
80
+ # Development: resources are in project root
81
+ # File is at: src/setiastro/saspro/resources.py
82
+ # Need to go up 4 levels to reach project root
83
+ current_file = os.path.abspath(__file__)
84
+ # Go up from resources.py -> saspro -> setiastro -> src -> project root
85
+ base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_file))))
86
+
87
+ # Verify we found the right directory by checking for images/ folder
88
+ images_dir = os.path.join(base, 'images')
89
+ if os.path.exists(images_dir):
90
+ return base
91
+
92
+ # Fallback: try going up one more level (in case structure is different)
93
+ base = os.path.dirname(base)
94
+ if os.path.exists(os.path.join(base, 'images')):
95
+ return base
96
+
97
+ # Check if we're in an installed package (last resort)
98
+ # When installed via pip, the package is in site-packages
99
+ try:
100
+ import setiastro
101
+ package_dir = os.path.dirname(os.path.abspath(setiastro.__file__))
102
+ # Check if images/ exists at package root level (for pip-installed packages)
103
+ # The images/ directory should be at the same level as setiastro/ in site-packages
104
+ package_parent = os.path.dirname(package_dir)
105
+ images_dir = os.path.join(package_parent, 'images')
106
+ if os.path.exists(images_dir):
107
+ return package_parent
108
+
109
+ # Also check if images/ is inside the package directory
110
+ images_in_package = os.path.join(package_dir, 'images')
111
+ if os.path.exists(images_in_package):
112
+ return package_dir
113
+ except (ImportError, AttributeError):
114
+ pass
115
+
116
+ # Last resort: return the calculated path anyway
117
+ return os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_file))))
118
+
119
+
120
+ def _resource_path(filename: str) -> str:
121
+ """Get full path to a resource file."""
122
+ base = _get_base_path()
123
+
124
+ # Check if it's an image file - look in images/ subdirectory
125
+ if filename.endswith(('.png', '.ico', '.gif', '.icns', '.svg')):
126
+ images_path = os.path.join(base, 'images', filename)
127
+ if os.path.exists(images_path):
128
+ return images_path
129
+
130
+ # Fallback to root directory (for data files like .csv, .fits, etc.)
131
+ return os.path.join(base, filename)
132
+
133
+
134
+ class Icons:
135
+ """
136
+ Centralized icon paths.
137
+
138
+ All paths are computed lazily and cached.
139
+ Access via class attributes: Icons.WRENCH, Icons.HISTOGRAM, etc.
140
+ """
141
+
142
+ # Application
143
+ APP = property(lambda self: _resource_path('astrosuitepro.png'))
144
+ APP_ICO = property(lambda self: _resource_path('astrosuitepro.ico'))
145
+ BACKGROUND = property(lambda self: _resource_path('background.png'))
146
+
147
+ # Processing tools
148
+ GREEN = property(lambda self: _resource_path('green.png'))
149
+ NEUTRAL = property(lambda self: _resource_path('neutral.png'))
150
+ WHITE_BALANCE = property(lambda self: _resource_path('whitebalance.png'))
151
+ MORPHOLOGY = property(lambda self: _resource_path('morpho.png'))
152
+ CLAHE = property(lambda self: _resource_path('clahe.png'))
153
+ HDR = property(lambda self: _resource_path('hdr.png'))
154
+ INVERT = property(lambda self: _resource_path('invert.png'))
155
+
156
+ # Star operations
157
+ STARNET = property(lambda self: _resource_path('starnet.png'))
158
+ STAR_ADD = property(lambda self: _resource_path('staradd.png'))
159
+ STAR_ALIGN = property(lambda self: _resource_path('staralign.png'))
160
+ STAR_REGISTRATION = property(lambda self: _resource_path('starregistration.png'))
161
+ STAR_SPIKE = property(lambda self: _resource_path('starspike.png'))
162
+ ASTRO_SPIKE = property(lambda self: _resource_path('Astro_Spikes.png'))
163
+ STAR_STRETCH = property(lambda self: _resource_path('starstretch.png'))
164
+
165
+ # Luminance
166
+ L_EXTRACT = property(lambda self: _resource_path('LExtract.png'))
167
+ L_INSERT = property(lambda self: _resource_path('LInsert.png'))
168
+
169
+ # Slots (0-9)
170
+ SLOT_0 = property(lambda self: _resource_path('slot0.png'))
171
+ SLOT_1 = property(lambda self: _resource_path('slot1.png'))
172
+ SLOT_2 = property(lambda self: _resource_path('slot2.png'))
173
+ SLOT_3 = property(lambda self: _resource_path('slot3.png'))
174
+ SLOT_4 = property(lambda self: _resource_path('slot4.png'))
175
+ SLOT_5 = property(lambda self: _resource_path('slot5.png'))
176
+ SLOT_6 = property(lambda self: _resource_path('slot6.png'))
177
+ SLOT_7 = property(lambda self: _resource_path('slot7.png'))
178
+ SLOT_8 = property(lambda self: _resource_path('slot8.png'))
179
+ SLOT_9 = property(lambda self: _resource_path('slot9.png'))
180
+
181
+ # RGB operations
182
+ RGB_COMBO = property(lambda self: _resource_path('rgbcombo.png'))
183
+ RGB_EXTRACT = property(lambda self: _resource_path('rgbextract.png'))
184
+ RGB_ALIGN = property(lambda self: _resource_path('rgbalign.png'))
185
+ COPY_SLOT = property(lambda self: _resource_path('copyslot.png'))
186
+
187
+ # External tools
188
+ GRAXPERT = property(lambda self: _resource_path('graxpert.png'))
189
+ COSMIC = property(lambda self: _resource_path('cosmic.png'))
190
+ COSMIC_SAT = property(lambda self: _resource_path('cosmicsat.png'))
191
+
192
+ # Image operations
193
+ CROP = property(lambda self: _resource_path('cropicon.png'))
194
+ OPEN_FILE = property(lambda self: _resource_path('openfile.png'))
195
+ ABE = property(lambda self: _resource_path('abeicon.png'))
196
+ BLASTER = property(lambda self: _resource_path('blaster.png'))
197
+
198
+ # Undo/Redo
199
+ UNDO = property(lambda self: _resource_path('undoicon.png'))
200
+ REDO = property(lambda self: _resource_path('redoicon.png'))
201
+
202
+ # Transformations
203
+ FLIP_HORIZONTAL = property(lambda self: _resource_path('fliphorizontal.png'))
204
+ FLIP_VERTICAL = property(lambda self: _resource_path('flipvertical.png'))
205
+ ROTATE_CW = property(lambda self: _resource_path('rotateclockwise.png'))
206
+ ROTATE_CCW = property(lambda self: _resource_path('rotatecounterclockwise.png'))
207
+ ROTATE_180 = property(lambda self: _resource_path('rotate180.png'))
208
+ RESCALE = property(lambda self: _resource_path('rescale.png'))
209
+
210
+ # Masks
211
+ MASK_CREATE = property(lambda self: _resource_path('maskcreate.png'))
212
+ MASK_APPLY = property(lambda self: _resource_path('maskapply.png'))
213
+ MASK_REMOVE = property(lambda self: _resource_path('maskremove.png'))
214
+
215
+ # Analysis
216
+ PIXEL_MATH = property(lambda self: _resource_path('pixelmath.png'))
217
+ HISTOGRAM = property(lambda self: _resource_path('histogram.png'))
218
+ MOSAIC = property(lambda self: _resource_path('mosaic.png'))
219
+ PLATE_SOLVE = property(lambda self: _resource_path('platesolve.png'))
220
+ PSF = property(lambda self: _resource_path('psf.png'))
221
+ ISOPHOTE = property(lambda self: _resource_path('isophote.png'))
222
+
223
+ # Stacking
224
+ STACKING = property(lambda self: _resource_path('stacking.png'))
225
+ LIVE_STACKING = property(lambda self: _resource_path('livestacking.png'))
226
+ IMAGE_COMBINE = property(lambda self: _resource_path('imagecombine.png'))
227
+
228
+ # Special features
229
+ SUPERNOVA = property(lambda self: _resource_path('supernova.png'))
230
+ PEDESTAL = property(lambda self: _resource_path('pedestal.png'))
231
+ APERTURE = property(lambda self: _resource_path('aperture.png'))
232
+ JWST_PUPIL = property(lambda self: _resource_path('jwstpupil.png'))
233
+ SIGNATURE = property(lambda self: _resource_path('pen.png'))
234
+ HR_DIAGRAM = property(lambda self: _resource_path('HRDiagram.png'))
235
+ EXOPLANET = property(lambda self: _resource_path('exoicon.png'))
236
+
237
+ # Deconvolution & filters
238
+ CONVO = property(lambda self: _resource_path('convo.png'))
239
+ FREQ_SEP = property(lambda self: _resource_path('freqsep.png'))
240
+ MULTISCALE_DECOMP = property(lambda self: _resource_path('multiscale_decomp.png'))
241
+ CONT_SUB = property(lambda self: _resource_path('contsub.png'))
242
+ HALO = property(lambda self: _resource_path('halo.png'))
243
+ ABERRATION = property(lambda self: _resource_path('aberration.png'))
244
+
245
+ # Color
246
+ SPCC = property(lambda self: _resource_path('spcc.png'))
247
+ DSE = property(lambda self: _resource_path('dse.png'))
248
+ COLOR_WHEEL = property(lambda self: _resource_path('colorwheel.png'))
249
+ SELECTIVE_COLOR = property(lambda self: _resource_path('selectivecolor.png'))
250
+ NB_TO_RGB = property(lambda self: _resource_path('nbtorgb.png'))
251
+
252
+ # Stretching
253
+ STAT_STRETCH = property(lambda self: _resource_path('statstretch.png'))
254
+ CURVES = property(lambda self: _resource_path('curves.png'))
255
+ UHS = property(lambda self: _resource_path('uhs.png'))
256
+
257
+ # UI elements
258
+ WRENCH = property(lambda self: _resource_path('wrench_icon.png'))
259
+ EYE = property(lambda self: _resource_path('eye.png'))
260
+ DISK = property(lambda self: _resource_path('disk.png'))
261
+ NUKE = property(lambda self: _resource_path('nuke.png'))
262
+ GRID = property(lambda self: _resource_path('gridicon.png'))
263
+ FONT = property(lambda self: _resource_path('font.png'))
264
+ CSV = property(lambda self: _resource_path('cvs.png'))
265
+ PPP = property(lambda self: _resource_path('ppp.png'))
266
+ SCRIPT = property(lambda self: _resource_path('script.png'))
267
+
268
+ # Blink & comparison
269
+ BLINK = property(lambda self: _resource_path('blink.png'))
270
+ HUBBLE = property(lambda self: _resource_path('hubble.png'))
271
+ COLLAGE = property(lambda self: _resource_path('collage.png'))
272
+ ANNOTATED = property(lambda self: _resource_path('annotated.png'))
273
+
274
+ # WIMS/WIMI
275
+ WIMS = property(lambda self: _resource_path('wims.png'))
276
+ WIMI = property(lambda self: _resource_path('wimi_icon_256x256.png'))
277
+
278
+ # Other
279
+ LINEAR_FIT = property(lambda self: _resource_path('linearfit.png'))
280
+ DEBAYER = property(lambda self: _resource_path('debayer.png'))
281
+ FUNCTION_BUNDLES = property(lambda self: _resource_path('functionbundle.png'))
282
+ VIEW_BUNDLES = property(lambda self: _resource_path('viewbundle.png'))
283
+
284
+
285
+ class Resources:
286
+ """
287
+ Centralized data resource paths.
288
+ """
289
+ SASP_DATA = property(lambda self: _resource_path('data/SASP_data.fits'))
290
+ ASTROBIN_FILTERS_CSV = property(lambda self: _resource_path('data/catalogs/astrobin_filters.csv'))
291
+ SPINNER_GIF = property(lambda self: _resource_path('spinner.gif'))
292
+
293
+
294
+ # Singleton instances for easy access
295
+ _icons_instance = None
296
+ _resources_instance = None
297
+
298
+
299
+ def get_icons() -> Icons:
300
+ """Get the Icons singleton instance."""
301
+ global _icons_instance
302
+ if _icons_instance is None:
303
+ _icons_instance = Icons()
304
+ return _icons_instance
305
+
306
+
307
+ def get_resources() -> Resources:
308
+ """Get the Resources singleton instance."""
309
+ global _resources_instance
310
+ if _resources_instance is None:
311
+ _resources_instance = Resources()
312
+ return _resources_instance
313
+
314
+
315
+ # Convenience functions for direct path access
316
+ @lru_cache(maxsize=128)
317
+ def get_icon_path(name: str) -> str:
318
+ """
319
+ Get icon path by name.
320
+
321
+ Args:
322
+ name: Icon filename (with or without extension)
323
+
324
+ Returns:
325
+ Full path to icon file
326
+
327
+ Example:
328
+ path = get_icon_path('wrench_icon.png')
329
+ path = get_icon_path('histogram') # .png added automatically
330
+ """
331
+ if not name.endswith(('.png', '.ico', '.gif', '.svg')):
332
+ name = f"{name}.png"
333
+ return _resource_path(name)
334
+
335
+
336
+ @lru_cache(maxsize=32)
337
+ def get_data_path(name: str) -> str:
338
+ """
339
+ Get data file path by name.
340
+
341
+ Args:
342
+ name: Data filename
343
+
344
+ Returns:
345
+ Full path to data file
346
+ """
347
+ return _resource_path(name)
348
+
349
+
350
+ # Legacy compatibility: export paths as module-level variables
351
+ # These match the original variable names in setiastrosuitepro.py
352
+ def _init_legacy_paths():
353
+ """Initialize legacy path variables for backward compatibility."""
354
+ return {
355
+ 'icon_path': get_icon_path('astrosuitepro.png'),
356
+ 'windowslogo_path': get_icon_path('astrosuitepro.ico'),
357
+ 'green_path': get_icon_path('green.png'),
358
+ 'neutral_path': get_icon_path('neutral.png'),
359
+ 'whitebalance_path': get_icon_path('whitebalance.png'),
360
+ 'morpho_path': get_icon_path('morpho.png'),
361
+ 'clahe_path': get_icon_path('clahe.png'),
362
+ 'starnet_path': get_icon_path('starnet.png'),
363
+ 'staradd_path': get_icon_path('staradd.png'),
364
+ 'LExtract_path': get_icon_path('LExtract.png'),
365
+ 'LInsert_path': get_icon_path('LInsert.png'),
366
+ 'slot0_path': get_icon_path('slot0.png'),
367
+ 'slot1_path': get_icon_path('slot1.png'),
368
+ 'slot2_path': get_icon_path('slot2.png'),
369
+ 'slot3_path': get_icon_path('slot3.png'),
370
+ 'slot4_path': get_icon_path('slot4.png'),
371
+ 'slot5_path': get_icon_path('slot5.png'),
372
+ 'slot6_path': get_icon_path('slot6.png'),
373
+ 'slot7_path': get_icon_path('slot7.png'),
374
+ 'slot8_path': get_icon_path('slot8.png'),
375
+ 'slot9_path': get_icon_path('slot9.png'),
376
+ 'rgbcombo_path': get_icon_path('rgbcombo.png'),
377
+ 'rgbextract_path': get_icon_path('rgbextract.png'),
378
+ 'copyslot_path': get_icon_path('copyslot.png'),
379
+ 'graxperticon_path': get_icon_path('graxpert.png'),
380
+ 'cropicon_path': get_icon_path('cropicon.png'),
381
+ 'openfile_path': get_icon_path('openfile.png'),
382
+ 'abeicon_path': get_icon_path('abeicon.png'),
383
+ 'undoicon_path': get_icon_path('undoicon.png'),
384
+ 'redoicon_path': get_icon_path('redoicon.png'),
385
+ 'blastericon_path': get_icon_path('blaster.png'),
386
+ 'hdr_path': get_icon_path('hdr.png'),
387
+ 'invert_path': get_icon_path('invert.png'),
388
+ 'fliphorizontal_path': get_icon_path('fliphorizontal.png'),
389
+ 'flipvertical_path': get_icon_path('flipvertical.png'),
390
+ 'rotateclockwise_path': get_icon_path('rotateclockwise.png'),
391
+ 'rotatecounterclockwise_path': get_icon_path('rotatecounterclockwise.png'),
392
+ 'rotate180_path': get_icon_path('rotate180.png'),
393
+ 'maskcreate_path': get_icon_path('maskcreate.png'),
394
+ 'maskapply_path': get_icon_path('maskapply.png'),
395
+ 'maskremove_path': get_icon_path('maskremove.png'),
396
+ 'pixelmath_path': get_icon_path('pixelmath.png'),
397
+ 'histogram_path': get_icon_path('histogram.png'),
398
+ 'mosaic_path': get_icon_path('mosaic.png'),
399
+ 'rescale_path': get_icon_path('rescale.png'),
400
+ 'staralign_path': get_icon_path('staralign.png'),
401
+ 'mask_path': get_icon_path('maskapply.png'),
402
+ 'platesolve_path': get_icon_path('platesolve.png'),
403
+ 'psf_path': get_icon_path('psf.png'),
404
+ 'supernova_path': get_icon_path('supernova.png'),
405
+ 'starregistration_path': get_icon_path('starregistration.png'),
406
+ 'stacking_path': get_icon_path('stacking.png'),
407
+ 'pedestal_icon_path': get_icon_path('pedestal.png'),
408
+ 'starspike_path': get_icon_path('starspike.png'),
409
+ 'astrospike_path': get_icon_path('Astro_Spikes.png'),
410
+ 'aperture_path': get_icon_path('aperture.png'),
411
+ 'jwstpupil_path': get_icon_path('jwstpupil.png'),
412
+ 'signature_icon_path': get_icon_path('pen.png'),
413
+ 'livestacking_path': get_icon_path('livestacking.png'),
414
+ 'hrdiagram_path': get_icon_path('HRDiagram.png'),
415
+ 'convoicon_path': get_icon_path('convo.png'),
416
+ 'spcc_icon_path': get_icon_path('spcc.png'),
417
+ 'sasp_data_path': get_data_path('data/SASP_data.fits'),
418
+ 'exoicon_path': get_icon_path('exoicon.png'),
419
+ 'peeker_icon': get_icon_path('gridicon.png'),
420
+ 'dse_icon_path': get_icon_path('dse.png'),
421
+ 'astrobin_filters_csv_path': get_data_path('data/catalogs/astrobin_filters.csv'),
422
+ 'isophote_path': get_icon_path('isophote.png'),
423
+ 'statstretch_path': get_icon_path('statstretch.png'),
424
+ 'starstretch_path': get_icon_path('starstretch.png'),
425
+ 'curves_path': get_icon_path('curves.png'),
426
+ 'disk_path': get_icon_path('disk.png'),
427
+ 'uhs_path': get_icon_path('uhs.png'),
428
+ 'blink_path': get_icon_path('blink.png'),
429
+ 'ppp_path': get_icon_path('ppp.png'),
430
+ 'nbtorgb_path': get_icon_path('nbtorgb.png'),
431
+ 'freqsep_path': get_icon_path('freqsep.png'),
432
+ 'multiscale_decomp_path': get_icon_path('multiscale_decomp.png'),
433
+ 'contsub_path': get_icon_path('contsub.png'),
434
+ 'halo_path': get_icon_path('halo.png'),
435
+ 'cosmic_path': get_icon_path('cosmic.png'),
436
+ 'satellite_path': get_icon_path('cosmicsat.png'),
437
+ 'imagecombine_path': get_icon_path('imagecombine.png'),
438
+ 'wrench_path': get_icon_path('wrench_icon.png'),
439
+ 'eye_icon_path': get_icon_path('eye.png'),
440
+ 'disk_icon_path': get_icon_path('disk.png'),
441
+ 'nuke_path': get_icon_path('nuke.png'),
442
+ 'hubble_path': get_icon_path('hubble.png'),
443
+ 'collage_path': get_icon_path('collage.png'),
444
+ 'annotated_path': get_icon_path('annotated.png'),
445
+ 'colorwheel_path': get_icon_path('colorwheel.png'),
446
+ 'font_path': get_icon_path('font.png'),
447
+ 'csv_icon_path': get_icon_path('cvs.png'),
448
+ 'spinner_path': get_data_path('spinner.gif'),
449
+ 'wims_path': get_icon_path('wims.png'),
450
+ 'wimi_path': get_icon_path('wimi_icon_256x256.png'),
451
+ 'linearfit_path': get_icon_path('linearfit.png'),
452
+ 'debayer_path': get_icon_path('debayer.png'),
453
+ 'aberration_path': get_icon_path('aberration.png'),
454
+ 'functionbundles_path': get_icon_path('functionbundle.png'),
455
+ 'viewbundles_path': get_icon_path('viewbundle.png'),
456
+ 'selectivecolor_path': get_icon_path('selectivecolor.png'),
457
+ 'rgbalign_path': get_icon_path('rgbalign.png'),
458
+ 'background_path': get_icon_path('background.png'),
459
+ 'script_icon_path': get_icon_path('script.png'),
460
+ }
461
+
462
+
463
+ # Export all legacy paths as module-level variables
464
+ _legacy = _init_legacy_paths()
465
+ globals().update(_legacy)
466
+
467
+ # Export list for `from setiastro.saspro.resources import *`
468
+ __all__ = [
469
+ 'Icons', 'Resources',
470
+ 'get_icons', 'get_resources',
471
+ 'get_icon_path', 'get_data_path',
472
+ ] + list(_legacy.keys())
@@ -0,0 +1,207 @@
1
+ # pro/rgb_combination.py
2
+ from __future__ import annotations
3
+ import os
4
+ import numpy as np
5
+ from PyQt6.QtWidgets import (
6
+ QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QRadioButton,
7
+ QGroupBox, QFileDialog, QComboBox, QButtonGroup, QMessageBox
8
+ )
9
+ from PyQt6.QtCore import Qt
10
+
11
+ try:
12
+ from setiastro.saspro.legacy.image_manager import load_image
13
+ except Exception:
14
+ load_image = None
15
+
16
+
17
+ def _to_f01(x: np.ndarray) -> np.ndarray:
18
+ a = np.asarray(x)
19
+ if a.dtype.kind in "ui":
20
+ a = a.astype(np.float32)
21
+ elif a.dtype.kind == "f" and a.dtype != np.float32:
22
+ a = a.astype(np.float32)
23
+ if a.size:
24
+ m = float(np.nanmax(a))
25
+ if m > 1.0 and np.isfinite(m):
26
+ a = a / m
27
+ return np.clip(a, 0.0, 1.0).astype(np.float32, copy=False)
28
+
29
+
30
+ def _mono_from_any(arr: np.ndarray) -> np.ndarray:
31
+ a = _to_f01(arr)
32
+ if a.ndim == 2:
33
+ return a
34
+ if a.ndim == 3 and a.shape[2] >= 1:
35
+ return a[:, :, 0]
36
+ raise ValueError("Expected mono (HxW) or RGB (HxWx3) image.")
37
+
38
+
39
+ class RGBCombinationDialogPro(QDialog):
40
+ """
41
+ Combine R/G/B from open views or files and ALWAYS create a new document.
42
+ """
43
+ def __init__(self, parent, list_open_docs_fn=None, doc_manager=None):
44
+ super().__init__(parent)
45
+ self.setWindowTitle("RGB Combination")
46
+ self.setWindowFlag(Qt.WindowType.Window, True)
47
+ self.setModal(False)
48
+ #self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
49
+ self._list_open_docs = list_open_docs_fn or (lambda: [])
50
+ self._docman = doc_manager
51
+
52
+ # file-mode state
53
+ self.r_path = None
54
+ self.g_path = None
55
+ self.b_path = None
56
+
57
+ # ── mode choose
58
+ self.load_files_radio = QRadioButton("Load Individual Files")
59
+ self.use_views_radio = QRadioButton("Use Open Views")
60
+ self.use_views_radio.setChecked(True)
61
+
62
+ self.mode_group = QButtonGroup(self)
63
+ self.mode_group.addButton(self.load_files_radio)
64
+ self.mode_group.addButton(self.use_views_radio)
65
+
66
+ mode_box = QGroupBox("Select RGB Combination Mode")
67
+ ml = QVBoxLayout(mode_box)
68
+ ml.addWidget(self.use_views_radio)
69
+ ml.addWidget(self.load_files_radio)
70
+
71
+ # ── file mode widgets
72
+ self.r_label = QLabel("Red: Not selected")
73
+ self.g_label = QLabel("Green: Not selected")
74
+ self.b_label = QLabel("Blue: Not selected")
75
+ self.btn_load_r = QPushButton("Load Red…"); self.btn_load_r.clicked.connect(lambda: self._pick_file("R"))
76
+ self.btn_load_g = QPushButton("Load Green…"); self.btn_load_g.clicked.connect(lambda: self._pick_file("G"))
77
+ self.btn_load_b = QPushButton("Load Blue…"); self.btn_load_b.clicked.connect(lambda: self._pick_file("B"))
78
+
79
+ file_box = QGroupBox("Files")
80
+ fl = QVBoxLayout(file_box)
81
+ for lab, btn in [(self.r_label, self.btn_load_r),
82
+ (self.g_label, self.btn_load_g),
83
+ (self.b_label, self.btn_load_b)]:
84
+ row = QHBoxLayout(); row.addWidget(lab); row.addStretch(); row.addWidget(btn)
85
+ fl.addLayout(row)
86
+
87
+ # ── open-views widgets
88
+ views_box = QGroupBox("Select Open Views for R / G / B")
89
+ vl = QHBoxLayout(views_box)
90
+ self.cmb_r = QComboBox(); self.cmb_g = QComboBox(); self.cmb_b = QComboBox()
91
+ vl.addLayout(self._labeled("Red:", self.cmb_r))
92
+ vl.addLayout(self._labeled("Green:", self.cmb_g))
93
+ vl.addLayout(self._labeled("Blue:", self.cmb_b))
94
+
95
+ # ── buttons
96
+ self.btn_combine = QPushButton("Combine")
97
+ self.btn_cancel = QPushButton("Cancel")
98
+ self.btn_cancel.clicked.connect(self.reject)
99
+ self.btn_combine.clicked.connect(self._combine)
100
+
101
+ btns = QHBoxLayout()
102
+ btns.addStretch(); btns.addWidget(self.btn_combine); btns.addWidget(self.btn_cancel)
103
+
104
+ # ── layout
105
+ L = QVBoxLayout(self)
106
+ L.addWidget(mode_box)
107
+ L.addWidget(views_box)
108
+ L.addWidget(file_box)
109
+ L.addLayout(btns)
110
+
111
+ self.mode_group.buttonClicked.connect(self._refresh_mode)
112
+ self._populate_views()
113
+ self._refresh_mode()
114
+
115
+ # --- helpers
116
+ def _labeled(self, text, w):
117
+ box = QVBoxLayout()
118
+ box.addWidget(QLabel(text))
119
+ box.addWidget(w)
120
+ return box
121
+
122
+ def _populate_views(self):
123
+ self.cmb_r.clear(); self.cmb_g.clear(); self.cmb_b.clear()
124
+ for title, doc in self._list_open_docs():
125
+ self.cmb_r.addItem(title, doc)
126
+ self.cmb_g.addItem(title, doc)
127
+ self.cmb_b.addItem(title, doc)
128
+
129
+ def _refresh_mode(self):
130
+ use_views = self.use_views_radio.isChecked()
131
+ for w in (self.cmb_r, self.cmb_g, self.cmb_b):
132
+ w.setEnabled(use_views)
133
+ for w in (self.r_label, self.g_label, self.b_label, self.btn_load_r, self.btn_load_g, self.btn_load_b):
134
+ w.setEnabled(not use_views)
135
+ if not load_image:
136
+ self.load_files_radio.setEnabled(False)
137
+ self.use_views_radio.setChecked(True)
138
+
139
+ def _pick_file(self, which):
140
+ fp, _ = QFileDialog.getOpenFileName(
141
+ self, f"Select {'Red' if which=='R' else 'Green' if which=='G' else 'Blue'} Image", "",
142
+ "Images (*.png *.tif *.tiff *.fits *.fit *.xisf *.jpg *.jpeg);;All Files (*)"
143
+ )
144
+ if not fp: return
145
+ base = os.path.basename(fp)
146
+ if which == "R":
147
+ self.r_path = fp; self.r_label.setText(f"Red: {base}")
148
+ elif which == "G":
149
+ self.g_path = fp; self.g_label.setText(f"Green: {base}")
150
+ else:
151
+ self.b_path = fp; self.b_label.setText(f"Blue: {base}")
152
+
153
+ def _load_file_mono(self, path: str) -> np.ndarray:
154
+ if load_image is None:
155
+ raise RuntimeError("File loading not available.")
156
+ arr, _, _, _ = load_image(path)
157
+ if arr is None:
158
+ raise RuntimeError(f"Failed to load: {path}")
159
+ return _mono_from_any(arr)
160
+
161
+ # --- combine → ALWAYS new document
162
+ def _combine(self):
163
+ try:
164
+ if self.use_views_radio.isChecked():
165
+ rdoc = self.cmb_r.currentData()
166
+ gdoc = self.cmb_g.currentData()
167
+ bdoc = self.cmb_b.currentData()
168
+ if not (rdoc and gdoc and bdoc):
169
+ QMessageBox.information(self, "RGB Combination", "Please select three views.")
170
+ return
171
+ r = _mono_from_any(getattr(rdoc, "image", None))
172
+ g = _mono_from_any(getattr(gdoc, "image", None))
173
+ b = _mono_from_any(getattr(bdoc, "image", None))
174
+ # propose a title from selections
175
+ title = "RGB_Combined"
176
+ else:
177
+ if not (self.r_path and self.g_path and self.b_path):
178
+ QMessageBox.information(self, "RGB Combination", "Please choose three files.")
179
+ return
180
+ r = self._load_file_mono(self.r_path)
181
+ g = self._load_file_mono(self.g_path)
182
+ b = self._load_file_mono(self.b_path)
183
+ title = f"RGB_{os.path.splitext(os.path.basename(self.r_path))[0]}"
184
+
185
+ if r.shape != g.shape or r.shape != b.shape:
186
+ raise ValueError("All three images must have identical dimensions.")
187
+
188
+ rgb = np.stack([r, g, b], axis=2).astype(np.float32, copy=False)
189
+ rgb = np.clip(rgb, 0.0, 1.0)
190
+
191
+ if not self._docman:
192
+ raise RuntimeError("No document manager available.")
193
+ # create new document every time
194
+ if hasattr(self._docman, "open_array"):
195
+ newdoc = self._docman.open_array(rgb, metadata={"step_name": "RGB Combination"}, title=title)
196
+ elif hasattr(self._docman, "open_numpy"):
197
+ newdoc = self._docman.open_numpy(rgb, metadata={"step_name": "RGB Combination"}, title=title)
198
+ else:
199
+ newdoc = self._docman.create_document(image=rgb, metadata={"step_name": "RGB Combination"}, name=title)
200
+
201
+ if hasattr(self.parent(), "_spawn_subwindow_for"):
202
+ self.parent()._spawn_subwindow_for(newdoc)
203
+
204
+ self.accept()
205
+
206
+ except Exception as e:
207
+ QMessageBox.critical(self, "RGB Combination", f"Failed to combine:\n{e}")