setiastrosuitepro 1.6.1.post1__py3-none-any.whl → 1.6.4__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/images/Background_startup.jpg +0 -0
- setiastro/images/rotatearbitrary.png +0 -0
- setiastro/qml/ResourceMonitor.qml +126 -0
- setiastro/saspro/__main__.py +162 -25
- setiastro/saspro/_generated/build_info.py +2 -1
- setiastro/saspro/abe.py +62 -11
- setiastro/saspro/aberration_ai.py +3 -3
- setiastro/saspro/add_stars.py +5 -2
- setiastro/saspro/astrobin_exporter.py +3 -0
- setiastro/saspro/astrospike_python.py +3 -1
- setiastro/saspro/autostretch.py +4 -2
- setiastro/saspro/backgroundneutral.py +60 -9
- setiastro/saspro/batch_convert.py +3 -0
- setiastro/saspro/batch_renamer.py +3 -0
- setiastro/saspro/blemish_blaster.py +3 -0
- setiastro/saspro/blink_comparator_pro.py +474 -251
- setiastro/saspro/cheat_sheet.py +50 -15
- setiastro/saspro/clahe.py +27 -1
- setiastro/saspro/comet_stacking.py +103 -38
- setiastro/saspro/convo.py +3 -0
- setiastro/saspro/copyastro.py +3 -0
- setiastro/saspro/cosmicclarity.py +70 -45
- setiastro/saspro/crop_dialog_pro.py +28 -1
- setiastro/saspro/curve_editor_pro.py +18 -0
- setiastro/saspro/debayer.py +3 -0
- setiastro/saspro/doc_manager.py +40 -17
- setiastro/saspro/fitsmodifier.py +3 -0
- setiastro/saspro/frequency_separation.py +8 -2
- setiastro/saspro/function_bundle.py +18 -16
- setiastro/saspro/generate_translations.py +715 -1
- setiastro/saspro/ghs_dialog_pro.py +3 -0
- setiastro/saspro/graxpert.py +3 -0
- setiastro/saspro/gui/main_window.py +364 -92
- setiastro/saspro/gui/mixins/dock_mixin.py +119 -7
- setiastro/saspro/gui/mixins/file_mixin.py +7 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +105 -5
- setiastro/saspro/gui/mixins/menu_mixin.py +29 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +33 -10
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +29 -3
- setiastro/saspro/histogram.py +3 -0
- setiastro/saspro/history_explorer.py +2 -0
- setiastro/saspro/i18n.py +22 -10
- setiastro/saspro/image_combine.py +3 -0
- setiastro/saspro/image_peeker_pro.py +3 -0
- 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 +3 -0
- setiastro/saspro/mfdeconv.py +5 -0
- setiastro/saspro/morphology.py +30 -5
- setiastro/saspro/multiscale_decomp.py +713 -256
- setiastro/saspro/nbtorgb_stars.py +12 -2
- setiastro/saspro/numba_utils.py +148 -47
- setiastro/saspro/ops/scripts.py +77 -17
- setiastro/saspro/ops/settings.py +1 -43
- setiastro/saspro/perfect_palette_picker.py +1 -0
- setiastro/saspro/pixelmath.py +6 -2
- setiastro/saspro/plate_solver.py +1 -0
- setiastro/saspro/remove_green.py +18 -1
- setiastro/saspro/remove_stars.py +136 -162
- setiastro/saspro/remove_stars_preset.py +55 -13
- setiastro/saspro/resources.py +36 -10
- setiastro/saspro/rgb_combination.py +1 -0
- setiastro/saspro/rgbalign.py +4 -4
- setiastro/saspro/save_options.py +1 -0
- setiastro/saspro/selective_color.py +79 -20
- setiastro/saspro/sfcc.py +50 -8
- setiastro/saspro/shortcuts.py +94 -21
- setiastro/saspro/signature_insert.py +3 -0
- setiastro/saspro/stacking_suite.py +924 -446
- setiastro/saspro/star_alignment.py +291 -331
- setiastro/saspro/star_spikes.py +116 -32
- setiastro/saspro/star_stretch.py +38 -1
- setiastro/saspro/stat_stretch.py +35 -3
- setiastro/saspro/status_log_dock.py +1 -1
- setiastro/saspro/subwindow.py +63 -2
- setiastro/saspro/supernovaasteroidhunter.py +3 -0
- 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 +441 -446
- setiastro/saspro/translations/es_translations.py +278 -32
- setiastro/saspro/translations/fr_translations.py +280 -32
- setiastro/saspro/translations/hi_translations.py +3803 -0
- setiastro/saspro/translations/integrate_translations.py +38 -1
- setiastro/saspro/translations/it_translations.py +1211 -145
- setiastro/saspro/translations/ja_translations.py +556 -307
- setiastro/saspro/translations/pt_translations.py +3316 -3322
- 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 +14428 -133
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +11503 -7821
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +11168 -7812
- 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 +14347 -7821
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +14860 -137
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +14904 -137
- 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 +10581 -7812
- setiastro/saspro/translations/sw_translations.py +3897 -0
- setiastro/saspro/translations/uk_translations.py +3929 -0
- setiastro/saspro/translations/zh_translations.py +283 -32
- setiastro/saspro/versioning.py +36 -5
- setiastro/saspro/view_bundle.py +20 -17
- setiastro/saspro/wavescale_hdr.py +22 -1
- setiastro/saspro/wavescalede.py +23 -1
- setiastro/saspro/whitebalance.py +39 -3
- 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/resource_monitor.py +263 -0
- setiastro/saspro/widgets/spinboxes.py +18 -0
- setiastro/saspro/widgets/wavelet_utils.py +52 -20
- setiastro/saspro/wimi.py +100 -80
- setiastro/saspro/wims.py +33 -33
- setiastro/saspro/window_shelf.py +2 -2
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/METADATA +15 -4
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/RECORD +139 -115
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/licenses/license.txt +0 -0
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import QtQuick 2.15
|
|
2
|
+
import QtQuick.Controls 2.15
|
|
3
|
+
import QtQuick.Layouts 1.15
|
|
4
|
+
|
|
5
|
+
Rectangle {
|
|
6
|
+
id: root
|
|
7
|
+
// Wider to fit 3 gauges + spacing
|
|
8
|
+
width: 200
|
|
9
|
+
height: 60
|
|
10
|
+
// 50% opacity black
|
|
11
|
+
color: "#80000000"
|
|
12
|
+
radius: 30
|
|
13
|
+
border.color: "#555"
|
|
14
|
+
border.width: 1
|
|
15
|
+
|
|
16
|
+
property double cpuUsage: 0.0
|
|
17
|
+
property double ramUsage: 0.0
|
|
18
|
+
property double gpuUsage: 0.0
|
|
19
|
+
property double appRamUsage: 0.0
|
|
20
|
+
property string appRamString: "0 MB"
|
|
21
|
+
|
|
22
|
+
// Helper component for gauges
|
|
23
|
+
component MiniGauge: Item {
|
|
24
|
+
Layout.preferredWidth: 40
|
|
25
|
+
Layout.preferredHeight: 40
|
|
26
|
+
property string label: ""
|
|
27
|
+
property color barColor: "#0f0"
|
|
28
|
+
property double value: 0
|
|
29
|
+
|
|
30
|
+
// Trigger repaint when value changes
|
|
31
|
+
onValueChanged: if (gaugeCanvas) gaugeCanvas.requestPaint()
|
|
32
|
+
|
|
33
|
+
Canvas {
|
|
34
|
+
id: gaugeCanvas
|
|
35
|
+
anchors.fill: parent
|
|
36
|
+
antialiasing: true
|
|
37
|
+
onPaint: {
|
|
38
|
+
var ctx = getContext("2d");
|
|
39
|
+
var cx = width / 2;
|
|
40
|
+
var cy = height / 2;
|
|
41
|
+
var r = (width / 2) - 3;
|
|
42
|
+
|
|
43
|
+
ctx.reset();
|
|
44
|
+
|
|
45
|
+
// Track
|
|
46
|
+
ctx.beginPath();
|
|
47
|
+
ctx.arc(cx, cy, r, 0, 2*Math.PI);
|
|
48
|
+
ctx.lineWidth = 4;
|
|
49
|
+
ctx.strokeStyle = "#444";
|
|
50
|
+
ctx.stroke();
|
|
51
|
+
|
|
52
|
+
// Value Arc
|
|
53
|
+
// start at -90 deg (top)
|
|
54
|
+
var start = -Math.PI/2;
|
|
55
|
+
var end = start + (value/100 * 2*Math.PI);
|
|
56
|
+
|
|
57
|
+
ctx.beginPath();
|
|
58
|
+
ctx.arc(cx, cy, r, start, end);
|
|
59
|
+
ctx.lineWidth = 4;
|
|
60
|
+
ctx.lineCap = "round";
|
|
61
|
+
ctx.strokeStyle = barColor;
|
|
62
|
+
ctx.stroke();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Numeric Percent in center
|
|
67
|
+
Text {
|
|
68
|
+
anchors.centerIn: parent
|
|
69
|
+
text: Math.round(value) + "%"
|
|
70
|
+
font.pixelSize: 10
|
|
71
|
+
font.bold: true
|
|
72
|
+
color: "#fff"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
RowLayout {
|
|
77
|
+
anchors.centerIn: parent
|
|
78
|
+
spacing: 15
|
|
79
|
+
|
|
80
|
+
// --- CPU ---
|
|
81
|
+
ColumnLayout {
|
|
82
|
+
spacing: 2
|
|
83
|
+
MiniGauge {
|
|
84
|
+
value: root.cpuUsage
|
|
85
|
+
// Dynamic color
|
|
86
|
+
barColor: root.cpuUsage > 80 ? "#ff4444" : (root.cpuUsage > 50 ? "#ffbb33" : "#00C851")
|
|
87
|
+
}
|
|
88
|
+
Text {
|
|
89
|
+
Layout.alignment: Qt.AlignHCenter
|
|
90
|
+
text: "CPU"
|
|
91
|
+
color: "#aaaaaa"
|
|
92
|
+
font.pixelSize: 9
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- RAM ---
|
|
97
|
+
ColumnLayout {
|
|
98
|
+
spacing: 2
|
|
99
|
+
MiniGauge {
|
|
100
|
+
value: root.ramUsage
|
|
101
|
+
barColor: "#33b5e5"
|
|
102
|
+
}
|
|
103
|
+
Text {
|
|
104
|
+
Layout.alignment: Qt.AlignHCenter
|
|
105
|
+
text: "RAM"
|
|
106
|
+
color: "#aaaaaa"
|
|
107
|
+
font.pixelSize: 9
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- GPU ---
|
|
112
|
+
ColumnLayout {
|
|
113
|
+
spacing: 2
|
|
114
|
+
MiniGauge {
|
|
115
|
+
value: root.gpuUsage
|
|
116
|
+
barColor: "#aa66cc"
|
|
117
|
+
}
|
|
118
|
+
Text {
|
|
119
|
+
Layout.alignment: Qt.AlignHCenter
|
|
120
|
+
text: "GPU"
|
|
121
|
+
color: "#aaaaaa"
|
|
122
|
+
font.pixelSize: 9
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
setiastro/saspro/__main__.py
CHANGED
|
@@ -36,7 +36,10 @@ _app = None
|
|
|
36
36
|
_splash_initialized = False
|
|
37
37
|
|
|
38
38
|
from setiastro.saspro.versioning import get_app_version
|
|
39
|
-
_EARLY_VERSION = "
|
|
39
|
+
_EARLY_VERSION = get_app_version("setiastrosuitepro")
|
|
40
|
+
|
|
41
|
+
VERSION = _EARLY_VERSION
|
|
42
|
+
|
|
40
43
|
def _init_splash():
|
|
41
44
|
"""Initialize the splash screen. Safe to call multiple times."""
|
|
42
45
|
global _splash, _app, _splash_initialized
|
|
@@ -46,7 +49,8 @@ def _init_splash():
|
|
|
46
49
|
|
|
47
50
|
# Minimal imports for splash screen
|
|
48
51
|
from PyQt6.QtWidgets import QApplication, QWidget
|
|
49
|
-
from PyQt6.QtCore import Qt, QCoreApplication, QRect
|
|
52
|
+
from PyQt6.QtCore import Qt, QCoreApplication, QRect, QPropertyAnimation, QEasingCurve
|
|
53
|
+
import time
|
|
50
54
|
from PyQt6.QtGui import QGuiApplication, QIcon, QPixmap, QColor, QPainter, QFont, QLinearGradient
|
|
51
55
|
|
|
52
56
|
|
|
@@ -128,13 +132,20 @@ def _init_splash():
|
|
|
128
132
|
|
|
129
133
|
# NEW: Prefer centralized resources resolver
|
|
130
134
|
try:
|
|
131
|
-
from setiastro.saspro.resources import icon_path
|
|
135
|
+
from setiastro.saspro.resources import icon_path, background_startup_path
|
|
132
136
|
_early_icon_path = icon_path
|
|
133
137
|
if not os.path.exists(_early_icon_path):
|
|
134
138
|
# fall back to legacy search if for some reason this is missing
|
|
135
139
|
_early_icon_path = _find_icon_path()
|
|
140
|
+
|
|
141
|
+
# Load startup background path
|
|
142
|
+
_startup_bg_path = background_startup_path
|
|
143
|
+
if not os.path.exists(_startup_bg_path):
|
|
144
|
+
_startup_bg_path = None
|
|
145
|
+
|
|
136
146
|
except Exception:
|
|
137
147
|
_early_icon_path = _find_icon_path()
|
|
148
|
+
_startup_bg_path = None
|
|
138
149
|
|
|
139
150
|
|
|
140
151
|
# =========================================================================
|
|
@@ -146,7 +157,7 @@ def _init_splash():
|
|
|
146
157
|
"""
|
|
147
158
|
def __init__(self, logo_path: str):
|
|
148
159
|
super().__init__()
|
|
149
|
-
self._version =
|
|
160
|
+
self._version = _EARLY_VERSION
|
|
150
161
|
self._build = ""
|
|
151
162
|
self.current_message = QCoreApplication.translate("Splash", "Starting...")
|
|
152
163
|
self.progress_value = 0
|
|
@@ -176,6 +187,11 @@ def _init_splash():
|
|
|
176
187
|
# Load and scale logo
|
|
177
188
|
self.logo_pixmap = self._load_logo(logo_path)
|
|
178
189
|
|
|
190
|
+
# Load background image
|
|
191
|
+
self.bg_image_pixmap = QPixmap()
|
|
192
|
+
if _startup_bg_path:
|
|
193
|
+
self.bg_image_pixmap = QPixmap(_startup_bg_path)
|
|
194
|
+
|
|
179
195
|
# Fonts
|
|
180
196
|
self.title_font = QFont("Segoe UI", 28, QFont.Weight.Bold)
|
|
181
197
|
self.subtitle_font = QFont("Segoe UI", 11)
|
|
@@ -214,15 +230,42 @@ def _init_splash():
|
|
|
214
230
|
_app.processEvents()
|
|
215
231
|
|
|
216
232
|
def setProgress(self, value: int):
|
|
217
|
-
"""Update progress (0-100)."""
|
|
218
|
-
|
|
233
|
+
"""Update progress (0-100) with smooth animation."""
|
|
234
|
+
target = max(0, min(100, value))
|
|
235
|
+
start = self.progress_value
|
|
236
|
+
|
|
237
|
+
# If jumping backwards or small change, just set it
|
|
238
|
+
if target <= start or (target - start) < 1:
|
|
239
|
+
self.progress_value = target
|
|
240
|
+
self.repaint()
|
|
241
|
+
if _app: _app.processEvents()
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
# Animate forward
|
|
245
|
+
steps = 15 # number of frames for the slide
|
|
246
|
+
# We want the total slide to take ~100-150ms max to feel responsive but smooth
|
|
247
|
+
dt = 0.005 # 5ms per frame
|
|
248
|
+
|
|
249
|
+
for i in range(1, steps + 1):
|
|
250
|
+
# Ease out interpolator
|
|
251
|
+
t = i / steps
|
|
252
|
+
# Quadratic ease out: f(t) = -t*(t-2)
|
|
253
|
+
factor = -t * (t - 2)
|
|
254
|
+
|
|
255
|
+
cur = start + (target - start) * factor
|
|
256
|
+
self.progress_value = cur
|
|
257
|
+
self.repaint()
|
|
258
|
+
if _app: _app.processEvents()
|
|
259
|
+
time.sleep(dt)
|
|
260
|
+
|
|
261
|
+
self.progress_value = target
|
|
219
262
|
self.repaint()
|
|
220
263
|
if _app:
|
|
221
264
|
_app.processEvents()
|
|
222
265
|
|
|
223
266
|
def setBuildInfo(self, version: str, build: str):
|
|
224
267
|
"""Update version and build info once available."""
|
|
225
|
-
self._version =
|
|
268
|
+
self._version = _EARLY_VERSION
|
|
226
269
|
self._build = build
|
|
227
270
|
self.repaint()
|
|
228
271
|
|
|
@@ -241,6 +284,45 @@ def _init_splash():
|
|
|
241
284
|
gradient.setColorAt(1.0, QColor(10, 10, 20))
|
|
242
285
|
painter.fillRect(0, 0, w, h, gradient)
|
|
243
286
|
|
|
287
|
+
# --- Background Image (Centered with Fade Out) ---
|
|
288
|
+
if not self.bg_image_pixmap.isNull():
|
|
289
|
+
# Create a temporary pixmap to handle the masking
|
|
290
|
+
temp = QPixmap(w, h)
|
|
291
|
+
temp.fill(Qt.GlobalColor.transparent)
|
|
292
|
+
|
|
293
|
+
ptmp = QPainter(temp)
|
|
294
|
+
ptmp.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
295
|
+
ptmp.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
|
|
296
|
+
|
|
297
|
+
# Scale image to cover the entire splash screen
|
|
298
|
+
scaled = self.bg_image_pixmap.scaled(
|
|
299
|
+
w, h,
|
|
300
|
+
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
|
|
301
|
+
Qt.TransformationMode.SmoothTransformation
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Center the image
|
|
305
|
+
sx = (w - scaled.width()) // 2
|
|
306
|
+
sy = (h - scaled.height()) // 2
|
|
307
|
+
ptmp.drawPixmap(sx, sy, scaled)
|
|
308
|
+
|
|
309
|
+
# Apply Fade Out Mask (Gradient Alpha)
|
|
310
|
+
ptmp.setCompositionMode(QPainter.CompositionMode.CompositionMode_DestinationIn)
|
|
311
|
+
fade_gradient = QLinearGradient(0, 0, 0, h)
|
|
312
|
+
# Keep top half fully visible (subject to global opacity)
|
|
313
|
+
fade_gradient.setColorAt(0.0, QColor(0, 0, 0, 255))
|
|
314
|
+
fade_gradient.setColorAt(0.5, QColor(0, 0, 0, 255))
|
|
315
|
+
# Fade out completely at the bottom
|
|
316
|
+
fade_gradient.setColorAt(1.0, QColor(0, 0, 0, 0))
|
|
317
|
+
ptmp.fillRect(0, 0, w, h, fade_gradient)
|
|
318
|
+
ptmp.end()
|
|
319
|
+
|
|
320
|
+
# Draw combined result with 50% opacity
|
|
321
|
+
painter.save()
|
|
322
|
+
painter.setOpacity(0.25)
|
|
323
|
+
painter.drawPixmap(0, 0, temp)
|
|
324
|
+
painter.restore()
|
|
325
|
+
|
|
244
326
|
# --- Subtle border ---
|
|
245
327
|
painter.setPen(QColor(60, 60, 80))
|
|
246
328
|
painter.drawRect(0, 0, w - 1, h - 1)
|
|
@@ -311,20 +393,52 @@ def _init_splash():
|
|
|
311
393
|
self.hide()
|
|
312
394
|
self.close()
|
|
313
395
|
self.deleteLater()
|
|
396
|
+
|
|
397
|
+
def start_fade_out(self):
|
|
398
|
+
"""Smoothly fade out the splash screen."""
|
|
399
|
+
self._anim = QPropertyAnimation(self, b"windowOpacity")
|
|
400
|
+
self._anim.setDuration(1000)
|
|
401
|
+
self._anim.setStartValue(1.0)
|
|
402
|
+
self._anim.setEndValue(0.0)
|
|
403
|
+
self._anim.setEasingCurve(QEasingCurve.Type.OutQuad)
|
|
404
|
+
self._anim.finished.connect(self.finish)
|
|
405
|
+
self._anim.start()
|
|
314
406
|
|
|
407
|
+
def start_fade_in(self):
|
|
408
|
+
"""Smoothly fade in the splash screen."""
|
|
409
|
+
self.setWindowOpacity(0.0)
|
|
410
|
+
self._anim = QPropertyAnimation(self, b"windowOpacity")
|
|
411
|
+
self._anim.setDuration(800)
|
|
412
|
+
self._anim.setStartValue(0.0)
|
|
413
|
+
self._anim.setEndValue(1.0)
|
|
414
|
+
self._anim.setEasingCurve(QEasingCurve.Type.InQuad)
|
|
415
|
+
self._anim.start()
|
|
416
|
+
|
|
315
417
|
# --- Show splash IMMEDIATELY ---
|
|
316
418
|
_splash = _EarlySplash(_early_icon_path)
|
|
419
|
+
_splash.start_fade_in()
|
|
317
420
|
_splash.show()
|
|
421
|
+
|
|
422
|
+
# Block briefly to allow fade-in to progress smoothly before heavy imports start
|
|
423
|
+
# We use a busy loop with processEvents to keep the UI responsive during fade
|
|
424
|
+
t_start = time.time()
|
|
425
|
+
while time.time() - t_start < 0.85: # slightly longer than animation
|
|
426
|
+
_app.processEvents()
|
|
427
|
+
if _splash.windowOpacity() >= 0.99:
|
|
428
|
+
break
|
|
429
|
+
time.sleep(0.01)
|
|
430
|
+
|
|
318
431
|
_splash.setMessage(QCoreApplication.translate("Splash", "Initializing Python runtime..."))
|
|
319
432
|
_splash.setProgress(2)
|
|
320
433
|
_app.processEvents()
|
|
321
434
|
|
|
322
435
|
# Load translation BEFORE any other widgets are created
|
|
323
436
|
try:
|
|
324
|
-
from setiastro.saspro.i18n import load_language
|
|
325
|
-
load_language(app=_app)
|
|
326
|
-
except Exception:
|
|
327
|
-
|
|
437
|
+
from setiastro.saspro.i18n import load_language, get_translations_dir
|
|
438
|
+
ok = load_language(app=_app)
|
|
439
|
+
except Exception as e:
|
|
440
|
+
print("i18n load failed:", repr(e))
|
|
441
|
+
|
|
328
442
|
|
|
329
443
|
_splash_initialized = True
|
|
330
444
|
|
|
@@ -515,7 +629,8 @@ from PyQt6.QtGui import (QPixmap, QColor, QIcon, QKeySequence, QShortcut, QGuiAp
|
|
|
515
629
|
)
|
|
516
630
|
|
|
517
631
|
# ----- QtCore -----
|
|
518
|
-
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject
|
|
632
|
+
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject,
|
|
633
|
+
QPropertyAnimation, QEasingCurve
|
|
519
634
|
)
|
|
520
635
|
|
|
521
636
|
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
|
@@ -528,8 +643,6 @@ except Exception:
|
|
|
528
643
|
BUILD_TIMESTAMP = "dev"
|
|
529
644
|
|
|
530
645
|
|
|
531
|
-
from setiastro.saspro.versioning import get_app_version
|
|
532
|
-
VERSION = "1.6.1"
|
|
533
646
|
|
|
534
647
|
_update_splash(QCoreApplication.translate("Splash", "Loading resources..."), 50)
|
|
535
648
|
|
|
@@ -746,7 +859,33 @@ def main():
|
|
|
746
859
|
if _splash:
|
|
747
860
|
_splash.setMessage(QCoreApplication.translate("Splash", "Showing main window..."))
|
|
748
861
|
_splash.setProgress(95)
|
|
862
|
+
|
|
863
|
+
# --- Smooth Transition: App Fade In + Splash Fade Out ---
|
|
864
|
+
# MITIGATION: Prevent "White Flash" on startup
|
|
865
|
+
# 1. Force a dark background immediately so if opacity lags, it's dark not white
|
|
866
|
+
win.setStyleSheet("QMainWindow { background-color: #0F0F19; }")
|
|
867
|
+
# 2. Ensure native window handle exists so setWindowOpacity works immediately
|
|
868
|
+
win.winId()
|
|
869
|
+
# 3. Set opacity to 0
|
|
870
|
+
win.setWindowOpacity(0.0)
|
|
871
|
+
|
|
749
872
|
win.show()
|
|
873
|
+
|
|
874
|
+
# 1. Animate Main Window Fade In
|
|
875
|
+
anim_app = QPropertyAnimation(win, b"windowOpacity")
|
|
876
|
+
anim_app.setDuration(1200)
|
|
877
|
+
anim_app.setStartValue(0.0)
|
|
878
|
+
anim_app.setEndValue(1.0)
|
|
879
|
+
anim_app.setEasingCurve(QEasingCurve.Type.OutQuad)
|
|
880
|
+
|
|
881
|
+
# Cleanup temp stylesheet upon completion to avoid interfering with ThemeMixin
|
|
882
|
+
def _on_fade_in_finished():
|
|
883
|
+
win.setStyleSheet("")
|
|
884
|
+
if hasattr(win, "on_fade_in_complete"):
|
|
885
|
+
win.on_fade_in_complete()
|
|
886
|
+
|
|
887
|
+
anim_app.finished.connect(_on_fade_in_finished)
|
|
888
|
+
anim_app.start()
|
|
750
889
|
|
|
751
890
|
# Start background Numba warmup after UI is visible
|
|
752
891
|
try:
|
|
@@ -760,18 +899,16 @@ def main():
|
|
|
760
899
|
_splash.setProgress(100)
|
|
761
900
|
_app.processEvents()
|
|
762
901
|
|
|
763
|
-
# Small delay to
|
|
902
|
+
# Small delay to ensure "Ready!" is seen briefly before fade starts
|
|
764
903
|
import time
|
|
765
|
-
time.sleep(0.
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
#
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
_splash.close()
|
|
774
|
-
_splash.deleteLater()
|
|
904
|
+
time.sleep(0.1)
|
|
905
|
+
|
|
906
|
+
# 2. Animate Splash Fade Out
|
|
907
|
+
# Note: We do NOT use finish() directly here. The animation calls it when done.
|
|
908
|
+
_splash.start_fade_out()
|
|
909
|
+
|
|
910
|
+
# NOTE: We keep a reference to _splash (global) so it doesn't get GC'd during animation.
|
|
911
|
+
# It will deleteLater() itself.
|
|
775
912
|
|
|
776
913
|
if BUILD_TIMESTAMP == "dev":
|
|
777
914
|
build_label = "running from local source code"
|
setiastro/saspro/abe.py
CHANGED
|
@@ -75,15 +75,28 @@ def _fit_poly_on_small(small: np.ndarray, points: np.ndarray, degree: int, patch
|
|
|
75
75
|
|
|
76
76
|
if small.ndim == 3 and small.shape[2] == 3:
|
|
77
77
|
bg_small = np.zeros_like(small, dtype=np.float32)
|
|
78
|
+
|
|
79
|
+
# Batch collect samples: (num_samples, 3)
|
|
80
|
+
# We need N samples. z will be list of (3,) arrays
|
|
81
|
+
|
|
82
|
+
# Pre-allocate Z: (N, 3)
|
|
83
|
+
Z = np.zeros((len(xs), 3), dtype=np.float32)
|
|
84
|
+
|
|
85
|
+
for k, (x, y) in enumerate(zip(xs, ys)):
|
|
86
|
+
x0, x1 = max(0, x - half), min(W, x + half + 1)
|
|
87
|
+
y0, y1 = max(0, y - half), min(H, y + half + 1)
|
|
88
|
+
# Efficiently compute median for all channels in this patch
|
|
89
|
+
patch = small[y0:y1, x0:x1, :]
|
|
90
|
+
Z[k] = np.median(patch, axis=(0, 1))
|
|
91
|
+
|
|
92
|
+
# Solve once: A is (N, terms), Z is (N, 3) -> coeffs is (terms, 3)
|
|
93
|
+
coeffs_all, *_ = np.linalg.lstsq(A, Z, rcond=None)
|
|
94
|
+
|
|
95
|
+
# Evaluate per channel
|
|
78
96
|
for c in range(3):
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
y0, y1 = max(0, y - half), min(H, y + half + 1)
|
|
83
|
-
z.append(np.median(small[y0:y1, x0:x1, c]))
|
|
84
|
-
z = np.asarray(z, dtype=np.float32)
|
|
85
|
-
coeffs, *_ = np.linalg.lstsq(A, z, rcond=None)
|
|
86
|
-
bg_small[..., c] = evaluate_polynomial(H, W, coeffs.astype(np.float32), degree)
|
|
97
|
+
# coeffs_all[:, c] gives the terms for channel c
|
|
98
|
+
bg_small[..., c] = evaluate_polynomial(H, W, coeffs_all[:, c].astype(np.float32), degree)
|
|
99
|
+
|
|
87
100
|
return bg_small
|
|
88
101
|
else:
|
|
89
102
|
z = []
|
|
@@ -475,13 +488,18 @@ class ABEDialog(QDialog):
|
|
|
475
488
|
|
|
476
489
|
# IMPORTANT: avoid “attached modal sheet” behavior on some Linux WMs
|
|
477
490
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
478
|
-
#
|
|
479
|
-
self.setWindowModality(Qt.WindowModality.
|
|
491
|
+
# Non-modal: allow user to switch between images while dialog is open
|
|
492
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
480
493
|
self.setModal(False)
|
|
481
494
|
#self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
482
495
|
|
|
496
|
+
self._main = parent
|
|
483
497
|
self.doc = document
|
|
484
498
|
|
|
499
|
+
# Connect to active document change signal
|
|
500
|
+
if hasattr(self._main, "currentDocumentChanged"):
|
|
501
|
+
self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
|
|
502
|
+
|
|
485
503
|
self._preview_scale = 1.0
|
|
486
504
|
self._preview_qimg = None
|
|
487
505
|
self._last_preview = None # backing ndarray for QImage lifetime
|
|
@@ -612,6 +630,17 @@ class ABEDialog(QDialog):
|
|
|
612
630
|
bar.addWidget(self.btn_autostr)
|
|
613
631
|
return bar
|
|
614
632
|
|
|
633
|
+
# ----- active document change -----
|
|
634
|
+
def _on_active_doc_changed(self, doc):
|
|
635
|
+
"""Called when user clicks a different image window."""
|
|
636
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
637
|
+
return
|
|
638
|
+
self.doc = doc
|
|
639
|
+
self._polygons.clear()
|
|
640
|
+
self._drawing_poly = None
|
|
641
|
+
self._preview_source_f01 = None
|
|
642
|
+
self._populate_initial_preview()
|
|
643
|
+
|
|
615
644
|
# ----- data helpers -----
|
|
616
645
|
def _get_source_float(self) -> np.ndarray | None:
|
|
617
646
|
src = np.asarray(self.doc.image)
|
|
@@ -813,12 +842,34 @@ class ABEDialog(QDialog):
|
|
|
813
842
|
pass
|
|
814
843
|
|
|
815
844
|
self._set_status("Done")
|
|
816
|
-
|
|
845
|
+
# Dialog stays open so user can apply to other images
|
|
846
|
+
# Refresh to use the now-active document for next operation
|
|
847
|
+
self._refresh_document_from_active()
|
|
817
848
|
|
|
818
849
|
except Exception as e:
|
|
819
850
|
self._set_status("Error")
|
|
820
851
|
QMessageBox.critical(self, "Apply failed", str(e))
|
|
821
852
|
|
|
853
|
+
def _refresh_document_from_active(self):
|
|
854
|
+
"""
|
|
855
|
+
Refresh the dialog's document reference to the currently active document.
|
|
856
|
+
This allows reusing the same dialog on different images.
|
|
857
|
+
"""
|
|
858
|
+
try:
|
|
859
|
+
main = self.parent()
|
|
860
|
+
if main and hasattr(main, "_active_doc"):
|
|
861
|
+
new_doc = main._active_doc()
|
|
862
|
+
if new_doc is not None and new_doc is not self.doc:
|
|
863
|
+
self.doc = new_doc
|
|
864
|
+
# Reset preview state for new document
|
|
865
|
+
self._preview_source_f01 = None
|
|
866
|
+
self._last_preview = None
|
|
867
|
+
self._preview_qimg = None
|
|
868
|
+
# Clear polygons since they were for old image
|
|
869
|
+
self._clear_polys()
|
|
870
|
+
except Exception:
|
|
871
|
+
pass
|
|
872
|
+
|
|
822
873
|
|
|
823
874
|
# ----- exclusion polygons & mask -----
|
|
824
875
|
def _clear_polys(self):
|
|
@@ -298,8 +298,8 @@ class AberrationAIDialog(QDialog):
|
|
|
298
298
|
|
|
299
299
|
# Normalize window behavior across platforms
|
|
300
300
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
301
|
-
#
|
|
302
|
-
self.setWindowModality(Qt.WindowModality.
|
|
301
|
+
# Non-modal: allow user to switch between images while dialog is open
|
|
302
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
303
303
|
self.setModal(False)
|
|
304
304
|
#self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
305
305
|
|
|
@@ -680,7 +680,7 @@ class AberrationAIDialog(QDialog):
|
|
|
680
680
|
)
|
|
681
681
|
|
|
682
682
|
self.progress.setValue(100)
|
|
683
|
-
|
|
683
|
+
# Dialog stays open so user can apply to other images
|
|
684
684
|
|
|
685
685
|
def _on_worker_finished(self):
|
|
686
686
|
# If dialog is already gone, this method is never called because the receiver (self)
|
setiastro/saspro/add_stars.py
CHANGED
|
@@ -227,7 +227,8 @@ class AddStarsDialog(QDialog):
|
|
|
227
227
|
self.setWindowTitle(self.tr("Add Stars to Image"))
|
|
228
228
|
|
|
229
229
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
230
|
-
|
|
230
|
+
# Non-modal: allow user to switch between images while dialog is open
|
|
231
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
231
232
|
self.setModal(False)
|
|
232
233
|
#self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
233
234
|
|
|
@@ -574,7 +575,9 @@ class AddStarsDialog(QDialog):
|
|
|
574
575
|
|
|
575
576
|
# Emit (target_doc, blended_image)
|
|
576
577
|
self.stars_added.emit(target_doc, self.blended_image.astype(np.float32, copy=False))
|
|
577
|
-
|
|
578
|
+
# Dialog stays open so user can apply to other images
|
|
579
|
+
# Refresh combo boxes for next operation
|
|
580
|
+
self._populate_doc_combos()
|
|
578
581
|
|
|
579
582
|
|
|
580
583
|
# Ensure initial fit once shown
|
|
@@ -997,6 +997,9 @@ class AstrobinExporterDialog(QDialog):
|
|
|
997
997
|
def __init__(self, parent=None, offline_filters_csv: Optional[str] = None):
|
|
998
998
|
super().__init__(parent)
|
|
999
999
|
self.setWindowTitle(self.tr("AstroBin Exporter"))
|
|
1000
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
1001
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
1002
|
+
self.setModal(False)
|
|
1000
1003
|
self.resize(980, 640)
|
|
1001
1004
|
v = QVBoxLayout(self)
|
|
1002
1005
|
self.tab = AstrobinExportTab(self, offline_filters_csv=offline_filters_csv)
|
|
@@ -1040,7 +1040,9 @@ class AstroSpikeWindow(QDialog):
|
|
|
1040
1040
|
def __init__(self, image_data_255: np.ndarray, image_data_float: np.ndarray, ctx):
|
|
1041
1041
|
super().__init__()
|
|
1042
1042
|
self.setWindowTitle("AstroSpike - Star Diffraction Spikes")
|
|
1043
|
-
self.
|
|
1043
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
1044
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
1045
|
+
self.setModal(False)
|
|
1044
1046
|
|
|
1045
1047
|
self.ctx = ctx
|
|
1046
1048
|
self.image_data = image_data_255 # uint8 for detection
|
setiastro/saspro/autostretch.py
CHANGED
|
@@ -180,8 +180,10 @@ def autostretch(
|
|
|
180
180
|
lut = _compute_lut_from_sample(lum, target_median, sigma, maxv)
|
|
181
181
|
|
|
182
182
|
out = np.empty_like(u, dtype=np.float32)
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
# Vectorized LUT application: apply to all RGB channels at once
|
|
184
|
+
# lut is 1D array of float32; u[..., :3] selects RGB indices
|
|
185
|
+
out[..., :3] = lut[u[..., :3]]
|
|
186
|
+
|
|
185
187
|
if C > 3: # pass-through non-RGB channels
|
|
186
188
|
out[..., 3:] = u[..., 3:] / float(maxv)
|
|
187
189
|
return out
|