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
setiastro/saspro/remove_stars.py
CHANGED
|
@@ -136,154 +136,82 @@ def _mtf_params_unlinked(img_rgb01: np.ndarray,
|
|
|
136
136
|
Works on float32 data assumed in [0,1].
|
|
137
137
|
Returns dict with arrays: {'s': (C,), 'm': (C,), 'h': (C,)}.
|
|
138
138
|
"""
|
|
139
|
-
x = np.asarray(img_rgb01, dtype=np.float32)
|
|
140
|
-
# Force 3 channels internally (Siril expects 1 or 3; we always give it 3 here)
|
|
141
|
-
if x.ndim == 2:
|
|
142
|
-
x = np.stack([x] * 3, axis=-1)
|
|
143
|
-
elif x.ndim == 3 and x.shape[2] == 1:
|
|
144
|
-
x = np.repeat(x, 3, axis=2)
|
|
145
|
-
|
|
146
|
-
C = x.shape[2]
|
|
147
|
-
s = np.zeros(C, dtype=np.float32)
|
|
148
|
-
m = np.zeros(C, dtype=np.float32)
|
|
149
|
-
h = np.zeros(C, dtype=np.float32)
|
|
150
|
-
|
|
151
|
-
med = np.zeros(C, dtype=np.float32)
|
|
152
|
-
mad = np.zeros(C, dtype=np.float32)
|
|
153
|
-
inverted_flags = np.zeros(C, dtype=bool)
|
|
154
|
-
|
|
155
|
-
# --- stats per channel (Siril: median / normValue, mad / normValue * MAD_NORM) ---
|
|
156
|
-
# Here normValue == 1.0 because we're already in [0,1]
|
|
157
|
-
for c in range(C):
|
|
158
|
-
ch = x[..., c].astype(np.float32, copy=False)
|
|
159
|
-
med_c = float(np.median(ch))
|
|
160
|
-
mad_raw = float(np.median(np.abs(ch - med_c)))
|
|
161
|
-
mad_c = mad_raw * _MAD_NORM
|
|
162
|
-
if mad_c == 0.0:
|
|
163
|
-
mad_c = 0.001
|
|
164
|
-
|
|
165
|
-
med[c] = med_c
|
|
166
|
-
mad[c] = mad_c
|
|
167
|
-
if med_c > 0.5:
|
|
168
|
-
inverted_flags[c] = True
|
|
169
|
-
|
|
170
|
-
inverted_channels = int(inverted_flags.sum())
|
|
171
|
-
|
|
172
|
-
# --- Main branch (non-inverted dominant) ---
|
|
173
|
-
if inverted_channels < C:
|
|
174
|
-
for c in range(C):
|
|
175
|
-
median = float(med[c])
|
|
176
|
-
mad_c = float(mad[c])
|
|
177
|
-
|
|
178
|
-
c0 = median + shadows_clipping * mad_c
|
|
179
|
-
if c0 < 0.0:
|
|
180
|
-
c0 = 0.0
|
|
181
|
-
# Siril: m2 = median - c0; midtones = MTF(m2, target_bg, 0,1)
|
|
182
|
-
m2 = median - c0
|
|
183
|
-
mid = float(_mtf_scalar(m2, targetbg, 0.0, 1.0))
|
|
184
|
-
|
|
185
|
-
s[c] = c0
|
|
186
|
-
m[c] = mid
|
|
187
|
-
h[c] = 1.0
|
|
188
|
-
|
|
189
|
-
# --- Inverted channel branch ---
|
|
190
|
-
else:
|
|
191
|
-
for c in range(C):
|
|
192
|
-
median = float(med[c])
|
|
193
|
-
mad_c = float(mad[c])
|
|
194
|
-
|
|
195
|
-
c1 = median - shadows_clipping * mad_c
|
|
196
|
-
if c1 > 1.0:
|
|
197
|
-
c1 = 1.0
|
|
198
|
-
m2 = c1 - median
|
|
199
|
-
mid = 1.0 - float(_mtf_scalar(m2, targetbg, 0.0, 1.0))
|
|
200
|
-
|
|
201
|
-
s[c] = 0.0
|
|
202
|
-
m[c] = mid
|
|
203
|
-
h[c] = c1
|
|
204
|
-
|
|
205
|
-
return {"s": s, "m": m, "h": h}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def _apply_mtf_unlinked_rgb(img_rgb01: np.ndarray, p: dict) -> np.ndarray:
|
|
209
139
|
"""
|
|
210
|
-
|
|
140
|
+
Siril-style per-channel MTF parameter estimation, matching
|
|
141
|
+
find_unlinked_midtones_balance_default() / find_unlinked_midtones_balance().
|
|
142
|
+
|
|
143
|
+
Works on float32 data assumed in [0,1].
|
|
144
|
+
Returns dict with arrays: {'s': (C,), 'm': (C,), 'h': (C,)}.
|
|
211
145
|
"""
|
|
212
146
|
x = np.asarray(img_rgb01, dtype=np.float32)
|
|
147
|
+
|
|
148
|
+
# Analyze input shape to handle mono efficiently
|
|
213
149
|
if x.ndim == 2:
|
|
214
|
-
|
|
150
|
+
# (H, W) -> treat as single channel
|
|
151
|
+
x_in = x[..., None] # Virtual 3D (H,W,1)
|
|
152
|
+
C_in = 1
|
|
215
153
|
elif x.ndim == 3 and x.shape[2] == 1:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
154
|
+
x_in = x
|
|
155
|
+
C_in = 1
|
|
156
|
+
else:
|
|
157
|
+
x_in = x
|
|
158
|
+
C_in = x.shape[2]
|
|
159
|
+
|
|
160
|
+
# Vectorized stats calculation on actual data only
|
|
161
|
+
med = np.median(x_in, axis=(0, 1)).astype(np.float32) # shape (C_in,)
|
|
162
|
+
|
|
163
|
+
# MAD requires centered abs diff
|
|
164
|
+
diff = np.abs(x_in - med.reshape(1, 1, C_in))
|
|
165
|
+
mad_raw = np.median(diff, axis=(0, 1)).astype(np.float32) # shape (C_in,)
|
|
166
|
+
|
|
167
|
+
mad = mad_raw * _MAD_NORM
|
|
168
|
+
mad[mad == 0] = 0.001
|
|
169
|
+
|
|
170
|
+
inverted_flags = (med > 0.5)
|
|
171
|
+
# If mono, we just check the one channel. If RGB, we check all.
|
|
172
|
+
# Logic below assumes we return 3-channel params s,m,h even for mono input (broadcasted).
|
|
173
|
+
|
|
174
|
+
# To match original behavior which always returned 3-element arrays for s,m,h:
|
|
175
|
+
# We will compute s_in, m_in, h_in for the input channels, then broadcast to 3.
|
|
176
|
+
|
|
177
|
+
s_in = np.zeros(C_in, dtype=np.float32)
|
|
178
|
+
m_in = np.zeros(C_in, dtype=np.float32)
|
|
179
|
+
h_in = np.zeros(C_in, dtype=np.float32)
|
|
180
|
+
|
|
181
|
+
# We iterate C_in times (1 or 3)
|
|
182
|
+
for c in range(C_in):
|
|
183
|
+
is_inv = inverted_flags[c]
|
|
184
|
+
md = med[c]
|
|
185
|
+
md_dev = mad[c]
|
|
186
|
+
|
|
187
|
+
if not is_inv:
|
|
188
|
+
# Normal
|
|
189
|
+
c0 = max(md + shadows_clipping * md_dev, 0.0)
|
|
190
|
+
m2 = md - c0
|
|
191
|
+
|
|
192
|
+
s_in[c] = c0
|
|
193
|
+
m_in[c] = float(_mtf_scalar(m2, targetbg, 0.0, 1.0))
|
|
194
|
+
h_in[c] = 1.0
|
|
195
|
+
else:
|
|
196
|
+
# Inverted
|
|
197
|
+
c1 = min(md - shadows_clipping * md_dev, 1.0)
|
|
198
|
+
m2 = c1 - md
|
|
199
|
+
|
|
200
|
+
s_in[c] = 0.0
|
|
201
|
+
m_in[c] = 1.0 - float(_mtf_scalar(m2, targetbg, 0.0, 1.0))
|
|
202
|
+
h_in[c] = c1
|
|
203
|
+
|
|
204
|
+
# Broadcast to 3 channels if needed
|
|
205
|
+
if C_in == 1:
|
|
206
|
+
s = np.repeat(s_in, 3)
|
|
207
|
+
m = np.repeat(m_in, 3)
|
|
208
|
+
h = np.repeat(h_in, 3)
|
|
209
|
+
else:
|
|
210
|
+
s = s_in
|
|
211
|
+
m = m_in
|
|
212
|
+
h = h_in
|
|
254
213
|
|
|
255
|
-
|
|
256
|
-
ch = x[..., c]
|
|
257
|
-
lo = float(np.percentile(ch, lo_pct))
|
|
258
|
-
hi = float(np.percentile(ch, hi_pct))
|
|
259
|
-
if not np.isfinite(lo): lo = 0.0
|
|
260
|
-
if not np.isfinite(hi): hi = 1.0
|
|
261
|
-
if hi - lo < 1e-6:
|
|
262
|
-
hi = lo + 1e-6
|
|
263
|
-
lo_vals.append(lo); hi_vals.append(hi)
|
|
264
|
-
out[..., c] = (ch - lo) / (hi - lo)
|
|
265
|
-
|
|
266
|
-
out = np.clip(out, 0.0, 1.0)
|
|
267
|
-
params = {"lo": lo_vals, "hi": hi_vals, "was_single": was_single}
|
|
268
|
-
return out, params
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def _stat_unstretch_rgb(img: np.ndarray, params: dict) -> np.ndarray:
|
|
272
|
-
"""
|
|
273
|
-
Inverse of _stat_stretch_rgb. Expects img RGB float32 [0,1].
|
|
274
|
-
"""
|
|
275
|
-
lo = np.asarray(params["lo"], dtype=np.float32)
|
|
276
|
-
hi = np.asarray(params["hi"], dtype=np.float32)
|
|
277
|
-
out = img.astype(np.float32, copy=True)
|
|
278
|
-
for c in range(3):
|
|
279
|
-
out[..., c] = out[..., c] * (hi[c] - lo[c]) + lo[c]
|
|
280
|
-
out = np.clip(out, 0.0, 1.0)
|
|
281
|
-
if params.get("was_single", False):
|
|
282
|
-
out = out.mean(axis=2, keepdims=False) # back to single channel if needed
|
|
283
|
-
# StarNet needs RGB during processing; we keep RGB after removal for consistency.
|
|
284
|
-
# If you want to return mono to the doc when the source was mono, do it at the very end.
|
|
285
|
-
out = np.stack([out] * 3, axis=-1)
|
|
286
|
-
return out
|
|
214
|
+
return {"s": s, "m": m, "h": h}
|
|
287
215
|
|
|
288
216
|
def _mtf_scalar(x: float, m: float, lo: float = 0.0, hi: float = 1.0) -> float:
|
|
289
217
|
"""
|
|
@@ -324,6 +252,36 @@ def _mtf_scalar(x: float, m: float, lo: float = 0.0, hi: float = 1.0) -> float:
|
|
|
324
252
|
return float(y)
|
|
325
253
|
|
|
326
254
|
|
|
255
|
+
def _apply_mtf_unlinked_rgb(img_rgb01: np.ndarray, p: dict) -> np.ndarray:
|
|
256
|
+
"""
|
|
257
|
+
Apply per-channel MTF exactly. p from _mtf_params_unlinked.
|
|
258
|
+
"""
|
|
259
|
+
x = np.asarray(img_rgb01, dtype=np.float32)
|
|
260
|
+
if x.ndim == 2:
|
|
261
|
+
x = np.stack([x]*3, axis=-1)
|
|
262
|
+
elif x.ndim == 3 and x.shape[2] == 1:
|
|
263
|
+
x = np.repeat(x, 3, axis=2)
|
|
264
|
+
|
|
265
|
+
out = np.empty_like(x, dtype=np.float32)
|
|
266
|
+
for c in range(x.shape[2]):
|
|
267
|
+
out[..., c] = _mtf_apply(x[..., c], float(p["s"][c]), float(p["m"][c]), float(p["h"][c]))
|
|
268
|
+
return np.clip(out, 0.0, 1.0)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _invert_mtf_unlinked_rgb(img_rgb01: np.ndarray, p: dict) -> np.ndarray:
|
|
272
|
+
"""
|
|
273
|
+
Exact analytic inverse per channel (uses same s/m/h arrays).
|
|
274
|
+
"""
|
|
275
|
+
y = np.asarray(img_rgb01, dtype=np.float32)
|
|
276
|
+
if y.ndim == 2:
|
|
277
|
+
y = np.stack([y]*3, axis=-1)
|
|
278
|
+
elif y.ndim == 3 and y.shape[2] == 1:
|
|
279
|
+
y = np.repeat(y, 3, axis=2)
|
|
280
|
+
|
|
281
|
+
out = np.empty_like(y, dtype=np.float32)
|
|
282
|
+
for c in range(y.shape[2]):
|
|
283
|
+
out[..., c] = _mtf_inverse(y[..., c], float(p["s"][c]), float(p["m"][c]), float(p["h"][c]))
|
|
284
|
+
return np.clip(out, 0.0, 1.0)
|
|
327
285
|
# ------------------------------------------------------------
|
|
328
286
|
# Settings helper
|
|
329
287
|
# ------------------------------------------------------------
|
|
@@ -370,24 +328,28 @@ def starnet_starless_from_array(arr_rgb01: np.ndarray, settings, *, tmp_prefix="
|
|
|
370
328
|
in_path = os.path.join(workdir, f"{tmp_prefix}_in.tif")
|
|
371
329
|
out_path = os.path.join(workdir, f"{tmp_prefix}_out.tif")
|
|
372
330
|
|
|
373
|
-
# --- Normalize input shape and safe values ---
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
331
|
+
# --- Normalize input shape (virtual) and safe values ---
|
|
332
|
+
x_in = np.asarray(arr, dtype=np.float32)
|
|
333
|
+
|
|
334
|
+
# If (H,W,1), collapse to (H,W) so mono flows cleanly
|
|
335
|
+
if x_in.ndim == 3 and x_in.shape[2] == 1:
|
|
336
|
+
x_in = x_in[..., 0]
|
|
337
|
+
|
|
338
|
+
# sanitize
|
|
339
|
+
x_in = np.nan_to_num(x_in, nan=0.0, posinf=0.0, neginf=0.0).astype(np.float32, copy=False)
|
|
380
340
|
|
|
381
341
|
# Preserve original numeric scale if users pass >1.0
|
|
382
|
-
xmax = float(np.max(
|
|
342
|
+
xmax = float(np.max(x_in)) if x_in.size else 1.0
|
|
383
343
|
scale_factor = xmax if xmax > 1.01 else 1.0
|
|
384
|
-
|
|
344
|
+
|
|
345
|
+
xin = (x_in / scale_factor) if scale_factor > 1.0 else x_in
|
|
385
346
|
xin = np.clip(xin, 0.0, 1.0)
|
|
386
347
|
|
|
387
348
|
# --- Siril-style unlinked MTF params + pre-stretch ---
|
|
388
349
|
mtf_params = _mtf_params_unlinked(xin, shadows_clipping=-2.8, targetbg=0.25)
|
|
389
350
|
x_for_starnet = _apply_mtf_unlinked_rgb(xin, mtf_params).astype(np.float32, copy=False)
|
|
390
351
|
|
|
352
|
+
|
|
391
353
|
# --- Write 16-bit TIFF for StarNet ---
|
|
392
354
|
save_image(
|
|
393
355
|
x_for_starnet, in_path,
|
|
@@ -662,18 +624,19 @@ def _run_starnet(main, doc):
|
|
|
662
624
|
)
|
|
663
625
|
except Exception:
|
|
664
626
|
pass
|
|
665
|
-
# --- Ensure RGB float32 in safe range
|
|
627
|
+
# --- Ensure RGB float32 in safe range (without expanding yet)
|
|
628
|
+
# Starnet needs RGB eventually, but we can compute stats/normalization on mono
|
|
666
629
|
src = np.asarray(doc.image)
|
|
667
|
-
if src.ndim == 2:
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
processing_image = np.repeat(src, 3, axis=2)
|
|
630
|
+
if src.ndim == 3 and src.shape[2] == 1:
|
|
631
|
+
# standardizing shape is cheap
|
|
632
|
+
processing_image = src[..., 0]
|
|
671
633
|
else:
|
|
672
634
|
processing_image = src
|
|
635
|
+
|
|
673
636
|
processing_image = np.nan_to_num(processing_image.astype(np.float32, copy=False),
|
|
674
637
|
nan=0.0, posinf=0.0, neginf=0.0)
|
|
675
638
|
|
|
676
|
-
# --- Scale normalization if >1.0
|
|
639
|
+
# --- Scale normalization if >1.0
|
|
677
640
|
scale_factor = float(np.max(processing_image))
|
|
678
641
|
if scale_factor > 1.0:
|
|
679
642
|
processing_norm = processing_image / scale_factor
|
|
@@ -1027,11 +990,10 @@ def _run_darkstar(main, doc):
|
|
|
1027
990
|
pass
|
|
1028
991
|
|
|
1029
992
|
# --- Build processing image (RGB float32, normalized) ---
|
|
993
|
+
# DarkStar needs RGB, but we can delay expansion until save
|
|
1030
994
|
src = np.asarray(doc.image)
|
|
1031
|
-
if src.ndim == 2:
|
|
1032
|
-
processing_image =
|
|
1033
|
-
elif src.ndim == 3 and src.shape[2] == 1:
|
|
1034
|
-
processing_image = np.repeat(src, 3, axis=2)
|
|
995
|
+
if src.ndim == 3 and src.shape[2] == 1:
|
|
996
|
+
processing_image = src[..., 0]
|
|
1035
997
|
else:
|
|
1036
998
|
processing_image = src
|
|
1037
999
|
|
|
@@ -1088,8 +1050,20 @@ def _run_darkstar(main, doc):
|
|
|
1088
1050
|
# --- Save pre-stretched image as 32-bit float TIFF for DarkStar ---
|
|
1089
1051
|
in_path = os.path.join(input_dir, "imagetoremovestars.tif")
|
|
1090
1052
|
try:
|
|
1053
|
+
# Check if we need to expand on-the-fly for DarkStar (it expects RGB input)
|
|
1054
|
+
# If img_for_darkstar is mono, save_image might save mono.
|
|
1055
|
+
# "is_mono=False" flag to save_image hints we want RGB.
|
|
1056
|
+
# If the array is 2D, save_image might still save mono unless we feed it 3D.
|
|
1057
|
+
# For safety with DarkStar, we create the 3D view now if needed.
|
|
1058
|
+
|
|
1059
|
+
to_save = img_for_darkstar
|
|
1060
|
+
if to_save.ndim == 2:
|
|
1061
|
+
to_save = np.stack([to_save]*3, axis=-1)
|
|
1062
|
+
elif to_save.ndim == 3 and to_save.shape[2] == 1:
|
|
1063
|
+
to_save = np.repeat(to_save, 3, axis=2)
|
|
1064
|
+
|
|
1091
1065
|
save_image(
|
|
1092
|
-
|
|
1066
|
+
to_save,
|
|
1093
1067
|
in_path,
|
|
1094
1068
|
original_format="tif",
|
|
1095
1069
|
bit_depth="32-bit floating point",
|
setiastro/saspro/resources.py
CHANGED
|
@@ -469,9 +469,16 @@ def _init_legacy_paths():
|
|
|
469
469
|
_legacy = _init_legacy_paths()
|
|
470
470
|
globals().update(_legacy)
|
|
471
471
|
|
|
472
|
+
|
|
473
|
+
# Background for startup
|
|
474
|
+
background_startup_path = os.path.join(_get_base_path(), 'images', 'Background_startup.jpg')
|
|
475
|
+
_legacy['background_startup_path'] = background_startup_path
|
|
476
|
+
|
|
472
477
|
# Export list for `from setiastro.saspro.resources import *`
|
|
473
478
|
__all__ = [
|
|
474
479
|
'Icons', 'Resources',
|
|
475
480
|
'get_icons', 'get_resources',
|
|
476
481
|
'get_icon_path', 'get_data_path',
|
|
482
|
+
'background_startup_path',
|
|
477
483
|
] + list(_legacy.keys())
|
|
484
|
+
|
|
@@ -44,6 +44,7 @@ class RGBCombinationDialogPro(QDialog):
|
|
|
44
44
|
super().__init__(parent)
|
|
45
45
|
self.setWindowTitle(self.tr("RGB Combination"))
|
|
46
46
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
47
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
47
48
|
self.setModal(False)
|
|
48
49
|
#self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
49
50
|
self._list_open_docs = list_open_docs_fn or (lambda: [])
|
setiastro/saspro/rgbalign.py
CHANGED
|
@@ -324,15 +324,12 @@ class RGBAlignWorker(QThread):
|
|
|
324
324
|
def _warp_channel(self, ch: np.ndarray, kind: str, X, ref_shape):
|
|
325
325
|
H, W = ref_shape[:2]
|
|
326
326
|
if kind == "affine":
|
|
327
|
-
|
|
328
|
-
return ch
|
|
327
|
+
# Just assume cv2 is available (standard dependency) for perf
|
|
329
328
|
A = np.asarray(X, dtype=np.float32).reshape(2, 3)
|
|
330
329
|
return cv2.warpAffine(ch, A, (W, H), flags=cv2.INTER_LANCZOS4,
|
|
331
330
|
borderMode=cv2.BORDER_CONSTANT, borderValue=0)
|
|
332
331
|
|
|
333
332
|
if kind == "homography":
|
|
334
|
-
if cv2 is None:
|
|
335
|
-
return ch
|
|
336
333
|
Hm = np.asarray(X, dtype=np.float32).reshape(3, 3)
|
|
337
334
|
return cv2.warpPerspective(ch, Hm, (W, H), flags=cv2.INTER_LANCZOS4,
|
|
338
335
|
borderMode=cv2.BORDER_CONSTANT, borderValue=0)
|
|
@@ -350,6 +347,9 @@ class RGBAlignDialog(QDialog):
|
|
|
350
347
|
def __init__(self, parent=None, document=None):
|
|
351
348
|
super().__init__(parent)
|
|
352
349
|
self.setWindowTitle(self.tr("RGB Align"))
|
|
350
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
351
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
352
|
+
self.setModal(False)
|
|
353
353
|
self.parent = parent
|
|
354
354
|
# document could be a view; try to unwrap
|
|
355
355
|
self.doc_view = document
|
setiastro/saspro/save_options.py
CHANGED
|
@@ -20,6 +20,7 @@ class SaveOptionsDialog(QDialog):
|
|
|
20
20
|
super().__init__(parent)
|
|
21
21
|
self.setWindowTitle(self.tr("Save Options"))
|
|
22
22
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
23
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
23
24
|
self.setModal(False)
|
|
24
25
|
#self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
25
26
|
|
setiastro/saspro/sfcc.py
CHANGED
|
@@ -346,6 +346,9 @@ class SFCCDialog(QDialog):
|
|
|
346
346
|
def __init__(self, doc_manager, sasp_data_path, parent=None):
|
|
347
347
|
super().__init__(parent)
|
|
348
348
|
self.setWindowTitle(self.tr("Spectral Flux Color Calibration"))
|
|
349
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
350
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
351
|
+
self.setModal(False)
|
|
349
352
|
self.setMinimumSize(800, 600)
|
|
350
353
|
|
|
351
354
|
self.doc_manager = doc_manager
|
|
@@ -1155,18 +1158,56 @@ class SFCCDialog(QDialog):
|
|
|
1155
1158
|
diag_meas_BG, diag_exp_BG = [], []
|
|
1156
1159
|
enriched = []
|
|
1157
1160
|
|
|
1161
|
+
# --- Optimization: Pre-calculate integrals for unique templates ---
|
|
1162
|
+
unique_simbad_types = set(m["template"] for m in raw_matches)
|
|
1163
|
+
|
|
1164
|
+
# Map simbad_type -> pickles_template_name
|
|
1165
|
+
simbad_to_pickles = {}
|
|
1166
|
+
pickles_templates_needed = set()
|
|
1167
|
+
|
|
1168
|
+
for sp in unique_simbad_types:
|
|
1169
|
+
cands = pickles_match_for_simbad(sp, getattr(self, "pickles_templates", []))
|
|
1170
|
+
if cands:
|
|
1171
|
+
pickles_name = cands[0]
|
|
1172
|
+
simbad_to_pickles[sp] = pickles_name
|
|
1173
|
+
pickles_templates_needed.add(pickles_name)
|
|
1174
|
+
|
|
1175
|
+
# Pre-calc integrals for each unique Pickles template
|
|
1176
|
+
# Cache structure: template_name -> (S_sr, S_sg, S_sb)
|
|
1177
|
+
template_integrals = {}
|
|
1178
|
+
|
|
1179
|
+
# Cache for load_sed to avoid re-reading even across different calls if desired,
|
|
1180
|
+
# but here we just optimize the loop.
|
|
1181
|
+
|
|
1182
|
+
for pname in pickles_templates_needed:
|
|
1183
|
+
try:
|
|
1184
|
+
wl_s, fl_s = load_sed(pname)
|
|
1185
|
+
fs_i = np.interp(wl_grid, wl_s, fl_s, left=0., right=0.)
|
|
1186
|
+
|
|
1187
|
+
S_sr = np.trapezoid(fs_i * T_sys_R, x=wl_grid)
|
|
1188
|
+
S_sg = np.trapezoid(fs_i * T_sys_G, x=wl_grid)
|
|
1189
|
+
S_sb = np.trapezoid(fs_i * T_sys_B, x=wl_grid)
|
|
1190
|
+
|
|
1191
|
+
template_integrals[pname] = (S_sr, S_sg, S_sb)
|
|
1192
|
+
except Exception as e:
|
|
1193
|
+
print(f"[SFCC] Warning: failed to load/integrate template {pname}: {e}")
|
|
1194
|
+
|
|
1195
|
+
# --- Main Match Loop ---
|
|
1158
1196
|
for m in raw_matches:
|
|
1159
1197
|
xi, yi, sp = m["x_pix"], m["y_pix"], m["template"]
|
|
1160
1198
|
Rm = float(base[yi, xi, 0]); Gm = float(base[yi, xi, 1]); Bm = float(base[yi, xi, 2])
|
|
1161
1199
|
if Gm <= 0: continue
|
|
1162
1200
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1201
|
+
# 1. Resolve Simbad -> Pickles
|
|
1202
|
+
pname = simbad_to_pickles.get(sp)
|
|
1203
|
+
if not pname: continue
|
|
1204
|
+
|
|
1205
|
+
# 2. Retrieve pre-calced integrals
|
|
1206
|
+
integrals = template_integrals.get(pname)
|
|
1207
|
+
if not integrals: continue
|
|
1208
|
+
|
|
1209
|
+
S_sr, S_sg, S_sb = integrals
|
|
1210
|
+
|
|
1170
1211
|
if S_sg <= 0: continue
|
|
1171
1212
|
|
|
1172
1213
|
exp_RG = S_sr / S_sg; exp_BG = S_sb / S_sg
|
|
@@ -1180,7 +1221,8 @@ class SFCCDialog(QDialog):
|
|
|
1180
1221
|
"S_star_R": S_sr, "S_star_G": S_sg, "S_star_B": S_sb,
|
|
1181
1222
|
"exp_RG": exp_RG, "exp_BG": exp_BG
|
|
1182
1223
|
})
|
|
1183
|
-
|
|
1224
|
+
|
|
1225
|
+
self._last_matched = enriched
|
|
1184
1226
|
diag_meas_RG = np.array(diag_meas_RG); diag_exp_RG = np.array(diag_exp_RG)
|
|
1185
1227
|
diag_meas_BG = np.array(diag_meas_BG); diag_exp_BG = np.array(diag_exp_BG)
|
|
1186
1228
|
if diag_meas_RG.size == 0 or diag_meas_BG.size == 0:
|
|
@@ -363,6 +363,9 @@ class SignatureInsertDialogPro(QDialog):
|
|
|
363
363
|
def __init__(self, parent, doc, icon: QIcon | None = None):
|
|
364
364
|
super().__init__(parent)
|
|
365
365
|
self.setWindowTitle(self.tr("Signature / Insert"))
|
|
366
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
367
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
368
|
+
self.setModal(False)
|
|
366
369
|
if icon:
|
|
367
370
|
try: self.setWindowIcon(icon)
|
|
368
371
|
except Exception as e:
|