setiastrosuitepro 1.6.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/__init__.py +2 -0
- setiastro/saspro/__init__.py +20 -0
- setiastro/saspro/__main__.py +784 -0
- setiastro/saspro/_generated/__init__.py +7 -0
- setiastro/saspro/_generated/build_info.py +2 -0
- setiastro/saspro/abe.py +1295 -0
- setiastro/saspro/abe_preset.py +196 -0
- setiastro/saspro/aberration_ai.py +694 -0
- setiastro/saspro/aberration_ai_preset.py +224 -0
- setiastro/saspro/accel_installer.py +218 -0
- setiastro/saspro/accel_workers.py +30 -0
- setiastro/saspro/add_stars.py +621 -0
- setiastro/saspro/astrobin_exporter.py +1007 -0
- setiastro/saspro/astrospike.py +153 -0
- setiastro/saspro/astrospike_python.py +1839 -0
- setiastro/saspro/autostretch.py +196 -0
- setiastro/saspro/backgroundneutral.py +560 -0
- setiastro/saspro/batch_convert.py +325 -0
- setiastro/saspro/batch_renamer.py +519 -0
- setiastro/saspro/blemish_blaster.py +488 -0
- setiastro/saspro/blink_comparator_pro.py +2923 -0
- setiastro/saspro/bundles.py +61 -0
- setiastro/saspro/bundles_dock.py +114 -0
- setiastro/saspro/cheat_sheet.py +168 -0
- setiastro/saspro/clahe.py +342 -0
- setiastro/saspro/comet_stacking.py +1377 -0
- setiastro/saspro/config.py +38 -0
- setiastro/saspro/config_bootstrap.py +40 -0
- setiastro/saspro/config_manager.py +316 -0
- setiastro/saspro/continuum_subtract.py +1617 -0
- setiastro/saspro/convo.py +1397 -0
- setiastro/saspro/convo_preset.py +414 -0
- setiastro/saspro/copyastro.py +187 -0
- setiastro/saspro/cosmicclarity.py +1564 -0
- setiastro/saspro/cosmicclarity_preset.py +407 -0
- setiastro/saspro/crop_dialog_pro.py +948 -0
- setiastro/saspro/crop_preset.py +189 -0
- setiastro/saspro/curve_editor_pro.py +2544 -0
- setiastro/saspro/curves_preset.py +375 -0
- setiastro/saspro/debayer.py +670 -0
- setiastro/saspro/debug_utils.py +29 -0
- setiastro/saspro/dnd_mime.py +35 -0
- setiastro/saspro/doc_manager.py +2634 -0
- setiastro/saspro/exoplanet_detector.py +2166 -0
- setiastro/saspro/file_utils.py +284 -0
- setiastro/saspro/fitsmodifier.py +744 -0
- setiastro/saspro/free_torch_memory.py +48 -0
- setiastro/saspro/frequency_separation.py +1343 -0
- setiastro/saspro/function_bundle.py +1594 -0
- setiastro/saspro/ghs_dialog_pro.py +660 -0
- setiastro/saspro/ghs_preset.py +284 -0
- setiastro/saspro/graxpert.py +634 -0
- setiastro/saspro/graxpert_preset.py +287 -0
- setiastro/saspro/gui/__init__.py +0 -0
- setiastro/saspro/gui/main_window.py +8494 -0
- setiastro/saspro/gui/mixins/__init__.py +33 -0
- setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
- setiastro/saspro/gui/mixins/file_mixin.py +445 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
- setiastro/saspro/gui/mixins/header_mixin.py +441 -0
- setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
- setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +1324 -0
- setiastro/saspro/gui/mixins/update_mixin.py +309 -0
- setiastro/saspro/gui/mixins/view_mixin.py +435 -0
- setiastro/saspro/halobgon.py +462 -0
- setiastro/saspro/header_viewer.py +445 -0
- setiastro/saspro/headless_utils.py +88 -0
- setiastro/saspro/histogram.py +753 -0
- setiastro/saspro/history_explorer.py +939 -0
- setiastro/saspro/image_combine.py +414 -0
- setiastro/saspro/image_peeker_pro.py +1596 -0
- setiastro/saspro/imageops/__init__.py +37 -0
- setiastro/saspro/imageops/mdi_snap.py +292 -0
- setiastro/saspro/imageops/scnr.py +36 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
- setiastro/saspro/imageops/stretch.py +244 -0
- setiastro/saspro/isophote.py +1179 -0
- setiastro/saspro/layers.py +208 -0
- setiastro/saspro/layers_dock.py +714 -0
- setiastro/saspro/lazy_imports.py +193 -0
- setiastro/saspro/legacy/__init__.py +2 -0
- setiastro/saspro/legacy/image_manager.py +2226 -0
- setiastro/saspro/legacy/numba_utils.py +3659 -0
- setiastro/saspro/legacy/xisf.py +1071 -0
- setiastro/saspro/linear_fit.py +534 -0
- setiastro/saspro/live_stacking.py +1830 -0
- setiastro/saspro/log_bus.py +5 -0
- setiastro/saspro/logging_config.py +460 -0
- setiastro/saspro/luminancerecombine.py +309 -0
- setiastro/saspro/main_helpers.py +201 -0
- setiastro/saspro/mask_creation.py +928 -0
- setiastro/saspro/masks_core.py +56 -0
- setiastro/saspro/mdi_widgets.py +353 -0
- setiastro/saspro/memory_utils.py +666 -0
- setiastro/saspro/metadata_patcher.py +75 -0
- setiastro/saspro/mfdeconv.py +3826 -0
- setiastro/saspro/mfdeconv_earlystop.py +71 -0
- setiastro/saspro/mfdeconvcudnn.py +3263 -0
- setiastro/saspro/mfdeconvsport.py +2382 -0
- setiastro/saspro/minorbodycatalog.py +567 -0
- setiastro/saspro/morphology.py +382 -0
- setiastro/saspro/multiscale_decomp.py +1290 -0
- setiastro/saspro/nbtorgb_stars.py +531 -0
- setiastro/saspro/numba_utils.py +3044 -0
- setiastro/saspro/numba_warmup.py +141 -0
- setiastro/saspro/ops/__init__.py +9 -0
- setiastro/saspro/ops/command_help_dialog.py +623 -0
- setiastro/saspro/ops/command_runner.py +217 -0
- setiastro/saspro/ops/commands.py +1594 -0
- setiastro/saspro/ops/script_editor.py +1102 -0
- setiastro/saspro/ops/scripts.py +1413 -0
- setiastro/saspro/ops/settings.py +560 -0
- setiastro/saspro/parallel_utils.py +554 -0
- setiastro/saspro/pedestal.py +121 -0
- setiastro/saspro/perfect_palette_picker.py +1053 -0
- setiastro/saspro/pipeline.py +110 -0
- setiastro/saspro/pixelmath.py +1600 -0
- setiastro/saspro/plate_solver.py +2435 -0
- setiastro/saspro/project_io.py +797 -0
- setiastro/saspro/psf_utils.py +136 -0
- setiastro/saspro/psf_viewer.py +549 -0
- setiastro/saspro/pyi_rthook_astroquery.py +95 -0
- setiastro/saspro/remove_green.py +314 -0
- setiastro/saspro/remove_stars.py +1625 -0
- setiastro/saspro/remove_stars_preset.py +404 -0
- setiastro/saspro/resources.py +472 -0
- setiastro/saspro/rgb_combination.py +207 -0
- setiastro/saspro/rgb_extract.py +19 -0
- setiastro/saspro/rgbalign.py +723 -0
- setiastro/saspro/runtime_imports.py +7 -0
- setiastro/saspro/runtime_torch.py +754 -0
- setiastro/saspro/save_options.py +72 -0
- setiastro/saspro/selective_color.py +1552 -0
- setiastro/saspro/sfcc.py +1425 -0
- setiastro/saspro/shortcuts.py +2807 -0
- setiastro/saspro/signature_insert.py +1099 -0
- setiastro/saspro/stacking_suite.py +17712 -0
- setiastro/saspro/star_alignment.py +7420 -0
- setiastro/saspro/star_alignment_preset.py +329 -0
- setiastro/saspro/star_metrics.py +49 -0
- setiastro/saspro/star_spikes.py +681 -0
- setiastro/saspro/star_stretch.py +470 -0
- setiastro/saspro/stat_stretch.py +502 -0
- setiastro/saspro/status_log_dock.py +78 -0
- setiastro/saspro/subwindow.py +3267 -0
- setiastro/saspro/supernovaasteroidhunter.py +1712 -0
- setiastro/saspro/swap_manager.py +99 -0
- setiastro/saspro/torch_backend.py +89 -0
- setiastro/saspro/torch_rejection.py +434 -0
- setiastro/saspro/view_bundle.py +1555 -0
- setiastro/saspro/wavescale_hdr.py +624 -0
- setiastro/saspro/wavescale_hdr_preset.py +100 -0
- setiastro/saspro/wavescalede.py +657 -0
- setiastro/saspro/wavescalede_preset.py +228 -0
- setiastro/saspro/wcs_update.py +374 -0
- setiastro/saspro/whitebalance.py +456 -0
- setiastro/saspro/widgets/__init__.py +48 -0
- setiastro/saspro/widgets/common_utilities.py +305 -0
- setiastro/saspro/widgets/graphics_views.py +122 -0
- setiastro/saspro/widgets/image_utils.py +518 -0
- setiastro/saspro/widgets/preview_dialogs.py +280 -0
- setiastro/saspro/widgets/spinboxes.py +275 -0
- setiastro/saspro/widgets/themed_buttons.py +13 -0
- setiastro/saspro/widgets/wavelet_utils.py +299 -0
- setiastro/saspro/window_shelf.py +185 -0
- setiastro/saspro/xisf.py +1123 -0
- setiastrosuitepro-1.6.0.dist-info/METADATA +266 -0
- setiastrosuitepro-1.6.0.dist-info/RECORD +174 -0
- setiastrosuitepro-1.6.0.dist-info/WHEEL +4 -0
- setiastrosuitepro-1.6.0.dist-info/entry_points.txt +6 -0
- setiastrosuitepro-1.6.0.dist-info/licenses/LICENSE +674 -0
- setiastrosuitepro-1.6.0.dist-info/licenses/license.txt +2580 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# pro/cosmicclarity_preset.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import glob
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer, QSettings, QLockFile
|
|
12
|
+
from PyQt6.QtWidgets import QDialog, QFormLayout, QDialogButtonBox, QDoubleSpinBox, QComboBox, QCheckBox, QMessageBox, QApplication, QHBoxLayout, QVBoxLayout, QLabel, QPushButton
|
|
13
|
+
from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer, QSettings, QLockFile, QEventLoop
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# reuse your legacy IO + helpers
|
|
17
|
+
from setiastro.saspro.legacy.image_manager import load_image, save_image
|
|
18
|
+
|
|
19
|
+
from .remove_stars import _ProcThread, _ProcDialog
|
|
20
|
+
from .cosmicclarity import CosmicClarityDialogPro
|
|
21
|
+
|
|
22
|
+
# pull tiny helpers from the main CC module (or re-declare)
|
|
23
|
+
def _platform_exe_names(mode: str) -> str:
|
|
24
|
+
is_win = os.name == "nt"
|
|
25
|
+
is_mac = sys.platform == "darwin"
|
|
26
|
+
if mode == "sharpen":
|
|
27
|
+
return "SetiAstroCosmicClarity.exe" if is_win else ("SetiAstroCosmicClaritymac" if is_mac else "SetiAstroCosmicClarity")
|
|
28
|
+
elif mode == "denoise":
|
|
29
|
+
return "SetiAstroCosmicClarity_denoise.exe" if is_win else ("SetiAstroCosmicClarity_denoisemac" if is_mac else "SetiAstroCosmicClarity_denoise")
|
|
30
|
+
elif mode == "superres":
|
|
31
|
+
return "setiastrocosmicclarity_superres.exe" if is_win else "setiastrocosmicclarity_superres"
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
def _cosmic_root(main) -> str:
|
|
35
|
+
s = getattr(main, "settings", None)
|
|
36
|
+
if not s:
|
|
37
|
+
return ""
|
|
38
|
+
try:
|
|
39
|
+
return s.value("paths/cosmic_clarity", "", type=str) or ""
|
|
40
|
+
except Exception:
|
|
41
|
+
return s.value("paths/cosmic_clarity", "") or ""
|
|
42
|
+
|
|
43
|
+
def _base_from_doc(doc) -> str:
|
|
44
|
+
fp = getattr(doc, "file_path", None)
|
|
45
|
+
if isinstance(fp, str) and fp:
|
|
46
|
+
return os.path.splitext(os.path.basename(fp))[0]
|
|
47
|
+
name = getattr(doc, "display_name", None)
|
|
48
|
+
if callable(name):
|
|
49
|
+
try:
|
|
50
|
+
n = name() or ""
|
|
51
|
+
if n:
|
|
52
|
+
return "".join(ch if ch.isalnum() or ch in "-_" else "_" for ch in n).strip("_") or "image"
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
return "image"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ---------------- Worker (no UI) ----------------
|
|
59
|
+
class _CCHeadlessWorker(QThread):
|
|
60
|
+
finished_ok = pyqtSignal(np.ndarray)
|
|
61
|
+
failed = pyqtSignal(str)
|
|
62
|
+
log = pyqtSignal(str)
|
|
63
|
+
progress = pyqtSignal(int) # 0..100
|
|
64
|
+
step_changed= pyqtSignal(str) # "sharpen" | "denoise" | "superres"
|
|
65
|
+
|
|
66
|
+
def __init__(self, cosmic_root: str, doc, ops: list[tuple[str, str]], params: dict, create_new: bool):
|
|
67
|
+
super().__init__()
|
|
68
|
+
self.root = cosmic_root
|
|
69
|
+
self.doc = doc
|
|
70
|
+
self.ops = ops
|
|
71
|
+
self.p = params
|
|
72
|
+
self.create_new = create_new
|
|
73
|
+
self._stop = False
|
|
74
|
+
self._proc = None
|
|
75
|
+
|
|
76
|
+
def cancel(self):
|
|
77
|
+
self._stop = True
|
|
78
|
+
try:
|
|
79
|
+
if self._proc and self._proc.poll() is None:
|
|
80
|
+
self._proc.terminate()
|
|
81
|
+
try: self._proc.wait(timeout=5)
|
|
82
|
+
except Exception: self._proc.kill()
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
def _emit_progress_line(self, line: str):
|
|
87
|
+
s = line.strip()
|
|
88
|
+
if not s:
|
|
89
|
+
return
|
|
90
|
+
self.log.emit(s)
|
|
91
|
+
# Parse both formats used by your tools
|
|
92
|
+
if s.startswith("Progress:"):
|
|
93
|
+
try: self.progress.emit(int(float(s.split()[1].replace("%",""))))
|
|
94
|
+
except Exception as e:
|
|
95
|
+
import logging
|
|
96
|
+
logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
|
|
97
|
+
elif s.startswith("PROGRESS:"):
|
|
98
|
+
try: self.progress.emit(int(s.split(":",1)[1].strip().replace("%","")))
|
|
99
|
+
except Exception as e:
|
|
100
|
+
import logging
|
|
101
|
+
logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
|
|
102
|
+
|
|
103
|
+
def run(self):
|
|
104
|
+
try:
|
|
105
|
+
print("starting cc headless worker")
|
|
106
|
+
in_dir = os.path.join(self.root, "input")
|
|
107
|
+
out_dir = os.path.join(self.root, "output")
|
|
108
|
+
os.makedirs(in_dir, exist_ok=True)
|
|
109
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
110
|
+
|
|
111
|
+
base = _base_from_doc(self.doc)
|
|
112
|
+
in_path = os.path.join(in_dir, f"{base}.tif")
|
|
113
|
+
|
|
114
|
+
# Stage current image
|
|
115
|
+
img = np.clip(np.asarray(self.doc.image), 0.0, 1.0).astype(np.float32, copy=False)
|
|
116
|
+
save_image(img, in_path, "tiff", "32-bit floating point",
|
|
117
|
+
getattr(self.doc, "original_header", None),
|
|
118
|
+
getattr(self.doc, "is_mono", False))
|
|
119
|
+
|
|
120
|
+
staged_input = in_path
|
|
121
|
+
result = None
|
|
122
|
+
print("entering cc headless run")
|
|
123
|
+
for (mode, suffix) in self.ops:
|
|
124
|
+
if self._stop: raise RuntimeError("Cancelled")
|
|
125
|
+
self.step_changed.emit(mode)
|
|
126
|
+
|
|
127
|
+
exe = os.path.join(self.root, _platform_exe_names(mode))
|
|
128
|
+
if not os.path.exists(exe):
|
|
129
|
+
raise RuntimeError(f"Cosmic Clarity executable not found:\n{exe}")
|
|
130
|
+
|
|
131
|
+
# Build args
|
|
132
|
+
args = []
|
|
133
|
+
if mode == "sharpen":
|
|
134
|
+
if not self.p.get("gpu", True):
|
|
135
|
+
args.append("--disable_gpu")
|
|
136
|
+
if self.p.get("auto_psf", True):
|
|
137
|
+
args.append("--auto_detect_psf")
|
|
138
|
+
args += [
|
|
139
|
+
"--sharpening_mode", self.p.get("sharpening_mode", "Both"),
|
|
140
|
+
"--stellar_amount", f"{float(self.p.get('stellar_amount', 0.50)):.2f}",
|
|
141
|
+
"--nonstellar_strength", f"{float(self.p.get('nonstellar_psf', 3.0)):.1f}",
|
|
142
|
+
"--nonstellar_amount", f"{float(self.p.get('nonstellar_amount', 0.50)):.2f}",
|
|
143
|
+
]
|
|
144
|
+
# NEW: per-channel sharpen flag from preset
|
|
145
|
+
if self.p.get("sharpen_channels_separately", False):
|
|
146
|
+
args.append("--sharpen_channels_separately")
|
|
147
|
+
elif mode == "denoise":
|
|
148
|
+
if not self.p.get("gpu", True): args.append("--disable_gpu")
|
|
149
|
+
if self.p.get("separate_channels", False): args.append("--separate_channels")
|
|
150
|
+
args += [
|
|
151
|
+
"--denoise_strength", f"{float(self.p.get('denoise_luma', 0.50)):.2f}",
|
|
152
|
+
"--color_denoise_strength", f"{float(self.p.get('denoise_color', 0.50)):.2f}",
|
|
153
|
+
"--denoise_mode", self.p.get("denoise_mode", "full"),
|
|
154
|
+
]
|
|
155
|
+
elif mode == "superres":
|
|
156
|
+
scale = int(self.p.get("scale", 2))
|
|
157
|
+
args = ["--input", staged_input, "--output_dir", out_dir,
|
|
158
|
+
"--scale", str(scale), "--model_dir", self.root]
|
|
159
|
+
else:
|
|
160
|
+
raise RuntimeError(f"Unknown mode: {mode}")
|
|
161
|
+
|
|
162
|
+
self.log.emit(f"Launching: {os.path.basename(exe)} {' '.join(args)}")
|
|
163
|
+
self.progress.emit(0)
|
|
164
|
+
|
|
165
|
+
# Run process and stream stdout
|
|
166
|
+
self._proc = subprocess.Popen([exe] + args, cwd=self.root,
|
|
167
|
+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
168
|
+
text=True, universal_newlines=True)
|
|
169
|
+
try:
|
|
170
|
+
for line in self._proc.stdout:
|
|
171
|
+
if self._stop: break
|
|
172
|
+
self._emit_progress_line(line)
|
|
173
|
+
except Exception:
|
|
174
|
+
pass
|
|
175
|
+
rc = self._proc.wait()
|
|
176
|
+
self._proc = None
|
|
177
|
+
if self._stop: raise RuntimeError("Cancelled")
|
|
178
|
+
if rc != 0:
|
|
179
|
+
raise RuntimeError(f"{os.path.basename(exe)} exited with code {rc}")
|
|
180
|
+
|
|
181
|
+
# Wait for produced file
|
|
182
|
+
if mode == "superres":
|
|
183
|
+
scale = int(self.p.get("scale", 2))
|
|
184
|
+
pat = os.path.join(out_dir, f"{base}_upscaled{scale}.*")
|
|
185
|
+
else:
|
|
186
|
+
pat = os.path.join(out_dir, f"{base}{suffix}.*")
|
|
187
|
+
self.log.emit("Waiting for output file…")
|
|
188
|
+
out_path = self._wait_for_file(pat, timeout=1800)
|
|
189
|
+
if not out_path:
|
|
190
|
+
raise RuntimeError("Output file not found.")
|
|
191
|
+
|
|
192
|
+
arr, _, _, _ = load_image(out_path)
|
|
193
|
+
if arr is None:
|
|
194
|
+
raise RuntimeError("Failed to load output image.")
|
|
195
|
+
|
|
196
|
+
result = np.asarray(arr).astype(np.float32, copy=False)
|
|
197
|
+
self.progress.emit(100)
|
|
198
|
+
|
|
199
|
+
# Stage as next input if more to do
|
|
200
|
+
if (mode, suffix) != self.ops[-1]:
|
|
201
|
+
save_image(result, in_path, "tiff", "32-bit floating point",
|
|
202
|
+
getattr(self.doc, "original_header", None),
|
|
203
|
+
getattr(self.doc, "is_mono", False))
|
|
204
|
+
staged_input = in_path
|
|
205
|
+
|
|
206
|
+
# Cleanup produced out file
|
|
207
|
+
try:
|
|
208
|
+
if os.path.exists(out_path): os.remove(out_path)
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
# Cleanup input
|
|
213
|
+
try:
|
|
214
|
+
if os.path.exists(in_path): os.remove(in_path)
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
if result is None:
|
|
219
|
+
raise RuntimeError("No result produced.")
|
|
220
|
+
self.finished_ok.emit(result)
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
self.failed.emit(str(e))
|
|
224
|
+
|
|
225
|
+
def _wait_for_file(self, pattern: str, timeout: float = 1800.0, poll: float = 0.25):
|
|
226
|
+
"""
|
|
227
|
+
Wait for a file matching glob `pattern`. Returns most recent match or "".
|
|
228
|
+
"""
|
|
229
|
+
t0 = time.time()
|
|
230
|
+
last = ""
|
|
231
|
+
while time.time() - t0 < timeout:
|
|
232
|
+
matches = glob.glob(pattern)
|
|
233
|
+
if matches:
|
|
234
|
+
try:
|
|
235
|
+
# pick newest file (mtime)
|
|
236
|
+
matches.sort(key=lambda p: os.path.getmtime(p), reverse=True)
|
|
237
|
+
except Exception:
|
|
238
|
+
matches.sort()
|
|
239
|
+
last = matches[0]
|
|
240
|
+
# make sure it’s non-zero and stable-ish
|
|
241
|
+
try:
|
|
242
|
+
if os.path.getsize(last) > 0:
|
|
243
|
+
return last
|
|
244
|
+
except Exception:
|
|
245
|
+
return last
|
|
246
|
+
time.sleep(poll)
|
|
247
|
+
return ""
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ---------------- Public entry ----------------
|
|
251
|
+
def run_cosmicclarity_via_preset(main, preset: dict | None = None, *, doc=None):
|
|
252
|
+
"""Run CC headlessly by driving the same pipeline as the Execute button."""
|
|
253
|
+
p = dict(preset or {})
|
|
254
|
+
|
|
255
|
+
# ---- Record for Replay Last Action ----
|
|
256
|
+
try:
|
|
257
|
+
remember = getattr(main, "remember_last_headless_command", None)
|
|
258
|
+
if remember is None:
|
|
259
|
+
remember = getattr(main, "_remember_last_headless_command", None)
|
|
260
|
+
if callable(remember):
|
|
261
|
+
remember("cosmic_clarity", p, description="Cosmic Clarity")
|
|
262
|
+
else:
|
|
263
|
+
setattr(main, "_last_headless_command", {
|
|
264
|
+
"command_id": "cosmic_clarity",
|
|
265
|
+
"preset": dict(p),
|
|
266
|
+
})
|
|
267
|
+
except Exception:
|
|
268
|
+
pass
|
|
269
|
+
# --------------------------------------
|
|
270
|
+
|
|
271
|
+
# Guard so users can’t open another CC panel while this runs
|
|
272
|
+
setattr(main, "_cosmicclarity_headless_running", True)
|
|
273
|
+
setattr(main, "_cosmicclarity_guard", True)
|
|
274
|
+
s = QSettings()
|
|
275
|
+
try:
|
|
276
|
+
s.setValue("cc/headless_in_progress", True); s.sync()
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
# Prefer the explicit doc (from target_sw); otherwise fall back to active
|
|
282
|
+
if doc is None:
|
|
283
|
+
doc = getattr(main, "_active_doc", None)
|
|
284
|
+
if callable(doc):
|
|
285
|
+
doc = doc()
|
|
286
|
+
|
|
287
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
288
|
+
QMessageBox.warning(main, "Cosmic Clarity", "Load an image first.")
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
dlg = CosmicClarityDialogPro(main, doc, headless=True, bypass_guard=True)
|
|
292
|
+
if getattr(dlg, "_headless", False) is not True:
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
dlg.apply_preset(p)
|
|
297
|
+
except Exception:
|
|
298
|
+
mode = str(p.get("mode","sharpen")).lower()
|
|
299
|
+
dlg.cmb_mode.setCurrentIndex({"sharpen":0,"denoise":1,"both":2,"superres":3}.get(mode,0))
|
|
300
|
+
dlg.cmb_gpu.setCurrentIndex(0 if p.get("gpu", True) else 1)
|
|
301
|
+
dlg.cmb_target.setCurrentIndex(1 if p.get("create_new_view", False) else 0)
|
|
302
|
+
|
|
303
|
+
dlg._run_main()
|
|
304
|
+
|
|
305
|
+
loop = QEventLoop()
|
|
306
|
+
dlg.finished.connect(loop.quit)
|
|
307
|
+
loop.exec_() if hasattr(loop, "exec_") else loop.exec()
|
|
308
|
+
|
|
309
|
+
finally:
|
|
310
|
+
try:
|
|
311
|
+
s.setValue("cc/headless_in_progress", False); s.sync()
|
|
312
|
+
except Exception:
|
|
313
|
+
pass
|
|
314
|
+
for name in ("_cosmicclarity_headless_running", "_cosmicclarity_guard"):
|
|
315
|
+
try:
|
|
316
|
+
delattr(main, name)
|
|
317
|
+
except Exception:
|
|
318
|
+
setattr(main, name, False)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# ---------------- Optional: tiny preset editor for the shortcut button ----------------
|
|
323
|
+
class _CosmicClarityPresetDialog(QDialog):
|
|
324
|
+
"""
|
|
325
|
+
Minimal preset editor for the shortcut:
|
|
326
|
+
Mode, GPU, create_new_view plus a few key params per mode.
|
|
327
|
+
"""
|
|
328
|
+
def __init__(self, parent=None, initial: dict | None = None):
|
|
329
|
+
super().__init__(parent)
|
|
330
|
+
self.setWindowTitle("Cosmic Clarity — Preset")
|
|
331
|
+
p = dict(initial or {})
|
|
332
|
+
f = QFormLayout(self)
|
|
333
|
+
|
|
334
|
+
self.mode = QComboBox(); self.mode.addItems(["sharpen", "denoise", "both", "superres"])
|
|
335
|
+
self.mode.setCurrentText(str(p.get("mode", "sharpen")))
|
|
336
|
+
f.addRow("Mode:", self.mode)
|
|
337
|
+
|
|
338
|
+
self.gpu = QCheckBox("Use GPU"); self.gpu.setChecked(bool(p.get("gpu", True)))
|
|
339
|
+
f.addRow(self.gpu)
|
|
340
|
+
|
|
341
|
+
self.newview = QCheckBox("Create new view"); self.newview.setChecked(bool(p.get("create_new_view", False)))
|
|
342
|
+
f.addRow(self.newview)
|
|
343
|
+
|
|
344
|
+
# Sharpen
|
|
345
|
+
self.sh_mode = QComboBox(); self.sh_mode.addItems(["Both", "Stellar Only", "Non-Stellar Only"])
|
|
346
|
+
self.sh_mode.setCurrentText(p.get("sharpening_mode", "Both"))
|
|
347
|
+
self.auto_psf = QCheckBox("Auto PSF"); self.auto_psf.setChecked(bool(p.get("auto_psf", True)))
|
|
348
|
+
self.psf = QDoubleSpinBox(); self.psf.setRange(1.0, 8.0); self.psf.setSingleStep(0.1); self.psf.setValue(float(p.get("nonstellar_psf", 3.0)))
|
|
349
|
+
self.st_amt = QDoubleSpinBox(); self.st_amt.setRange(0.0, 1.0); self.st_amt.setSingleStep(0.05); self.st_amt.setValue(float(p.get("stellar_amount", 0.50)))
|
|
350
|
+
self.nst_amt= QDoubleSpinBox(); self.nst_amt.setRange(0.0, 1.0); self.nst_amt.setSingleStep(0.05); self.nst_amt.setValue(float(p.get("nonstellar_amount", 0.50)))
|
|
351
|
+
f.addRow("Sharpening Mode:", self.sh_mode)
|
|
352
|
+
f.addRow(self.auto_psf)
|
|
353
|
+
f.addRow("Non-stellar PSF:", self.psf)
|
|
354
|
+
f.addRow("Stellar Amount:", self.st_amt)
|
|
355
|
+
f.addRow("Non-stellar Amount:", self.nst_amt)
|
|
356
|
+
|
|
357
|
+
# NEW: Sharpen RGB channels separately
|
|
358
|
+
self.sh_sep = QCheckBox("Sharpen RGB channels separately")
|
|
359
|
+
self.sh_sep.setChecked(bool(p.get("sharpen_channels_separately", False)))
|
|
360
|
+
f.addRow(self.sh_sep)
|
|
361
|
+
# Denoise
|
|
362
|
+
self.dn_lum = QDoubleSpinBox(); self.dn_lum.setRange(0.0, 1.0); self.dn_lum.setSingleStep(0.05); self.dn_lum.setValue(float(p.get("denoise_luma", 0.50)))
|
|
363
|
+
self.dn_col = QDoubleSpinBox(); self.dn_col.setRange(0.0, 1.0); self.dn_col.setSingleStep(0.05); self.dn_col.setValue(float(p.get("denoise_color", 0.50)))
|
|
364
|
+
self.dn_mode= QComboBox(); self.dn_mode.addItems(["full","luminance"]); self.dn_mode.setCurrentText(p.get("denoise_mode", "full"))
|
|
365
|
+
self.dn_sep = QCheckBox("Separate RGB channels"); self.dn_sep.setChecked(bool(p.get("separate_channels", False)))
|
|
366
|
+
f.addRow("Denoise Luma:", self.dn_lum)
|
|
367
|
+
f.addRow("Denoise Color:", self.dn_col)
|
|
368
|
+
f.addRow("Denoise Mode:", self.dn_mode)
|
|
369
|
+
f.addRow(self.dn_sep)
|
|
370
|
+
|
|
371
|
+
# Super-res
|
|
372
|
+
self.scale = QComboBox(); self.scale.addItems(["2","3","4"]); self.scale.setCurrentText(str(int(p.get("scale", 2))))
|
|
373
|
+
f.addRow("Super-Res Scale:", self.scale)
|
|
374
|
+
|
|
375
|
+
# OK/Cancel
|
|
376
|
+
btns = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, parent=self)
|
|
377
|
+
btns.accepted.connect(self.accept); btns.rejected.connect(self.reject)
|
|
378
|
+
f.addRow(btns)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def result_dict(self) -> dict:
|
|
382
|
+
m = self.mode.currentText()
|
|
383
|
+
out = {
|
|
384
|
+
"mode": m,
|
|
385
|
+
"gpu": bool(self.gpu.isChecked()),
|
|
386
|
+
"create_new_view": bool(self.newview.isChecked()),
|
|
387
|
+
}
|
|
388
|
+
if m in ("sharpen","both"):
|
|
389
|
+
out.update({
|
|
390
|
+
"sharpening_mode": self.sh_mode.currentText(),
|
|
391
|
+
"auto_psf": bool(self.auto_psf.isChecked()),
|
|
392
|
+
"nonstellar_psf": float(self.psf.value()),
|
|
393
|
+
"stellar_amount": float(self.st_amt.value()),
|
|
394
|
+
"nonstellar_amount": float(self.nst_amt.value()),
|
|
395
|
+
# NEW: propagate sharpen-separate into preset dict
|
|
396
|
+
"sharpen_channels_separately": bool(self.sh_sep.isChecked()),
|
|
397
|
+
})
|
|
398
|
+
if m in ("denoise","both"):
|
|
399
|
+
out.update({
|
|
400
|
+
"denoise_luma": float(self.dn_lum.value()),
|
|
401
|
+
"denoise_color": float(self.dn_col.value()),
|
|
402
|
+
"denoise_mode": self.dn_mode.currentText(),
|
|
403
|
+
"separate_channels": bool(self.dn_sep.isChecked()),
|
|
404
|
+
})
|
|
405
|
+
if m == "superres":
|
|
406
|
+
out["scale"] = int(self.scale.currentText())
|
|
407
|
+
return out
|