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.
- 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/star_spikes.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# pro/tools/star_spikes.py
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
import numpy as np
|
|
4
|
-
|
|
4
|
+
import math
|
|
5
5
|
from PyQt6.QtCore import Qt
|
|
6
6
|
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSplitter, QSizePolicy, QWidget, QApplication,
|
|
7
7
|
QFormLayout, QGroupBox, QDoubleSpinBox, QSpinBox,
|
|
@@ -49,7 +49,10 @@ class StarSpikesDialogPro(QDialog):
|
|
|
49
49
|
aperture_help_path: str | None = None,
|
|
50
50
|
spinner_path: str | None = None):
|
|
51
51
|
super().__init__(parent)
|
|
52
|
-
self.setWindowTitle("Diffraction Spikes")
|
|
52
|
+
self.setWindowTitle(self.tr("Diffraction Spikes"))
|
|
53
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
54
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
55
|
+
self.setModal(False)
|
|
53
56
|
self.docman = doc_manager
|
|
54
57
|
self.doc = initial_doc or (self.docman.get_active_document() if self.docman else None)
|
|
55
58
|
self.jwstpupil_path = jwstpupil_path
|
|
@@ -97,7 +100,7 @@ class StarSpikesDialogPro(QDialog):
|
|
|
97
100
|
return sp
|
|
98
101
|
|
|
99
102
|
# --- Group: Star Detection ---
|
|
100
|
-
grp_detect = QGroupBox("Star Detection")
|
|
103
|
+
grp_detect = QGroupBox(self.tr("Star Detection"))
|
|
101
104
|
f_detect = QFormLayout(grp_detect)
|
|
102
105
|
f_detect.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
|
|
103
106
|
f_detect.setFormAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
|
@@ -108,11 +111,11 @@ class StarSpikesDialogPro(QDialog):
|
|
|
108
111
|
# keep self.advanced in sync if user edits
|
|
109
112
|
self.detect_thresh.valueChanged.connect(lambda v: self.advanced.__setitem__("detect_thresh", float(v)))
|
|
110
113
|
|
|
111
|
-
f_detect.addRow("Flux Min:", self.flux_min)
|
|
112
|
-
f_detect.addRow("Detection Threshold (σ):", self.detect_thresh)
|
|
114
|
+
f_detect.addRow(self.tr("Flux Min:"), self.flux_min)
|
|
115
|
+
f_detect.addRow(self.tr("Detection Threshold (σ):"), self.detect_thresh)
|
|
113
116
|
|
|
114
117
|
# --- Group: Aperture (Geometry) ---
|
|
115
|
-
grp_ap = QGroupBox("Aperture")
|
|
118
|
+
grp_ap = QGroupBox(self.tr("Aperture"))
|
|
116
119
|
f_ap = QFormLayout(grp_ap)
|
|
117
120
|
f_ap.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
|
|
118
121
|
f_ap.setFormAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
|
@@ -127,7 +130,7 @@ class StarSpikesDialogPro(QDialog):
|
|
|
127
130
|
border-radius: 14px; background:#ccc; border:1px solid #999;}
|
|
128
131
|
QPushButton:checked { background:#66bb6a; }
|
|
129
132
|
""")
|
|
130
|
-
f_ap.addRow("Aperture Type:", self.pupil_jwst)
|
|
133
|
+
f_ap.addRow(self.tr("Aperture Type:"), self.pupil_jwst)
|
|
131
134
|
|
|
132
135
|
self.radius = dspin(1.0, 512.0, 1.0, 128.0, decimals=1)
|
|
133
136
|
self.obstruction = dspin(0.0, 0.99, 0.01, 0.2, decimals=2)
|
|
@@ -135,14 +138,14 @@ class StarSpikesDialogPro(QDialog):
|
|
|
135
138
|
self.vane_width = dspin(0.0, 50.0, 0.5, 4.0, decimals=2)
|
|
136
139
|
self.rotation = dspin(0.0, 360.0, 1.0, 0.0, decimals=1)
|
|
137
140
|
|
|
138
|
-
f_ap.addRow("Pupil Radius:", self.radius)
|
|
139
|
-
f_ap.addRow("Obstruction:", self.obstruction)
|
|
140
|
-
f_ap.addRow("Number of Vanes:", self.num_vanes)
|
|
141
|
-
f_ap.addRow("Vane Width:", self.vane_width)
|
|
142
|
-
f_ap.addRow("Rotation (deg):", self.rotation)
|
|
141
|
+
f_ap.addRow(self.tr("Pupil Radius:"), self.radius)
|
|
142
|
+
f_ap.addRow(self.tr("Obstruction:"), self.obstruction)
|
|
143
|
+
f_ap.addRow(self.tr("Number of Vanes:"), self.num_vanes)
|
|
144
|
+
f_ap.addRow(self.tr("Vane Width:"), self.vane_width)
|
|
145
|
+
f_ap.addRow(self.tr("Rotation (deg):"), self.rotation)
|
|
143
146
|
|
|
144
147
|
# --- Group: PSF & Synthesis ---
|
|
145
|
-
grp_psf = QGroupBox("PSF & Synthesis")
|
|
148
|
+
grp_psf = QGroupBox(self.tr("PSF & Synthesis"))
|
|
146
149
|
f_psf = QFormLayout(grp_psf)
|
|
147
150
|
f_psf.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
|
|
148
151
|
f_psf.setFormAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
|
@@ -150,18 +153,18 @@ class StarSpikesDialogPro(QDialog):
|
|
|
150
153
|
self.color_boost = dspin(0.1, 10.0, 0.1, 1.5, decimals=2)
|
|
151
154
|
self.blur_sigma = dspin(0.1, 10.0, 0.1, 2.0, decimals=2)
|
|
152
155
|
|
|
153
|
-
f_psf.addRow("Spike Boost:", self.color_boost)
|
|
154
|
-
f_psf.addRow("PSF Blur Sigma:", self.blur_sigma)
|
|
156
|
+
f_psf.addRow(self.tr("Spike Boost:"), self.color_boost)
|
|
157
|
+
f_psf.addRow(self.tr("PSF Blur Sigma:"), self.blur_sigma)
|
|
155
158
|
|
|
156
159
|
# --- Actions ---
|
|
157
160
|
row_actions = QHBoxLayout()
|
|
158
161
|
row_actions.setSpacing(8)
|
|
159
|
-
self.btn_run = QPushButton("Generate Spikes")
|
|
162
|
+
self.btn_run = QPushButton(self.tr("Generate Spikes"))
|
|
160
163
|
self.btn_run.clicked.connect(self._run)
|
|
161
|
-
self.btn_apply = QPushButton("Apply to Active Document")
|
|
164
|
+
self.btn_apply = QPushButton(self.tr("Apply to Active Document"))
|
|
162
165
|
self.btn_apply.clicked.connect(self._apply_to_doc)
|
|
163
166
|
self.btn_apply.setEnabled(False)
|
|
164
|
-
self.btn_help = QPushButton("Aperture Help")
|
|
167
|
+
self.btn_help = QPushButton(self.tr("Aperture Help"))
|
|
165
168
|
self.btn_help.clicked.connect(self._show_help)
|
|
166
169
|
row_actions.addWidget(self.btn_run)
|
|
167
170
|
row_actions.addWidget(self.btn_apply)
|
|
@@ -169,7 +172,7 @@ class StarSpikesDialogPro(QDialog):
|
|
|
169
172
|
row_actions.addStretch(1)
|
|
170
173
|
|
|
171
174
|
# --- Status ---
|
|
172
|
-
self.status = QLabel("Ready")
|
|
175
|
+
self.status = QLabel(self.tr("Ready"))
|
|
173
176
|
self.status.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
174
177
|
self.status.setWordWrap(True)
|
|
175
178
|
|
|
@@ -189,9 +192,9 @@ class StarSpikesDialogPro(QDialog):
|
|
|
189
192
|
|
|
190
193
|
# zoom toolbar
|
|
191
194
|
zrow = QHBoxLayout()
|
|
192
|
-
self.btn_zoom_in = QPushButton("Zoom In")
|
|
193
|
-
self.btn_zoom_out = QPushButton("Zoom Out")
|
|
194
|
-
self.btn_fit = QPushButton("Fit to Preview")
|
|
195
|
+
self.btn_zoom_in = QPushButton(self.tr("Zoom In"))
|
|
196
|
+
self.btn_zoom_out = QPushButton(self.tr("Zoom Out"))
|
|
197
|
+
self.btn_fit = QPushButton(self.tr("Fit to Preview"))
|
|
195
198
|
self.btn_zoom_in.clicked.connect(self._zoom_in)
|
|
196
199
|
self.btn_zoom_out.clicked.connect(self._zoom_out)
|
|
197
200
|
self.btn_fit.clicked.connect(self._fit_to_preview)
|
|
@@ -381,6 +384,13 @@ class StarSpikesDialogPro(QDialog):
|
|
|
381
384
|
shrink_max = self.advanced["shrink_max"]
|
|
382
385
|
color_boost = self.color_boost.value()
|
|
383
386
|
|
|
387
|
+
# Try OpenCV for faster zoom/blur
|
|
388
|
+
try:
|
|
389
|
+
import cv2
|
|
390
|
+
_HAS_CV2 = True
|
|
391
|
+
except ImportError:
|
|
392
|
+
_HAS_CV2 = False
|
|
393
|
+
|
|
384
394
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
385
395
|
def star_runner(x, y, flux, a, b):
|
|
386
396
|
brightness = np.clip(np.log1p(flux)/8.0, 0.1, 3.0)
|
|
@@ -388,39 +398,79 @@ class StarSpikesDialogPro(QDialog):
|
|
|
388
398
|
tile_size = min(tile_size, 768)
|
|
389
399
|
tile_size += tile_size % 2
|
|
390
400
|
pad = tile_size // 2
|
|
391
|
-
|
|
401
|
+
|
|
402
|
+
# Guard against fully out-of-bounds, but allow partial overlaps
|
|
403
|
+
if not (0 <= x < W and 0 <= y < H):
|
|
392
404
|
return None
|
|
393
405
|
|
|
406
|
+
# Measure star color
|
|
394
407
|
r_ratio, g_ratio, b_ratio = self._measure_star_color(img, x, y, sampling_radius=3)
|
|
408
|
+
|
|
409
|
+
# Extract PSF tiles
|
|
395
410
|
tile_r = self._extract_center_tile(psf_r, tile_size) * brightness * r_ratio * color_boost
|
|
396
411
|
tile_g = self._extract_center_tile(psf_g, tile_size) * brightness * g_ratio * color_boost
|
|
397
412
|
tile_b = self._extract_center_tile(psf_b, tile_size) * brightness * b_ratio * color_boost
|
|
398
413
|
|
|
414
|
+
# Boost/Shrink
|
|
399
415
|
b_scale, s_factor = self._boost_shrink_from_flux(flux, self.flux_min.value(), flux_max,
|
|
400
416
|
bscale_min, bscale_max, shrink_min, shrink_max)
|
|
401
|
-
final_r = self._shrink_and_boost(tile_r, b_scale, s_factor)
|
|
402
|
-
final_g = self._shrink_and_boost(tile_g, b_scale, s_factor)
|
|
403
|
-
final_b = self._shrink_and_boost(tile_b, b_scale, s_factor)
|
|
404
|
-
|
|
405
|
-
new_size = final_r.shape[0]
|
|
406
|
-
pad_new = new_size // 2
|
|
407
|
-
y0, y1 = y - pad_new, y - pad_new + new_size
|
|
408
|
-
x0, x1 = x - pad_new, x - pad_new + new_size
|
|
409
|
-
if (y0 < 0 or y1 > H or x0 < 0 or x1 > W):
|
|
410
|
-
return None
|
|
411
417
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
418
|
+
# --- Fast Resize (Zoom) ---
|
|
419
|
+
def _fast_zoom(arr, z):
|
|
420
|
+
if z == 1.0: return arr
|
|
421
|
+
if _HAS_CV2:
|
|
422
|
+
h, w = arr.shape
|
|
423
|
+
nw, nh = int(round(w * z)), int(round(h * z))
|
|
424
|
+
if nw <= 0 or nh <= 0: return np.zeros((2,2), dtype=np.float32)
|
|
425
|
+
return cv2.resize(arr, (nw, nh), interpolation=cv2.INTER_LINEAR)
|
|
426
|
+
else:
|
|
427
|
+
return ndi.zoom(arr, z, order=1)
|
|
428
|
+
|
|
429
|
+
final_r = np.clip(_fast_zoom(tile_r * b_scale, 1.0/s_factor), 0.0, 1.0)
|
|
430
|
+
final_g = np.clip(_fast_zoom(tile_g * b_scale, 1.0/s_factor), 0.0, 1.0)
|
|
431
|
+
final_b = np.clip(_fast_zoom(tile_b * b_scale, 1.0/s_factor), 0.0, 1.0)
|
|
432
|
+
|
|
433
|
+
# --- Return Patch Data (y, x, patch) ---
|
|
434
|
+
new_h, new_w = final_r.shape
|
|
435
|
+
|
|
436
|
+
# Coords of the *patch top-left* relative to the image
|
|
437
|
+
# The star is at (x,y), and the patch center is approx (new_w//2, new_h//2)
|
|
438
|
+
# We want to center the patch on the star.
|
|
439
|
+
py0 = y - (new_h // 2)
|
|
440
|
+
px0 = x - (new_w // 2)
|
|
441
|
+
|
|
442
|
+
# Combine channels
|
|
443
|
+
patch = np.dstack((final_r, final_g, final_b)).astype(np.float32)
|
|
444
|
+
return (int(py0), int(px0), patch)
|
|
417
445
|
|
|
418
446
|
with ThreadPoolExecutor() as ex:
|
|
419
447
|
futs = [ex.submit(star_runner, *s) for s in stars]
|
|
420
448
|
for f in as_completed(futs):
|
|
421
|
-
|
|
422
|
-
if
|
|
423
|
-
|
|
449
|
+
res = f.result()
|
|
450
|
+
if res is None:
|
|
451
|
+
continue
|
|
452
|
+
|
|
453
|
+
py0, px0, patch = res
|
|
454
|
+
ph, pw, _ = patch.shape
|
|
455
|
+
|
|
456
|
+
# Calculate intersection with canvas
|
|
457
|
+
y_start = max(0, py0)
|
|
458
|
+
y_end = min(H, py0 + ph)
|
|
459
|
+
x_start = max(0, px0)
|
|
460
|
+
x_end = min(W, px0 + pw)
|
|
461
|
+
|
|
462
|
+
# If no overlap, skip
|
|
463
|
+
if y_start >= y_end or x_start >= x_end:
|
|
464
|
+
continue
|
|
465
|
+
|
|
466
|
+
# Offsets into the patch
|
|
467
|
+
patch_y_start = y_start - py0
|
|
468
|
+
patch_y_end = patch_y_start + (y_end - y_start)
|
|
469
|
+
patch_x_start = x_start - px0
|
|
470
|
+
patch_x_end = patch_x_start + (x_end - x_start)
|
|
471
|
+
|
|
472
|
+
# Add to canvas
|
|
473
|
+
canvas[y_start:y_end, x_start:x_end] += patch[patch_y_start:patch_y_end, patch_x_start:patch_x_end]
|
|
424
474
|
|
|
425
475
|
self.status.setText("Compositing…")
|
|
426
476
|
QApplication.processEvents()
|
|
@@ -543,14 +593,56 @@ class StarSpikesDialogPro(QDialog):
|
|
|
543
593
|
return img
|
|
544
594
|
|
|
545
595
|
def _simulate_psf(self, pupil, wavelength_scale=1.0, blur_sigma=1.0):
|
|
546
|
-
|
|
596
|
+
# Try to use OpenCV for speed
|
|
597
|
+
if getattr(self, "_cv2_checked", False):
|
|
598
|
+
has_cv2 = True
|
|
599
|
+
import cv2
|
|
600
|
+
else:
|
|
601
|
+
try:
|
|
602
|
+
import cv2
|
|
603
|
+
has_cv2 = True
|
|
604
|
+
except ImportError:
|
|
605
|
+
has_cv2 = False
|
|
606
|
+
self._cv2_checked = has_cv2
|
|
607
|
+
|
|
608
|
+
if has_cv2:
|
|
609
|
+
# Gaussian blur on pupil
|
|
610
|
+
# kernel size usually ~6*sigma, must be odd
|
|
611
|
+
k_pupil = int(math.ceil(6 * (0.1 * wavelength_scale))) | 1
|
|
612
|
+
sp = cv2.GaussianBlur(pupil, (k_pupil, k_pupil), 0.1 * wavelength_scale)
|
|
613
|
+
else:
|
|
614
|
+
sp = gaussian_filter(pupil, sigma=0.1 * wavelength_scale)
|
|
615
|
+
|
|
547
616
|
fft = np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(sp)))
|
|
548
617
|
intensity = np.abs(fft)**2
|
|
549
618
|
intensity /= (intensity.max() + 1e-8)
|
|
550
|
-
|
|
619
|
+
|
|
620
|
+
if has_cv2 and blur_sigma > 0:
|
|
621
|
+
k_blur = int(math.ceil(6 * blur_sigma)) | 1
|
|
622
|
+
blurred = cv2.GaussianBlur(intensity, (k_blur, k_blur), blur_sigma)
|
|
623
|
+
else:
|
|
624
|
+
blurred = gaussian_filter(intensity, sigma=blur_sigma)
|
|
625
|
+
|
|
551
626
|
psf = blurred / max(blurred.max(), 1e-8)
|
|
627
|
+
|
|
552
628
|
if wavelength_scale != 1.0:
|
|
553
|
-
|
|
629
|
+
if has_cv2:
|
|
630
|
+
h, w = psf.shape
|
|
631
|
+
# Zoom uses size, NOT scale factor in resize(..., dsize=(w,h))
|
|
632
|
+
# wavelength_scale > 1 => zoom in => crop middle? or simply scale?
|
|
633
|
+
# The original used ndi.zoom(psf, zoom=wavelength_scale).
|
|
634
|
+
# New size:
|
|
635
|
+
nw, nh = int(round(w * wavelength_scale)), int(round(h * wavelength_scale))
|
|
636
|
+
if nw > 0 and nh > 0:
|
|
637
|
+
scaled = cv2.resize(psf, (nw, nh), interpolation=cv2.INTER_LINEAR)
|
|
638
|
+
# We might need to crop back to original size or pad?
|
|
639
|
+
# ndi.zoom changes the array size.
|
|
640
|
+
# The simulator seems to assume we handle whatever size comes out?
|
|
641
|
+
# Let's check _extract_center_tile usage.
|
|
642
|
+
psf = scaled
|
|
643
|
+
else:
|
|
644
|
+
psf = ndi.zoom(psf, zoom=wavelength_scale, order=1)
|
|
645
|
+
|
|
554
646
|
psf /= psf.max() + 1e-12
|
|
555
647
|
return psf
|
|
556
648
|
|
|
@@ -591,15 +683,7 @@ class StarSpikesDialogPro(QDialog):
|
|
|
591
683
|
stars.append((int(obj['x']), int(obj['y']), float(flux), float(a), float(b)))
|
|
592
684
|
return stars
|
|
593
685
|
|
|
594
|
-
|
|
595
|
-
def _shrink_and_boost(tile, brightness_scale=2.0, shrink_factor=1.5):
|
|
596
|
-
tile = np.clip(tile * float(brightness_scale), 0.0, 1.0)
|
|
597
|
-
in_sz = tile.shape[0]
|
|
598
|
-
out_sz = int(in_sz // float(shrink_factor))
|
|
599
|
-
out_sz += out_sz % 2
|
|
600
|
-
if out_sz <= 0: out_sz = 2
|
|
601
|
-
z = out_sz / float(in_sz)
|
|
602
|
-
return np.clip(ndi.zoom(tile, z, order=1), 0.0, 1.0)
|
|
686
|
+
# _shrink_and_boost removed (replaced by inline _fast_zoom for performance)
|
|
603
687
|
|
|
604
688
|
@staticmethod
|
|
605
689
|
def _boost_shrink_from_flux(flux, flux_min, flux_max, bmin, bmax, smin, smax):
|
setiastro/saspro/star_stretch.py
CHANGED
|
@@ -126,9 +126,17 @@ class StarStretchDialog(QDialog):
|
|
|
126
126
|
"""
|
|
127
127
|
def __init__(self, parent, document):
|
|
128
128
|
super().__init__(parent)
|
|
129
|
-
self.setWindowTitle("Star Stretch")
|
|
129
|
+
self.setWindowTitle(self.tr("Star Stretch"))
|
|
130
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
131
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
132
|
+
self.setModal(False)
|
|
133
|
+
self._main = parent
|
|
130
134
|
self.doc = document
|
|
131
135
|
self._preview: np.ndarray | None = None
|
|
136
|
+
|
|
137
|
+
# Connect to active document change signal
|
|
138
|
+
if hasattr(self._main, "currentDocumentChanged"):
|
|
139
|
+
self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
|
|
132
140
|
self._pix: QPixmap | None = None
|
|
133
141
|
self._zoom = 0.25
|
|
134
142
|
self._panning = False
|
|
@@ -150,7 +158,7 @@ class StarStretchDialog(QDialog):
|
|
|
150
158
|
left.addWidget(info)
|
|
151
159
|
|
|
152
160
|
# Stretch slider (0..8.00)
|
|
153
|
-
self.lbl_st = QLabel("Stretch Amount: 5.00")
|
|
161
|
+
self.lbl_st = QLabel(self.tr("Stretch Amount:") + " 5.00")
|
|
154
162
|
self.sld_st = QSlider(Qt.Orientation.Horizontal)
|
|
155
163
|
self.sld_st.setRange(0, 800)
|
|
156
164
|
self.sld_st.setValue(500)
|
|
@@ -159,7 +167,7 @@ class StarStretchDialog(QDialog):
|
|
|
159
167
|
left.addWidget(self.sld_st)
|
|
160
168
|
|
|
161
169
|
# Saturation slider (0..2.00)
|
|
162
|
-
self.lbl_sat = QLabel("Color Boost: 1.00")
|
|
170
|
+
self.lbl_sat = QLabel(self.tr("Color Boost:") + " 1.00")
|
|
163
171
|
self.sld_sat = QSlider(Qt.Orientation.Horizontal)
|
|
164
172
|
self.sld_sat.setRange(0, 200)
|
|
165
173
|
self.sld_sat.setValue(100)
|
|
@@ -168,13 +176,13 @@ class StarStretchDialog(QDialog):
|
|
|
168
176
|
left.addWidget(self.sld_sat)
|
|
169
177
|
|
|
170
178
|
# SCNR checkbox
|
|
171
|
-
self.chk_scnr = QCheckBox("Remove Green via SCNR (Optional)")
|
|
179
|
+
self.chk_scnr = QCheckBox(self.tr("Remove Green via SCNR (Optional)"))
|
|
172
180
|
left.addWidget(self.chk_scnr)
|
|
173
181
|
|
|
174
182
|
# Buttons row
|
|
175
183
|
rowb = QHBoxLayout()
|
|
176
|
-
self.btn_preview = QPushButton("Preview")
|
|
177
|
-
self.btn_apply = QPushButton("Apply to Document")
|
|
184
|
+
self.btn_preview = QPushButton(self.tr("Preview"))
|
|
185
|
+
self.btn_apply = QPushButton(self.tr("Apply to Document"))
|
|
178
186
|
rowb.addWidget(self.btn_preview)
|
|
179
187
|
rowb.addWidget(self.btn_apply)
|
|
180
188
|
left.addLayout(rowb)
|
|
@@ -198,9 +206,9 @@ class StarStretchDialog(QDialog):
|
|
|
198
206
|
# Right column (preview with zoom/pan)
|
|
199
207
|
right = QVBoxLayout()
|
|
200
208
|
zoombar = QHBoxLayout()
|
|
201
|
-
b_out = QPushButton("Zoom Out")
|
|
202
|
-
b_in = QPushButton("Zoom In")
|
|
203
|
-
b_fit = QPushButton("Fit to Preview")
|
|
209
|
+
b_out = QPushButton(self.tr("Zoom Out"))
|
|
210
|
+
b_in = QPushButton(self.tr("Zoom In"))
|
|
211
|
+
b_fit = QPushButton(self.tr("Fit to Preview"))
|
|
204
212
|
b_out.clicked.connect(self._zoom_out)
|
|
205
213
|
b_in.clicked.connect(self._zoom_in)
|
|
206
214
|
b_fit.clicked.connect(self._fit)
|
|
@@ -225,6 +233,15 @@ class StarStretchDialog(QDialog):
|
|
|
225
233
|
# initialize preview with current doc image
|
|
226
234
|
self._update_preview_pix(self.doc.image)
|
|
227
235
|
|
|
236
|
+
# --- active document change ---
|
|
237
|
+
def _on_active_doc_changed(self, doc):
|
|
238
|
+
"""Called when user clicks a different image window."""
|
|
239
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
240
|
+
return
|
|
241
|
+
self.doc = doc
|
|
242
|
+
self._preview = None
|
|
243
|
+
self._update_preview_pix(self.doc.image)
|
|
244
|
+
|
|
228
245
|
# --- UI change handlers ---
|
|
229
246
|
def _on_stretch_changed(self, v: int):
|
|
230
247
|
self.lbl_st.setText(f"Stretch Amount: {v/100.0:.2f}")
|
|
@@ -325,7 +342,27 @@ class StarStretchDialog(QDialog):
|
|
|
325
342
|
except Exception as e:
|
|
326
343
|
QMessageBox.critical(self, "Apply failed", str(e))
|
|
327
344
|
return
|
|
328
|
-
|
|
345
|
+
|
|
346
|
+
# Dialog stays open so user can apply to other images
|
|
347
|
+
# Refresh document reference for next operation
|
|
348
|
+
self._refresh_document_from_active()
|
|
349
|
+
|
|
350
|
+
def _refresh_document_from_active(self):
|
|
351
|
+
"""
|
|
352
|
+
Refresh the dialog's document reference to the currently active document.
|
|
353
|
+
This allows reusing the same dialog on different images.
|
|
354
|
+
"""
|
|
355
|
+
try:
|
|
356
|
+
main = self._find_main_window()
|
|
357
|
+
if main and hasattr(main, "_active_doc"):
|
|
358
|
+
new_doc = main._active_doc()
|
|
359
|
+
if new_doc is not None and new_doc is not self.doc:
|
|
360
|
+
self.doc = new_doc
|
|
361
|
+
# Reset preview for new document
|
|
362
|
+
self._preview = None
|
|
363
|
+
self._compute_and_show_preview()
|
|
364
|
+
except Exception:
|
|
365
|
+
pass
|
|
329
366
|
|
|
330
367
|
|
|
331
368
|
# --- preview rendering ---
|
setiastro/saspro/stat_stretch.py
CHANGED
|
@@ -20,18 +20,23 @@ class StatisticalStretchDialog(QDialog):
|
|
|
20
20
|
"""
|
|
21
21
|
def __init__(self, parent, document: ImageDocument):
|
|
22
22
|
super().__init__(parent)
|
|
23
|
-
self.setWindowTitle("Statistical Stretch")
|
|
23
|
+
self.setWindowTitle(self.tr("Statistical Stretch"))
|
|
24
24
|
|
|
25
25
|
# --- IMPORTANT: avoid “attached modal” behavior on some Linux WMs ---
|
|
26
26
|
# Make this a proper top-level window (tool-style) rather than an attached sheet.
|
|
27
27
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
28
|
-
#
|
|
29
|
-
self.setWindowModality(Qt.WindowModality.
|
|
28
|
+
# Non-modal: allow user to switch between images while dialog is open
|
|
29
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
30
30
|
# Don’t let the generic modal flag override the explicit modality
|
|
31
31
|
self.setModal(False)
|
|
32
32
|
|
|
33
|
+
self._main = parent
|
|
33
34
|
self.doc = document
|
|
34
35
|
self._last_preview = None
|
|
36
|
+
|
|
37
|
+
# Connect to active document change signal
|
|
38
|
+
if hasattr(self._main, "currentDocumentChanged"):
|
|
39
|
+
self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
|
|
35
40
|
self._panning = False
|
|
36
41
|
self._pan_last = None # QPoint
|
|
37
42
|
self._preview_scale = 1.0 # NEW: zoom factor for preview
|
|
@@ -45,20 +50,20 @@ class StatisticalStretchDialog(QDialog):
|
|
|
45
50
|
self.spin_target.setValue(0.25)
|
|
46
51
|
self.spin_target.setDecimals(3)
|
|
47
52
|
|
|
48
|
-
self.chk_linked = QCheckBox("Linked channels")
|
|
53
|
+
self.chk_linked = QCheckBox(self.tr("Linked channels"))
|
|
49
54
|
self.chk_linked.setChecked(False)
|
|
50
55
|
|
|
51
|
-
self.chk_normalize = QCheckBox("Normalize to [0..1]")
|
|
56
|
+
self.chk_normalize = QCheckBox(self.tr("Normalize to [0..1]"))
|
|
52
57
|
self.chk_normalize.setChecked(False)
|
|
53
58
|
|
|
54
59
|
# NEW: Curves boost
|
|
55
|
-
self.chk_curves = QCheckBox("Curves boost")
|
|
60
|
+
self.chk_curves = QCheckBox(self.tr("Curves boost"))
|
|
56
61
|
self.chk_curves.setChecked(False)
|
|
57
62
|
|
|
58
63
|
self.curves_row = QWidget()
|
|
59
64
|
cr_lay = QHBoxLayout(self.curves_row); cr_lay.setContentsMargins(0,0,0,0)
|
|
60
65
|
cr_lay.setSpacing(8)
|
|
61
|
-
cr_lay.addWidget(QLabel("Strength:"))
|
|
66
|
+
cr_lay.addWidget(QLabel(self.tr("Strength:")))
|
|
62
67
|
self.sld_curves = QSlider(Qt.Orientation.Horizontal)
|
|
63
68
|
self.sld_curves.setRange(0, 100) # 0.00 … 1.00 mapped to 0…100
|
|
64
69
|
self.sld_curves.setSingleStep(1)
|
|
@@ -84,11 +89,15 @@ class StatisticalStretchDialog(QDialog):
|
|
|
84
89
|
self._fit_mode = True # NEW: start in Fit mode
|
|
85
90
|
|
|
86
91
|
# --- Zoom buttons row (place before the main layout or right above preview) ---
|
|
92
|
+
# --- Zoom buttons row ---
|
|
87
93
|
zoom_row = QHBoxLayout()
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
self.
|
|
91
|
-
self.
|
|
94
|
+
|
|
95
|
+
# Use themed tool buttons (consistent with the rest of SASpro)
|
|
96
|
+
self.btn_zoom_out = themed_toolbtn("zoom-out", "Zoom Out")
|
|
97
|
+
self.btn_zoom_in = themed_toolbtn("zoom-in", "Zoom In")
|
|
98
|
+
self.btn_zoom_100 = themed_toolbtn("zoom-original", "1:1")
|
|
99
|
+
self.btn_zoom_fit = themed_toolbtn("zoom-fit-best", "Fit")
|
|
100
|
+
|
|
92
101
|
|
|
93
102
|
zoom_row.addStretch(1)
|
|
94
103
|
for b in (self.btn_zoom_out, self.btn_zoom_in, self.btn_zoom_100, self.btn_zoom_fit):
|
|
@@ -96,9 +105,9 @@ class StatisticalStretchDialog(QDialog):
|
|
|
96
105
|
zoom_row.addStretch(1)
|
|
97
106
|
|
|
98
107
|
# Buttons
|
|
99
|
-
self.btn_preview = QPushButton("Preview")
|
|
100
|
-
self.btn_apply = QPushButton("Apply")
|
|
101
|
-
self.btn_close = QPushButton("Close")
|
|
108
|
+
self.btn_preview = QPushButton(self.tr("Preview"))
|
|
109
|
+
self.btn_apply = QPushButton(self.tr("Apply"))
|
|
110
|
+
self.btn_close = QPushButton(self.tr("Close"))
|
|
102
111
|
|
|
103
112
|
self.btn_preview.clicked.connect(self._do_preview)
|
|
104
113
|
self.btn_apply.clicked.connect(self._do_apply)
|
|
@@ -106,7 +115,7 @@ class StatisticalStretchDialog(QDialog):
|
|
|
106
115
|
|
|
107
116
|
# --- Layout ---
|
|
108
117
|
form = QFormLayout()
|
|
109
|
-
form.addRow("Target median:", self.spin_target)
|
|
118
|
+
form.addRow(self.tr("Target median:"), self.spin_target)
|
|
110
119
|
form.addRow("", self.chk_linked)
|
|
111
120
|
form.addRow("", self.chk_normalize)
|
|
112
121
|
form.addRow("", self.chk_curves)
|
|
@@ -325,6 +334,14 @@ class StatisticalStretchDialog(QDialog):
|
|
|
325
334
|
self._preview_qimg = qimg
|
|
326
335
|
self._apply_current_zoom()
|
|
327
336
|
|
|
337
|
+
# ----- active document change -----
|
|
338
|
+
def _on_active_doc_changed(self, doc):
|
|
339
|
+
"""Called when user clicks a different image window."""
|
|
340
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
341
|
+
return
|
|
342
|
+
self.doc = doc
|
|
343
|
+
self._populate_initial_preview()
|
|
344
|
+
|
|
328
345
|
# ----- slots -----
|
|
329
346
|
def _populate_initial_preview(self):
|
|
330
347
|
# show the current (unstretched) image as baseline
|
|
@@ -429,12 +446,31 @@ class StatisticalStretchDialog(QDialog):
|
|
|
429
446
|
# optional debug
|
|
430
447
|
print("Statistical Stretch: replay recording suppressed for this apply()")
|
|
431
448
|
|
|
432
|
-
|
|
449
|
+
# Dialog stays open so user can apply to other images
|
|
450
|
+
# Update the document reference to reflect the now-active document
|
|
451
|
+
self._refresh_document_from_active()
|
|
433
452
|
|
|
434
453
|
|
|
435
454
|
except Exception as e:
|
|
436
455
|
QMessageBox.critical(self, "Apply failed", str(e))
|
|
437
456
|
|
|
457
|
+
def _refresh_document_from_active(self):
|
|
458
|
+
"""
|
|
459
|
+
Refresh the dialog's document reference to the currently active document.
|
|
460
|
+
This allows reusing the same dialog on different images.
|
|
461
|
+
"""
|
|
462
|
+
try:
|
|
463
|
+
main = self.parent()
|
|
464
|
+
if main and hasattr(main, "_active_doc"):
|
|
465
|
+
new_doc = main._active_doc()
|
|
466
|
+
if new_doc is not None and new_doc is not self.doc:
|
|
467
|
+
self.doc = new_doc
|
|
468
|
+
# Reset preview state for new document
|
|
469
|
+
self._last_preview = None
|
|
470
|
+
self._preview_qimg = None
|
|
471
|
+
except Exception:
|
|
472
|
+
pass
|
|
473
|
+
|
|
438
474
|
|
|
439
475
|
def _update_preview_scaled(self):
|
|
440
476
|
if self._preview_qimg is None:
|
|
@@ -9,7 +9,7 @@ class StatusLogDock(QDockWidget):
|
|
|
9
9
|
MAX_BLOCKS = 2000
|
|
10
10
|
|
|
11
11
|
def __init__(self, parent=None):
|
|
12
|
-
super().__init__("Stacking Log", parent)
|
|
12
|
+
super().__init__(self.tr("Stacking Log"), parent)
|
|
13
13
|
self.setObjectName("StackingLogDock")
|
|
14
14
|
self.setAllowedAreas(
|
|
15
15
|
Qt.DockWidgetArea.BottomDockWidgetArea
|