setiastrosuitepro 1.6.7__py3-none-any.whl → 1.7.0__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/abeicon.svg +16 -0
- setiastro/images/colorwheel.svg +97 -0
- setiastro/images/cosmic.svg +40 -0
- setiastro/images/cosmicsat.svg +24 -0
- setiastro/images/graxpert.svg +19 -0
- setiastro/images/linearfit.svg +32 -0
- setiastro/images/narrowbandnormalization.png +0 -0
- setiastro/images/pixelmath.svg +42 -0
- setiastro/images/planetarystacker.png +0 -0
- setiastro/saspro/__main__.py +1 -1
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/aberration_ai.py +49 -11
- setiastro/saspro/aberration_ai_preset.py +29 -3
- setiastro/saspro/add_stars.py +29 -5
- setiastro/saspro/backgroundneutral.py +73 -33
- setiastro/saspro/blink_comparator_pro.py +150 -55
- setiastro/saspro/convo.py +9 -6
- setiastro/saspro/cosmicclarity.py +125 -18
- setiastro/saspro/crop_dialog_pro.py +96 -2
- setiastro/saspro/curve_editor_pro.py +132 -61
- setiastro/saspro/curves_preset.py +249 -47
- setiastro/saspro/doc_manager.py +178 -11
- setiastro/saspro/frequency_separation.py +1159 -208
- setiastro/saspro/gui/main_window.py +340 -88
- 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 +31 -1
- setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
- setiastro/saspro/gui/mixins/toolbar_mixin.py +132 -10
- setiastro/saspro/gui/mixins/update_mixin.py +121 -33
- setiastro/saspro/histogram.py +179 -7
- setiastro/saspro/imageops/narrowband_normalization.py +816 -0
- setiastro/saspro/imageops/serloader.py +769 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +23 -52
- setiastro/saspro/imageops/stretch.py +582 -62
- setiastro/saspro/layers.py +13 -9
- setiastro/saspro/layers_dock.py +183 -3
- setiastro/saspro/legacy/numba_utils.py +68 -48
- setiastro/saspro/live_stacking.py +181 -73
- setiastro/saspro/multiscale_decomp.py +77 -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 +5 -0
- setiastro/saspro/ops/scripts.py +119 -0
- setiastro/saspro/remove_green.py +1 -1
- setiastro/saspro/resources.py +4 -0
- setiastro/saspro/ser_stack_config.py +68 -0
- setiastro/saspro/ser_stacker.py +2245 -0
- setiastro/saspro/ser_stacker_dialog.py +1481 -0
- setiastro/saspro/ser_tracking.py +206 -0
- setiastro/saspro/serviewer.py +1242 -0
- setiastro/saspro/sfcc.py +602 -214
- setiastro/saspro/shortcuts.py +154 -25
- setiastro/saspro/signature_insert.py +688 -33
- setiastro/saspro/stacking_suite.py +853 -401
- setiastro/saspro/star_alignment.py +243 -122
- setiastro/saspro/stat_stretch.py +878 -131
- setiastro/saspro/subwindow.py +303 -74
- setiastro/saspro/whitebalance.py +24 -0
- setiastro/saspro/widgets/common_utilities.py +28 -21
- setiastro/saspro/widgets/resource_monitor.py +128 -80
- {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/METADATA +2 -2
- {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/RECORD +68 -51
- {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/numba_utils.py
CHANGED
|
@@ -317,61 +317,6 @@ def invert_image_numba(image):
|
|
|
317
317
|
output[y, x, c] = 1.0 - image[y, x, c]
|
|
318
318
|
return output
|
|
319
319
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
@njit(parallel=True, fastmath=True, cache=True)
|
|
323
|
-
def apply_flat_division_numba_2d(image, master_flat, master_bias=None):
|
|
324
|
-
"""
|
|
325
|
-
Mono version: image.shape == (H,W)
|
|
326
|
-
"""
|
|
327
|
-
if master_bias is not None:
|
|
328
|
-
master_flat = master_flat - master_bias
|
|
329
|
-
image = image - master_bias
|
|
330
|
-
|
|
331
|
-
median_flat = np.mean(master_flat)
|
|
332
|
-
height, width = image.shape
|
|
333
|
-
|
|
334
|
-
for y in prange(height):
|
|
335
|
-
for x in range(width):
|
|
336
|
-
image[y, x] /= (master_flat[y, x] / median_flat)
|
|
337
|
-
|
|
338
|
-
return image
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
@njit(parallel=True, fastmath=True, cache=True)
|
|
342
|
-
def apply_flat_division_numba_3d(image, master_flat, master_bias=None):
|
|
343
|
-
"""
|
|
344
|
-
Color version: image.shape == (H,W,C)
|
|
345
|
-
"""
|
|
346
|
-
if master_bias is not None:
|
|
347
|
-
master_flat = master_flat - master_bias
|
|
348
|
-
image = image - master_bias
|
|
349
|
-
|
|
350
|
-
median_flat = np.mean(master_flat)
|
|
351
|
-
height, width, channels = image.shape
|
|
352
|
-
|
|
353
|
-
for y in prange(height):
|
|
354
|
-
for x in range(width):
|
|
355
|
-
for c in range(channels):
|
|
356
|
-
image[y, x, c] /= (master_flat[y, x, c] / median_flat)
|
|
357
|
-
|
|
358
|
-
return image
|
|
359
|
-
|
|
360
|
-
def apply_flat_division_numba(image, master_flat, master_bias=None):
|
|
361
|
-
"""
|
|
362
|
-
Dispatcher that calls the correct Numba function
|
|
363
|
-
depending on whether 'image' is 2D or 3D.
|
|
364
|
-
"""
|
|
365
|
-
if image.ndim == 2:
|
|
366
|
-
# Mono
|
|
367
|
-
return apply_flat_division_numba_2d(image, master_flat, master_bias)
|
|
368
|
-
elif image.ndim == 3:
|
|
369
|
-
# Color
|
|
370
|
-
return apply_flat_division_numba_3d(image, master_flat, master_bias)
|
|
371
|
-
else:
|
|
372
|
-
raise ValueError(f"apply_flat_division_numba: expected 2D or 3D, got shape {image.shape}")
|
|
373
|
-
|
|
374
|
-
|
|
375
320
|
@njit(parallel=True, cache=True)
|
|
376
321
|
def subtract_dark_3d(frames, dark_frame):
|
|
377
322
|
"""
|
|
@@ -2495,7 +2440,77 @@ def drizzle_deposit_color_naive(image_data, affine_2x3, drizzle_buffer, coverage
|
|
|
2495
2440
|
|
|
2496
2441
|
return drizzle_buffer, coverage_buffer
|
|
2497
2442
|
|
|
2498
|
-
@njit(parallel=True, fastmath=True
|
|
2443
|
+
@njit(parallel=True, fastmath=True)
|
|
2444
|
+
def numba_mono_from_img(img, bp, denom, median_rescaled, target_median):
|
|
2445
|
+
H, W = img.shape
|
|
2446
|
+
out = np.empty_like(img)
|
|
2447
|
+
for y in prange(H):
|
|
2448
|
+
for x in range(W):
|
|
2449
|
+
r = (img[y, x] - bp) / denom
|
|
2450
|
+
numer = (median_rescaled - 1.0) * target_median * r
|
|
2451
|
+
denom2 = median_rescaled * (target_median + r - 1.0) - target_median * r
|
|
2452
|
+
if abs(denom2) < 1e-12:
|
|
2453
|
+
denom2 = 1e-12
|
|
2454
|
+
out[y, x] = numer / denom2
|
|
2455
|
+
return out
|
|
2456
|
+
|
|
2457
|
+
@njit(parallel=True, fastmath=True)
|
|
2458
|
+
def numba_color_linked_from_img(img, bp, denom, median_rescaled, target_median):
|
|
2459
|
+
H, W, C = img.shape
|
|
2460
|
+
out = np.empty_like(img)
|
|
2461
|
+
for y in prange(H):
|
|
2462
|
+
for x in range(W):
|
|
2463
|
+
for c in range(C):
|
|
2464
|
+
r = (img[y, x, c] - bp) / denom
|
|
2465
|
+
numer = (median_rescaled - 1.0) * target_median * r
|
|
2466
|
+
denom2 = median_rescaled * (target_median + r - 1.0) - target_median * r
|
|
2467
|
+
if abs(denom2) < 1e-12:
|
|
2468
|
+
denom2 = 1e-12
|
|
2469
|
+
out[y, x, c] = numer / denom2
|
|
2470
|
+
return out
|
|
2471
|
+
|
|
2472
|
+
@njit(parallel=True, fastmath=True)
|
|
2473
|
+
def numba_color_unlinked_from_img(img, bp3, denom3, meds_rescaled3, target_median):
|
|
2474
|
+
H, W, C = img.shape
|
|
2475
|
+
out = np.empty_like(img)
|
|
2476
|
+
for y in prange(H):
|
|
2477
|
+
for x in range(W):
|
|
2478
|
+
for c in range(C):
|
|
2479
|
+
r = (img[y, x, c] - bp3[c]) / denom3[c]
|
|
2480
|
+
med = meds_rescaled3[c]
|
|
2481
|
+
numer = (med - 1.0) * target_median * r
|
|
2482
|
+
denom2 = med * (target_median + r - 1.0) - target_median * r
|
|
2483
|
+
if abs(denom2) < 1e-12:
|
|
2484
|
+
denom2 = 1e-12
|
|
2485
|
+
out[y, x, c] = numer / denom2
|
|
2486
|
+
return out
|
|
2487
|
+
|
|
2488
|
+
@njit(parallel=True, fastmath=True)
|
|
2489
|
+
def numba_mono_final_formula(rescaled, median_rescaled, target_median):
|
|
2490
|
+
"""
|
|
2491
|
+
Applies the final formula *after* we already have the rescaled values.
|
|
2492
|
+
|
|
2493
|
+
rescaled[y,x] = (original[y,x] - black_point) / (1 - black_point)
|
|
2494
|
+
median_rescaled = median(rescaled)
|
|
2495
|
+
|
|
2496
|
+
out_val = ((median_rescaled - 1) * target_median * r) /
|
|
2497
|
+
( median_rescaled*(target_median + r -1) - target_median*r )
|
|
2498
|
+
"""
|
|
2499
|
+
H, W = rescaled.shape
|
|
2500
|
+
out = np.empty_like(rescaled)
|
|
2501
|
+
|
|
2502
|
+
for y in prange(H):
|
|
2503
|
+
for x in range(W):
|
|
2504
|
+
r = rescaled[y, x]
|
|
2505
|
+
numer = (median_rescaled - 1.0) * target_median * r
|
|
2506
|
+
denom = median_rescaled * (target_median + r - 1.0) - target_median * r
|
|
2507
|
+
if np.abs(denom) < 1e-12:
|
|
2508
|
+
denom = 1e-12
|
|
2509
|
+
out[y, x] = numer / denom
|
|
2510
|
+
|
|
2511
|
+
return out
|
|
2512
|
+
|
|
2513
|
+
@njit(parallel=True, fastmath=True)
|
|
2499
2514
|
def numba_color_final_formula_linked(rescaled, median_rescaled, target_median):
|
|
2500
2515
|
"""
|
|
2501
2516
|
Linked color transform: we use one median_rescaled for all channels.
|
|
@@ -2517,7 +2532,7 @@ def numba_color_final_formula_linked(rescaled, median_rescaled, target_median):
|
|
|
2517
2532
|
|
|
2518
2533
|
return out
|
|
2519
2534
|
|
|
2520
|
-
@njit(parallel=True, fastmath=True
|
|
2535
|
+
@njit(parallel=True, fastmath=True)
|
|
2521
2536
|
def numba_color_final_formula_unlinked(rescaled, medians_rescaled, target_median):
|
|
2522
2537
|
"""
|
|
2523
2538
|
Unlinked color transform: a separate median_rescaled per channel.
|
setiastro/saspro/ops/commands.py
CHANGED
|
@@ -202,7 +202,7 @@ register(CommandSpec(
|
|
|
202
202
|
"optional targets, inherit_target."
|
|
203
203
|
),
|
|
204
204
|
call_style="ctx.run_command",
|
|
205
|
-
import_path="
|
|
205
|
+
import_path="setiastro.saspro.function_bundle", # <── important
|
|
206
206
|
callable_name="run_function_bundle_command",# <── important
|
|
207
207
|
notes=(
|
|
208
208
|
"Use this command from scripts to run a saved Function Bundle or an "
|
|
@@ -274,7 +274,7 @@ register(CommandSpec(
|
|
|
274
274
|
group="Bundles",
|
|
275
275
|
summary="Internal bundle runner. steps=[...], targets='all_open'|[doc_ptrs], stop_on_error.",
|
|
276
276
|
call_style="ctx.run_command",
|
|
277
|
-
import_path="
|
|
277
|
+
import_path="setiastro.saspro.function_bundle",
|
|
278
278
|
callable_name="run_function_bundle_command",
|
|
279
279
|
))
|
|
280
280
|
|
|
@@ -388,7 +388,7 @@ register(CommandSpec(
|
|
|
388
388
|
id="ghs",
|
|
389
389
|
name="Generalized Hyperbolic Stretch",
|
|
390
390
|
group="Stretch",
|
|
391
|
-
import_path="
|
|
391
|
+
import_path="setiastro.saspro.ghs_preset",
|
|
392
392
|
callable_name="apply_ghs_via_preset",
|
|
393
393
|
ui_method="open_ghs_with_preset",
|
|
394
394
|
summary=(
|
|
@@ -499,7 +499,7 @@ register(CommandSpec(
|
|
|
499
499
|
id="curves",
|
|
500
500
|
title="Curves",
|
|
501
501
|
group="Stretch",
|
|
502
|
-
import_path="
|
|
502
|
+
import_path="setiastro.saspro.curves_preset",
|
|
503
503
|
callable_name="apply_curves_via_preset",
|
|
504
504
|
ui_method="open_curves_with_preset",
|
|
505
505
|
summary=(
|
|
@@ -597,7 +597,7 @@ register(CommandSpec(
|
|
|
597
597
|
id="abe",
|
|
598
598
|
title="Automatic Background Extraction",
|
|
599
599
|
group="Background",
|
|
600
|
-
import_path="
|
|
600
|
+
import_path="setiastro.saspro.abe_preset",
|
|
601
601
|
callable_name="apply_abe_via_preset",
|
|
602
602
|
ui_method="open_abe_with_preset", # ✅ matches your pro/abe_preset.py
|
|
603
603
|
summary=(
|
|
@@ -683,7 +683,7 @@ register(CommandSpec(
|
|
|
683
683
|
id="graxpert",
|
|
684
684
|
title="GraXpert Gradient / Denoise",
|
|
685
685
|
group="Background",
|
|
686
|
-
import_path="
|
|
686
|
+
import_path="setiastro.saspro.graxpert_preset",
|
|
687
687
|
callable_name="run_graxpert_via_preset",
|
|
688
688
|
# no ui_method here unless you want to open your optional preset dialog from drops
|
|
689
689
|
# ui_method="open_graxpert_with_preset", # (only if/when you add one)
|
|
@@ -807,7 +807,7 @@ register(CommandSpec(
|
|
|
807
807
|
id="background_neutral",
|
|
808
808
|
name="Background Neutralization",
|
|
809
809
|
group="Background",
|
|
810
|
-
import_path="
|
|
810
|
+
import_path="setiastro.saspro.backgroundneutral",
|
|
811
811
|
callable_name="run_background_neutral_via_preset",
|
|
812
812
|
summary=(
|
|
813
813
|
"Neutralizes RGB background either automatically or using a user-specified "
|
|
@@ -865,7 +865,7 @@ register(CommandSpec(
|
|
|
865
865
|
id="remove_green",
|
|
866
866
|
name="Remove Green (SCNR)",
|
|
867
867
|
group="Color",
|
|
868
|
-
import_path="
|
|
868
|
+
import_path="setiastro.saspro.remove_green",
|
|
869
869
|
callable_name="apply_remove_green_preset_to_doc",
|
|
870
870
|
ui_method="open_remove_green_dialog",
|
|
871
871
|
summary=(
|
|
@@ -1044,7 +1044,7 @@ register(CommandSpec(
|
|
|
1044
1044
|
id="recombine_luminance",
|
|
1045
1045
|
name="Recombine Luminance",
|
|
1046
1046
|
group="Luminance",
|
|
1047
|
-
import_path="
|
|
1047
|
+
import_path="setiastro.saspro.luminancerecombine",
|
|
1048
1048
|
callable_name="run_recombine_luminance_via_preset",
|
|
1049
1049
|
ui_method="_recombine_luminance_ui",
|
|
1050
1050
|
notes=(
|
|
@@ -1144,7 +1144,7 @@ register(CommandSpec(
|
|
|
1144
1144
|
id="wavescale_hdr",
|
|
1145
1145
|
name="WaveScale HDR",
|
|
1146
1146
|
group="Contrast",
|
|
1147
|
-
import_path="
|
|
1147
|
+
import_path="setiastro.saspro.wavescale_hdr_preset",
|
|
1148
1148
|
callable_name="run_wavescale_hdr_via_preset",
|
|
1149
1149
|
ui_method="_open_wavescale_hdr", # or whatever your main window uses
|
|
1150
1150
|
summary=(
|
|
@@ -1188,7 +1188,7 @@ register(CommandSpec(
|
|
|
1188
1188
|
id="wavescale_dark_enhance",
|
|
1189
1189
|
name="WaveScale Dark Enhance",
|
|
1190
1190
|
group="Contrast",
|
|
1191
|
-
import_path="
|
|
1191
|
+
import_path="setiastro.saspro.wavescalede_preset",
|
|
1192
1192
|
callable_name="run_wavescalede_via_preset",
|
|
1193
1193
|
ui_method="_open_wavescale_dark_enhance", # adjust if your main window uses a different name
|
|
1194
1194
|
summary=(
|
|
@@ -1295,7 +1295,7 @@ register(CommandSpec(
|
|
|
1295
1295
|
id="aberration_ai",
|
|
1296
1296
|
title="Aberration AI",
|
|
1297
1297
|
group="Optics",
|
|
1298
|
-
import_path="
|
|
1298
|
+
import_path="setiastro.saspro.aberration_ai_preset",
|
|
1299
1299
|
callable_name="run_aberration_ai_via_preset",
|
|
1300
1300
|
# ui_method="open_aberration_ai_dialog", # if you have one; otherwise omit
|
|
1301
1301
|
presets=[
|
|
@@ -1338,7 +1338,7 @@ register(CommandSpec(
|
|
|
1338
1338
|
id="convo",
|
|
1339
1339
|
title="Convolution / Deconvolution",
|
|
1340
1340
|
group="Blur & Sharpen",
|
|
1341
|
-
import_path="
|
|
1341
|
+
import_path="setiastro.saspro.convo_preset",
|
|
1342
1342
|
callable_name="run_convo_via_preset",
|
|
1343
1343
|
aliases=[
|
|
1344
1344
|
"convolution",
|
|
@@ -1433,7 +1433,7 @@ register(CommandSpec(
|
|
|
1433
1433
|
id="cosmic_clarity",
|
|
1434
1434
|
title="Cosmic Clarity",
|
|
1435
1435
|
group="AI",
|
|
1436
|
-
import_path="
|
|
1436
|
+
import_path="setiastro.saspro.cosmicclarity_preset",
|
|
1437
1437
|
callable_name="run_cosmicclarity_via_preset",
|
|
1438
1438
|
presets=[
|
|
1439
1439
|
PresetSpec("mode", "enum", default="sharpen",
|
|
@@ -1484,7 +1484,7 @@ register(CommandSpec(
|
|
|
1484
1484
|
id="debayer",
|
|
1485
1485
|
title="Debayer",
|
|
1486
1486
|
group="Color / CFA",
|
|
1487
|
-
import_path="
|
|
1487
|
+
import_path="setiastro.saspro.debayer",
|
|
1488
1488
|
callable_name="run_debayer_via_preset",
|
|
1489
1489
|
presets=[
|
|
1490
1490
|
PresetSpec(
|
|
@@ -1506,7 +1506,7 @@ register(CommandSpec(
|
|
|
1506
1506
|
id="linear_fit",
|
|
1507
1507
|
title="Linear Fit",
|
|
1508
1508
|
group="Calibration",
|
|
1509
|
-
import_path="
|
|
1509
|
+
import_path="setiastro.saspro.linear_fit",
|
|
1510
1510
|
callable_name="run_linear_fit_via_preset",
|
|
1511
1511
|
presets=[
|
|
1512
1512
|
PresetSpec(
|
|
@@ -1527,7 +1527,7 @@ register(CommandSpec(
|
|
|
1527
1527
|
id="morphology",
|
|
1528
1528
|
title="Morphology",
|
|
1529
1529
|
group="Masks & Morphology",
|
|
1530
|
-
import_path="
|
|
1530
|
+
import_path="setiastro.saspro.morphology",
|
|
1531
1531
|
callable_name="apply_morphology_to_doc",
|
|
1532
1532
|
presets=[
|
|
1533
1533
|
PresetSpec(
|
|
@@ -1556,7 +1556,7 @@ register(CommandSpec(
|
|
|
1556
1556
|
id="remove_stars",
|
|
1557
1557
|
title="Remove Stars",
|
|
1558
1558
|
group="Star Tools",
|
|
1559
|
-
import_path="
|
|
1559
|
+
import_path="setiastro.saspro.remove_stars_preset",
|
|
1560
1560
|
callable_name="run_remove_stars_via_preset",
|
|
1561
1561
|
replay_apply_name="apply_remove_stars_to_doc",
|
|
1562
1562
|
presets=[
|
|
@@ -15,6 +15,11 @@ from PyQt6.QtWidgets import (
|
|
|
15
15
|
QLineEdit, QToolButton, QCheckBox, QTextEdit
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from setiastro.saspro.ops.scripts import ScriptEntry
|
|
22
|
+
|
|
18
23
|
# -----------------------------------------------------------------------------
|
|
19
24
|
# Code editor with line numbers (QPlainTextEdit subclass)
|
|
20
25
|
# -----------------------------------------------------------------------------
|
setiastro/saspro/ops/scripts.py
CHANGED
|
@@ -294,6 +294,125 @@ class ScriptContext:
|
|
|
294
294
|
# ✅ Normal run: let DocManager decide (ROI preview vs full)
|
|
295
295
|
dm.update_active_document(img, metadata={}, step_name=step_name)
|
|
296
296
|
|
|
297
|
+
def _find_subwindow_for_doc(self, base_doc):
|
|
298
|
+
"""Return (sw, widget) for the first subwindow showing base_doc."""
|
|
299
|
+
for sw, w in self._iter_open_subwindows():
|
|
300
|
+
d = self._base_doc_for_widget(w)
|
|
301
|
+
if d is base_doc:
|
|
302
|
+
return sw, w
|
|
303
|
+
return None, None
|
|
304
|
+
|
|
305
|
+
def rename_active_view(self, new_title: str) -> bool:
|
|
306
|
+
"""Rename only the active MDI view title (this window)."""
|
|
307
|
+
w = self.active_view()
|
|
308
|
+
if w is None:
|
|
309
|
+
return False
|
|
310
|
+
|
|
311
|
+
t = (new_title or "").strip()
|
|
312
|
+
if not t:
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
# ImageSubWindow convention
|
|
317
|
+
setattr(w, "_view_title_override", t)
|
|
318
|
+
if hasattr(w, "_sync_host_title"):
|
|
319
|
+
w._sync_host_title()
|
|
320
|
+
return True
|
|
321
|
+
except Exception:
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
def rename_active_document(self, new_name: str) -> bool:
|
|
325
|
+
"""Rename the underlying document display name (affects explorer + other views)."""
|
|
326
|
+
doc = self.active_document()
|
|
327
|
+
if doc is None:
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
n = (new_name or "").strip()
|
|
331
|
+
if not n:
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
old = ""
|
|
336
|
+
try:
|
|
337
|
+
old = str(doc.display_name() or "")
|
|
338
|
+
except Exception:
|
|
339
|
+
old = str(getattr(doc, "metadata", {}).get("display_name", "") or "")
|
|
340
|
+
|
|
341
|
+
doc.metadata["display_name"] = n
|
|
342
|
+
try:
|
|
343
|
+
doc.changed.emit()
|
|
344
|
+
except Exception:
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
# If the active view had an override equal to the old doc name, drop it (matches your UI behavior)
|
|
348
|
+
w = self.active_view()
|
|
349
|
+
if w is not None:
|
|
350
|
+
try:
|
|
351
|
+
if getattr(w, "_view_title_override", None) == old:
|
|
352
|
+
setattr(w, "_view_title_override", None)
|
|
353
|
+
except Exception:
|
|
354
|
+
pass
|
|
355
|
+
try:
|
|
356
|
+
if hasattr(w, "_sync_host_title"):
|
|
357
|
+
w._sync_host_title()
|
|
358
|
+
except Exception:
|
|
359
|
+
pass
|
|
360
|
+
|
|
361
|
+
return True
|
|
362
|
+
except Exception:
|
|
363
|
+
return False
|
|
364
|
+
|
|
365
|
+
def rename_view(self, view_name_or_uid: str, new_title: str) -> bool:
|
|
366
|
+
"""Rename a specific *view/window* by name/title/uid (first match)."""
|
|
367
|
+
doc = self.get_document(view_name_or_uid)
|
|
368
|
+
if doc is None:
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
sw, w = self._find_subwindow_for_doc(doc)
|
|
372
|
+
if w is None:
|
|
373
|
+
return False
|
|
374
|
+
|
|
375
|
+
t = (new_title or "").strip()
|
|
376
|
+
if not t:
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
try:
|
|
380
|
+
setattr(w, "_view_title_override", t)
|
|
381
|
+
if hasattr(w, "_sync_host_title"):
|
|
382
|
+
w._sync_host_title()
|
|
383
|
+
return True
|
|
384
|
+
except Exception:
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
def rename_document(self, view_name_or_uid: str, new_name: str) -> bool:
|
|
388
|
+
"""Rename a specific *document* by view name/title/uid."""
|
|
389
|
+
doc = self.get_document(view_name_or_uid)
|
|
390
|
+
if doc is None:
|
|
391
|
+
return False
|
|
392
|
+
n = (new_name or "").strip()
|
|
393
|
+
if not n:
|
|
394
|
+
return False
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
doc.metadata["display_name"] = n
|
|
398
|
+
try:
|
|
399
|
+
doc.changed.emit()
|
|
400
|
+
except Exception:
|
|
401
|
+
pass
|
|
402
|
+
|
|
403
|
+
# resync any window currently showing it
|
|
404
|
+
sw, w = self._find_subwindow_for_doc(doc)
|
|
405
|
+
if w is not None and hasattr(w, "_sync_host_title"):
|
|
406
|
+
try:
|
|
407
|
+
w._sync_host_title()
|
|
408
|
+
except Exception:
|
|
409
|
+
pass
|
|
410
|
+
|
|
411
|
+
return True
|
|
412
|
+
except Exception:
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
|
|
297
416
|
# ---- convenience wrappers into main window ----
|
|
298
417
|
def run_command(self, command_id: str, preset=None, **kwargs):
|
|
299
418
|
return _run_command(self, command_id, preset, **kwargs)
|
setiastro/saspro/remove_green.py
CHANGED
|
@@ -164,7 +164,7 @@ class RemoveGreenDialog(QDialog):
|
|
|
164
164
|
|
|
165
165
|
def _build_ui(self):
|
|
166
166
|
lay = QVBoxLayout(self)
|
|
167
|
-
lay.addWidget(QLabel(self.tr("Select the amount to remove green
|
|
167
|
+
lay.addWidget(QLabel(self.tr("Select the amount to remove green:")))
|
|
168
168
|
|
|
169
169
|
# amount
|
|
170
170
|
self.slider = QSlider(Qt.Orientation.Horizontal)
|
setiastro/saspro/resources.py
CHANGED
|
@@ -244,6 +244,7 @@ class Icons:
|
|
|
244
244
|
STACKING = property(lambda self: _resource_path('stacking.png'))
|
|
245
245
|
LIVE_STACKING = property(lambda self: _resource_path('livestacking.png'))
|
|
246
246
|
IMAGE_COMBINE = property(lambda self: _resource_path('imagecombine.png'))
|
|
247
|
+
PLANETARY_STACKER = property(lambda self: _resource_path('planetarystacker.png'))
|
|
247
248
|
|
|
248
249
|
# Moon phase (WIMS)
|
|
249
250
|
MOON_NEW = property(lambda self: _resource_path('new_moon.png'))
|
|
@@ -301,6 +302,7 @@ class Icons:
|
|
|
301
302
|
COLOR_WHEEL = property(lambda self: _resource_path('colorwheel.png'))
|
|
302
303
|
SELECTIVE_COLOR = property(lambda self: _resource_path('selectivecolor.png'))
|
|
303
304
|
NB_TO_RGB = property(lambda self: _resource_path('nbtorgb.png'))
|
|
305
|
+
NARROWBANDNORMALIZATION = property(lambda self: _resource_path('narrowbandnormalization.png'))
|
|
304
306
|
|
|
305
307
|
# Stretching
|
|
306
308
|
STAT_STRETCH = property(lambda self: _resource_path('statstretch.png'))
|
|
@@ -531,6 +533,7 @@ def _init_legacy_paths():
|
|
|
531
533
|
'collage_path': get_icon_path('collage.png'),
|
|
532
534
|
'annotated_path': get_icon_path('annotated.png'),
|
|
533
535
|
'colorwheel_path': get_icon_path('colorwheel.png'),
|
|
536
|
+
'narrowbandnormalization_path': get_icon_path('narrowbandnormalization.png'),
|
|
534
537
|
'font_path': get_icon_path('font.png'),
|
|
535
538
|
'csv_icon_path': get_icon_path('cvs.png'),
|
|
536
539
|
'spinner_path': get_data_path('spinner.gif'),
|
|
@@ -540,6 +543,7 @@ def _init_legacy_paths():
|
|
|
540
543
|
'debayer_path': get_icon_path('debayer.png'),
|
|
541
544
|
'aberration_path': get_icon_path('aberration.png'),
|
|
542
545
|
'functionbundles_path': get_icon_path('functionbundle.png'),
|
|
546
|
+
'planetarystacker_path': get_icon_path('planetarystacker.png'),
|
|
543
547
|
'viewbundles_path': get_icon_path('viewbundle.png'),
|
|
544
548
|
'selectivecolor_path': get_icon_path('selectivecolor.png'),
|
|
545
549
|
'rgbalign_path': get_icon_path('rgbalign.png'),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# src/setiastro/saspro/ser_stack_config.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional, Tuple, Literal, Union, Sequence
|
|
5
|
+
|
|
6
|
+
from setiastro.saspro.imageops.serloader import PlanetaryFrameSource
|
|
7
|
+
|
|
8
|
+
TrackMode = Literal["off", "planetary", "surface"]
|
|
9
|
+
PlanetarySource = Union[str, Sequence[str], PlanetaryFrameSource]
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SERStackConfig:
|
|
13
|
+
source: PlanetarySource
|
|
14
|
+
roi: Optional[Tuple[int, int, int, int]] = None
|
|
15
|
+
track_mode: TrackMode = "planetary"
|
|
16
|
+
surface_anchor: Optional[Tuple[int, int, int, int]] = None
|
|
17
|
+
keep_percent: float = 20.0
|
|
18
|
+
|
|
19
|
+
# AP / alignment
|
|
20
|
+
ap_size: int = 64
|
|
21
|
+
ap_spacing: int = 48
|
|
22
|
+
ap_min_mean: float = 0.03
|
|
23
|
+
ap_multiscale: bool = False
|
|
24
|
+
ssd_refine_bruteforce: bool = False
|
|
25
|
+
|
|
26
|
+
# ✅ Drizzle
|
|
27
|
+
drizzle_scale: float = 1.0 # 1.0 = off, 1.5, 2.0
|
|
28
|
+
drizzle_pixfrac: float = 0.80 # "drop shrink" in output pixels (roughly)
|
|
29
|
+
drizzle_kernel: str = "gaussian" # "square" | "circle" | "gaussian"
|
|
30
|
+
drizzle_sigma: float = 0.0 # only used for gaussian; 0 => auto from pixfrac
|
|
31
|
+
|
|
32
|
+
def __init__(self, source: PlanetarySource, **kwargs):
|
|
33
|
+
# Allow deprecated/ignored kwargs without crashing
|
|
34
|
+
kwargs.pop("multipoint", None) # accept but ignore
|
|
35
|
+
|
|
36
|
+
self.source = source
|
|
37
|
+
self.roi = kwargs.pop("roi", None)
|
|
38
|
+
self.track_mode = kwargs.pop("track_mode", "planetary")
|
|
39
|
+
self.surface_anchor = kwargs.pop("surface_anchor", None)
|
|
40
|
+
self.keep_percent = float(kwargs.pop("keep_percent", 20.0))
|
|
41
|
+
|
|
42
|
+
self.ap_size = int(kwargs.pop("ap_size", 64))
|
|
43
|
+
self.ap_spacing = int(kwargs.pop("ap_spacing", 48))
|
|
44
|
+
self.ap_min_mean = float(kwargs.pop("ap_min_mean", 0.03))
|
|
45
|
+
self.ap_multiscale = bool(kwargs.pop("ap_multiscale", False))
|
|
46
|
+
self.ssd_refine_bruteforce = bool(kwargs.pop("ssd_refine_bruteforce", False))
|
|
47
|
+
|
|
48
|
+
# ✅ NEW: Drizzle params
|
|
49
|
+
self.drizzle_scale = float(kwargs.pop("drizzle_scale", 1.0))
|
|
50
|
+
if self.drizzle_scale not in (1.0, 1.5, 2.0):
|
|
51
|
+
self.drizzle_scale = 1.0
|
|
52
|
+
|
|
53
|
+
self.drizzle_pixfrac = float(kwargs.pop("drizzle_pixfrac", 0.80))
|
|
54
|
+
self.drizzle_kernel = str(kwargs.pop("drizzle_kernel", "gaussian")).strip().lower()
|
|
55
|
+
self.drizzle_sigma = float(kwargs.pop("drizzle_sigma", 0.0))
|
|
56
|
+
|
|
57
|
+
# sanitize a bit
|
|
58
|
+
if self.drizzle_scale < 1.0:
|
|
59
|
+
self.drizzle_scale = 1.0
|
|
60
|
+
if self.drizzle_pixfrac <= 0.0:
|
|
61
|
+
self.drizzle_pixfrac = 0.01
|
|
62
|
+
if self.drizzle_kernel not in ("square", "circle", "gaussian"):
|
|
63
|
+
self.drizzle_kernel = "gaussian"
|
|
64
|
+
if self.drizzle_sigma < 0.0:
|
|
65
|
+
self.drizzle_sigma = 0.0
|
|
66
|
+
|
|
67
|
+
if kwargs:
|
|
68
|
+
raise TypeError(f"Unexpected config keys: {sorted(kwargs.keys())}")
|