setiastrosuitepro 1.6.4__py3-none-any.whl → 1.7.1.post2__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/TextureClarity.svg +56 -0
- setiastro/images/abeicon.svg +16 -0
- setiastro/images/acv_icon.png +0 -0
- setiastro/images/colorwheel.svg +97 -0
- setiastro/images/cosmic.svg +40 -0
- setiastro/images/cosmicsat.svg +24 -0
- setiastro/images/first_quarter.png +0 -0
- setiastro/images/full_moon.png +0 -0
- setiastro/images/graxpert.svg +19 -0
- setiastro/images/last_quarter.png +0 -0
- setiastro/images/linearfit.svg +32 -0
- setiastro/images/narrowbandnormalization.png +0 -0
- setiastro/images/new_moon.png +0 -0
- setiastro/images/pixelmath.svg +42 -0
- setiastro/images/planetarystacker.png +0 -0
- setiastro/images/waning_crescent_1.png +0 -0
- setiastro/images/waning_crescent_2.png +0 -0
- setiastro/images/waning_crescent_3.png +0 -0
- setiastro/images/waning_crescent_4.png +0 -0
- setiastro/images/waning_crescent_5.png +0 -0
- setiastro/images/waning_gibbous_1.png +0 -0
- setiastro/images/waning_gibbous_2.png +0 -0
- setiastro/images/waning_gibbous_3.png +0 -0
- setiastro/images/waning_gibbous_4.png +0 -0
- setiastro/images/waning_gibbous_5.png +0 -0
- setiastro/images/waxing_crescent_1.png +0 -0
- setiastro/images/waxing_crescent_2.png +0 -0
- setiastro/images/waxing_crescent_3.png +0 -0
- setiastro/images/waxing_crescent_4.png +0 -0
- setiastro/images/waxing_crescent_5.png +0 -0
- setiastro/images/waxing_gibbous_1.png +0 -0
- setiastro/images/waxing_gibbous_2.png +0 -0
- setiastro/images/waxing_gibbous_3.png +0 -0
- setiastro/images/waxing_gibbous_4.png +0 -0
- setiastro/images/waxing_gibbous_5.png +0 -0
- setiastro/qml/ResourceMonitor.qml +84 -82
- setiastro/saspro/__main__.py +20 -1
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/abe.py +37 -4
- setiastro/saspro/aberration_ai.py +364 -33
- setiastro/saspro/aberration_ai_preset.py +29 -3
- setiastro/saspro/acv_exporter.py +379 -0
- setiastro/saspro/add_stars.py +33 -6
- setiastro/saspro/astrospike_python.py +45 -3
- setiastro/saspro/backgroundneutral.py +108 -40
- setiastro/saspro/blemish_blaster.py +4 -1
- setiastro/saspro/blink_comparator_pro.py +150 -55
- setiastro/saspro/clahe.py +4 -1
- setiastro/saspro/continuum_subtract.py +4 -1
- setiastro/saspro/convo.py +13 -7
- setiastro/saspro/cosmicclarity.py +129 -18
- setiastro/saspro/crop_dialog_pro.py +123 -7
- setiastro/saspro/curve_editor_pro.py +181 -64
- setiastro/saspro/curves_preset.py +249 -47
- setiastro/saspro/doc_manager.py +245 -15
- setiastro/saspro/exoplanet_detector.py +120 -28
- setiastro/saspro/frequency_separation.py +1158 -204
- setiastro/saspro/ghs_dialog_pro.py +81 -16
- setiastro/saspro/graxpert.py +1 -0
- setiastro/saspro/gui/main_window.py +706 -264
- setiastro/saspro/gui/mixins/dock_mixin.py +245 -24
- setiastro/saspro/gui/mixins/file_mixin.py +35 -16
- setiastro/saspro/gui/mixins/menu_mixin.py +35 -1
- setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
- setiastro/saspro/gui/mixins/toolbar_mixin.py +499 -24
- setiastro/saspro/gui/mixins/update_mixin.py +138 -36
- setiastro/saspro/gui/mixins/view_mixin.py +42 -0
- setiastro/saspro/halobgon.py +4 -0
- setiastro/saspro/histogram.py +184 -8
- setiastro/saspro/image_combine.py +4 -0
- setiastro/saspro/image_peeker_pro.py +4 -0
- setiastro/saspro/imageops/narrowband_normalization.py +816 -0
- setiastro/saspro/imageops/serloader.py +1345 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +23 -52
- setiastro/saspro/imageops/stretch.py +582 -62
- setiastro/saspro/isophote.py +4 -0
- setiastro/saspro/layers.py +13 -9
- setiastro/saspro/layers_dock.py +183 -3
- setiastro/saspro/legacy/image_manager.py +154 -20
- setiastro/saspro/legacy/numba_utils.py +68 -48
- setiastro/saspro/legacy/xisf.py +240 -98
- setiastro/saspro/live_stacking.py +203 -82
- setiastro/saspro/luminancerecombine.py +228 -27
- setiastro/saspro/mask_creation.py +174 -15
- setiastro/saspro/mfdeconv.py +113 -35
- setiastro/saspro/mfdeconvcudnn.py +119 -70
- setiastro/saspro/mfdeconvsport.py +112 -35
- setiastro/saspro/morphology.py +4 -0
- setiastro/saspro/multiscale_decomp.py +81 -29
- setiastro/saspro/narrowband_normalization.py +1618 -0
- setiastro/saspro/numba_utils.py +72 -57
- setiastro/saspro/ops/commands.py +18 -18
- setiastro/saspro/ops/script_editor.py +10 -2
- setiastro/saspro/ops/scripts.py +122 -0
- setiastro/saspro/perfect_palette_picker.py +37 -3
- setiastro/saspro/plate_solver.py +84 -49
- setiastro/saspro/psf_viewer.py +119 -37
- setiastro/saspro/remove_green.py +1 -1
- setiastro/saspro/resources.py +73 -0
- setiastro/saspro/rgbalign.py +460 -12
- setiastro/saspro/selective_color.py +4 -1
- setiastro/saspro/ser_stack_config.py +82 -0
- setiastro/saspro/ser_stacker.py +2321 -0
- setiastro/saspro/ser_stacker_dialog.py +1838 -0
- setiastro/saspro/ser_tracking.py +206 -0
- setiastro/saspro/serviewer.py +1625 -0
- setiastro/saspro/sfcc.py +662 -216
- setiastro/saspro/shortcuts.py +171 -33
- setiastro/saspro/signature_insert.py +692 -33
- setiastro/saspro/stacking_suite.py +1347 -485
- setiastro/saspro/star_alignment.py +247 -123
- setiastro/saspro/star_spikes.py +4 -0
- setiastro/saspro/star_stretch.py +38 -3
- setiastro/saspro/stat_stretch.py +892 -129
- setiastro/saspro/subwindow.py +787 -363
- setiastro/saspro/supernovaasteroidhunter.py +1 -1
- setiastro/saspro/texture_clarity.py +593 -0
- setiastro/saspro/wavescale_hdr.py +4 -1
- setiastro/saspro/wavescalede.py +4 -1
- setiastro/saspro/whitebalance.py +84 -12
- setiastro/saspro/widgets/common_utilities.py +28 -21
- setiastro/saspro/widgets/resource_monitor.py +209 -111
- setiastro/saspro/widgets/spinboxes.py +10 -13
- setiastro/saspro/wimi.py +27 -656
- setiastro/saspro/wims.py +13 -3
- setiastro/saspro/xisf.py +101 -11
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/METADATA +4 -2
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/RECORD +132 -87
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/mfdeconv.py
CHANGED
|
@@ -250,14 +250,20 @@ def _probe_hw(path: str) -> tuple[int, int, int | None]:
|
|
|
250
250
|
raise ValueError(f"Unsupported ndim={a.ndim} for {path}")
|
|
251
251
|
|
|
252
252
|
def _common_hw_from_paths(paths: list[str]) -> tuple[int, int]:
|
|
253
|
-
"""
|
|
254
|
-
Replacement for the old FITS-only version: min(H), min(W) across files.
|
|
255
|
-
"""
|
|
256
253
|
Hs, Ws = [], []
|
|
257
254
|
for p in paths:
|
|
258
255
|
h, w, _ = _probe_hw(p)
|
|
259
|
-
|
|
260
|
-
|
|
256
|
+
h = int(h); w = int(w)
|
|
257
|
+
if h > 0 and w > 0:
|
|
258
|
+
Hs.append(h); Ws.append(w)
|
|
259
|
+
|
|
260
|
+
if not Hs:
|
|
261
|
+
raise ValueError("Could not determine any valid frame sizes.")
|
|
262
|
+
Ht = min(Hs); Wt = min(Ws)
|
|
263
|
+
if Ht < 8 or Wt < 8:
|
|
264
|
+
raise ValueError(f"Intersection too small: {Ht}x{Wt}")
|
|
265
|
+
return Ht, Wt
|
|
266
|
+
|
|
261
267
|
|
|
262
268
|
def _to_chw_float32(img: np.ndarray, color_mode: str) -> np.ndarray:
|
|
263
269
|
"""
|
|
@@ -367,22 +373,36 @@ def _compute_frame_assets(i, arr, hdr, *, make_masks, make_varmaps,
|
|
|
367
373
|
f_whm = 2.5
|
|
368
374
|
k_auto = _auto_ksize_from_fwhm(f_whm)
|
|
369
375
|
|
|
370
|
-
# --- Star-derived PSF with retries ---
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
376
|
+
# --- Star-derived PSF with retries (dynamic det_sigma ladder) ---
|
|
377
|
+
psf = None
|
|
378
|
+
|
|
379
|
+
# Your existing ksize ladder
|
|
380
|
+
k_ladder = [k_auto, max(k_auto - 4, 11), 21, 17, 15, 13, 11]
|
|
381
|
+
|
|
382
|
+
# New: start high to avoid detecting 10k stars; step down only if needed
|
|
383
|
+
sigma_ladder = [50.0, 25.0, 12.0, 6.0]
|
|
384
|
+
|
|
385
|
+
tried = set()
|
|
386
|
+
for det_sigma in sigma_ladder:
|
|
387
|
+
for k_try in k_ladder:
|
|
388
|
+
if (det_sigma, k_try) in tried:
|
|
389
|
+
continue
|
|
390
|
+
tried.add((det_sigma, k_try))
|
|
391
|
+
try:
|
|
392
|
+
out = compute_psf_kernel_for_image(arr, ksize=k_try, det_sigma=det_sigma, max_stars=80)
|
|
393
|
+
psf_try = out[0] if (isinstance(out, tuple) and len(out) >= 1) else out
|
|
394
|
+
if psf_try is not None:
|
|
395
|
+
psf = psf_try
|
|
396
|
+
break
|
|
397
|
+
except Exception:
|
|
398
|
+
psf = None
|
|
399
|
+
if psf is not None:
|
|
400
|
+
break
|
|
401
|
+
|
|
383
402
|
if psf is None:
|
|
384
403
|
psf = _gaussian_psf(f_whm, ksize=k_auto)
|
|
385
|
-
|
|
404
|
+
|
|
405
|
+
psf = _soften_psf(_normalize_psf(psf.astype(np.float32, copy=False)), sigma_px=0.25)
|
|
386
406
|
|
|
387
407
|
mask = None
|
|
388
408
|
var = None
|
|
@@ -425,6 +445,7 @@ def _compute_frame_assets(i, arr, hdr, *, make_masks, make_varmaps,
|
|
|
425
445
|
|
|
426
446
|
return i, psf, mask, var, logs
|
|
427
447
|
|
|
448
|
+
|
|
428
449
|
def _compute_one_worker(args):
|
|
429
450
|
"""
|
|
430
451
|
Top-level picklable worker for ProcessPoolExecutor.
|
|
@@ -1039,25 +1060,82 @@ SOFT_SIGMA = 2.0
|
|
|
1039
1060
|
ELLIPSE_SCALE = 1.2
|
|
1040
1061
|
|
|
1041
1062
|
def _sep_background_precompute(img_2d: np.ndarray, bw: int = 64, bh: int = 64):
|
|
1042
|
-
"""
|
|
1063
|
+
"""
|
|
1064
|
+
One-time SEP background build; returns (sky_map, rms_map, err_scalar).
|
|
1065
|
+
|
|
1066
|
+
Guarantees:
|
|
1067
|
+
- Always returns a 3-tuple (sky, rms, err)
|
|
1068
|
+
- sky/rms are float32 and same shape as img_2d
|
|
1069
|
+
- Robust to sep missing, sep errors, NaNs/Infs, and tiny frames
|
|
1070
|
+
"""
|
|
1071
|
+
a = np.asarray(img_2d, dtype=np.float32)
|
|
1072
|
+
if a.ndim != 2:
|
|
1073
|
+
# be strict; callers expect 2D
|
|
1074
|
+
raise ValueError(f"_sep_background_precompute expects 2D, got shape={a.shape}")
|
|
1075
|
+
|
|
1076
|
+
H, W = int(a.shape[0]), int(a.shape[1])
|
|
1077
|
+
if H == 0 or W == 0:
|
|
1078
|
+
# should never happen, but don't return empty tuple
|
|
1079
|
+
sky = np.zeros((H, W), dtype=np.float32)
|
|
1080
|
+
rms = np.ones((H, W), dtype=np.float32)
|
|
1081
|
+
return sky, rms, 1.0
|
|
1082
|
+
|
|
1083
|
+
# --- robust fallback builder (works for any input) ---
|
|
1084
|
+
def _fallback():
|
|
1085
|
+
# Use finite-only stats if possible
|
|
1086
|
+
finite = np.isfinite(a)
|
|
1087
|
+
if finite.any():
|
|
1088
|
+
vals = a[finite]
|
|
1089
|
+
med = float(np.median(vals))
|
|
1090
|
+
mad = float(np.median(np.abs(vals - med))) + 1e-6
|
|
1091
|
+
else:
|
|
1092
|
+
med = 0.0
|
|
1093
|
+
mad = 1.0
|
|
1094
|
+
sky = np.full((H, W), med, dtype=np.float32)
|
|
1095
|
+
rms = np.full((H, W), 1.4826 * mad, dtype=np.float32)
|
|
1096
|
+
err = float(np.median(rms))
|
|
1097
|
+
return sky, rms, err
|
|
1098
|
+
|
|
1099
|
+
# If sep isn't available, always fallback
|
|
1043
1100
|
if sep is None:
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1101
|
+
return _fallback()
|
|
1102
|
+
|
|
1103
|
+
# SEP is present: sanitize input and clamp tile sizes
|
|
1104
|
+
# sep can choke on NaNs/Infs
|
|
1105
|
+
if not np.isfinite(a).all():
|
|
1106
|
+
# replace non-finite with median of finite values (or 0)
|
|
1107
|
+
finite = np.isfinite(a)
|
|
1108
|
+
fill = float(np.median(a[finite])) if finite.any() else 0.0
|
|
1109
|
+
a = np.where(finite, a, fill).astype(np.float32, copy=False)
|
|
1110
|
+
|
|
1111
|
+
a = np.ascontiguousarray(a, dtype=np.float32)
|
|
1112
|
+
|
|
1113
|
+
# Clamp bw/bh to image size; SEP doesn't like bw/bh > dims
|
|
1114
|
+
bw = int(max(8, min(int(bw), W)))
|
|
1115
|
+
bh = int(max(8, min(int(bh), H)))
|
|
1116
|
+
|
|
1054
1117
|
try:
|
|
1055
|
-
|
|
1056
|
-
|
|
1118
|
+
b = sep.Background(a, bw=bw, bh=bh, fw=3, fh=3)
|
|
1119
|
+
|
|
1120
|
+
sky = np.asarray(b.back(), dtype=np.float32)
|
|
1121
|
+
rms = np.asarray(b.rms(), dtype=np.float32)
|
|
1122
|
+
|
|
1123
|
+
# Ensure shape sanity (SEP should match, but be paranoid)
|
|
1124
|
+
if sky.shape != a.shape or rms.shape != a.shape:
|
|
1125
|
+
return _fallback()
|
|
1126
|
+
|
|
1127
|
+
# globalrms sometimes isn't available depending on SEP build
|
|
1128
|
+
err = float(getattr(b, "globalrms", np.nan))
|
|
1129
|
+
if not np.isfinite(err) or err <= 0:
|
|
1130
|
+
# robust scalar: median rms
|
|
1131
|
+
err = float(np.median(rms)) if rms.size else 1.0
|
|
1132
|
+
|
|
1133
|
+
return sky, rms, err
|
|
1134
|
+
|
|
1057
1135
|
except Exception:
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1136
|
+
# If SEP blows up for any reason, degrade gracefully
|
|
1137
|
+
return _fallback()
|
|
1138
|
+
|
|
1061
1139
|
|
|
1062
1140
|
|
|
1063
1141
|
def _auto_star_mask_sep(
|
|
@@ -311,14 +311,20 @@ def _probe_hw(path: str) -> tuple[int, int, int | None]:
|
|
|
311
311
|
raise ValueError(f"Unsupported ndim={a.ndim} for {path}")
|
|
312
312
|
|
|
313
313
|
def _common_hw_from_paths(paths: list[str]) -> tuple[int, int]:
|
|
314
|
-
"""
|
|
315
|
-
Replacement for the old FITS-only version: min(H), min(W) across files.
|
|
316
|
-
"""
|
|
317
314
|
Hs, Ws = [], []
|
|
318
315
|
for p in paths:
|
|
319
316
|
h, w, _ = _probe_hw(p)
|
|
320
|
-
|
|
321
|
-
|
|
317
|
+
h = int(h); w = int(w)
|
|
318
|
+
if h > 0 and w > 0:
|
|
319
|
+
Hs.append(h); Ws.append(w)
|
|
320
|
+
|
|
321
|
+
if not Hs:
|
|
322
|
+
raise ValueError("Could not determine any valid frame sizes.")
|
|
323
|
+
Ht = min(Hs); Wt = min(Ws)
|
|
324
|
+
if Ht < 8 or Wt < 8:
|
|
325
|
+
raise ValueError(f"Intersection too small: {Ht}x{Wt}")
|
|
326
|
+
return Ht, Wt
|
|
327
|
+
|
|
322
328
|
|
|
323
329
|
def _to_chw_float32(img: np.ndarray, color_mode: str) -> np.ndarray:
|
|
324
330
|
"""
|
|
@@ -395,15 +401,12 @@ def _safe_primary_header(path: str) -> fits.Header:
|
|
|
395
401
|
except Exception:
|
|
396
402
|
return fits.Header()
|
|
397
403
|
|
|
398
|
-
|
|
399
404
|
def _compute_frame_assets(i, arr, hdr, *, make_masks, make_varmaps,
|
|
400
405
|
star_mask_cfg, varmap_cfg, status_sink=lambda s: None):
|
|
401
406
|
"""
|
|
402
407
|
Worker function: compute PSF and optional star mask / varmap for one frame.
|
|
403
|
-
Returns (index, psf, mask_or_None, var_or_None,
|
|
408
|
+
Returns (index, psf, mask_or_None, var_or_None, log_lines)
|
|
404
409
|
"""
|
|
405
|
-
|
|
406
|
-
|
|
407
410
|
logs = []
|
|
408
411
|
def log(s): logs.append(s)
|
|
409
412
|
|
|
@@ -415,36 +418,48 @@ def _compute_frame_assets(i, arr, hdr, *, make_masks, make_varmaps,
|
|
|
415
418
|
f_whm = 2.5
|
|
416
419
|
k_auto = _auto_ksize_from_fwhm(f_whm)
|
|
417
420
|
|
|
418
|
-
# --- Star-derived PSF with retries ---
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
421
|
+
# --- Star-derived PSF with retries (dynamic det_sigma ladder) ---
|
|
422
|
+
psf = None
|
|
423
|
+
|
|
424
|
+
# Your existing ksize ladder
|
|
425
|
+
k_ladder = [k_auto, max(k_auto - 4, 11), 21, 17, 15, 13, 11]
|
|
426
|
+
|
|
427
|
+
# New: start high to avoid detecting 10k stars; step down only if needed
|
|
428
|
+
sigma_ladder = [50.0, 25.0, 12.0, 6.0]
|
|
429
|
+
|
|
430
|
+
tried = set()
|
|
431
|
+
for det_sigma in sigma_ladder:
|
|
432
|
+
for k_try in k_ladder:
|
|
433
|
+
if (det_sigma, k_try) in tried:
|
|
434
|
+
continue
|
|
435
|
+
tried.add((det_sigma, k_try))
|
|
436
|
+
try:
|
|
437
|
+
out = compute_psf_kernel_for_image(arr, ksize=k_try, det_sigma=det_sigma, max_stars=80)
|
|
438
|
+
psf_try = out[0] if (isinstance(out, tuple) and len(out) >= 1) else out
|
|
439
|
+
if psf_try is not None:
|
|
440
|
+
psf = psf_try
|
|
441
|
+
break
|
|
442
|
+
except Exception:
|
|
443
|
+
psf = None
|
|
444
|
+
if psf is not None:
|
|
445
|
+
break
|
|
446
|
+
|
|
432
447
|
if psf is None:
|
|
433
448
|
psf = _gaussian_psf(f_whm, ksize=k_auto)
|
|
449
|
+
|
|
434
450
|
psf = _soften_psf(_normalize_psf(psf.astype(np.float32, copy=False)), sigma_px=0.25)
|
|
435
451
|
|
|
436
452
|
mask = None
|
|
437
453
|
var = None
|
|
438
|
-
var_path = None
|
|
439
454
|
|
|
440
455
|
if make_masks or make_varmaps:
|
|
456
|
+
# one background per frame (reused by both)
|
|
441
457
|
luma = _to_luma_local(arr)
|
|
442
458
|
vmc = (varmap_cfg or {})
|
|
443
459
|
sky_map, rms_map, err_scalar = _sep_background_precompute(
|
|
444
460
|
luma, bw=int(vmc.get("bw", 64)), bh=int(vmc.get("bh", 64))
|
|
445
461
|
)
|
|
446
462
|
|
|
447
|
-
# ---------- Star mask ----------
|
|
448
463
|
if make_masks:
|
|
449
464
|
smc = star_mask_cfg or {}
|
|
450
465
|
mask = _star_mask_from_precomputed(
|
|
@@ -459,44 +474,22 @@ def _compute_frame_assets(i, arr, hdr, *, make_masks, make_varmaps,
|
|
|
459
474
|
max_side = smc.get("max_side", STAR_MASK_MAXSIDE),
|
|
460
475
|
status_cb = log,
|
|
461
476
|
)
|
|
462
|
-
# keep masks compact
|
|
463
|
-
if mask is not None and mask.dtype != np.uint8:
|
|
464
|
-
mask = (mask > 0.5).astype(np.uint8, copy=False)
|
|
465
477
|
|
|
466
|
-
# ---------- Variance map (memmap path; Option B) ----------
|
|
467
478
|
if make_varmaps:
|
|
468
479
|
vmc = varmap_cfg or {}
|
|
469
|
-
|
|
470
|
-
try: log(f"__PROGRESS__ {0.16 + 0.02*float(frac):.4f} {msg}")
|
|
471
|
-
except Exception as e:
|
|
472
|
-
import logging
|
|
473
|
-
logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
|
|
474
|
-
|
|
475
|
-
var_path = _variance_map_from_precomputed_memmap(
|
|
480
|
+
var = _variance_map_from_precomputed(
|
|
476
481
|
luma, sky_map, rms_map, hdr,
|
|
477
|
-
smooth_sigma =
|
|
478
|
-
floor =
|
|
479
|
-
tile_hw = tuple(vmc.get("tile_hw", (512, 512))),
|
|
480
|
-
scratch_dir = vmc.get("scratch_dir", None),
|
|
481
|
-
tag = f"varmap_{i:04d}",
|
|
482
|
+
smooth_sigma = vmc.get("smooth_sigma", 1.0),
|
|
483
|
+
floor = vmc.get("floor", 1e-8),
|
|
482
484
|
status_cb = log,
|
|
483
|
-
progress_cb = _vprog,
|
|
484
485
|
)
|
|
485
|
-
var = None # Option B: don't keep an open memmap handle
|
|
486
|
-
|
|
487
|
-
# 🔻 free heavy temporaries immediately
|
|
488
|
-
try:
|
|
489
|
-
del luma
|
|
490
|
-
del sky_map
|
|
491
|
-
del rms_map
|
|
492
|
-
except Exception:
|
|
493
|
-
pass
|
|
494
|
-
gc.collect()
|
|
495
486
|
|
|
496
|
-
# per-frame summary
|
|
487
|
+
# small per-frame summary
|
|
497
488
|
fwhm_est = _psf_fwhm_px(psf)
|
|
498
489
|
logs.insert(0, f"MFDeconv: PSF{i}: ksize={psf.shape[0]} | FWHM≈{fwhm_est:.2f}px")
|
|
499
|
-
|
|
490
|
+
|
|
491
|
+
return i, psf, mask, var, logs
|
|
492
|
+
|
|
500
493
|
|
|
501
494
|
def _compute_one_worker(args):
|
|
502
495
|
"""
|
|
@@ -1186,25 +1179,81 @@ SOFT_SIGMA = 2.0
|
|
|
1186
1179
|
ELLIPSE_SCALE = 1.2
|
|
1187
1180
|
|
|
1188
1181
|
def _sep_background_precompute(img_2d: np.ndarray, bw: int = 64, bh: int = 64):
|
|
1189
|
-
"""
|
|
1182
|
+
"""
|
|
1183
|
+
One-time SEP background build; returns (sky_map, rms_map, err_scalar).
|
|
1184
|
+
|
|
1185
|
+
Guarantees:
|
|
1186
|
+
- Always returns a 3-tuple (sky, rms, err)
|
|
1187
|
+
- sky/rms are float32 and same shape as img_2d
|
|
1188
|
+
- Robust to sep missing, sep errors, NaNs/Infs, and tiny frames
|
|
1189
|
+
"""
|
|
1190
|
+
a = np.asarray(img_2d, dtype=np.float32)
|
|
1191
|
+
if a.ndim != 2:
|
|
1192
|
+
# be strict; callers expect 2D
|
|
1193
|
+
raise ValueError(f"_sep_background_precompute expects 2D, got shape={a.shape}")
|
|
1194
|
+
|
|
1195
|
+
H, W = int(a.shape[0]), int(a.shape[1])
|
|
1196
|
+
if H == 0 or W == 0:
|
|
1197
|
+
# should never happen, but don't return empty tuple
|
|
1198
|
+
sky = np.zeros((H, W), dtype=np.float32)
|
|
1199
|
+
rms = np.ones((H, W), dtype=np.float32)
|
|
1200
|
+
return sky, rms, 1.0
|
|
1201
|
+
|
|
1202
|
+
# --- robust fallback builder (works for any input) ---
|
|
1203
|
+
def _fallback():
|
|
1204
|
+
# Use finite-only stats if possible
|
|
1205
|
+
finite = np.isfinite(a)
|
|
1206
|
+
if finite.any():
|
|
1207
|
+
vals = a[finite]
|
|
1208
|
+
med = float(np.median(vals))
|
|
1209
|
+
mad = float(np.median(np.abs(vals - med))) + 1e-6
|
|
1210
|
+
else:
|
|
1211
|
+
med = 0.0
|
|
1212
|
+
mad = 1.0
|
|
1213
|
+
sky = np.full((H, W), med, dtype=np.float32)
|
|
1214
|
+
rms = np.full((H, W), 1.4826 * mad, dtype=np.float32)
|
|
1215
|
+
err = float(np.median(rms))
|
|
1216
|
+
return sky, rms, err
|
|
1217
|
+
|
|
1218
|
+
# If sep isn't available, always fallback
|
|
1190
1219
|
if sep is None:
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1220
|
+
return _fallback()
|
|
1221
|
+
|
|
1222
|
+
# SEP is present: sanitize input and clamp tile sizes
|
|
1223
|
+
# sep can choke on NaNs/Infs
|
|
1224
|
+
if not np.isfinite(a).all():
|
|
1225
|
+
# replace non-finite with median of finite values (or 0)
|
|
1226
|
+
finite = np.isfinite(a)
|
|
1227
|
+
fill = float(np.median(a[finite])) if finite.any() else 0.0
|
|
1228
|
+
a = np.where(finite, a, fill).astype(np.float32, copy=False)
|
|
1229
|
+
|
|
1230
|
+
a = np.ascontiguousarray(a, dtype=np.float32)
|
|
1231
|
+
|
|
1232
|
+
# Clamp bw/bh to image size; SEP doesn't like bw/bh > dims
|
|
1233
|
+
bw = int(max(8, min(int(bw), W)))
|
|
1234
|
+
bh = int(max(8, min(int(bh), H)))
|
|
1235
|
+
|
|
1201
1236
|
try:
|
|
1202
|
-
|
|
1203
|
-
|
|
1237
|
+
b = sep.Background(a, bw=bw, bh=bh, fw=3, fh=3)
|
|
1238
|
+
|
|
1239
|
+
sky = np.asarray(b.back(), dtype=np.float32)
|
|
1240
|
+
rms = np.asarray(b.rms(), dtype=np.float32)
|
|
1241
|
+
|
|
1242
|
+
# Ensure shape sanity (SEP should match, but be paranoid)
|
|
1243
|
+
if sky.shape != a.shape or rms.shape != a.shape:
|
|
1244
|
+
return _fallback()
|
|
1245
|
+
|
|
1246
|
+
# globalrms sometimes isn't available depending on SEP build
|
|
1247
|
+
err = float(getattr(b, "globalrms", np.nan))
|
|
1248
|
+
if not np.isfinite(err) or err <= 0:
|
|
1249
|
+
# robust scalar: median rms
|
|
1250
|
+
err = float(np.median(rms)) if rms.size else 1.0
|
|
1251
|
+
|
|
1252
|
+
return sky, rms, err
|
|
1253
|
+
|
|
1204
1254
|
except Exception:
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
return sky, rmsm, err
|
|
1255
|
+
# If SEP blows up for any reason, degrade gracefully
|
|
1256
|
+
return _fallback()
|
|
1208
1257
|
|
|
1209
1258
|
|
|
1210
1259
|
def _star_mask_from_precomputed(
|
|
@@ -278,14 +278,20 @@ def _probe_hw(path: str) -> tuple[int, int, int | None]:
|
|
|
278
278
|
raise ValueError(f"Unsupported ndim={a.ndim} for {path}")
|
|
279
279
|
|
|
280
280
|
def _common_hw_from_paths(paths: list[str]) -> tuple[int, int]:
|
|
281
|
-
"""
|
|
282
|
-
Replacement for the old FITS-only version: min(H), min(W) across files.
|
|
283
|
-
"""
|
|
284
281
|
Hs, Ws = [], []
|
|
285
282
|
for p in paths:
|
|
286
283
|
h, w, _ = _probe_hw(p)
|
|
287
|
-
|
|
288
|
-
|
|
284
|
+
h = int(h); w = int(w)
|
|
285
|
+
if h > 0 and w > 0:
|
|
286
|
+
Hs.append(h); Ws.append(w)
|
|
287
|
+
|
|
288
|
+
if not Hs:
|
|
289
|
+
raise ValueError("Could not determine any valid frame sizes.")
|
|
290
|
+
Ht = min(Hs); Wt = min(Ws)
|
|
291
|
+
if Ht < 8 or Wt < 8:
|
|
292
|
+
raise ValueError(f"Intersection too small: {Ht}x{Wt}")
|
|
293
|
+
return Ht, Wt
|
|
294
|
+
|
|
289
295
|
|
|
290
296
|
def _to_chw_float32(img: np.ndarray, color_mode: str) -> np.ndarray:
|
|
291
297
|
"""
|
|
@@ -362,7 +368,6 @@ def _safe_primary_header(path: str) -> fits.Header:
|
|
|
362
368
|
except Exception:
|
|
363
369
|
return fits.Header()
|
|
364
370
|
|
|
365
|
-
|
|
366
371
|
def _compute_frame_assets(i, arr, hdr, *, make_masks, make_varmaps,
|
|
367
372
|
star_mask_cfg, varmap_cfg, status_sink=lambda s: None):
|
|
368
373
|
"""
|
|
@@ -380,21 +385,35 @@ def _compute_frame_assets(i, arr, hdr, *, make_masks, make_varmaps,
|
|
|
380
385
|
f_whm = 2.5
|
|
381
386
|
k_auto = _auto_ksize_from_fwhm(f_whm)
|
|
382
387
|
|
|
383
|
-
# --- Star-derived PSF with retries ---
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
388
|
+
# --- Star-derived PSF with retries (dynamic det_sigma ladder) ---
|
|
389
|
+
psf = None
|
|
390
|
+
|
|
391
|
+
# Your existing ksize ladder
|
|
392
|
+
k_ladder = [k_auto, max(k_auto - 4, 11), 21, 17, 15, 13, 11]
|
|
393
|
+
|
|
394
|
+
# New: start high to avoid detecting 10k stars; step down only if needed
|
|
395
|
+
sigma_ladder = [50.0, 25.0, 12.0, 6.0]
|
|
396
|
+
|
|
397
|
+
tried = set()
|
|
398
|
+
for det_sigma in sigma_ladder:
|
|
399
|
+
for k_try in k_ladder:
|
|
400
|
+
if (det_sigma, k_try) in tried:
|
|
401
|
+
continue
|
|
402
|
+
tried.add((det_sigma, k_try))
|
|
403
|
+
try:
|
|
404
|
+
out = compute_psf_kernel_for_image(arr, ksize=k_try, det_sigma=det_sigma, max_stars=80)
|
|
405
|
+
psf_try = out[0] if (isinstance(out, tuple) and len(out) >= 1) else out
|
|
406
|
+
if psf_try is not None:
|
|
407
|
+
psf = psf_try
|
|
408
|
+
break
|
|
409
|
+
except Exception:
|
|
410
|
+
psf = None
|
|
411
|
+
if psf is not None:
|
|
412
|
+
break
|
|
413
|
+
|
|
396
414
|
if psf is None:
|
|
397
415
|
psf = _gaussian_psf(f_whm, ksize=k_auto)
|
|
416
|
+
|
|
398
417
|
psf = _soften_psf(_normalize_psf(psf.astype(np.float32, copy=False)), sigma_px=0.25)
|
|
399
418
|
|
|
400
419
|
mask = None
|
|
@@ -438,6 +457,7 @@ def _compute_frame_assets(i, arr, hdr, *, make_masks, make_varmaps,
|
|
|
438
457
|
|
|
439
458
|
return i, psf, mask, var, logs
|
|
440
459
|
|
|
460
|
+
|
|
441
461
|
def _compute_one_worker(args):
|
|
442
462
|
"""
|
|
443
463
|
Top-level picklable worker for ProcessPoolExecutor.
|
|
@@ -1026,25 +1046,82 @@ SOFT_SIGMA = 2.0
|
|
|
1026
1046
|
ELLIPSE_SCALE = 1.2
|
|
1027
1047
|
|
|
1028
1048
|
def _sep_background_precompute(img_2d: np.ndarray, bw: int = 64, bh: int = 64):
|
|
1029
|
-
"""
|
|
1049
|
+
"""
|
|
1050
|
+
One-time SEP background build; returns (sky_map, rms_map, err_scalar).
|
|
1051
|
+
|
|
1052
|
+
Guarantees:
|
|
1053
|
+
- Always returns a 3-tuple (sky, rms, err)
|
|
1054
|
+
- sky/rms are float32 and same shape as img_2d
|
|
1055
|
+
- Robust to sep missing, sep errors, NaNs/Infs, and tiny frames
|
|
1056
|
+
"""
|
|
1057
|
+
a = np.asarray(img_2d, dtype=np.float32)
|
|
1058
|
+
if a.ndim != 2:
|
|
1059
|
+
# be strict; callers expect 2D
|
|
1060
|
+
raise ValueError(f"_sep_background_precompute expects 2D, got shape={a.shape}")
|
|
1061
|
+
|
|
1062
|
+
H, W = int(a.shape[0]), int(a.shape[1])
|
|
1063
|
+
if H == 0 or W == 0:
|
|
1064
|
+
# should never happen, but don't return empty tuple
|
|
1065
|
+
sky = np.zeros((H, W), dtype=np.float32)
|
|
1066
|
+
rms = np.ones((H, W), dtype=np.float32)
|
|
1067
|
+
return sky, rms, 1.0
|
|
1068
|
+
|
|
1069
|
+
# --- robust fallback builder (works for any input) ---
|
|
1070
|
+
def _fallback():
|
|
1071
|
+
# Use finite-only stats if possible
|
|
1072
|
+
finite = np.isfinite(a)
|
|
1073
|
+
if finite.any():
|
|
1074
|
+
vals = a[finite]
|
|
1075
|
+
med = float(np.median(vals))
|
|
1076
|
+
mad = float(np.median(np.abs(vals - med))) + 1e-6
|
|
1077
|
+
else:
|
|
1078
|
+
med = 0.0
|
|
1079
|
+
mad = 1.0
|
|
1080
|
+
sky = np.full((H, W), med, dtype=np.float32)
|
|
1081
|
+
rms = np.full((H, W), 1.4826 * mad, dtype=np.float32)
|
|
1082
|
+
err = float(np.median(rms))
|
|
1083
|
+
return sky, rms, err
|
|
1084
|
+
|
|
1085
|
+
# If sep isn't available, always fallback
|
|
1030
1086
|
if sep is None:
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1087
|
+
return _fallback()
|
|
1088
|
+
|
|
1089
|
+
# SEP is present: sanitize input and clamp tile sizes
|
|
1090
|
+
# sep can choke on NaNs/Infs
|
|
1091
|
+
if not np.isfinite(a).all():
|
|
1092
|
+
# replace non-finite with median of finite values (or 0)
|
|
1093
|
+
finite = np.isfinite(a)
|
|
1094
|
+
fill = float(np.median(a[finite])) if finite.any() else 0.0
|
|
1095
|
+
a = np.where(finite, a, fill).astype(np.float32, copy=False)
|
|
1096
|
+
|
|
1097
|
+
a = np.ascontiguousarray(a, dtype=np.float32)
|
|
1098
|
+
|
|
1099
|
+
# Clamp bw/bh to image size; SEP doesn't like bw/bh > dims
|
|
1100
|
+
bw = int(max(8, min(int(bw), W)))
|
|
1101
|
+
bh = int(max(8, min(int(bh), H)))
|
|
1102
|
+
|
|
1041
1103
|
try:
|
|
1042
|
-
|
|
1043
|
-
|
|
1104
|
+
b = sep.Background(a, bw=bw, bh=bh, fw=3, fh=3)
|
|
1105
|
+
|
|
1106
|
+
sky = np.asarray(b.back(), dtype=np.float32)
|
|
1107
|
+
rms = np.asarray(b.rms(), dtype=np.float32)
|
|
1108
|
+
|
|
1109
|
+
# Ensure shape sanity (SEP should match, but be paranoid)
|
|
1110
|
+
if sky.shape != a.shape or rms.shape != a.shape:
|
|
1111
|
+
return _fallback()
|
|
1112
|
+
|
|
1113
|
+
# globalrms sometimes isn't available depending on SEP build
|
|
1114
|
+
err = float(getattr(b, "globalrms", np.nan))
|
|
1115
|
+
if not np.isfinite(err) or err <= 0:
|
|
1116
|
+
# robust scalar: median rms
|
|
1117
|
+
err = float(np.median(rms)) if rms.size else 1.0
|
|
1118
|
+
|
|
1119
|
+
return sky, rms, err
|
|
1120
|
+
|
|
1044
1121
|
except Exception:
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1122
|
+
# If SEP blows up for any reason, degrade gracefully
|
|
1123
|
+
return _fallback()
|
|
1124
|
+
|
|
1048
1125
|
|
|
1049
1126
|
|
|
1050
1127
|
def _star_mask_from_precomputed(
|
setiastro/saspro/morphology.py
CHANGED
|
@@ -94,6 +94,10 @@ class MorphologyDialogPro(QDialog):
|
|
|
94
94
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
95
95
|
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
96
96
|
self.setModal(False)
|
|
97
|
+
try:
|
|
98
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
99
|
+
except Exception:
|
|
100
|
+
pass # older PyQt6 versions
|
|
97
101
|
if icon:
|
|
98
102
|
try: self.setWindowIcon(icon)
|
|
99
103
|
except Exception as e:
|