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/histogram.py
CHANGED
|
@@ -4,8 +4,8 @@ import numpy as np
|
|
|
4
4
|
|
|
5
5
|
from PyQt6.QtCore import Qt, QSettings, QTimer, QEvent, pyqtSignal
|
|
6
6
|
from PyQt6.QtWidgets import (
|
|
7
|
-
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QSlider, QPushButton, QScrollArea,
|
|
8
|
-
QTableWidget, QTableWidgetItem, QMessageBox, QToolButton, QInputDialog, QSplitter, QSizePolicy, QHeaderView
|
|
7
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QSlider, QPushButton, QScrollArea,QWidget,
|
|
8
|
+
QTableWidget, QTableWidgetItem, QMessageBox, QToolButton, QInputDialog, QSplitter, QSizePolicy, QHeaderView, QApplication
|
|
9
9
|
)
|
|
10
10
|
from PyQt6.QtGui import QPixmap, QPainter, QPen, QColor, QFont, QPalette
|
|
11
11
|
|
|
@@ -125,7 +125,12 @@ class HistogramDialog(QDialog):
|
|
|
125
125
|
|
|
126
126
|
splitter.addWidget(self.scroll_area)
|
|
127
127
|
|
|
128
|
-
# right: stats table
|
|
128
|
+
# right: stats panel (table + button under it)
|
|
129
|
+
stats_panel = QWidget(self)
|
|
130
|
+
stats_v = QVBoxLayout(stats_panel)
|
|
131
|
+
stats_v.setContentsMargins(0, 0, 0, 0)
|
|
132
|
+
stats_v.setSpacing(6)
|
|
133
|
+
|
|
129
134
|
self.stats_table = QTableWidget(self)
|
|
130
135
|
self.stats_table.setRowCount(7)
|
|
131
136
|
self.stats_table.setColumnCount(1)
|
|
@@ -137,15 +142,21 @@ class HistogramDialog(QDialog):
|
|
|
137
142
|
# Let it grow/shrink with the splitter
|
|
138
143
|
self.stats_table.setMinimumWidth(320)
|
|
139
144
|
self.stats_table.setSizePolicy(
|
|
140
|
-
QSizePolicy.Policy.Preferred,
|
|
145
|
+
QSizePolicy.Policy.Preferred,
|
|
141
146
|
QSizePolicy.Policy.Expanding,
|
|
142
147
|
)
|
|
143
148
|
|
|
144
|
-
# Make the columns use available width nicely
|
|
145
149
|
hdr = self.stats_table.horizontalHeader()
|
|
146
150
|
hdr.setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
|
|
152
|
+
stats_v.addWidget(self.stats_table, 1)
|
|
153
|
+
|
|
154
|
+
# NEW: button directly under the table
|
|
155
|
+
self.btn_more_stats = QPushButton(self.tr("More Stats…"), self)
|
|
156
|
+
self.btn_more_stats.clicked.connect(self._show_more_stats)
|
|
157
|
+
stats_v.addWidget(self.btn_more_stats, 0)
|
|
158
|
+
|
|
159
|
+
splitter.addWidget(stats_panel)
|
|
149
160
|
|
|
150
161
|
# Give more space to histogram side by default
|
|
151
162
|
splitter.setStretchFactor(0, 3)
|
|
@@ -631,6 +642,167 @@ class HistogramDialog(QDialog):
|
|
|
631
642
|
|
|
632
643
|
self._adjust_stats_width()
|
|
633
644
|
|
|
645
|
+
def _show_more_stats(self):
|
|
646
|
+
if self.image is None:
|
|
647
|
+
return
|
|
648
|
+
|
|
649
|
+
dlg = QDialog(self)
|
|
650
|
+
dlg.setWindowTitle(self.tr("Image Statistics"))
|
|
651
|
+
dlg.setWindowModality(Qt.WindowModality.NonModal)
|
|
652
|
+
dlg.setModal(False)
|
|
653
|
+
|
|
654
|
+
root = QVBoxLayout(dlg)
|
|
655
|
+
|
|
656
|
+
info = QLabel(self.tr(
|
|
657
|
+
"Detailed robust statistics and percentiles.\n"
|
|
658
|
+
"Computed on normalized float image values in [0,1]."
|
|
659
|
+
))
|
|
660
|
+
info.setWordWrap(True)
|
|
661
|
+
root.addWidget(info)
|
|
662
|
+
|
|
663
|
+
tbl = QTableWidget(dlg)
|
|
664
|
+
tbl.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
665
|
+
root.addWidget(tbl, 1)
|
|
666
|
+
|
|
667
|
+
btn_row = QHBoxLayout()
|
|
668
|
+
btn_copy = QPushButton(self.tr("Copy as Text"), dlg)
|
|
669
|
+
btn_close = QPushButton(self.tr("Close"), dlg)
|
|
670
|
+
btn_row.addStretch(1)
|
|
671
|
+
btn_row.addWidget(btn_copy)
|
|
672
|
+
btn_row.addWidget(btn_close)
|
|
673
|
+
root.addLayout(btn_row)
|
|
674
|
+
|
|
675
|
+
btn_close.clicked.connect(dlg.accept)
|
|
676
|
+
|
|
677
|
+
# ---- compute stats ----
|
|
678
|
+
img = self.image
|
|
679
|
+
if img.ndim == 3 and img.shape[2] == 3:
|
|
680
|
+
chans = [img[..., 0], img[..., 1], img[..., 2]]
|
|
681
|
+
col_names = ["R", "G", "B"]
|
|
682
|
+
else:
|
|
683
|
+
chan = img if img.ndim == 2 else img[..., 0]
|
|
684
|
+
chans = [chan]
|
|
685
|
+
col_names = ["Gray"]
|
|
686
|
+
|
|
687
|
+
row_defs = [
|
|
688
|
+
("Min", "min"),
|
|
689
|
+
("Max", "max"),
|
|
690
|
+
("Mean", "mean"),
|
|
691
|
+
("Median", "median"),
|
|
692
|
+
("StdDev", "std"),
|
|
693
|
+
("Variance", "var"),
|
|
694
|
+
("MAD", "mad"),
|
|
695
|
+
("IQR (p75-p25)", "iqr"),
|
|
696
|
+
("p0.1", "p0.1"),
|
|
697
|
+
("p1", "p1"),
|
|
698
|
+
("p5", "p5"),
|
|
699
|
+
("p25", "p25"),
|
|
700
|
+
("p50", "p50"),
|
|
701
|
+
("p75", "p75"),
|
|
702
|
+
("p95", "p95"),
|
|
703
|
+
("p99", "p99"),
|
|
704
|
+
("p99.9", "p99.9"),
|
|
705
|
+
("Low Clipped (<=0)", "lowclip"),
|
|
706
|
+
("High Clipped (>=TrueMax)", "highclip"),
|
|
707
|
+
]
|
|
708
|
+
|
|
709
|
+
tbl.setRowCount(len(row_defs))
|
|
710
|
+
tbl.setColumnCount(len(chans))
|
|
711
|
+
tbl.setHorizontalHeaderLabels(col_names)
|
|
712
|
+
tbl.setVerticalHeaderLabels([r[0] for r in row_defs])
|
|
713
|
+
|
|
714
|
+
# Precompute thresholds for clipping
|
|
715
|
+
eps = 1e-6
|
|
716
|
+
hi_thr = max(eps, float(self.sensor_max01) - eps)
|
|
717
|
+
|
|
718
|
+
def _fmt(x):
|
|
719
|
+
return f"{float(x):.6f}"
|
|
720
|
+
|
|
721
|
+
def _fmt_clip(k, n):
|
|
722
|
+
pct = 100.0 * float(k) / float(max(1, n))
|
|
723
|
+
return f"{int(k)} ({pct:.3f}%)"
|
|
724
|
+
|
|
725
|
+
# Percentiles we want
|
|
726
|
+
pct_list = [0.1, 1, 5, 25, 50, 75, 95, 99, 99.9]
|
|
727
|
+
|
|
728
|
+
# Fill table
|
|
729
|
+
for c_idx, c_arr in enumerate(chans):
|
|
730
|
+
flat = np.asarray(c_arr, dtype=np.float32).ravel()
|
|
731
|
+
if flat.size > 20_000_000:
|
|
732
|
+
idx = np.random.default_rng(0).choice(flat.size, size=5_000_000, replace=False)
|
|
733
|
+
flat = flat[idx]
|
|
734
|
+
|
|
735
|
+
n = int(flat.size) if flat.size else 1
|
|
736
|
+
|
|
737
|
+
# basic moments
|
|
738
|
+
cmin = float(np.min(flat))
|
|
739
|
+
cmax = float(np.max(flat))
|
|
740
|
+
cmean = float(np.mean(flat))
|
|
741
|
+
cmed = float(np.median(flat))
|
|
742
|
+
cstd = float(np.std(flat))
|
|
743
|
+
cvar = float(np.var(flat))
|
|
744
|
+
|
|
745
|
+
# robust
|
|
746
|
+
mad = float(np.median(np.abs(flat - cmed)))
|
|
747
|
+
pcts = np.percentile(flat, pct_list) if flat.size else np.zeros(len(pct_list), dtype=np.float32)
|
|
748
|
+
p25, p75 = float(pcts[3]), float(pcts[5])
|
|
749
|
+
iqr = float(p75 - p25)
|
|
750
|
+
|
|
751
|
+
# clipping
|
|
752
|
+
low_k = int(np.count_nonzero(flat <= eps))
|
|
753
|
+
high_k = int(np.count_nonzero(flat >= hi_thr))
|
|
754
|
+
|
|
755
|
+
values = {
|
|
756
|
+
"min": cmin,
|
|
757
|
+
"max": cmax,
|
|
758
|
+
"mean": cmean,
|
|
759
|
+
"median": cmed,
|
|
760
|
+
"std": cstd,
|
|
761
|
+
"var": cvar,
|
|
762
|
+
"mad": mad,
|
|
763
|
+
"iqr": iqr,
|
|
764
|
+
"p0.1": float(pcts[0]),
|
|
765
|
+
"p1": float(pcts[1]),
|
|
766
|
+
"p5": float(pcts[2]),
|
|
767
|
+
"p25": float(pcts[3]),
|
|
768
|
+
"p50": float(pcts[4]),
|
|
769
|
+
"p75": float(pcts[5]),
|
|
770
|
+
"p95": float(pcts[6]),
|
|
771
|
+
"p99": float(pcts[7]),
|
|
772
|
+
"p99.9": float(pcts[8]),
|
|
773
|
+
"lowclip": _fmt_clip(low_k, n),
|
|
774
|
+
"highclip": _fmt_clip(high_k, n),
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
for r, (_, key) in enumerate(row_defs):
|
|
778
|
+
v = values[key]
|
|
779
|
+
text = v if isinstance(v, str) else _fmt(v)
|
|
780
|
+
it = QTableWidgetItem(text)
|
|
781
|
+
it.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
782
|
+
tbl.setItem(r, c_idx, it)
|
|
783
|
+
|
|
784
|
+
hdr = tbl.horizontalHeader()
|
|
785
|
+
hdr.setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
|
786
|
+
|
|
787
|
+
def _copy_as_text():
|
|
788
|
+
# TSV with rows
|
|
789
|
+
lines = []
|
|
790
|
+
lines.append("\t" + "\t".join(col_names))
|
|
791
|
+
for r, (lab, _) in enumerate(row_defs):
|
|
792
|
+
row = [lab]
|
|
793
|
+
for c in range(len(col_names)):
|
|
794
|
+
item = tbl.item(r, c)
|
|
795
|
+
row.append(item.text() if item else "")
|
|
796
|
+
lines.append("\t".join(row))
|
|
797
|
+
QApplication.clipboard().setText("\n".join(lines))
|
|
798
|
+
QMessageBox.information(dlg, self.tr("Copied"), self.tr("Statistics copied to clipboard."))
|
|
799
|
+
|
|
800
|
+
btn_copy.clicked.connect(_copy_as_text)
|
|
801
|
+
|
|
802
|
+
dlg.resize(720, 520)
|
|
803
|
+
dlg.show()
|
|
804
|
+
|
|
805
|
+
|
|
634
806
|
def _theoretical_native_max_from_meta(self):
|
|
635
807
|
meta = getattr(self.doc, "metadata", None) or {}
|
|
636
808
|
bd = str(meta.get("bit_depth", "")).lower()
|