setiastrosuitepro 1.6.1.post1__py3-none-any.whl → 1.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of setiastrosuitepro might be problematic. Click here for more details.
- setiastro/images/Background_startup.jpg +0 -0
- setiastro/images/rotatearbitrary.png +0 -0
- setiastro/qml/ResourceMonitor.qml +126 -0
- setiastro/saspro/__main__.py +162 -25
- setiastro/saspro/_generated/build_info.py +2 -1
- setiastro/saspro/abe.py +62 -11
- setiastro/saspro/aberration_ai.py +3 -3
- setiastro/saspro/add_stars.py +5 -2
- setiastro/saspro/astrobin_exporter.py +3 -0
- setiastro/saspro/astrospike_python.py +3 -1
- setiastro/saspro/autostretch.py +4 -2
- setiastro/saspro/backgroundneutral.py +60 -9
- setiastro/saspro/batch_convert.py +3 -0
- setiastro/saspro/batch_renamer.py +3 -0
- setiastro/saspro/blemish_blaster.py +3 -0
- setiastro/saspro/blink_comparator_pro.py +474 -251
- setiastro/saspro/cheat_sheet.py +50 -15
- setiastro/saspro/clahe.py +27 -1
- setiastro/saspro/comet_stacking.py +103 -38
- setiastro/saspro/convo.py +3 -0
- setiastro/saspro/copyastro.py +3 -0
- setiastro/saspro/cosmicclarity.py +70 -45
- setiastro/saspro/crop_dialog_pro.py +28 -1
- setiastro/saspro/curve_editor_pro.py +18 -0
- setiastro/saspro/debayer.py +3 -0
- setiastro/saspro/doc_manager.py +40 -17
- setiastro/saspro/fitsmodifier.py +3 -0
- setiastro/saspro/frequency_separation.py +8 -2
- setiastro/saspro/function_bundle.py +18 -16
- setiastro/saspro/generate_translations.py +715 -1
- setiastro/saspro/ghs_dialog_pro.py +3 -0
- setiastro/saspro/graxpert.py +3 -0
- setiastro/saspro/gui/main_window.py +364 -92
- setiastro/saspro/gui/mixins/dock_mixin.py +119 -7
- setiastro/saspro/gui/mixins/file_mixin.py +7 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +105 -5
- setiastro/saspro/gui/mixins/menu_mixin.py +29 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +33 -10
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +29 -3
- setiastro/saspro/histogram.py +3 -0
- setiastro/saspro/history_explorer.py +2 -0
- setiastro/saspro/i18n.py +22 -10
- setiastro/saspro/image_combine.py +3 -0
- setiastro/saspro/image_peeker_pro.py +3 -0
- setiastro/saspro/imageops/stretch.py +5 -13
- setiastro/saspro/isophote.py +3 -0
- setiastro/saspro/legacy/numba_utils.py +64 -47
- setiastro/saspro/linear_fit.py +3 -0
- setiastro/saspro/live_stacking.py +13 -2
- setiastro/saspro/mask_creation.py +3 -0
- setiastro/saspro/mfdeconv.py +5 -0
- setiastro/saspro/morphology.py +30 -5
- setiastro/saspro/multiscale_decomp.py +713 -256
- setiastro/saspro/nbtorgb_stars.py +12 -2
- setiastro/saspro/numba_utils.py +148 -47
- setiastro/saspro/ops/scripts.py +77 -17
- setiastro/saspro/ops/settings.py +1 -43
- setiastro/saspro/perfect_palette_picker.py +1 -0
- setiastro/saspro/pixelmath.py +6 -2
- setiastro/saspro/plate_solver.py +1 -0
- setiastro/saspro/remove_green.py +18 -1
- setiastro/saspro/remove_stars.py +136 -162
- setiastro/saspro/remove_stars_preset.py +55 -13
- setiastro/saspro/resources.py +36 -10
- setiastro/saspro/rgb_combination.py +1 -0
- setiastro/saspro/rgbalign.py +4 -4
- setiastro/saspro/save_options.py +1 -0
- setiastro/saspro/selective_color.py +79 -20
- setiastro/saspro/sfcc.py +50 -8
- setiastro/saspro/shortcuts.py +94 -21
- setiastro/saspro/signature_insert.py +3 -0
- setiastro/saspro/stacking_suite.py +924 -446
- setiastro/saspro/star_alignment.py +291 -331
- setiastro/saspro/star_spikes.py +116 -32
- setiastro/saspro/star_stretch.py +38 -1
- setiastro/saspro/stat_stretch.py +35 -3
- setiastro/saspro/status_log_dock.py +1 -1
- setiastro/saspro/subwindow.py +63 -2
- setiastro/saspro/supernovaasteroidhunter.py +3 -0
- setiastro/saspro/swap_manager.py +77 -42
- setiastro/saspro/translations/all_source_strings.json +4726 -0
- setiastro/saspro/translations/ar_translations.py +4096 -0
- setiastro/saspro/translations/de_translations.py +441 -446
- setiastro/saspro/translations/es_translations.py +278 -32
- setiastro/saspro/translations/fr_translations.py +280 -32
- setiastro/saspro/translations/hi_translations.py +3803 -0
- setiastro/saspro/translations/integrate_translations.py +38 -1
- setiastro/saspro/translations/it_translations.py +1211 -145
- setiastro/saspro/translations/ja_translations.py +556 -307
- setiastro/saspro/translations/pt_translations.py +3316 -3322
- setiastro/saspro/translations/ru_translations.py +3082 -0
- setiastro/saspro/translations/saspro_ar.qm +0 -0
- setiastro/saspro/translations/saspro_ar.ts +16019 -0
- setiastro/saspro/translations/saspro_de.qm +0 -0
- setiastro/saspro/translations/saspro_de.ts +14428 -133
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +11503 -7821
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +11168 -7812
- setiastro/saspro/translations/saspro_hi.qm +0 -0
- setiastro/saspro/translations/saspro_hi.ts +14855 -0
- setiastro/saspro/translations/saspro_it.qm +0 -0
- setiastro/saspro/translations/saspro_it.ts +14347 -7821
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +14860 -137
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +14904 -137
- setiastro/saspro/translations/saspro_ru.qm +0 -0
- setiastro/saspro/translations/saspro_ru.ts +11835 -0
- setiastro/saspro/translations/saspro_sw.qm +0 -0
- setiastro/saspro/translations/saspro_sw.ts +15237 -0
- setiastro/saspro/translations/saspro_uk.qm +0 -0
- setiastro/saspro/translations/saspro_uk.ts +15248 -0
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +10581 -7812
- setiastro/saspro/translations/sw_translations.py +3897 -0
- setiastro/saspro/translations/uk_translations.py +3929 -0
- setiastro/saspro/translations/zh_translations.py +283 -32
- setiastro/saspro/versioning.py +36 -5
- setiastro/saspro/view_bundle.py +20 -17
- setiastro/saspro/wavescale_hdr.py +22 -1
- setiastro/saspro/wavescalede.py +23 -1
- setiastro/saspro/whitebalance.py +39 -3
- setiastro/saspro/widgets/minigame/game.js +991 -0
- setiastro/saspro/widgets/minigame/index.html +53 -0
- setiastro/saspro/widgets/minigame/style.css +241 -0
- setiastro/saspro/widgets/resource_monitor.py +263 -0
- setiastro/saspro/widgets/spinboxes.py +18 -0
- setiastro/saspro/widgets/wavelet_utils.py +52 -20
- setiastro/saspro/wimi.py +100 -80
- setiastro/saspro/wims.py +33 -33
- setiastro/saspro/window_shelf.py +2 -2
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/METADATA +15 -4
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/RECORD +139 -115
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/licenses/license.txt +0 -0
|
@@ -91,6 +91,9 @@ class ImageCombineDialog(QDialog):
|
|
|
91
91
|
def __init__(self, main_window):
|
|
92
92
|
super().__init__(main_window)
|
|
93
93
|
self.setWindowTitle("Image Combine")
|
|
94
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
95
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
96
|
+
self.setModal(False)
|
|
94
97
|
self.mw = main_window
|
|
95
98
|
self.dm = getattr(main_window, "doc_manager", None) or getattr(main_window, "dm", None)
|
|
96
99
|
self.zoom = 1.0
|
|
@@ -1314,6 +1314,9 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1314
1314
|
def __init__(self, parent, document, settings):
|
|
1315
1315
|
super().__init__(parent)
|
|
1316
1316
|
self.setWindowTitle(self.tr("Image Peeker"))
|
|
1317
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
1318
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
1319
|
+
self.setModal(False)
|
|
1317
1320
|
self.document = self._coerce_doc(document) # <- ensure we hold a real doc
|
|
1318
1321
|
self.settings = settings
|
|
1319
1322
|
# status / progress line
|
|
@@ -95,19 +95,11 @@ def apply_curves_adjustment(image: np.ndarray,
|
|
|
95
95
|
xvals, yvals = _calculate_curve_points(target_median, curves_boost)
|
|
96
96
|
|
|
97
97
|
# Apply the 1D LUT per channel using np.interp (piecewise linear)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
out = np.empty_like(img, dtype=np.float32)
|
|
104
|
-
# Apply same curve to each color channel
|
|
105
|
-
for ch in range(c):
|
|
106
|
-
flat = img[..., ch].ravel()
|
|
107
|
-
out[..., ch] = np.interp(flat, xvals, yvals).reshape(h, w)
|
|
108
|
-
else:
|
|
109
|
-
# Fallback: just return clamped image
|
|
110
|
-
out = img
|
|
98
|
+
# Apply the 1D LUT per channel using np.interp (piecewise linear)
|
|
99
|
+
# Optimization: np.interp can handle N-D 'x' array directly.
|
|
100
|
+
# No need to loop over channels or flatten/reshape if we pass the whole array.
|
|
101
|
+
|
|
102
|
+
out = np.interp(img, xvals, yvals).astype(np.float32, copy=False)
|
|
111
103
|
|
|
112
104
|
return np.clip(out, 0.0, 1.0)
|
|
113
105
|
|
setiastro/saspro/isophote.py
CHANGED
|
@@ -360,6 +360,9 @@ class IsophoteModelerDialog(QDialog):
|
|
|
360
360
|
def __init__(self, mono_image: np.ndarray, parent: Optional[QWidget] = None,
|
|
361
361
|
title_hint: Optional[str] = None, image_manager=None, doc_manager=None):
|
|
362
362
|
super().__init__(parent)
|
|
363
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
364
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
365
|
+
self.setModal(False)
|
|
363
366
|
self.image_manager = image_manager
|
|
364
367
|
self.doc_manager = doc_manager
|
|
365
368
|
|
|
@@ -802,39 +802,46 @@ def kappa_sigma_clip_weighted_3d(stack, weights, kappa=2.5, iterations=3):
|
|
|
802
802
|
pixel_weights = weights[:]
|
|
803
803
|
else:
|
|
804
804
|
pixel_weights = weights[:, i, j].copy()
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
current_idx[f] = f
|
|
809
|
-
current_vals = pixel_values
|
|
810
|
-
current_w = pixel_weights
|
|
811
|
-
current_indices = current_idx
|
|
805
|
+
|
|
806
|
+
valid_mask = pixel_values != 0
|
|
807
|
+
|
|
812
808
|
med = 0.0
|
|
813
809
|
for _ in range(iterations):
|
|
814
|
-
|
|
810
|
+
count = 0
|
|
811
|
+
for k in range(num_frames):
|
|
812
|
+
if valid_mask[k]:
|
|
813
|
+
count += 1
|
|
814
|
+
|
|
815
|
+
if count == 0:
|
|
815
816
|
break
|
|
817
|
+
|
|
818
|
+
current_vals = pixel_values[valid_mask]
|
|
819
|
+
|
|
816
820
|
med = np.median(current_vals)
|
|
817
821
|
std = np.std(current_vals)
|
|
818
822
|
lower_bound = med - kappa * std
|
|
819
823
|
upper_bound = med + kappa * std
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
824
|
+
|
|
825
|
+
for k in range(num_frames):
|
|
826
|
+
if valid_mask[k]:
|
|
827
|
+
val = pixel_values[k]
|
|
828
|
+
if val < lower_bound or val > upper_bound:
|
|
829
|
+
valid_mask[k] = False
|
|
830
|
+
|
|
825
831
|
for f in range(num_frames):
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
832
|
+
rej_mask[f, i, j] = not valid_mask[f]
|
|
833
|
+
|
|
834
|
+
wsum = 0.0
|
|
835
|
+
vsum = 0.0
|
|
836
|
+
for k in range(num_frames):
|
|
837
|
+
if valid_mask[k]:
|
|
838
|
+
w = pixel_weights[k]
|
|
839
|
+
v = pixel_values[k]
|
|
840
|
+
wsum += w
|
|
841
|
+
vsum += v * w
|
|
842
|
+
|
|
843
|
+
if wsum > 0:
|
|
844
|
+
clipped[i, j] = vsum / wsum
|
|
838
845
|
else:
|
|
839
846
|
clipped[i, j] = med
|
|
840
847
|
return clipped, rej_mask
|
|
@@ -859,36 +866,46 @@ def kappa_sigma_clip_weighted_4d(stack, weights, kappa=2.5, iterations=3):
|
|
|
859
866
|
pixel_weights = weights[:]
|
|
860
867
|
else:
|
|
861
868
|
pixel_weights = weights[:, i, j, c].copy()
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
current_vals = pixel_values
|
|
866
|
-
current_w = pixel_weights
|
|
867
|
-
current_indices = current_idx
|
|
869
|
+
|
|
870
|
+
valid_mask = pixel_values != 0
|
|
871
|
+
|
|
868
872
|
med = 0.0
|
|
869
873
|
for _ in range(iterations):
|
|
870
|
-
|
|
874
|
+
count = 0
|
|
875
|
+
for k in range(num_frames):
|
|
876
|
+
if valid_mask[k]:
|
|
877
|
+
count += 1
|
|
878
|
+
|
|
879
|
+
if count == 0:
|
|
871
880
|
break
|
|
881
|
+
|
|
882
|
+
current_vals = pixel_values[valid_mask]
|
|
883
|
+
|
|
872
884
|
med = np.median(current_vals)
|
|
873
885
|
std = np.std(current_vals)
|
|
874
886
|
lower_bound = med - kappa * std
|
|
875
887
|
upper_bound = med + kappa * std
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
888
|
+
|
|
889
|
+
for k in range(num_frames):
|
|
890
|
+
if valid_mask[k]:
|
|
891
|
+
val = pixel_values[k]
|
|
892
|
+
if val < lower_bound or val > upper_bound:
|
|
893
|
+
valid_mask[k] = False
|
|
894
|
+
|
|
880
895
|
for f in range(num_frames):
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
896
|
+
rej_mask[f, i, j, c] = not valid_mask[f]
|
|
897
|
+
|
|
898
|
+
wsum = 0.0
|
|
899
|
+
vsum = 0.0
|
|
900
|
+
for k in range(num_frames):
|
|
901
|
+
if valid_mask[k]:
|
|
902
|
+
w = pixel_weights[k]
|
|
903
|
+
v = pixel_values[k]
|
|
904
|
+
wsum += w
|
|
905
|
+
vsum += v * w
|
|
906
|
+
|
|
907
|
+
if wsum > 0:
|
|
908
|
+
clipped[i, j, c] = vsum / wsum
|
|
892
909
|
else:
|
|
893
910
|
clipped[i, j, c] = med
|
|
894
911
|
return clipped, rej_mask
|
setiastro/saspro/linear_fit.py
CHANGED
|
@@ -224,6 +224,9 @@ class LinearFitDialog(QDialog):
|
|
|
224
224
|
def __init__(self, parent, doc_manager, active_doc):
|
|
225
225
|
super().__init__(parent)
|
|
226
226
|
self.setWindowTitle("Linear Fit")
|
|
227
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
228
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
229
|
+
self.setModal(False)
|
|
227
230
|
self.dm = doc_manager
|
|
228
231
|
self.doc = active_doc
|
|
229
232
|
self.worker: Optional[_LinearFitWorker] = None
|
|
@@ -48,6 +48,9 @@ class LiveStackSettingsDialog(QDialog):
|
|
|
48
48
|
def __init__(self, parent):
|
|
49
49
|
super().__init__(parent)
|
|
50
50
|
self.setWindowTitle("Live Stack & Culling Settings")
|
|
51
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
52
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
53
|
+
self.setModal(False)
|
|
51
54
|
|
|
52
55
|
# — Live Stack Settings —
|
|
53
56
|
# Bootstrap frames (int)
|
|
@@ -290,8 +293,16 @@ def estimate_global_snr(
|
|
|
290
293
|
|
|
291
294
|
# 1) Collapse to simple 2D float array (grayscale)
|
|
292
295
|
if stack_image.ndim == 3 and stack_image.shape[2] == 3:
|
|
293
|
-
|
|
294
|
-
|
|
296
|
+
try:
|
|
297
|
+
import cv2
|
|
298
|
+
# cv2.cvtColor is significantly faster than mean(axis=2)
|
|
299
|
+
# Assuming RGB input, but even if BGR, for SNR estimation luma difference is negligible
|
|
300
|
+
gray = cv2.cvtColor(stack_image, cv2.COLOR_RGB2GRAY)
|
|
301
|
+
if gray.dtype != np.float32:
|
|
302
|
+
gray = gray.astype(np.float32)
|
|
303
|
+
except ImportError:
|
|
304
|
+
# Fallback
|
|
305
|
+
gray = stack_image.mean(axis=2).astype(np.float32)
|
|
295
306
|
else:
|
|
296
307
|
# Already mono: just cast to float32
|
|
297
308
|
gray = stack_image.astype(np.float32)
|
|
@@ -555,6 +555,9 @@ class MaskCreationDialog(QDialog):
|
|
|
555
555
|
def __init__(self, image01: np.ndarray, parent=None, auto_push_on_ok: bool = True):
|
|
556
556
|
super().__init__(parent)
|
|
557
557
|
self.setWindowTitle(self.tr("Mask Creation"))
|
|
558
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
559
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
560
|
+
self.setModal(False)
|
|
558
561
|
self.image = np.asarray(image01, dtype=np.float32).copy()
|
|
559
562
|
self.mask: np.ndarray | None = None
|
|
560
563
|
self.live_preview = LivePreviewDialog(self.image, parent=self)
|
setiastro/saspro/mfdeconv.py
CHANGED
|
@@ -822,6 +822,11 @@ def _to_luma_local(a: np.ndarray) -> np.ndarray:
|
|
|
822
822
|
return a
|
|
823
823
|
# (H,W,3) or (3,H,W)
|
|
824
824
|
if a.ndim == 3 and a.shape[-1] == 3:
|
|
825
|
+
try:
|
|
826
|
+
import cv2
|
|
827
|
+
return cv2.cvtColor(a, cv2.COLOR_RGB2GRAY).astype(np.float32, copy=False)
|
|
828
|
+
except Exception:
|
|
829
|
+
pass
|
|
825
830
|
r, g, b = a[..., 0], a[..., 1], a[..., 2]
|
|
826
831
|
return (0.2126*r + 0.7152*g + 0.0722*b).astype(np.float32, copy=False)
|
|
827
832
|
if a.ndim == 3 and a.shape[0] == 3:
|
setiastro/saspro/morphology.py
CHANGED
|
@@ -46,10 +46,9 @@ def apply_morphology(image: np.ndarray, *, operation: str = "erosion",
|
|
|
46
46
|
|
|
47
47
|
if img.ndim == 3 and img.shape[2] == 3:
|
|
48
48
|
u8 = (img * 255.0).astype(np.uint8)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return np.clip(out, 0.0, 1.0)
|
|
49
|
+
# OpenCV morphology functions handle multi-channel images natively (independent channels)
|
|
50
|
+
out_u8 = _do(u8)
|
|
51
|
+
return (out_u8.astype(np.float32) / 255.0).clip(0.0, 1.0)
|
|
53
52
|
|
|
54
53
|
raise ValueError("Input image must be mono (H,W)/(H,W,1) or RGB (H,W,3).")
|
|
55
54
|
|
|
@@ -92,6 +91,9 @@ class MorphologyDialogPro(QDialog):
|
|
|
92
91
|
def __init__(self, parent, doc, icon: QIcon | None = None, initial: dict | None = None):
|
|
93
92
|
super().__init__(parent)
|
|
94
93
|
self.setWindowTitle(self.tr("Morphological Operations"))
|
|
94
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
95
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
96
|
+
self.setModal(False)
|
|
95
97
|
if icon:
|
|
96
98
|
try: self.setWindowIcon(icon)
|
|
97
99
|
except Exception as e:
|
|
@@ -258,10 +260,33 @@ class MorphologyDialogPro(QDialog):
|
|
|
258
260
|
pass
|
|
259
261
|
# ────────────────────────────────────────────────────────────
|
|
260
262
|
|
|
261
|
-
|
|
263
|
+
# Dialog stays open so user can apply to other images
|
|
264
|
+
# Refresh document reference for next operation
|
|
265
|
+
self._refresh_document_from_active()
|
|
262
266
|
except Exception as e:
|
|
263
267
|
QMessageBox.critical(self, "Morphology", f"Failed to apply:\n{e}")
|
|
264
268
|
|
|
269
|
+
def _refresh_document_from_active(self):
|
|
270
|
+
"""
|
|
271
|
+
Refresh the dialog's document reference to the currently active document.
|
|
272
|
+
This allows reusing the same dialog on different images.
|
|
273
|
+
"""
|
|
274
|
+
try:
|
|
275
|
+
main = self.parent()
|
|
276
|
+
if main and hasattr(main, "_active_doc"):
|
|
277
|
+
new_doc = main._active_doc()
|
|
278
|
+
if new_doc is not None and new_doc is not self.doc:
|
|
279
|
+
self.doc = new_doc
|
|
280
|
+
# Refresh preview for new document
|
|
281
|
+
self.orig = np.clip(np.asarray(new_doc.image, dtype=np.float32), 0.0, 1.0)
|
|
282
|
+
disp = self.orig
|
|
283
|
+
if disp.ndim == 2: disp = disp[..., None].repeat(3, axis=2)
|
|
284
|
+
elif disp.ndim == 3 and disp.shape[2] == 1: disp = disp.repeat(3, axis=2)
|
|
285
|
+
self._disp_base = disp
|
|
286
|
+
self._update_preview()
|
|
287
|
+
except Exception:
|
|
288
|
+
pass
|
|
289
|
+
|
|
265
290
|
|
|
266
291
|
|
|
267
292
|
def _reset(self):
|