setiastrosuitepro 1.6.0__py3-none-any.whl → 1.6.4.post1__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.
- setiastro/data/SASP_data.fits +0 -0
- setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
- setiastro/data/catalogs/astrobin_filters.csv +890 -0
- setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
- setiastro/data/catalogs/cali2.csv +63 -0
- setiastro/data/catalogs/cali2color.csv +65 -0
- setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
- setiastro/data/catalogs/celestial_catalog.csv +24031 -0
- setiastro/data/catalogs/detected_stars.csv +24784 -0
- setiastro/data/catalogs/fits_header_data.csv +46 -0
- setiastro/data/catalogs/test.csv +8 -0
- setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
- setiastro/images/Astro_Spikes.png +0 -0
- setiastro/images/Background_startup.jpg +0 -0
- setiastro/images/HRDiagram.png +0 -0
- setiastro/images/LExtract.png +0 -0
- setiastro/images/LInsert.png +0 -0
- setiastro/images/Oxygenation-atm-2.svg.png +0 -0
- setiastro/images/RGB080604.png +0 -0
- setiastro/images/abeicon.png +0 -0
- setiastro/images/aberration.png +0 -0
- setiastro/images/andromedatry.png +0 -0
- setiastro/images/andromedatry_satellited.png +0 -0
- setiastro/images/annotated.png +0 -0
- setiastro/images/aperture.png +0 -0
- setiastro/images/astrosuite.ico +0 -0
- setiastro/images/astrosuite.png +0 -0
- setiastro/images/astrosuitepro.icns +0 -0
- setiastro/images/astrosuitepro.ico +0 -0
- setiastro/images/astrosuitepro.png +0 -0
- setiastro/images/background.png +0 -0
- setiastro/images/background2.png +0 -0
- setiastro/images/benchmark.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
- setiastro/images/blaster.png +0 -0
- setiastro/images/blink.png +0 -0
- setiastro/images/clahe.png +0 -0
- setiastro/images/collage.png +0 -0
- setiastro/images/colorwheel.png +0 -0
- setiastro/images/contsub.png +0 -0
- setiastro/images/convo.png +0 -0
- setiastro/images/copyslot.png +0 -0
- setiastro/images/cosmic.png +0 -0
- setiastro/images/cosmicsat.png +0 -0
- setiastro/images/crop1.png +0 -0
- setiastro/images/cropicon.png +0 -0
- setiastro/images/curves.png +0 -0
- setiastro/images/cvs.png +0 -0
- setiastro/images/debayer.png +0 -0
- setiastro/images/denoise_cnn_custom.png +0 -0
- setiastro/images/denoise_cnn_graph.png +0 -0
- setiastro/images/disk.png +0 -0
- setiastro/images/dse.png +0 -0
- setiastro/images/exoicon.png +0 -0
- setiastro/images/eye.png +0 -0
- setiastro/images/fliphorizontal.png +0 -0
- setiastro/images/flipvertical.png +0 -0
- setiastro/images/font.png +0 -0
- setiastro/images/freqsep.png +0 -0
- setiastro/images/functionbundle.png +0 -0
- setiastro/images/graxpert.png +0 -0
- setiastro/images/green.png +0 -0
- setiastro/images/gridicon.png +0 -0
- setiastro/images/halo.png +0 -0
- setiastro/images/hdr.png +0 -0
- setiastro/images/histogram.png +0 -0
- setiastro/images/hubble.png +0 -0
- setiastro/images/imagecombine.png +0 -0
- setiastro/images/invert.png +0 -0
- setiastro/images/isophote.png +0 -0
- setiastro/images/isophote_demo_figure.png +0 -0
- setiastro/images/isophote_demo_image.png +0 -0
- setiastro/images/isophote_demo_model.png +0 -0
- setiastro/images/isophote_demo_residual.png +0 -0
- setiastro/images/jwstpupil.png +0 -0
- setiastro/images/linearfit.png +0 -0
- setiastro/images/livestacking.png +0 -0
- setiastro/images/mask.png +0 -0
- setiastro/images/maskapply.png +0 -0
- setiastro/images/maskcreate.png +0 -0
- setiastro/images/maskremove.png +0 -0
- setiastro/images/morpho.png +0 -0
- setiastro/images/mosaic.png +0 -0
- setiastro/images/multiscale_decomp.png +0 -0
- setiastro/images/nbtorgb.png +0 -0
- setiastro/images/neutral.png +0 -0
- setiastro/images/nuke.png +0 -0
- setiastro/images/openfile.png +0 -0
- setiastro/images/pedestal.png +0 -0
- setiastro/images/pen.png +0 -0
- setiastro/images/pixelmath.png +0 -0
- setiastro/images/platesolve.png +0 -0
- setiastro/images/ppp.png +0 -0
- setiastro/images/pro.png +0 -0
- setiastro/images/project.png +0 -0
- setiastro/images/psf.png +0 -0
- setiastro/images/redo.png +0 -0
- setiastro/images/redoicon.png +0 -0
- setiastro/images/rescale.png +0 -0
- setiastro/images/rgbalign.png +0 -0
- setiastro/images/rgbcombo.png +0 -0
- setiastro/images/rgbextract.png +0 -0
- setiastro/images/rotate180.png +0 -0
- setiastro/images/rotatearbitrary.png +0 -0
- setiastro/images/rotateclockwise.png +0 -0
- setiastro/images/rotatecounterclockwise.png +0 -0
- setiastro/images/satellite.png +0 -0
- setiastro/images/script.png +0 -0
- setiastro/images/selectivecolor.png +0 -0
- setiastro/images/simbad.png +0 -0
- setiastro/images/slot0.png +0 -0
- setiastro/images/slot1.png +0 -0
- setiastro/images/slot2.png +0 -0
- setiastro/images/slot3.png +0 -0
- setiastro/images/slot4.png +0 -0
- setiastro/images/slot5.png +0 -0
- setiastro/images/slot6.png +0 -0
- setiastro/images/slot7.png +0 -0
- setiastro/images/slot8.png +0 -0
- setiastro/images/slot9.png +0 -0
- setiastro/images/spcc.png +0 -0
- setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
- setiastro/images/spinner.gif +0 -0
- setiastro/images/stacking.png +0 -0
- setiastro/images/staradd.png +0 -0
- setiastro/images/staralign.png +0 -0
- setiastro/images/starnet.png +0 -0
- setiastro/images/starregistration.png +0 -0
- setiastro/images/starspike.png +0 -0
- setiastro/images/starstretch.png +0 -0
- setiastro/images/statstretch.png +0 -0
- setiastro/images/supernova.png +0 -0
- setiastro/images/uhs.png +0 -0
- setiastro/images/undoicon.png +0 -0
- setiastro/images/upscale.png +0 -0
- setiastro/images/viewbundle.png +0 -0
- setiastro/images/whitebalance.png +0 -0
- setiastro/images/wimi_icon_256x256.png +0 -0
- setiastro/images/wimilogo.png +0 -0
- setiastro/images/wims.png +0 -0
- setiastro/images/wrench_icon.png +0 -0
- setiastro/images/xisfliberator.png +0 -0
- setiastro/qml/ResourceMonitor.qml +126 -0
- setiastro/saspro/__main__.py +228 -67
- setiastro/saspro/_generated/build_info.py +2 -1
- setiastro/saspro/abe.py +76 -25
- setiastro/saspro/aberration_ai.py +14 -14
- setiastro/saspro/add_stars.py +15 -12
- setiastro/saspro/astrobin_exporter.py +61 -58
- setiastro/saspro/astrospike_python.py +3 -1
- setiastro/saspro/autostretch.py +4 -2
- setiastro/saspro/backgroundneutral.py +65 -14
- setiastro/saspro/batch_convert.py +8 -5
- setiastro/saspro/batch_renamer.py +39 -36
- setiastro/saspro/blemish_blaster.py +15 -12
- setiastro/saspro/blink_comparator_pro.py +605 -379
- setiastro/saspro/cheat_sheet.py +62 -17
- setiastro/saspro/clahe.py +34 -8
- setiastro/saspro/comet_stacking.py +103 -38
- setiastro/saspro/common_tr.py +107 -0
- setiastro/saspro/continuum_subtract.py +7 -7
- setiastro/saspro/convo.py +12 -9
- setiastro/saspro/copyastro.py +3 -0
- setiastro/saspro/cosmicclarity.py +77 -52
- setiastro/saspro/crop_dialog_pro.py +80 -45
- setiastro/saspro/curve_editor_pro.py +51 -33
- setiastro/saspro/debayer.py +6 -3
- setiastro/saspro/doc_manager.py +49 -19
- setiastro/saspro/exoplanet_detector.py +11 -11
- setiastro/saspro/fitsmodifier.py +48 -44
- setiastro/saspro/fix_bom.py +32 -0
- setiastro/saspro/frequency_separation.py +18 -12
- setiastro/saspro/function_bundle.py +18 -16
- setiastro/saspro/generate_translations.py +3092 -0
- setiastro/saspro/ghs_dialog_pro.py +19 -16
- setiastro/saspro/graxpert.py +3 -0
- setiastro/saspro/gui/main_window.py +471 -126
- setiastro/saspro/gui/mixins/dock_mixin.py +123 -11
- setiastro/saspro/gui/mixins/file_mixin.py +25 -20
- setiastro/saspro/gui/mixins/geometry_mixin.py +115 -15
- setiastro/saspro/gui/mixins/header_mixin.py +6 -6
- setiastro/saspro/gui/mixins/mask_mixin.py +8 -8
- setiastro/saspro/gui/mixins/menu_mixin.py +62 -33
- setiastro/saspro/gui/mixins/toolbar_mixin.py +382 -226
- setiastro/saspro/gui/mixins/update_mixin.py +26 -26
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +29 -3
- setiastro/saspro/header_viewer.py +21 -18
- setiastro/saspro/histogram.py +29 -26
- setiastro/saspro/history_explorer.py +2 -0
- setiastro/saspro/i18n.py +168 -0
- setiastro/saspro/image_combine.py +3 -0
- setiastro/saspro/image_peeker_pro.py +52 -44
- setiastro/saspro/imageops/stretch.py +5 -13
- setiastro/saspro/isophote.py +3 -0
- setiastro/saspro/legacy/numba_utils.py +64 -47
- setiastro/saspro/linear_fit.py +3 -0
- setiastro/saspro/live_stacking.py +13 -2
- setiastro/saspro/mask_creation.py +180 -22
- setiastro/saspro/mfdeconv.py +5 -0
- setiastro/saspro/morphology.py +38 -13
- setiastro/saspro/multiscale_decomp.py +713 -256
- setiastro/saspro/nbtorgb_stars.py +12 -2
- setiastro/saspro/numba_utils.py +149 -48
- setiastro/saspro/ops/scripts.py +77 -17
- setiastro/saspro/ops/settings.py +177 -100
- setiastro/saspro/perfect_palette_picker.py +25 -7
- setiastro/saspro/pixelmath.py +114 -110
- setiastro/saspro/plate_solver.py +118 -108
- setiastro/saspro/remove_green.py +24 -7
- setiastro/saspro/remove_stars.py +136 -162
- setiastro/saspro/remove_stars_preset.py +55 -13
- setiastro/saspro/resources.py +46 -15
- setiastro/saspro/rgb_combination.py +19 -18
- setiastro/saspro/rgbalign.py +11 -11
- setiastro/saspro/save_options.py +5 -4
- setiastro/saspro/selective_color.py +84 -25
- setiastro/saspro/sfcc.py +119 -72
- setiastro/saspro/shortcuts.py +345 -36
- setiastro/saspro/signature_insert.py +4 -1
- setiastro/saspro/stacking_suite.py +2066 -1119
- setiastro/saspro/star_alignment.py +291 -331
- setiastro/saspro/star_spikes.py +137 -53
- setiastro/saspro/star_stretch.py +47 -10
- setiastro/saspro/stat_stretch.py +52 -16
- setiastro/saspro/status_log_dock.py +1 -1
- setiastro/saspro/subwindow.py +97 -36
- setiastro/saspro/supernovaasteroidhunter.py +68 -61
- setiastro/saspro/swap_manager.py +77 -42
- setiastro/saspro/translations/all_source_strings.json +4726 -0
- setiastro/saspro/translations/ar_translations.py +4096 -0
- setiastro/saspro/translations/de_translations.py +3728 -0
- setiastro/saspro/translations/es_translations.py +4169 -0
- setiastro/saspro/translations/fr_translations.py +4090 -0
- setiastro/saspro/translations/hi_translations.py +3803 -0
- setiastro/saspro/translations/integrate_translations.py +271 -0
- setiastro/saspro/translations/it_translations.py +4728 -0
- setiastro/saspro/translations/ja_translations.py +3834 -0
- setiastro/saspro/translations/pt_translations.py +3847 -0
- setiastro/saspro/translations/ru_translations.py +3082 -0
- setiastro/saspro/translations/saspro_ar.qm +0 -0
- setiastro/saspro/translations/saspro_ar.ts +16019 -0
- setiastro/saspro/translations/saspro_de.qm +0 -0
- setiastro/saspro/translations/saspro_de.ts +14548 -0
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +16202 -0
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +15870 -0
- setiastro/saspro/translations/saspro_hi.qm +0 -0
- setiastro/saspro/translations/saspro_hi.ts +14855 -0
- setiastro/saspro/translations/saspro_it.qm +0 -0
- setiastro/saspro/translations/saspro_it.ts +19046 -0
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +14980 -0
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +15024 -0
- setiastro/saspro/translations/saspro_ru.qm +0 -0
- setiastro/saspro/translations/saspro_ru.ts +11835 -0
- setiastro/saspro/translations/saspro_sw.qm +0 -0
- setiastro/saspro/translations/saspro_sw.ts +15237 -0
- setiastro/saspro/translations/saspro_uk.qm +0 -0
- setiastro/saspro/translations/saspro_uk.ts +15248 -0
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +15289 -0
- setiastro/saspro/translations/sw_translations.py +3897 -0
- setiastro/saspro/translations/uk_translations.py +3929 -0
- setiastro/saspro/translations/zh_translations.py +3910 -0
- setiastro/saspro/versioning.py +77 -0
- setiastro/saspro/view_bundle.py +20 -17
- setiastro/saspro/wavescale_hdr.py +54 -33
- setiastro/saspro/wavescale_hdr_preset.py +6 -5
- setiastro/saspro/wavescalede.py +54 -31
- setiastro/saspro/wavescalede_preset.py +9 -7
- setiastro/saspro/whitebalance.py +58 -22
- setiastro/saspro/widgets/common_utilities.py +12 -11
- setiastro/saspro/widgets/minigame/game.js +991 -0
- setiastro/saspro/widgets/minigame/index.html +53 -0
- setiastro/saspro/widgets/minigame/style.css +241 -0
- setiastro/saspro/widgets/preview_dialogs.py +8 -8
- setiastro/saspro/widgets/resource_monitor.py +263 -0
- setiastro/saspro/widgets/spinboxes.py +18 -0
- setiastro/saspro/widgets/wavelet_utils.py +52 -20
- setiastro/saspro/wimi.py +7996 -0
- setiastro/saspro/wims.py +578 -0
- setiastro/saspro/window_shelf.py +2 -2
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/METADATA +15 -3
- setiastrosuitepro-1.6.4.post1.dist-info/RECORD +368 -0
- setiastrosuitepro-1.6.0.dist-info/RECORD +0 -174
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/remove_green.py
CHANGED
|
@@ -156,12 +156,15 @@ class RemoveGreenDialog(QDialog):
|
|
|
156
156
|
super().__init__(parent)
|
|
157
157
|
self.main = main
|
|
158
158
|
self.doc = doc
|
|
159
|
-
self.setWindowTitle("Remove Green (SCNR)")
|
|
159
|
+
self.setWindowTitle(self.tr("Remove Green (SCNR)"))
|
|
160
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
161
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
162
|
+
self.setModal(False)
|
|
160
163
|
self._build_ui()
|
|
161
164
|
|
|
162
165
|
def _build_ui(self):
|
|
163
166
|
lay = QVBoxLayout(self)
|
|
164
|
-
lay.addWidget(QLabel("Select the amount to remove green noise:"))
|
|
167
|
+
lay.addWidget(QLabel(self.tr("Select the amount to remove green noise:")))
|
|
165
168
|
|
|
166
169
|
# amount
|
|
167
170
|
self.slider = QSlider(Qt.Orientation.Horizontal)
|
|
@@ -176,7 +179,7 @@ class RemoveGreenDialog(QDialog):
|
|
|
176
179
|
|
|
177
180
|
# mode dropdown
|
|
178
181
|
row_mode = QHBoxLayout()
|
|
179
|
-
row_mode.addWidget(QLabel("Neutral mode:"))
|
|
182
|
+
row_mode.addWidget(QLabel(self.tr("Neutral mode:")))
|
|
180
183
|
self.mode_box = QComboBox()
|
|
181
184
|
# order: avg (default), max, min
|
|
182
185
|
self.mode_box.addItem(_SCNR_MODE_LABELS["avg"], userData="avg")
|
|
@@ -188,14 +191,14 @@ class RemoveGreenDialog(QDialog):
|
|
|
188
191
|
lay.addLayout(row_mode)
|
|
189
192
|
|
|
190
193
|
# preserve lightness
|
|
191
|
-
self.cb_preserve = QCheckBox("Preserve lightness")
|
|
194
|
+
self.cb_preserve = QCheckBox(self.tr("Preserve lightness"))
|
|
192
195
|
self.cb_preserve.setChecked(True)
|
|
193
196
|
lay.addWidget(self.cb_preserve)
|
|
194
197
|
|
|
195
198
|
# buttons
|
|
196
199
|
row = QHBoxLayout()
|
|
197
|
-
btn_apply = QPushButton("Apply"); btn_apply.clicked.connect(self._apply)
|
|
198
|
-
btn_cancel= QPushButton("Cancel"); btn_cancel.clicked.connect(self.reject)
|
|
200
|
+
btn_apply = QPushButton(self.tr("Apply")); btn_apply.clicked.connect(self._apply)
|
|
201
|
+
btn_cancel= QPushButton(self.tr("Cancel")); btn_cancel.clicked.connect(self.reject)
|
|
199
202
|
row.addStretch(1); row.addWidget(btn_apply); row.addWidget(btn_cancel)
|
|
200
203
|
lay.addLayout(row)
|
|
201
204
|
|
|
@@ -263,8 +266,22 @@ class RemoveGreenDialog(QDialog):
|
|
|
263
266
|
# Never let replay bookkeeping kill the dialog
|
|
264
267
|
pass
|
|
265
268
|
|
|
266
|
-
|
|
269
|
+
# Dialog stays open so user can apply to other images
|
|
270
|
+
# Refresh document reference for next operation
|
|
271
|
+
self._refresh_document_from_active()
|
|
267
272
|
|
|
273
|
+
def _refresh_document_from_active(self):
|
|
274
|
+
"""
|
|
275
|
+
Refresh the dialog's document reference to the currently active document.
|
|
276
|
+
This allows reusing the same dialog on different images.
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
if self.main and hasattr(self.main, "_active_doc"):
|
|
280
|
+
new_doc = self.main._active_doc()
|
|
281
|
+
if new_doc is not None and new_doc is not self.doc:
|
|
282
|
+
self.doc = new_doc
|
|
283
|
+
except Exception:
|
|
284
|
+
pass
|
|
268
285
|
|
|
269
286
|
|
|
270
287
|
# ---------- entry points used by main ----------
|
setiastro/saspro/remove_stars.py
CHANGED
|
@@ -136,154 +136,82 @@ def _mtf_params_unlinked(img_rgb01: np.ndarray,
|
|
|
136
136
|
Works on float32 data assumed in [0,1].
|
|
137
137
|
Returns dict with arrays: {'s': (C,), 'm': (C,), 'h': (C,)}.
|
|
138
138
|
"""
|
|
139
|
-
x = np.asarray(img_rgb01, dtype=np.float32)
|
|
140
|
-
# Force 3 channels internally (Siril expects 1 or 3; we always give it 3 here)
|
|
141
|
-
if x.ndim == 2:
|
|
142
|
-
x = np.stack([x] * 3, axis=-1)
|
|
143
|
-
elif x.ndim == 3 and x.shape[2] == 1:
|
|
144
|
-
x = np.repeat(x, 3, axis=2)
|
|
145
|
-
|
|
146
|
-
C = x.shape[2]
|
|
147
|
-
s = np.zeros(C, dtype=np.float32)
|
|
148
|
-
m = np.zeros(C, dtype=np.float32)
|
|
149
|
-
h = np.zeros(C, dtype=np.float32)
|
|
150
|
-
|
|
151
|
-
med = np.zeros(C, dtype=np.float32)
|
|
152
|
-
mad = np.zeros(C, dtype=np.float32)
|
|
153
|
-
inverted_flags = np.zeros(C, dtype=bool)
|
|
154
|
-
|
|
155
|
-
# --- stats per channel (Siril: median / normValue, mad / normValue * MAD_NORM) ---
|
|
156
|
-
# Here normValue == 1.0 because we're already in [0,1]
|
|
157
|
-
for c in range(C):
|
|
158
|
-
ch = x[..., c].astype(np.float32, copy=False)
|
|
159
|
-
med_c = float(np.median(ch))
|
|
160
|
-
mad_raw = float(np.median(np.abs(ch - med_c)))
|
|
161
|
-
mad_c = mad_raw * _MAD_NORM
|
|
162
|
-
if mad_c == 0.0:
|
|
163
|
-
mad_c = 0.001
|
|
164
|
-
|
|
165
|
-
med[c] = med_c
|
|
166
|
-
mad[c] = mad_c
|
|
167
|
-
if med_c > 0.5:
|
|
168
|
-
inverted_flags[c] = True
|
|
169
|
-
|
|
170
|
-
inverted_channels = int(inverted_flags.sum())
|
|
171
|
-
|
|
172
|
-
# --- Main branch (non-inverted dominant) ---
|
|
173
|
-
if inverted_channels < C:
|
|
174
|
-
for c in range(C):
|
|
175
|
-
median = float(med[c])
|
|
176
|
-
mad_c = float(mad[c])
|
|
177
|
-
|
|
178
|
-
c0 = median + shadows_clipping * mad_c
|
|
179
|
-
if c0 < 0.0:
|
|
180
|
-
c0 = 0.0
|
|
181
|
-
# Siril: m2 = median - c0; midtones = MTF(m2, target_bg, 0,1)
|
|
182
|
-
m2 = median - c0
|
|
183
|
-
mid = float(_mtf_scalar(m2, targetbg, 0.0, 1.0))
|
|
184
|
-
|
|
185
|
-
s[c] = c0
|
|
186
|
-
m[c] = mid
|
|
187
|
-
h[c] = 1.0
|
|
188
|
-
|
|
189
|
-
# --- Inverted channel branch ---
|
|
190
|
-
else:
|
|
191
|
-
for c in range(C):
|
|
192
|
-
median = float(med[c])
|
|
193
|
-
mad_c = float(mad[c])
|
|
194
|
-
|
|
195
|
-
c1 = median - shadows_clipping * mad_c
|
|
196
|
-
if c1 > 1.0:
|
|
197
|
-
c1 = 1.0
|
|
198
|
-
m2 = c1 - median
|
|
199
|
-
mid = 1.0 - float(_mtf_scalar(m2, targetbg, 0.0, 1.0))
|
|
200
|
-
|
|
201
|
-
s[c] = 0.0
|
|
202
|
-
m[c] = mid
|
|
203
|
-
h[c] = c1
|
|
204
|
-
|
|
205
|
-
return {"s": s, "m": m, "h": h}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def _apply_mtf_unlinked_rgb(img_rgb01: np.ndarray, p: dict) -> np.ndarray:
|
|
209
139
|
"""
|
|
210
|
-
|
|
140
|
+
Siril-style per-channel MTF parameter estimation, matching
|
|
141
|
+
find_unlinked_midtones_balance_default() / find_unlinked_midtones_balance().
|
|
142
|
+
|
|
143
|
+
Works on float32 data assumed in [0,1].
|
|
144
|
+
Returns dict with arrays: {'s': (C,), 'm': (C,), 'h': (C,)}.
|
|
211
145
|
"""
|
|
212
146
|
x = np.asarray(img_rgb01, dtype=np.float32)
|
|
147
|
+
|
|
148
|
+
# Analyze input shape to handle mono efficiently
|
|
213
149
|
if x.ndim == 2:
|
|
214
|
-
|
|
150
|
+
# (H, W) -> treat as single channel
|
|
151
|
+
x_in = x[..., None] # Virtual 3D (H,W,1)
|
|
152
|
+
C_in = 1
|
|
215
153
|
elif x.ndim == 3 and x.shape[2] == 1:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
154
|
+
x_in = x
|
|
155
|
+
C_in = 1
|
|
156
|
+
else:
|
|
157
|
+
x_in = x
|
|
158
|
+
C_in = x.shape[2]
|
|
159
|
+
|
|
160
|
+
# Vectorized stats calculation on actual data only
|
|
161
|
+
med = np.median(x_in, axis=(0, 1)).astype(np.float32) # shape (C_in,)
|
|
162
|
+
|
|
163
|
+
# MAD requires centered abs diff
|
|
164
|
+
diff = np.abs(x_in - med.reshape(1, 1, C_in))
|
|
165
|
+
mad_raw = np.median(diff, axis=(0, 1)).astype(np.float32) # shape (C_in,)
|
|
166
|
+
|
|
167
|
+
mad = mad_raw * _MAD_NORM
|
|
168
|
+
mad[mad == 0] = 0.001
|
|
169
|
+
|
|
170
|
+
inverted_flags = (med > 0.5)
|
|
171
|
+
# If mono, we just check the one channel. If RGB, we check all.
|
|
172
|
+
# Logic below assumes we return 3-channel params s,m,h even for mono input (broadcasted).
|
|
173
|
+
|
|
174
|
+
# To match original behavior which always returned 3-element arrays for s,m,h:
|
|
175
|
+
# We will compute s_in, m_in, h_in for the input channels, then broadcast to 3.
|
|
176
|
+
|
|
177
|
+
s_in = np.zeros(C_in, dtype=np.float32)
|
|
178
|
+
m_in = np.zeros(C_in, dtype=np.float32)
|
|
179
|
+
h_in = np.zeros(C_in, dtype=np.float32)
|
|
180
|
+
|
|
181
|
+
# We iterate C_in times (1 or 3)
|
|
182
|
+
for c in range(C_in):
|
|
183
|
+
is_inv = inverted_flags[c]
|
|
184
|
+
md = med[c]
|
|
185
|
+
md_dev = mad[c]
|
|
186
|
+
|
|
187
|
+
if not is_inv:
|
|
188
|
+
# Normal
|
|
189
|
+
c0 = max(md + shadows_clipping * md_dev, 0.0)
|
|
190
|
+
m2 = md - c0
|
|
191
|
+
|
|
192
|
+
s_in[c] = c0
|
|
193
|
+
m_in[c] = float(_mtf_scalar(m2, targetbg, 0.0, 1.0))
|
|
194
|
+
h_in[c] = 1.0
|
|
195
|
+
else:
|
|
196
|
+
# Inverted
|
|
197
|
+
c1 = min(md - shadows_clipping * md_dev, 1.0)
|
|
198
|
+
m2 = c1 - md
|
|
199
|
+
|
|
200
|
+
s_in[c] = 0.0
|
|
201
|
+
m_in[c] = 1.0 - float(_mtf_scalar(m2, targetbg, 0.0, 1.0))
|
|
202
|
+
h_in[c] = c1
|
|
203
|
+
|
|
204
|
+
# Broadcast to 3 channels if needed
|
|
205
|
+
if C_in == 1:
|
|
206
|
+
s = np.repeat(s_in, 3)
|
|
207
|
+
m = np.repeat(m_in, 3)
|
|
208
|
+
h = np.repeat(h_in, 3)
|
|
209
|
+
else:
|
|
210
|
+
s = s_in
|
|
211
|
+
m = m_in
|
|
212
|
+
h = h_in
|
|
254
213
|
|
|
255
|
-
|
|
256
|
-
ch = x[..., c]
|
|
257
|
-
lo = float(np.percentile(ch, lo_pct))
|
|
258
|
-
hi = float(np.percentile(ch, hi_pct))
|
|
259
|
-
if not np.isfinite(lo): lo = 0.0
|
|
260
|
-
if not np.isfinite(hi): hi = 1.0
|
|
261
|
-
if hi - lo < 1e-6:
|
|
262
|
-
hi = lo + 1e-6
|
|
263
|
-
lo_vals.append(lo); hi_vals.append(hi)
|
|
264
|
-
out[..., c] = (ch - lo) / (hi - lo)
|
|
265
|
-
|
|
266
|
-
out = np.clip(out, 0.0, 1.0)
|
|
267
|
-
params = {"lo": lo_vals, "hi": hi_vals, "was_single": was_single}
|
|
268
|
-
return out, params
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def _stat_unstretch_rgb(img: np.ndarray, params: dict) -> np.ndarray:
|
|
272
|
-
"""
|
|
273
|
-
Inverse of _stat_stretch_rgb. Expects img RGB float32 [0,1].
|
|
274
|
-
"""
|
|
275
|
-
lo = np.asarray(params["lo"], dtype=np.float32)
|
|
276
|
-
hi = np.asarray(params["hi"], dtype=np.float32)
|
|
277
|
-
out = img.astype(np.float32, copy=True)
|
|
278
|
-
for c in range(3):
|
|
279
|
-
out[..., c] = out[..., c] * (hi[c] - lo[c]) + lo[c]
|
|
280
|
-
out = np.clip(out, 0.0, 1.0)
|
|
281
|
-
if params.get("was_single", False):
|
|
282
|
-
out = out.mean(axis=2, keepdims=False) # back to single channel if needed
|
|
283
|
-
# StarNet needs RGB during processing; we keep RGB after removal for consistency.
|
|
284
|
-
# If you want to return mono to the doc when the source was mono, do it at the very end.
|
|
285
|
-
out = np.stack([out] * 3, axis=-1)
|
|
286
|
-
return out
|
|
214
|
+
return {"s": s, "m": m, "h": h}
|
|
287
215
|
|
|
288
216
|
def _mtf_scalar(x: float, m: float, lo: float = 0.0, hi: float = 1.0) -> float:
|
|
289
217
|
"""
|
|
@@ -324,6 +252,36 @@ def _mtf_scalar(x: float, m: float, lo: float = 0.0, hi: float = 1.0) -> float:
|
|
|
324
252
|
return float(y)
|
|
325
253
|
|
|
326
254
|
|
|
255
|
+
def _apply_mtf_unlinked_rgb(img_rgb01: np.ndarray, p: dict) -> np.ndarray:
|
|
256
|
+
"""
|
|
257
|
+
Apply per-channel MTF exactly. p from _mtf_params_unlinked.
|
|
258
|
+
"""
|
|
259
|
+
x = np.asarray(img_rgb01, dtype=np.float32)
|
|
260
|
+
if x.ndim == 2:
|
|
261
|
+
x = np.stack([x]*3, axis=-1)
|
|
262
|
+
elif x.ndim == 3 and x.shape[2] == 1:
|
|
263
|
+
x = np.repeat(x, 3, axis=2)
|
|
264
|
+
|
|
265
|
+
out = np.empty_like(x, dtype=np.float32)
|
|
266
|
+
for c in range(x.shape[2]):
|
|
267
|
+
out[..., c] = _mtf_apply(x[..., c], float(p["s"][c]), float(p["m"][c]), float(p["h"][c]))
|
|
268
|
+
return np.clip(out, 0.0, 1.0)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _invert_mtf_unlinked_rgb(img_rgb01: np.ndarray, p: dict) -> np.ndarray:
|
|
272
|
+
"""
|
|
273
|
+
Exact analytic inverse per channel (uses same s/m/h arrays).
|
|
274
|
+
"""
|
|
275
|
+
y = np.asarray(img_rgb01, dtype=np.float32)
|
|
276
|
+
if y.ndim == 2:
|
|
277
|
+
y = np.stack([y]*3, axis=-1)
|
|
278
|
+
elif y.ndim == 3 and y.shape[2] == 1:
|
|
279
|
+
y = np.repeat(y, 3, axis=2)
|
|
280
|
+
|
|
281
|
+
out = np.empty_like(y, dtype=np.float32)
|
|
282
|
+
for c in range(y.shape[2]):
|
|
283
|
+
out[..., c] = _mtf_inverse(y[..., c], float(p["s"][c]), float(p["m"][c]), float(p["h"][c]))
|
|
284
|
+
return np.clip(out, 0.0, 1.0)
|
|
327
285
|
# ------------------------------------------------------------
|
|
328
286
|
# Settings helper
|
|
329
287
|
# ------------------------------------------------------------
|
|
@@ -370,24 +328,28 @@ def starnet_starless_from_array(arr_rgb01: np.ndarray, settings, *, tmp_prefix="
|
|
|
370
328
|
in_path = os.path.join(workdir, f"{tmp_prefix}_in.tif")
|
|
371
329
|
out_path = os.path.join(workdir, f"{tmp_prefix}_out.tif")
|
|
372
330
|
|
|
373
|
-
# --- Normalize input shape and safe values ---
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
331
|
+
# --- Normalize input shape (virtual) and safe values ---
|
|
332
|
+
x_in = np.asarray(arr, dtype=np.float32)
|
|
333
|
+
|
|
334
|
+
# If (H,W,1), collapse to (H,W) so mono flows cleanly
|
|
335
|
+
if x_in.ndim == 3 and x_in.shape[2] == 1:
|
|
336
|
+
x_in = x_in[..., 0]
|
|
337
|
+
|
|
338
|
+
# sanitize
|
|
339
|
+
x_in = np.nan_to_num(x_in, nan=0.0, posinf=0.0, neginf=0.0).astype(np.float32, copy=False)
|
|
380
340
|
|
|
381
341
|
# Preserve original numeric scale if users pass >1.0
|
|
382
|
-
xmax = float(np.max(
|
|
342
|
+
xmax = float(np.max(x_in)) if x_in.size else 1.0
|
|
383
343
|
scale_factor = xmax if xmax > 1.01 else 1.0
|
|
384
|
-
|
|
344
|
+
|
|
345
|
+
xin = (x_in / scale_factor) if scale_factor > 1.0 else x_in
|
|
385
346
|
xin = np.clip(xin, 0.0, 1.0)
|
|
386
347
|
|
|
387
348
|
# --- Siril-style unlinked MTF params + pre-stretch ---
|
|
388
349
|
mtf_params = _mtf_params_unlinked(xin, shadows_clipping=-2.8, targetbg=0.25)
|
|
389
350
|
x_for_starnet = _apply_mtf_unlinked_rgb(xin, mtf_params).astype(np.float32, copy=False)
|
|
390
351
|
|
|
352
|
+
|
|
391
353
|
# --- Write 16-bit TIFF for StarNet ---
|
|
392
354
|
save_image(
|
|
393
355
|
x_for_starnet, in_path,
|
|
@@ -662,18 +624,19 @@ def _run_starnet(main, doc):
|
|
|
662
624
|
)
|
|
663
625
|
except Exception:
|
|
664
626
|
pass
|
|
665
|
-
# --- Ensure RGB float32 in safe range
|
|
627
|
+
# --- Ensure RGB float32 in safe range (without expanding yet)
|
|
628
|
+
# Starnet needs RGB eventually, but we can compute stats/normalization on mono
|
|
666
629
|
src = np.asarray(doc.image)
|
|
667
|
-
if src.ndim == 2:
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
processing_image = np.repeat(src, 3, axis=2)
|
|
630
|
+
if src.ndim == 3 and src.shape[2] == 1:
|
|
631
|
+
# standardizing shape is cheap
|
|
632
|
+
processing_image = src[..., 0]
|
|
671
633
|
else:
|
|
672
634
|
processing_image = src
|
|
635
|
+
|
|
673
636
|
processing_image = np.nan_to_num(processing_image.astype(np.float32, copy=False),
|
|
674
637
|
nan=0.0, posinf=0.0, neginf=0.0)
|
|
675
638
|
|
|
676
|
-
# --- Scale normalization if >1.0
|
|
639
|
+
# --- Scale normalization if >1.0
|
|
677
640
|
scale_factor = float(np.max(processing_image))
|
|
678
641
|
if scale_factor > 1.0:
|
|
679
642
|
processing_norm = processing_image / scale_factor
|
|
@@ -1027,11 +990,10 @@ def _run_darkstar(main, doc):
|
|
|
1027
990
|
pass
|
|
1028
991
|
|
|
1029
992
|
# --- Build processing image (RGB float32, normalized) ---
|
|
993
|
+
# DarkStar needs RGB, but we can delay expansion until save
|
|
1030
994
|
src = np.asarray(doc.image)
|
|
1031
|
-
if src.ndim == 2:
|
|
1032
|
-
processing_image =
|
|
1033
|
-
elif src.ndim == 3 and src.shape[2] == 1:
|
|
1034
|
-
processing_image = np.repeat(src, 3, axis=2)
|
|
995
|
+
if src.ndim == 3 and src.shape[2] == 1:
|
|
996
|
+
processing_image = src[..., 0]
|
|
1035
997
|
else:
|
|
1036
998
|
processing_image = src
|
|
1037
999
|
|
|
@@ -1088,8 +1050,20 @@ def _run_darkstar(main, doc):
|
|
|
1088
1050
|
# --- Save pre-stretched image as 32-bit float TIFF for DarkStar ---
|
|
1089
1051
|
in_path = os.path.join(input_dir, "imagetoremovestars.tif")
|
|
1090
1052
|
try:
|
|
1053
|
+
# Check if we need to expand on-the-fly for DarkStar (it expects RGB input)
|
|
1054
|
+
# If img_for_darkstar is mono, save_image might save mono.
|
|
1055
|
+
# "is_mono=False" flag to save_image hints we want RGB.
|
|
1056
|
+
# If the array is 2D, save_image might still save mono unless we feed it 3D.
|
|
1057
|
+
# For safety with DarkStar, we create the 3D view now if needed.
|
|
1058
|
+
|
|
1059
|
+
to_save = img_for_darkstar
|
|
1060
|
+
if to_save.ndim == 2:
|
|
1061
|
+
to_save = np.stack([to_save]*3, axis=-1)
|
|
1062
|
+
elif to_save.ndim == 3 and to_save.shape[2] == 1:
|
|
1063
|
+
to_save = np.repeat(to_save, 3, axis=2)
|
|
1064
|
+
|
|
1091
1065
|
save_image(
|
|
1092
|
-
|
|
1066
|
+
to_save,
|
|
1093
1067
|
in_path,
|
|
1094
1068
|
original_format="tif",
|
|
1095
1069
|
bit_depth="32-bit floating point",
|
|
@@ -12,7 +12,7 @@ from setiastro.saspro.legacy.image_manager import save_image, load_image
|
|
|
12
12
|
# Reuse helpers & plumbing from the interactive module
|
|
13
13
|
from .remove_stars import (
|
|
14
14
|
_ProcThread, _ProcDialog,
|
|
15
|
-
|
|
15
|
+
_mtf_params_unlinked, _apply_mtf_unlinked_rgb, _invert_mtf_unlinked_rgb,
|
|
16
16
|
_active_mask3_from_doc, _mask_blend_with_doc_mask, _push_as_new_doc,
|
|
17
17
|
_ensure_exec_bit,
|
|
18
18
|
)
|
|
@@ -125,24 +125,48 @@ def _run_starnet_headless(main, doc, p):
|
|
|
125
125
|
processing_image = processing_image.astype(np.float32, copy=False)
|
|
126
126
|
|
|
127
127
|
is_linear = bool(p.get("linear", True))
|
|
128
|
-
did_stretch =
|
|
129
|
-
|
|
128
|
+
did_stretch = is_linear
|
|
129
|
+
|
|
130
|
+
# sanitize + normalize if needed (keep exactly like interactive)
|
|
131
|
+
processing_image = np.nan_to_num(processing_image, nan=0.0, posinf=0.0, neginf=0.0).astype(np.float32, copy=False)
|
|
132
|
+
|
|
133
|
+
scale_factor = float(np.max(processing_image)) if processing_image.size else 1.0
|
|
134
|
+
processing_norm = (processing_image / scale_factor) if scale_factor > 1.0 else processing_image
|
|
135
|
+
processing_norm = np.clip(processing_norm, 0.0, 1.0)
|
|
136
|
+
|
|
137
|
+
img_for_starnet = processing_norm
|
|
138
|
+
|
|
130
139
|
if is_linear:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
mtf_params = _mtf_params_unlinked(processing_norm, shadows_clipping=-2.8, targetbg=0.25)
|
|
141
|
+
img_for_starnet = _apply_mtf_unlinked_rgb(processing_norm, mtf_params)
|
|
142
|
+
|
|
143
|
+
# stash for inverse step (same keys as interactive)
|
|
144
|
+
try:
|
|
145
|
+
setattr(main, "_starnet_stat_meta", {
|
|
146
|
+
"scheme": "siril_mtf",
|
|
147
|
+
"s": np.asarray(mtf_params["s"], dtype=np.float32),
|
|
148
|
+
"m": np.asarray(mtf_params["m"], dtype=np.float32),
|
|
149
|
+
"h": np.asarray(mtf_params["h"], dtype=np.float32),
|
|
150
|
+
"scale": float(scale_factor),
|
|
151
|
+
})
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
134
154
|
else:
|
|
135
|
-
|
|
136
|
-
|
|
155
|
+
try:
|
|
156
|
+
if hasattr(main, "_starnet_stat_meta"):
|
|
157
|
+
delattr(main, "_starnet_stat_meta")
|
|
158
|
+
except Exception:
|
|
159
|
+
pass
|
|
160
|
+
|
|
137
161
|
|
|
138
162
|
starnet_dir = os.path.dirname(exe) or os.getcwd()
|
|
139
163
|
in_path = os.path.join(starnet_dir, "imagetoremovestars.tif")
|
|
140
164
|
out_path = os.path.join(starnet_dir, "starless.tif")
|
|
141
165
|
|
|
142
166
|
try:
|
|
143
|
-
save_image(
|
|
144
|
-
|
|
145
|
-
|
|
167
|
+
save_image(img_for_starnet, in_path, original_format="tif",
|
|
168
|
+
bit_depth="16-bit", original_header=None, is_mono=False,
|
|
169
|
+
image_meta=None, file_meta=None)
|
|
146
170
|
except Exception as e:
|
|
147
171
|
QMessageBox.critical(main, "StarNet", f"Failed to write input TIFF:\n{e}")
|
|
148
172
|
return
|
|
@@ -179,12 +203,30 @@ def _finish_starnet(main, doc, rc, dlg, in_path, out_path, did_stretch):
|
|
|
179
203
|
starless_rgb = starless_rgb.astype(np.float32, copy=False)
|
|
180
204
|
|
|
181
205
|
if did_stretch:
|
|
206
|
+
meta = getattr(main, "_starnet_stat_meta", None)
|
|
207
|
+
if isinstance(meta, dict) and meta.get("scheme") == "siril_mtf":
|
|
208
|
+
try:
|
|
209
|
+
p = {
|
|
210
|
+
"s": np.asarray(meta.get("s"), dtype=np.float32),
|
|
211
|
+
"m": np.asarray(meta.get("m"), dtype=np.float32),
|
|
212
|
+
"h": np.asarray(meta.get("h"), dtype=np.float32),
|
|
213
|
+
}
|
|
214
|
+
inv = _invert_mtf_unlinked_rgb(starless_rgb, p)
|
|
215
|
+
sc = float(meta.get("scale", 1.0))
|
|
216
|
+
if sc > 1.0:
|
|
217
|
+
inv *= sc
|
|
218
|
+
starless_rgb = np.clip(inv, 0.0, 1.0).astype(np.float32, copy=False)
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
# cleanup so it can't leak
|
|
182
223
|
try:
|
|
183
|
-
|
|
184
|
-
|
|
224
|
+
if hasattr(main, "_starnet_stat_meta"):
|
|
225
|
+
delattr(main, "_starnet_stat_meta")
|
|
185
226
|
except Exception:
|
|
186
227
|
pass
|
|
187
228
|
|
|
229
|
+
|
|
188
230
|
# original as RGB
|
|
189
231
|
orig = np.asarray(doc.image)
|
|
190
232
|
if orig.ndim == 2: original_rgb = np.stack([orig]*3, axis=-1)
|
setiastro/saspro/resources.py
CHANGED
|
@@ -77,14 +77,19 @@ def _get_base_path() -> str:
|
|
|
77
77
|
except Exception:
|
|
78
78
|
pass
|
|
79
79
|
|
|
80
|
-
# Development: resources are in
|
|
80
|
+
# Development: resources are in package directory (src/setiastro/images/)
|
|
81
81
|
# File is at: src/setiastro/saspro/resources.py
|
|
82
|
-
#
|
|
82
|
+
# Check if images/ exists in the setiastro package directory
|
|
83
83
|
current_file = os.path.abspath(__file__)
|
|
84
|
-
# Go up from resources.py -> saspro -> setiastro
|
|
85
|
-
|
|
84
|
+
# Go up from resources.py -> saspro -> setiastro (package directory)
|
|
85
|
+
package_dir = os.path.dirname(os.path.dirname(current_file))
|
|
86
|
+
images_dir = os.path.join(package_dir, 'images')
|
|
87
|
+
if os.path.exists(images_dir):
|
|
88
|
+
return package_dir
|
|
86
89
|
|
|
87
|
-
#
|
|
90
|
+
# Fallback: try project root (for backward compatibility)
|
|
91
|
+
# Go up from resources.py -> saspro -> setiastro -> src -> project root
|
|
92
|
+
base = os.path.dirname(os.path.dirname(package_dir))
|
|
88
93
|
images_dir = os.path.join(base, 'images')
|
|
89
94
|
if os.path.exists(images_dir):
|
|
90
95
|
return base
|
|
@@ -118,17 +123,31 @@ def _get_base_path() -> str:
|
|
|
118
123
|
|
|
119
124
|
|
|
120
125
|
def _resource_path(filename: str) -> str:
|
|
121
|
-
"""Get full path to a resource file."""
|
|
122
126
|
base = _get_base_path()
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
fn = filename
|
|
128
|
+
|
|
129
|
+
is_img = fn.lower().endswith(('.png','.ico','.gif','.icns','.svg','.jpg','.jpeg','.bmp'))
|
|
130
|
+
if is_img:
|
|
131
|
+
candidates = [
|
|
132
|
+
os.path.join(base, 'images', fn),
|
|
133
|
+
os.path.join(base, 'setiastro', 'images', fn),
|
|
134
|
+
os.path.join(base, 'setiastro', 'saspro', 'images', fn),
|
|
135
|
+
]
|
|
136
|
+
for p in candidates:
|
|
137
|
+
if os.path.exists(p):
|
|
138
|
+
return p
|
|
139
|
+
|
|
140
|
+
# data / other files
|
|
141
|
+
candidates = [
|
|
142
|
+
os.path.join(base, fn),
|
|
143
|
+
os.path.join(base, 'setiastro', fn),
|
|
144
|
+
os.path.join(base, 'setiastro', 'saspro', fn),
|
|
145
|
+
]
|
|
146
|
+
for p in candidates:
|
|
147
|
+
if os.path.exists(p):
|
|
148
|
+
return p
|
|
149
|
+
|
|
150
|
+
return os.path.join(base, fn)
|
|
132
151
|
|
|
133
152
|
|
|
134
153
|
class Icons:
|
|
@@ -205,6 +224,7 @@ class Icons:
|
|
|
205
224
|
ROTATE_CW = property(lambda self: _resource_path('rotateclockwise.png'))
|
|
206
225
|
ROTATE_CCW = property(lambda self: _resource_path('rotatecounterclockwise.png'))
|
|
207
226
|
ROTATE_180 = property(lambda self: _resource_path('rotate180.png'))
|
|
227
|
+
ROTATE_ANY = property(lambda self: _resource_path('rotatearbitrary.png'))
|
|
208
228
|
RESCALE = property(lambda self: _resource_path('rescale.png'))
|
|
209
229
|
|
|
210
230
|
# Masks
|
|
@@ -390,6 +410,7 @@ def _init_legacy_paths():
|
|
|
390
410
|
'rotateclockwise_path': get_icon_path('rotateclockwise.png'),
|
|
391
411
|
'rotatecounterclockwise_path': get_icon_path('rotatecounterclockwise.png'),
|
|
392
412
|
'rotate180_path': get_icon_path('rotate180.png'),
|
|
413
|
+
'rotatearbitrary_path': get_icon_path('rotatearbitrary.png'),
|
|
393
414
|
'maskcreate_path': get_icon_path('maskcreate.png'),
|
|
394
415
|
'maskapply_path': get_icon_path('maskapply.png'),
|
|
395
416
|
'maskremove_path': get_icon_path('maskremove.png'),
|
|
@@ -464,9 +485,19 @@ def _init_legacy_paths():
|
|
|
464
485
|
_legacy = _init_legacy_paths()
|
|
465
486
|
globals().update(_legacy)
|
|
466
487
|
|
|
488
|
+
|
|
489
|
+
# Background for startup
|
|
490
|
+
background_startup_path = _resource_path('Background_startup.jpg')
|
|
491
|
+
_legacy['background_startup_path'] = background_startup_path
|
|
492
|
+
|
|
493
|
+
# QML helper
|
|
494
|
+
resource_monitor_qml = _resource_path(os.path.join("qml", "ResourceMonitor.qml"))
|
|
495
|
+
|
|
467
496
|
# Export list for `from setiastro.saspro.resources import *`
|
|
468
497
|
__all__ = [
|
|
469
498
|
'Icons', 'Resources',
|
|
470
499
|
'get_icons', 'get_resources',
|
|
471
500
|
'get_icon_path', 'get_data_path',
|
|
501
|
+
'background_startup_path',
|
|
472
502
|
] + list(_legacy.keys())
|
|
503
|
+
|