setiastrosuitepro 1.6.1__py3-none-any.whl → 1.6.2__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/images/Background_startup.jpg +0 -0
- setiastro/qml/ResourceMonitor.qml +126 -0
- setiastro/saspro/__main__.py +159 -23
- 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 +52 -10
- setiastro/saspro/batch_convert.py +3 -0
- setiastro/saspro/batch_renamer.py +3 -0
- setiastro/saspro/blemish_blaster.py +3 -0
- 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 +17 -0
- setiastro/saspro/curve_editor_pro.py +18 -0
- setiastro/saspro/debayer.py +3 -0
- setiastro/saspro/doc_manager.py +39 -16
- setiastro/saspro/fitsmodifier.py +3 -0
- setiastro/saspro/frequency_separation.py +8 -2
- setiastro/saspro/function_bundle.py +2 -0
- 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 +275 -32
- setiastro/saspro/gui/mixins/dock_mixin.py +100 -1
- setiastro/saspro/gui/mixins/file_mixin.py +7 -0
- setiastro/saspro/gui/mixins/menu_mixin.py +28 -0
- 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 +3 -0
- 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 +2 -1
- setiastro/saspro/remove_green.py +18 -1
- setiastro/saspro/remove_stars.py +136 -162
- setiastro/saspro/resources.py +7 -0
- setiastro/saspro/rgb_combination.py +1 -0
- setiastro/saspro/rgbalign.py +4 -4
- setiastro/saspro/save_options.py +1 -0
- setiastro/saspro/sfcc.py +50 -8
- setiastro/saspro/signature_insert.py +3 -0
- setiastro/saspro/stacking_suite.py +630 -341
- setiastro/saspro/star_alignment.py +16 -1
- setiastro/saspro/star_spikes.py +116 -32
- setiastro/saspro/star_stretch.py +38 -1
- setiastro/saspro/stat_stretch.py +35 -3
- setiastro/saspro/subwindow.py +63 -2
- setiastro/saspro/supernovaasteroidhunter.py +3 -0
- setiastro/saspro/translations/all_source_strings.json +3654 -0
- setiastro/saspro/translations/ar_translations.py +3865 -0
- setiastro/saspro/translations/de_translations.py +16 -0
- setiastro/saspro/translations/es_translations.py +16 -0
- setiastro/saspro/translations/fr_translations.py +16 -0
- setiastro/saspro/translations/hi_translations.py +3571 -0
- setiastro/saspro/translations/integrate_translations.py +36 -0
- setiastro/saspro/translations/it_translations.py +16 -0
- setiastro/saspro/translations/ja_translations.py +16 -0
- setiastro/saspro/translations/pt_translations.py +16 -0
- setiastro/saspro/translations/ru_translations.py +2848 -0
- setiastro/saspro/translations/saspro_ar.qm +0 -0
- setiastro/saspro/translations/saspro_ar.ts +255 -0
- setiastro/saspro/translations/saspro_de.qm +0 -0
- setiastro/saspro/translations/saspro_de.ts +3 -3
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +3 -3
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +3 -3
- setiastro/saspro/translations/saspro_hi.qm +0 -0
- setiastro/saspro/translations/saspro_hi.ts +257 -0
- setiastro/saspro/translations/saspro_it.qm +0 -0
- setiastro/saspro/translations/saspro_it.ts +3 -3
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +4 -4
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +3 -3
- setiastro/saspro/translations/saspro_ru.qm +0 -0
- setiastro/saspro/translations/saspro_ru.ts +237 -0
- setiastro/saspro/translations/saspro_sw.qm +0 -0
- setiastro/saspro/translations/saspro_sw.ts +257 -0
- setiastro/saspro/translations/saspro_uk.qm +0 -0
- setiastro/saspro/translations/saspro_uk.ts +10771 -0
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +3 -3
- setiastro/saspro/translations/sw_translations.py +3671 -0
- setiastro/saspro/translations/uk_translations.py +3700 -0
- setiastro/saspro/translations/zh_translations.py +16 -0
- setiastro/saspro/versioning.py +12 -6
- setiastro/saspro/view_bundle.py +3 -0
- 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 +986 -0
- setiastro/saspro/widgets/minigame/index.html +53 -0
- setiastro/saspro/widgets/minigame/style.css +241 -0
- setiastro/saspro/widgets/resource_monitor.py +237 -0
- setiastro/saspro/widgets/wavelet_utils.py +52 -20
- setiastro/saspro/wimi.py +7996 -0
- setiastro/saspro/wims.py +578 -0
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.2.dist-info}/METADATA +15 -4
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.2.dist-info}/RECORD +128 -103
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.2.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.2.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.2.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.2.dist-info}/licenses/license.txt +0 -0
|
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
|
@@ -38,6 +38,8 @@ _splash_initialized = False
|
|
|
38
38
|
from setiastro.saspro.versioning import get_app_version
|
|
39
39
|
_EARLY_VERSION = get_app_version("setiastrosuitepro")
|
|
40
40
|
|
|
41
|
+
VERSION = _EARLY_VERSION
|
|
42
|
+
|
|
41
43
|
def _init_splash():
|
|
42
44
|
"""Initialize the splash screen. Safe to call multiple times."""
|
|
43
45
|
global _splash, _app, _splash_initialized
|
|
@@ -47,7 +49,8 @@ def _init_splash():
|
|
|
47
49
|
|
|
48
50
|
# Minimal imports for splash screen
|
|
49
51
|
from PyQt6.QtWidgets import QApplication, QWidget
|
|
50
|
-
from PyQt6.QtCore import Qt, QCoreApplication, QRect
|
|
52
|
+
from PyQt6.QtCore import Qt, QCoreApplication, QRect, QPropertyAnimation, QEasingCurve
|
|
53
|
+
import time
|
|
51
54
|
from PyQt6.QtGui import QGuiApplication, QIcon, QPixmap, QColor, QPainter, QFont, QLinearGradient
|
|
52
55
|
|
|
53
56
|
|
|
@@ -129,13 +132,20 @@ def _init_splash():
|
|
|
129
132
|
|
|
130
133
|
# NEW: Prefer centralized resources resolver
|
|
131
134
|
try:
|
|
132
|
-
from setiastro.saspro.resources import icon_path
|
|
135
|
+
from setiastro.saspro.resources import icon_path, background_startup_path
|
|
133
136
|
_early_icon_path = icon_path
|
|
134
137
|
if not os.path.exists(_early_icon_path):
|
|
135
138
|
# fall back to legacy search if for some reason this is missing
|
|
136
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
|
+
|
|
137
146
|
except Exception:
|
|
138
147
|
_early_icon_path = _find_icon_path()
|
|
148
|
+
_startup_bg_path = None
|
|
139
149
|
|
|
140
150
|
|
|
141
151
|
# =========================================================================
|
|
@@ -177,6 +187,11 @@ def _init_splash():
|
|
|
177
187
|
# Load and scale logo
|
|
178
188
|
self.logo_pixmap = self._load_logo(logo_path)
|
|
179
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
|
+
|
|
180
195
|
# Fonts
|
|
181
196
|
self.title_font = QFont("Segoe UI", 28, QFont.Weight.Bold)
|
|
182
197
|
self.subtitle_font = QFont("Segoe UI", 11)
|
|
@@ -215,15 +230,42 @@ def _init_splash():
|
|
|
215
230
|
_app.processEvents()
|
|
216
231
|
|
|
217
232
|
def setProgress(self, value: int):
|
|
218
|
-
"""Update progress (0-100)."""
|
|
219
|
-
|
|
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
|
|
220
262
|
self.repaint()
|
|
221
263
|
if _app:
|
|
222
264
|
_app.processEvents()
|
|
223
265
|
|
|
224
266
|
def setBuildInfo(self, version: str, build: str):
|
|
225
267
|
"""Update version and build info once available."""
|
|
226
|
-
self._version =
|
|
268
|
+
self._version = _EARLY_VERSION
|
|
227
269
|
self._build = build
|
|
228
270
|
self.repaint()
|
|
229
271
|
|
|
@@ -242,6 +284,45 @@ def _init_splash():
|
|
|
242
284
|
gradient.setColorAt(1.0, QColor(10, 10, 20))
|
|
243
285
|
painter.fillRect(0, 0, w, h, gradient)
|
|
244
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
|
+
|
|
245
326
|
# --- Subtle border ---
|
|
246
327
|
painter.setPen(QColor(60, 60, 80))
|
|
247
328
|
painter.drawRect(0, 0, w - 1, h - 1)
|
|
@@ -312,20 +393,52 @@ def _init_splash():
|
|
|
312
393
|
self.hide()
|
|
313
394
|
self.close()
|
|
314
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()
|
|
315
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
|
+
|
|
316
417
|
# --- Show splash IMMEDIATELY ---
|
|
317
418
|
_splash = _EarlySplash(_early_icon_path)
|
|
419
|
+
_splash.start_fade_in()
|
|
318
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
|
+
|
|
319
431
|
_splash.setMessage(QCoreApplication.translate("Splash", "Initializing Python runtime..."))
|
|
320
432
|
_splash.setProgress(2)
|
|
321
433
|
_app.processEvents()
|
|
322
434
|
|
|
323
435
|
# Load translation BEFORE any other widgets are created
|
|
324
436
|
try:
|
|
325
|
-
from setiastro.saspro.i18n import load_language
|
|
326
|
-
load_language(app=_app)
|
|
327
|
-
except Exception:
|
|
328
|
-
|
|
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
|
+
|
|
329
442
|
|
|
330
443
|
_splash_initialized = True
|
|
331
444
|
|
|
@@ -516,7 +629,8 @@ from PyQt6.QtGui import (QPixmap, QColor, QIcon, QKeySequence, QShortcut, QGuiAp
|
|
|
516
629
|
)
|
|
517
630
|
|
|
518
631
|
# ----- QtCore -----
|
|
519
|
-
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
|
|
520
634
|
)
|
|
521
635
|
|
|
522
636
|
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
|
@@ -529,8 +643,6 @@ except Exception:
|
|
|
529
643
|
BUILD_TIMESTAMP = "dev"
|
|
530
644
|
|
|
531
645
|
|
|
532
|
-
from setiastro.saspro.versioning import get_app_version
|
|
533
|
-
VERSION = get_app_version("setiastrosuitepro")
|
|
534
646
|
|
|
535
647
|
_update_splash(QCoreApplication.translate("Splash", "Loading resources..."), 50)
|
|
536
648
|
|
|
@@ -747,7 +859,33 @@ def main():
|
|
|
747
859
|
if _splash:
|
|
748
860
|
_splash.setMessage(QCoreApplication.translate("Splash", "Showing main window..."))
|
|
749
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
|
+
|
|
750
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()
|
|
751
889
|
|
|
752
890
|
# Start background Numba warmup after UI is visible
|
|
753
891
|
try:
|
|
@@ -761,18 +899,16 @@ def main():
|
|
|
761
899
|
_splash.setProgress(100)
|
|
762
900
|
_app.processEvents()
|
|
763
901
|
|
|
764
|
-
# Small delay to
|
|
902
|
+
# Small delay to ensure "Ready!" is seen briefly before fade starts
|
|
765
903
|
import time
|
|
766
|
-
time.sleep(0.
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
#
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
_splash.close()
|
|
775
|
-
_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.
|
|
776
912
|
|
|
777
913
|
if BUILD_TIMESTAMP == "dev":
|
|
778
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
|