setiastrosuitepro 1.6.12__py3-none-any.whl → 1.7.3__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/3dplanet.png +0 -0
- setiastro/images/TextureClarity.svg +56 -0
- setiastro/images/narrowbandnormalization.png +0 -0
- setiastro/images/planetarystacker.png +0 -0
- setiastro/saspro/__init__.py +9 -8
- setiastro/saspro/__main__.py +326 -285
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/aberration_ai.py +128 -13
- setiastro/saspro/aberration_ai_preset.py +29 -3
- setiastro/saspro/astrospike_python.py +45 -3
- setiastro/saspro/blink_comparator_pro.py +116 -71
- setiastro/saspro/curve_editor_pro.py +72 -22
- setiastro/saspro/curves_preset.py +249 -47
- setiastro/saspro/doc_manager.py +4 -1
- setiastro/saspro/gui/main_window.py +326 -46
- setiastro/saspro/gui/mixins/file_mixin.py +41 -18
- setiastro/saspro/gui/mixins/menu_mixin.py +9 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +123 -7
- setiastro/saspro/histogram.py +179 -7
- setiastro/saspro/imageops/narrowband_normalization.py +816 -0
- setiastro/saspro/imageops/serloader.py +1429 -0
- setiastro/saspro/layers.py +186 -10
- setiastro/saspro/layers_dock.py +198 -5
- setiastro/saspro/legacy/image_manager.py +10 -4
- setiastro/saspro/legacy/numba_utils.py +1 -1
- setiastro/saspro/live_stacking.py +24 -4
- setiastro/saspro/multiscale_decomp.py +30 -17
- setiastro/saspro/narrowband_normalization.py +1618 -0
- setiastro/saspro/planetprojection.py +3854 -0
- setiastro/saspro/remove_green.py +1 -1
- setiastro/saspro/resources.py +8 -0
- setiastro/saspro/rgbalign.py +456 -12
- setiastro/saspro/save_options.py +45 -13
- setiastro/saspro/ser_stack_config.py +102 -0
- setiastro/saspro/ser_stacker.py +2327 -0
- setiastro/saspro/ser_stacker_dialog.py +1865 -0
- setiastro/saspro/ser_tracking.py +228 -0
- setiastro/saspro/serviewer.py +1773 -0
- setiastro/saspro/sfcc.py +298 -64
- setiastro/saspro/shortcuts.py +14 -7
- setiastro/saspro/stacking_suite.py +21 -6
- setiastro/saspro/stat_stretch.py +179 -31
- setiastro/saspro/subwindow.py +38 -5
- setiastro/saspro/texture_clarity.py +593 -0
- setiastro/saspro/widgets/resource_monitor.py +122 -74
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/METADATA +3 -2
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/RECORD +51 -37
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/stat_stretch.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from PyQt6.QtCore import Qt, QSize, QEvent
|
|
4
4
|
from PyQt6.QtWidgets import (
|
|
5
5
|
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QLabel, QDoubleSpinBox,
|
|
6
|
-
QCheckBox, QPushButton, QScrollArea, QWidget, QMessageBox, QSlider, QToolBar, QToolButton, QComboBox
|
|
6
|
+
QCheckBox, QPushButton, QScrollArea, QWidget, QMessageBox, QSlider, QToolBar, QToolButton, QComboBox,QProgressBar, QApplication
|
|
7
7
|
)
|
|
8
8
|
from PyQt6.QtGui import QImage, QPixmap, QMouseEvent, QCursor
|
|
9
9
|
import numpy as np
|
|
@@ -320,6 +320,7 @@ class StatisticalStretchDialog(QDialog):
|
|
|
320
320
|
self.btn_preview = QPushButton(self.tr("Preview"))
|
|
321
321
|
self.btn_apply = QPushButton(self.tr("Apply"))
|
|
322
322
|
self.btn_close = QPushButton(self.tr("Close"))
|
|
323
|
+
self.btn_reset = QPushButton(self.tr("Reset ⟳"))
|
|
323
324
|
|
|
324
325
|
self.btn_clipstats = QPushButton(self.tr("Clip stats"))
|
|
325
326
|
self.lbl_clipstats = QLabel("")
|
|
@@ -330,6 +331,24 @@ class StatisticalStretchDialog(QDialog):
|
|
|
330
331
|
self.lbl_clipstats.setFrameShadow(QLabel.Shadow.Sunken)
|
|
331
332
|
self.lbl_clipstats.setContentsMargins(6, 4, 6, 4)
|
|
332
333
|
|
|
334
|
+
# --- In-UI busy indicator (Wayland-friendly) ---
|
|
335
|
+
self.busy_row = QWidget()
|
|
336
|
+
br = QHBoxLayout(self.busy_row)
|
|
337
|
+
br.setContentsMargins(0, 0, 0, 0)
|
|
338
|
+
br.setSpacing(8)
|
|
339
|
+
|
|
340
|
+
self.lbl_busy = QLabel(self.tr("Processing…"))
|
|
341
|
+
self.lbl_busy.setStyleSheet("color:#888;")
|
|
342
|
+
self.pbar_busy = QProgressBar()
|
|
343
|
+
self.pbar_busy.setRange(0, 0) # indeterminate
|
|
344
|
+
self.pbar_busy.setTextVisible(False)
|
|
345
|
+
self.pbar_busy.setFixedHeight(10)
|
|
346
|
+
|
|
347
|
+
br.addWidget(self.lbl_busy)
|
|
348
|
+
br.addWidget(self.pbar_busy, 1)
|
|
349
|
+
|
|
350
|
+
self.busy_row.setVisible(False) # hidden until needed
|
|
351
|
+
|
|
333
352
|
# ------------------------------------------------------------------
|
|
334
353
|
# Layout
|
|
335
354
|
# ------------------------------------------------------------------
|
|
@@ -353,10 +372,13 @@ class StatisticalStretchDialog(QDialog):
|
|
|
353
372
|
btn_row.addWidget(self.btn_preview)
|
|
354
373
|
btn_row.addWidget(self.btn_apply)
|
|
355
374
|
btn_row.addWidget(self.btn_clipstats)
|
|
375
|
+
btn_row.addStretch(1)
|
|
376
|
+
btn_row.addWidget(self.btn_reset)
|
|
356
377
|
btn_row.addStretch(1)
|
|
357
378
|
left.addLayout(btn_row)
|
|
358
379
|
|
|
359
380
|
left.addWidget(self.lbl_clipstats)
|
|
381
|
+
left.addWidget(self.busy_row)
|
|
360
382
|
left.addStretch(1)
|
|
361
383
|
|
|
362
384
|
right = QVBoxLayout()
|
|
@@ -409,14 +431,6 @@ class StatisticalStretchDialog(QDialog):
|
|
|
409
431
|
|
|
410
432
|
self.spin_target.valueChanged.connect(_suggest_hdr_knee_from_target)
|
|
411
433
|
|
|
412
|
-
# Luma-only: enables dropdown, disables "linked channels"
|
|
413
|
-
self.chk_luma_only.toggled.connect(self.cmb_luma.setEnabled)
|
|
414
|
-
|
|
415
|
-
def _on_luma_only_toggled(on: bool):
|
|
416
|
-
self.chk_linked.setEnabled(not on)
|
|
417
|
-
|
|
418
|
-
self.chk_luma_only.toggled.connect(_on_luma_only_toggled)
|
|
419
|
-
|
|
420
434
|
# Zoom buttons
|
|
421
435
|
self.btn_zoom_in.clicked.connect(lambda: self._zoom_by(1.25))
|
|
422
436
|
self.btn_zoom_out.clicked.connect(lambda: self._zoom_by(1/1.25))
|
|
@@ -426,6 +440,7 @@ class StatisticalStretchDialog(QDialog):
|
|
|
426
440
|
# Main buttons
|
|
427
441
|
self.btn_preview.clicked.connect(self._do_preview)
|
|
428
442
|
self.btn_apply.clicked.connect(self._do_apply)
|
|
443
|
+
self.btn_reset.clicked.connect(self._reset_defaults)
|
|
429
444
|
self.btn_close.clicked.connect(self.close)
|
|
430
445
|
self.btn_clipstats.clicked.connect(self._do_clip_stats)
|
|
431
446
|
|
|
@@ -441,55 +456,187 @@ class StatisticalStretchDialog(QDialog):
|
|
|
441
456
|
lambda v: self.lbl_luma_blend.setText(f"{v/100:.2f}")
|
|
442
457
|
)
|
|
443
458
|
|
|
459
|
+
# Luma-only: one unified handler for all dependent UI state
|
|
444
460
|
def _on_luma_only_toggled(on: bool):
|
|
461
|
+
# enable luma mode dropdown only when luma-only is on
|
|
462
|
+
self.cmb_luma.setEnabled(on)
|
|
463
|
+
|
|
464
|
+
# linked channels doesn't make sense in luma-only mode
|
|
445
465
|
self.chk_linked.setEnabled(not on)
|
|
466
|
+
|
|
467
|
+
# luma blend row only meaningful when luma-only is enabled
|
|
446
468
|
self.luma_blend_row.setEnabled(on)
|
|
447
469
|
|
|
470
|
+
# mode-affecting => refresh clip stats
|
|
471
|
+
self._schedule_clip_stats()
|
|
472
|
+
|
|
448
473
|
self.chk_luma_only.toggled.connect(_on_luma_only_toggled)
|
|
449
474
|
_on_luma_only_toggled(self.chk_luma_only.isChecked())
|
|
450
475
|
|
|
476
|
+
|
|
451
477
|
# Initial preview + clip stats
|
|
452
478
|
self._populate_initial_preview()
|
|
453
479
|
|
|
454
480
|
|
|
455
481
|
# ----- helpers -----
|
|
456
482
|
def _show_busy(self, title: str, text: str):
|
|
457
|
-
#
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
dlg.setCancelButton(None) # no cancel button (keeps it simple)
|
|
466
|
-
dlg.setAutoClose(False)
|
|
467
|
-
dlg.setAutoReset(False)
|
|
468
|
-
dlg.setFixedWidth(320)
|
|
469
|
-
dlg.show()
|
|
470
|
-
|
|
471
|
-
# Ensure it paints before heavy work starts
|
|
472
|
-
QApplication.processEvents()
|
|
473
|
-
self._busy = dlg
|
|
483
|
+
# title kept for signature compatibility; not shown
|
|
484
|
+
try:
|
|
485
|
+
self.lbl_busy.setText(text or self.tr("Processing…"))
|
|
486
|
+
self.busy_row.setVisible(True)
|
|
487
|
+
# make sure UI repaints before thread work starts
|
|
488
|
+
QApplication.processEvents()
|
|
489
|
+
except Exception:
|
|
490
|
+
pass
|
|
474
491
|
|
|
475
492
|
def _hide_busy(self):
|
|
476
493
|
try:
|
|
477
|
-
if getattr(self, "
|
|
478
|
-
self.
|
|
479
|
-
self._busy.deleteLater()
|
|
494
|
+
if getattr(self, "busy_row", None) is not None:
|
|
495
|
+
self.busy_row.setVisible(False)
|
|
480
496
|
except Exception:
|
|
481
497
|
pass
|
|
482
|
-
|
|
498
|
+
|
|
483
499
|
|
|
484
500
|
def _set_controls_enabled(self, enabled: bool):
|
|
485
501
|
try:
|
|
486
502
|
self.btn_preview.setEnabled(enabled)
|
|
487
503
|
self.btn_apply.setEnabled(enabled)
|
|
504
|
+
if getattr(self, "btn_reset", None) is not None:
|
|
505
|
+
self.btn_reset.setEnabled(enabled) # <-- NEW
|
|
488
506
|
if getattr(self, "btn_clipstats", None) is not None:
|
|
489
507
|
self.btn_clipstats.setEnabled(enabled)
|
|
490
508
|
except Exception:
|
|
491
509
|
pass
|
|
492
510
|
|
|
511
|
+
def _reset_defaults(self):
|
|
512
|
+
"""Reset all controls back to factory defaults."""
|
|
513
|
+
if getattr(self, "_job_running", False):
|
|
514
|
+
return
|
|
515
|
+
|
|
516
|
+
# Defaults (must match your __init__ setValue/setChecked calls)
|
|
517
|
+
DEFAULT_TARGET = 0.25
|
|
518
|
+
DEFAULT_LINKED = False
|
|
519
|
+
DEFAULT_NORMALIZE = False
|
|
520
|
+
DEFAULT_BP_SLIDER = 500 # 5.00
|
|
521
|
+
DEFAULT_NO_BLACK_CLIP = False
|
|
522
|
+
|
|
523
|
+
DEFAULT_HDR_ON = False
|
|
524
|
+
DEFAULT_HDR_AMT = 15 # 0.15
|
|
525
|
+
DEFAULT_HDR_KNEE = 75 # 0.75
|
|
526
|
+
|
|
527
|
+
DEFAULT_LUMA_ONLY = False
|
|
528
|
+
DEFAULT_LUMA_MODE = "rec709"
|
|
529
|
+
DEFAULT_LUMA_BLEND = 60 # 0.60
|
|
530
|
+
|
|
531
|
+
DEFAULT_CURVES_ON = False
|
|
532
|
+
DEFAULT_CURVES_STRENGTH = 20 # 0.20
|
|
533
|
+
|
|
534
|
+
# Avoid cascading signal storms while we set everything
|
|
535
|
+
widgets = [
|
|
536
|
+
self.spin_target,
|
|
537
|
+
self.chk_linked,
|
|
538
|
+
self.chk_normalize,
|
|
539
|
+
self.sld_bp,
|
|
540
|
+
self.chk_no_black_clip,
|
|
541
|
+
self.chk_hdr,
|
|
542
|
+
self.sld_hdr_amt,
|
|
543
|
+
self.sld_hdr_knee,
|
|
544
|
+
self.chk_luma_only,
|
|
545
|
+
self.cmb_luma,
|
|
546
|
+
self.sld_luma_blend,
|
|
547
|
+
self.chk_curves,
|
|
548
|
+
self.sld_curves,
|
|
549
|
+
]
|
|
550
|
+
|
|
551
|
+
old_blocks = []
|
|
552
|
+
for w in widgets:
|
|
553
|
+
try:
|
|
554
|
+
old_blocks.append((w, w.blockSignals(True)))
|
|
555
|
+
except Exception:
|
|
556
|
+
pass
|
|
557
|
+
|
|
558
|
+
try:
|
|
559
|
+
# Reset “user locked” HDR knee behavior
|
|
560
|
+
self._hdr_knee_user_locked = False
|
|
561
|
+
|
|
562
|
+
# Core controls
|
|
563
|
+
self.spin_target.setValue(DEFAULT_TARGET)
|
|
564
|
+
self.chk_linked.setChecked(DEFAULT_LINKED)
|
|
565
|
+
self.chk_normalize.setChecked(DEFAULT_NORMALIZE)
|
|
566
|
+
|
|
567
|
+
# Black point
|
|
568
|
+
self.chk_no_black_clip.setChecked(DEFAULT_NO_BLACK_CLIP)
|
|
569
|
+
self.sld_bp.setValue(DEFAULT_BP_SLIDER)
|
|
570
|
+
self.lbl_bp.setText(f"{DEFAULT_BP_SLIDER/100:.2f}")
|
|
571
|
+
|
|
572
|
+
# HDR
|
|
573
|
+
self.chk_hdr.setChecked(DEFAULT_HDR_ON)
|
|
574
|
+
self.sld_hdr_amt.setValue(DEFAULT_HDR_AMT)
|
|
575
|
+
self.lbl_hdr_amt.setText(f"{DEFAULT_HDR_AMT/100:.2f}")
|
|
576
|
+
self.sld_hdr_knee.setValue(DEFAULT_HDR_KNEE)
|
|
577
|
+
self.lbl_hdr_knee.setText(f"{DEFAULT_HDR_KNEE/100:.2f}")
|
|
578
|
+
|
|
579
|
+
# Luma-only + mode + blend
|
|
580
|
+
self.chk_luma_only.setChecked(DEFAULT_LUMA_ONLY)
|
|
581
|
+
if DEFAULT_LUMA_MODE:
|
|
582
|
+
self.cmb_luma.setCurrentText(DEFAULT_LUMA_MODE)
|
|
583
|
+
self.sld_luma_blend.setValue(DEFAULT_LUMA_BLEND)
|
|
584
|
+
self.lbl_luma_blend.setText(f"{DEFAULT_LUMA_BLEND/100:.2f}")
|
|
585
|
+
|
|
586
|
+
# Curves
|
|
587
|
+
self.chk_curves.setChecked(DEFAULT_CURVES_ON)
|
|
588
|
+
self.sld_curves.setValue(DEFAULT_CURVES_STRENGTH)
|
|
589
|
+
self.lbl_curves_val.setText(f"{DEFAULT_CURVES_STRENGTH/100:.2f}")
|
|
590
|
+
|
|
591
|
+
finally:
|
|
592
|
+
# Restore signal states
|
|
593
|
+
for w, _prev in old_blocks:
|
|
594
|
+
try:
|
|
595
|
+
w.blockSignals(False)
|
|
596
|
+
except Exception:
|
|
597
|
+
pass
|
|
598
|
+
|
|
599
|
+
# Re-apply dependent enable/disable states exactly like normal interactions
|
|
600
|
+
try:
|
|
601
|
+
# no-black-clip disables BP row
|
|
602
|
+
self.row_bp.setEnabled(not self.chk_no_black_clip.isChecked())
|
|
603
|
+
except Exception:
|
|
604
|
+
pass
|
|
605
|
+
|
|
606
|
+
try:
|
|
607
|
+
# HDR enables HDR row
|
|
608
|
+
self.hdr_row.setEnabled(self.chk_hdr.isChecked())
|
|
609
|
+
except Exception:
|
|
610
|
+
pass
|
|
611
|
+
|
|
612
|
+
try:
|
|
613
|
+
# Curves enables curves row
|
|
614
|
+
self.curves_row.setEnabled(self.chk_curves.isChecked())
|
|
615
|
+
except Exception:
|
|
616
|
+
pass
|
|
617
|
+
|
|
618
|
+
try:
|
|
619
|
+
# Luma-only enables dropdown + blend row, disables linked
|
|
620
|
+
luma_on = self.chk_luma_only.isChecked()
|
|
621
|
+
self.cmb_luma.setEnabled(luma_on)
|
|
622
|
+
self.luma_blend_row.setEnabled(luma_on)
|
|
623
|
+
self.chk_linked.setEnabled(not luma_on)
|
|
624
|
+
except Exception:
|
|
625
|
+
pass
|
|
626
|
+
|
|
627
|
+
# Auto-suggest HDR knee from target (since we cleared lock)
|
|
628
|
+
try:
|
|
629
|
+
t = float(self.spin_target.value())
|
|
630
|
+
knee = float(np.clip(t + 0.10, 0.10, 0.95))
|
|
631
|
+
self.sld_hdr_knee.setValue(int(round(knee * 100)))
|
|
632
|
+
self.lbl_hdr_knee.setText(f"{knee:.2f}")
|
|
633
|
+
except Exception:
|
|
634
|
+
pass
|
|
635
|
+
|
|
636
|
+
# Refresh baseline preview + clip stats
|
|
637
|
+
self._populate_initial_preview()
|
|
638
|
+
|
|
639
|
+
|
|
493
640
|
def _clip_mode_label(self, imgf: np.ndarray) -> str:
|
|
494
641
|
# Mono image
|
|
495
642
|
if imgf.ndim == 2 or (imgf.ndim == 3 and imgf.shape[2] == 1):
|
|
@@ -626,7 +773,8 @@ class StatisticalStretchDialog(QDialog):
|
|
|
626
773
|
self._job_mode = mode
|
|
627
774
|
|
|
628
775
|
self._set_controls_enabled(False)
|
|
629
|
-
self._show_busy("Statistical Stretch", "Processing…")
|
|
776
|
+
self._show_busy("Statistical Stretch", "Processing preview…" if mode == "preview" else "Applying stretch…")
|
|
777
|
+
|
|
630
778
|
|
|
631
779
|
self._thread = QThread(self._main)
|
|
632
780
|
self._worker = _StretchWorker(self)
|
setiastro/saspro/subwindow.py
CHANGED
|
@@ -590,6 +590,7 @@ class ImageSubWindow(QWidget):
|
|
|
590
590
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
591
591
|
self.customContextMenuRequested.connect(self._show_ctx_menu)
|
|
592
592
|
QShortcut(QKeySequence("F2"), self, activated=self._rename_view)
|
|
593
|
+
QShortcut(QKeySequence("F3"), self, activated=self._rename_document)
|
|
593
594
|
#QShortcut(QKeySequence("A"), self, activated=self.toggle_autostretch)
|
|
594
595
|
QShortcut(QKeySequence("Ctrl+Space"), self, activated=self.toggle_autostretch)
|
|
595
596
|
QShortcut(QKeySequence("Alt+Shift+A"), self, activated=self.toggle_autostretch)
|
|
@@ -1726,7 +1727,7 @@ class ImageSubWindow(QWidget):
|
|
|
1726
1727
|
def _show_ctx_menu(self, pos):
|
|
1727
1728
|
menu = QMenu(self)
|
|
1728
1729
|
a_view = menu.addAction(self.tr("Rename View… (F2)"))
|
|
1729
|
-
a_doc = menu.addAction(self.tr("Rename Document…"))
|
|
1730
|
+
a_doc = menu.addAction(self.tr("Rename Document… (F3)"))
|
|
1730
1731
|
menu.addSeparator()
|
|
1731
1732
|
a_min = menu.addAction(self.tr("Send to Shelf"))
|
|
1732
1733
|
a_clear = menu.addAction(self.tr("Clear View Name (use doc name)"))
|
|
@@ -1736,6 +1737,29 @@ class ImageSubWindow(QWidget):
|
|
|
1736
1737
|
a_help = menu.addAction(self.tr("Show pixel/WCS readout hint"))
|
|
1737
1738
|
menu.addSeparator()
|
|
1738
1739
|
a_prev = menu.addAction(self.tr("Create Preview (drag rectangle)"))
|
|
1740
|
+
# --- Mask actions (requested in zoom/context menu) ---
|
|
1741
|
+
mw = self._find_main_window()
|
|
1742
|
+
vw = self # this ImageSubWindow is the view
|
|
1743
|
+
doc = getattr(vw, "document", None)
|
|
1744
|
+
|
|
1745
|
+
has_mask = bool(doc and getattr(doc, "active_mask_id", None))
|
|
1746
|
+
|
|
1747
|
+
menu.addSeparator()
|
|
1748
|
+
menu.addSection(self.tr("Mask"))
|
|
1749
|
+
|
|
1750
|
+
# 1) Toggle overlay (single item: Show/Hide)
|
|
1751
|
+
a_mask_overlay = menu.addAction(self.tr("Show Mask Overlay"))
|
|
1752
|
+
a_mask_overlay.setCheckable(True)
|
|
1753
|
+
a_mask_overlay.setChecked(bool(getattr(vw, "show_mask_overlay", False)))
|
|
1754
|
+
a_mask_overlay.setEnabled(has_mask)
|
|
1755
|
+
|
|
1756
|
+
# 2) Invert mask
|
|
1757
|
+
a_invert = menu.addAction(self.tr("Invert Mask"))
|
|
1758
|
+
a_invert.setEnabled(has_mask)
|
|
1759
|
+
|
|
1760
|
+
# 3) Remove mask
|
|
1761
|
+
a_remove = menu.addAction(self.tr("Remove Mask"))
|
|
1762
|
+
a_remove.setEnabled(has_mask)
|
|
1739
1763
|
|
|
1740
1764
|
act = menu.exec(self.mapToGlobal(pos))
|
|
1741
1765
|
|
|
@@ -1755,7 +1779,19 @@ class ImageSubWindow(QWidget):
|
|
|
1755
1779
|
elif act == a_prev:
|
|
1756
1780
|
self._preview_btn.setChecked(True)
|
|
1757
1781
|
self._toggle_preview_select_mode(True)
|
|
1758
|
-
|
|
1782
|
+
# --- Mask dispatch ---
|
|
1783
|
+
elif act == a_mask_overlay:
|
|
1784
|
+
if mw:
|
|
1785
|
+
if a_mask_overlay.isChecked():
|
|
1786
|
+
mw._show_mask_overlay()
|
|
1787
|
+
else:
|
|
1788
|
+
mw._hide_mask_overlay()
|
|
1789
|
+
elif act == a_invert:
|
|
1790
|
+
if mw:
|
|
1791
|
+
mw._invert_mask()
|
|
1792
|
+
elif act == a_remove:
|
|
1793
|
+
if mw:
|
|
1794
|
+
mw._remove_mask_menu()
|
|
1759
1795
|
|
|
1760
1796
|
|
|
1761
1797
|
def _send_to_shelf(self):
|
|
@@ -3081,7 +3117,6 @@ class ImageSubWindow(QWidget):
|
|
|
3081
3117
|
yi = int(round(py / s))
|
|
3082
3118
|
return xi, yi
|
|
3083
3119
|
|
|
3084
|
-
|
|
3085
3120
|
def _finish_preview_rect(self, vp_rect: QRect):
|
|
3086
3121
|
if vp_rect.width() < 4 or vp_rect.height() < 4:
|
|
3087
3122
|
self._cancel_rubber()
|
|
@@ -3187,8 +3222,6 @@ class ImageSubWindow(QWidget):
|
|
|
3187
3222
|
|
|
3188
3223
|
super().mousePressEvent(e)
|
|
3189
3224
|
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
3225
|
def _show_readout(self, xi, yi, sample):
|
|
3193
3226
|
mw = self._find_main_window()
|
|
3194
3227
|
if mw is None:
|