setiastrosuitepro 1.6.0__py3-none-any.whl → 1.6.4.post1__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.

Files changed (293) hide show
  1. setiastro/data/SASP_data.fits +0 -0
  2. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  3. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  4. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  5. setiastro/data/catalogs/cali2.csv +63 -0
  6. setiastro/data/catalogs/cali2color.csv +65 -0
  7. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  8. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  9. setiastro/data/catalogs/detected_stars.csv +24784 -0
  10. setiastro/data/catalogs/fits_header_data.csv +46 -0
  11. setiastro/data/catalogs/test.csv +8 -0
  12. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  13. setiastro/images/Astro_Spikes.png +0 -0
  14. setiastro/images/Background_startup.jpg +0 -0
  15. setiastro/images/HRDiagram.png +0 -0
  16. setiastro/images/LExtract.png +0 -0
  17. setiastro/images/LInsert.png +0 -0
  18. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  19. setiastro/images/RGB080604.png +0 -0
  20. setiastro/images/abeicon.png +0 -0
  21. setiastro/images/aberration.png +0 -0
  22. setiastro/images/andromedatry.png +0 -0
  23. setiastro/images/andromedatry_satellited.png +0 -0
  24. setiastro/images/annotated.png +0 -0
  25. setiastro/images/aperture.png +0 -0
  26. setiastro/images/astrosuite.ico +0 -0
  27. setiastro/images/astrosuite.png +0 -0
  28. setiastro/images/astrosuitepro.icns +0 -0
  29. setiastro/images/astrosuitepro.ico +0 -0
  30. setiastro/images/astrosuitepro.png +0 -0
  31. setiastro/images/background.png +0 -0
  32. setiastro/images/background2.png +0 -0
  33. setiastro/images/benchmark.png +0 -0
  34. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  35. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  36. setiastro/images/blaster.png +0 -0
  37. setiastro/images/blink.png +0 -0
  38. setiastro/images/clahe.png +0 -0
  39. setiastro/images/collage.png +0 -0
  40. setiastro/images/colorwheel.png +0 -0
  41. setiastro/images/contsub.png +0 -0
  42. setiastro/images/convo.png +0 -0
  43. setiastro/images/copyslot.png +0 -0
  44. setiastro/images/cosmic.png +0 -0
  45. setiastro/images/cosmicsat.png +0 -0
  46. setiastro/images/crop1.png +0 -0
  47. setiastro/images/cropicon.png +0 -0
  48. setiastro/images/curves.png +0 -0
  49. setiastro/images/cvs.png +0 -0
  50. setiastro/images/debayer.png +0 -0
  51. setiastro/images/denoise_cnn_custom.png +0 -0
  52. setiastro/images/denoise_cnn_graph.png +0 -0
  53. setiastro/images/disk.png +0 -0
  54. setiastro/images/dse.png +0 -0
  55. setiastro/images/exoicon.png +0 -0
  56. setiastro/images/eye.png +0 -0
  57. setiastro/images/fliphorizontal.png +0 -0
  58. setiastro/images/flipvertical.png +0 -0
  59. setiastro/images/font.png +0 -0
  60. setiastro/images/freqsep.png +0 -0
  61. setiastro/images/functionbundle.png +0 -0
  62. setiastro/images/graxpert.png +0 -0
  63. setiastro/images/green.png +0 -0
  64. setiastro/images/gridicon.png +0 -0
  65. setiastro/images/halo.png +0 -0
  66. setiastro/images/hdr.png +0 -0
  67. setiastro/images/histogram.png +0 -0
  68. setiastro/images/hubble.png +0 -0
  69. setiastro/images/imagecombine.png +0 -0
  70. setiastro/images/invert.png +0 -0
  71. setiastro/images/isophote.png +0 -0
  72. setiastro/images/isophote_demo_figure.png +0 -0
  73. setiastro/images/isophote_demo_image.png +0 -0
  74. setiastro/images/isophote_demo_model.png +0 -0
  75. setiastro/images/isophote_demo_residual.png +0 -0
  76. setiastro/images/jwstpupil.png +0 -0
  77. setiastro/images/linearfit.png +0 -0
  78. setiastro/images/livestacking.png +0 -0
  79. setiastro/images/mask.png +0 -0
  80. setiastro/images/maskapply.png +0 -0
  81. setiastro/images/maskcreate.png +0 -0
  82. setiastro/images/maskremove.png +0 -0
  83. setiastro/images/morpho.png +0 -0
  84. setiastro/images/mosaic.png +0 -0
  85. setiastro/images/multiscale_decomp.png +0 -0
  86. setiastro/images/nbtorgb.png +0 -0
  87. setiastro/images/neutral.png +0 -0
  88. setiastro/images/nuke.png +0 -0
  89. setiastro/images/openfile.png +0 -0
  90. setiastro/images/pedestal.png +0 -0
  91. setiastro/images/pen.png +0 -0
  92. setiastro/images/pixelmath.png +0 -0
  93. setiastro/images/platesolve.png +0 -0
  94. setiastro/images/ppp.png +0 -0
  95. setiastro/images/pro.png +0 -0
  96. setiastro/images/project.png +0 -0
  97. setiastro/images/psf.png +0 -0
  98. setiastro/images/redo.png +0 -0
  99. setiastro/images/redoicon.png +0 -0
  100. setiastro/images/rescale.png +0 -0
  101. setiastro/images/rgbalign.png +0 -0
  102. setiastro/images/rgbcombo.png +0 -0
  103. setiastro/images/rgbextract.png +0 -0
  104. setiastro/images/rotate180.png +0 -0
  105. setiastro/images/rotatearbitrary.png +0 -0
  106. setiastro/images/rotateclockwise.png +0 -0
  107. setiastro/images/rotatecounterclockwise.png +0 -0
  108. setiastro/images/satellite.png +0 -0
  109. setiastro/images/script.png +0 -0
  110. setiastro/images/selectivecolor.png +0 -0
  111. setiastro/images/simbad.png +0 -0
  112. setiastro/images/slot0.png +0 -0
  113. setiastro/images/slot1.png +0 -0
  114. setiastro/images/slot2.png +0 -0
  115. setiastro/images/slot3.png +0 -0
  116. setiastro/images/slot4.png +0 -0
  117. setiastro/images/slot5.png +0 -0
  118. setiastro/images/slot6.png +0 -0
  119. setiastro/images/slot7.png +0 -0
  120. setiastro/images/slot8.png +0 -0
  121. setiastro/images/slot9.png +0 -0
  122. setiastro/images/spcc.png +0 -0
  123. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  124. setiastro/images/spinner.gif +0 -0
  125. setiastro/images/stacking.png +0 -0
  126. setiastro/images/staradd.png +0 -0
  127. setiastro/images/staralign.png +0 -0
  128. setiastro/images/starnet.png +0 -0
  129. setiastro/images/starregistration.png +0 -0
  130. setiastro/images/starspike.png +0 -0
  131. setiastro/images/starstretch.png +0 -0
  132. setiastro/images/statstretch.png +0 -0
  133. setiastro/images/supernova.png +0 -0
  134. setiastro/images/uhs.png +0 -0
  135. setiastro/images/undoicon.png +0 -0
  136. setiastro/images/upscale.png +0 -0
  137. setiastro/images/viewbundle.png +0 -0
  138. setiastro/images/whitebalance.png +0 -0
  139. setiastro/images/wimi_icon_256x256.png +0 -0
  140. setiastro/images/wimilogo.png +0 -0
  141. setiastro/images/wims.png +0 -0
  142. setiastro/images/wrench_icon.png +0 -0
  143. setiastro/images/xisfliberator.png +0 -0
  144. setiastro/qml/ResourceMonitor.qml +126 -0
  145. setiastro/saspro/__main__.py +228 -67
  146. setiastro/saspro/_generated/build_info.py +2 -1
  147. setiastro/saspro/abe.py +76 -25
  148. setiastro/saspro/aberration_ai.py +14 -14
  149. setiastro/saspro/add_stars.py +15 -12
  150. setiastro/saspro/astrobin_exporter.py +61 -58
  151. setiastro/saspro/astrospike_python.py +3 -1
  152. setiastro/saspro/autostretch.py +4 -2
  153. setiastro/saspro/backgroundneutral.py +65 -14
  154. setiastro/saspro/batch_convert.py +8 -5
  155. setiastro/saspro/batch_renamer.py +39 -36
  156. setiastro/saspro/blemish_blaster.py +15 -12
  157. setiastro/saspro/blink_comparator_pro.py +605 -379
  158. setiastro/saspro/cheat_sheet.py +62 -17
  159. setiastro/saspro/clahe.py +34 -8
  160. setiastro/saspro/comet_stacking.py +103 -38
  161. setiastro/saspro/common_tr.py +107 -0
  162. setiastro/saspro/continuum_subtract.py +7 -7
  163. setiastro/saspro/convo.py +12 -9
  164. setiastro/saspro/copyastro.py +3 -0
  165. setiastro/saspro/cosmicclarity.py +77 -52
  166. setiastro/saspro/crop_dialog_pro.py +80 -45
  167. setiastro/saspro/curve_editor_pro.py +51 -33
  168. setiastro/saspro/debayer.py +6 -3
  169. setiastro/saspro/doc_manager.py +49 -19
  170. setiastro/saspro/exoplanet_detector.py +11 -11
  171. setiastro/saspro/fitsmodifier.py +48 -44
  172. setiastro/saspro/fix_bom.py +32 -0
  173. setiastro/saspro/frequency_separation.py +18 -12
  174. setiastro/saspro/function_bundle.py +18 -16
  175. setiastro/saspro/generate_translations.py +3092 -0
  176. setiastro/saspro/ghs_dialog_pro.py +19 -16
  177. setiastro/saspro/graxpert.py +3 -0
  178. setiastro/saspro/gui/main_window.py +471 -126
  179. setiastro/saspro/gui/mixins/dock_mixin.py +123 -11
  180. setiastro/saspro/gui/mixins/file_mixin.py +25 -20
  181. setiastro/saspro/gui/mixins/geometry_mixin.py +115 -15
  182. setiastro/saspro/gui/mixins/header_mixin.py +6 -6
  183. setiastro/saspro/gui/mixins/mask_mixin.py +8 -8
  184. setiastro/saspro/gui/mixins/menu_mixin.py +62 -33
  185. setiastro/saspro/gui/mixins/toolbar_mixin.py +382 -226
  186. setiastro/saspro/gui/mixins/update_mixin.py +26 -26
  187. setiastro/saspro/gui/statistics_dialog.py +47 -0
  188. setiastro/saspro/halobgon.py +29 -3
  189. setiastro/saspro/header_viewer.py +21 -18
  190. setiastro/saspro/histogram.py +29 -26
  191. setiastro/saspro/history_explorer.py +2 -0
  192. setiastro/saspro/i18n.py +168 -0
  193. setiastro/saspro/image_combine.py +3 -0
  194. setiastro/saspro/image_peeker_pro.py +52 -44
  195. setiastro/saspro/imageops/stretch.py +5 -13
  196. setiastro/saspro/isophote.py +3 -0
  197. setiastro/saspro/legacy/numba_utils.py +64 -47
  198. setiastro/saspro/linear_fit.py +3 -0
  199. setiastro/saspro/live_stacking.py +13 -2
  200. setiastro/saspro/mask_creation.py +180 -22
  201. setiastro/saspro/mfdeconv.py +5 -0
  202. setiastro/saspro/morphology.py +38 -13
  203. setiastro/saspro/multiscale_decomp.py +713 -256
  204. setiastro/saspro/nbtorgb_stars.py +12 -2
  205. setiastro/saspro/numba_utils.py +149 -48
  206. setiastro/saspro/ops/scripts.py +77 -17
  207. setiastro/saspro/ops/settings.py +177 -100
  208. setiastro/saspro/perfect_palette_picker.py +25 -7
  209. setiastro/saspro/pixelmath.py +114 -110
  210. setiastro/saspro/plate_solver.py +118 -108
  211. setiastro/saspro/remove_green.py +24 -7
  212. setiastro/saspro/remove_stars.py +136 -162
  213. setiastro/saspro/remove_stars_preset.py +55 -13
  214. setiastro/saspro/resources.py +46 -15
  215. setiastro/saspro/rgb_combination.py +19 -18
  216. setiastro/saspro/rgbalign.py +11 -11
  217. setiastro/saspro/save_options.py +5 -4
  218. setiastro/saspro/selective_color.py +84 -25
  219. setiastro/saspro/sfcc.py +119 -72
  220. setiastro/saspro/shortcuts.py +345 -36
  221. setiastro/saspro/signature_insert.py +4 -1
  222. setiastro/saspro/stacking_suite.py +2066 -1119
  223. setiastro/saspro/star_alignment.py +291 -331
  224. setiastro/saspro/star_spikes.py +137 -53
  225. setiastro/saspro/star_stretch.py +47 -10
  226. setiastro/saspro/stat_stretch.py +52 -16
  227. setiastro/saspro/status_log_dock.py +1 -1
  228. setiastro/saspro/subwindow.py +97 -36
  229. setiastro/saspro/supernovaasteroidhunter.py +68 -61
  230. setiastro/saspro/swap_manager.py +77 -42
  231. setiastro/saspro/translations/all_source_strings.json +4726 -0
  232. setiastro/saspro/translations/ar_translations.py +4096 -0
  233. setiastro/saspro/translations/de_translations.py +3728 -0
  234. setiastro/saspro/translations/es_translations.py +4169 -0
  235. setiastro/saspro/translations/fr_translations.py +4090 -0
  236. setiastro/saspro/translations/hi_translations.py +3803 -0
  237. setiastro/saspro/translations/integrate_translations.py +271 -0
  238. setiastro/saspro/translations/it_translations.py +4728 -0
  239. setiastro/saspro/translations/ja_translations.py +3834 -0
  240. setiastro/saspro/translations/pt_translations.py +3847 -0
  241. setiastro/saspro/translations/ru_translations.py +3082 -0
  242. setiastro/saspro/translations/saspro_ar.qm +0 -0
  243. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  244. setiastro/saspro/translations/saspro_de.qm +0 -0
  245. setiastro/saspro/translations/saspro_de.ts +14548 -0
  246. setiastro/saspro/translations/saspro_es.qm +0 -0
  247. setiastro/saspro/translations/saspro_es.ts +16202 -0
  248. setiastro/saspro/translations/saspro_fr.qm +0 -0
  249. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  250. setiastro/saspro/translations/saspro_hi.qm +0 -0
  251. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  252. setiastro/saspro/translations/saspro_it.qm +0 -0
  253. setiastro/saspro/translations/saspro_it.ts +19046 -0
  254. setiastro/saspro/translations/saspro_ja.qm +0 -0
  255. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  256. setiastro/saspro/translations/saspro_pt.qm +0 -0
  257. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  258. setiastro/saspro/translations/saspro_ru.qm +0 -0
  259. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  260. setiastro/saspro/translations/saspro_sw.qm +0 -0
  261. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  262. setiastro/saspro/translations/saspro_uk.qm +0 -0
  263. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  264. setiastro/saspro/translations/saspro_zh.qm +0 -0
  265. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  266. setiastro/saspro/translations/sw_translations.py +3897 -0
  267. setiastro/saspro/translations/uk_translations.py +3929 -0
  268. setiastro/saspro/translations/zh_translations.py +3910 -0
  269. setiastro/saspro/versioning.py +77 -0
  270. setiastro/saspro/view_bundle.py +20 -17
  271. setiastro/saspro/wavescale_hdr.py +54 -33
  272. setiastro/saspro/wavescale_hdr_preset.py +6 -5
  273. setiastro/saspro/wavescalede.py +54 -31
  274. setiastro/saspro/wavescalede_preset.py +9 -7
  275. setiastro/saspro/whitebalance.py +58 -22
  276. setiastro/saspro/widgets/common_utilities.py +12 -11
  277. setiastro/saspro/widgets/minigame/game.js +991 -0
  278. setiastro/saspro/widgets/minigame/index.html +53 -0
  279. setiastro/saspro/widgets/minigame/style.css +241 -0
  280. setiastro/saspro/widgets/preview_dialogs.py +8 -8
  281. setiastro/saspro/widgets/resource_monitor.py +263 -0
  282. setiastro/saspro/widgets/spinboxes.py +18 -0
  283. setiastro/saspro/widgets/wavelet_utils.py +52 -20
  284. setiastro/saspro/wimi.py +7996 -0
  285. setiastro/saspro/wims.py +578 -0
  286. setiastro/saspro/window_shelf.py +2 -2
  287. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/METADATA +15 -3
  288. setiastrosuitepro-1.6.4.post1.dist-info/RECORD +368 -0
  289. setiastrosuitepro-1.6.0.dist-info/RECORD +0 -174
  290. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/WHEEL +0 -0
  291. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/entry_points.txt +0 -0
  292. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/LICENSE +0 -0
  293. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/license.txt +0 -0
@@ -15,6 +15,9 @@ class CopyAstrometryDialog(QDialog):
15
15
  def __init__(self, parent=None, target=None):
16
16
  super().__init__(parent)
17
17
  self.setWindowTitle("Copy Astrometric Solution")
18
+ self.setWindowFlag(Qt.WindowType.Window, True)
19
+ self.setWindowModality(Qt.WindowModality.NonModal)
20
+ self.setModal(False)
18
21
  self.setMinimumWidth(420)
19
22
 
20
23
  self._mw = parent
@@ -270,7 +270,10 @@ class CosmicClarityDialogPro(QDialog):
270
270
  logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
271
271
  QTimer.singleShot(0, self.reject)
272
272
  return
273
- self.setWindowTitle("Cosmic Clarity")
273
+ self.setWindowTitle(self.tr("Cosmic Clarity"))
274
+ self.setWindowFlag(Qt.WindowType.Window, True)
275
+ self.setWindowModality(Qt.WindowModality.NonModal)
276
+ self.setModal(False)
274
277
  if icon:
275
278
  try: self.setWindowIcon(icon)
276
279
  except Exception as e:
@@ -285,19 +288,19 @@ class CosmicClarityDialogPro(QDialog):
285
288
  v = QVBoxLayout(self)
286
289
 
287
290
  # ---------------- Controls ----------------
288
- grp = QGroupBox("Parameters")
291
+ grp = QGroupBox(self.tr("Parameters"))
289
292
  grid = QGridLayout(grp)
290
293
 
291
294
  # Mode
292
- grid.addWidget(QLabel("Mode:"), 0, 0)
295
+ grid.addWidget(QLabel(self.tr("Mode:")), 0, 0)
293
296
  self.cmb_mode = QComboBox()
294
297
  self.cmb_mode.addItems(["Sharpen", "Denoise", "Both", "Super Resolution"])
295
298
  self.cmb_mode.currentIndexChanged.connect(self._mode_changed)
296
299
  grid.addWidget(self.cmb_mode, 0, 1, 1, 2)
297
300
 
298
301
  # GPU
299
- grid.addWidget(QLabel("Use GPU:"), 1, 0)
300
- self.cmb_gpu = QComboBox(); self.cmb_gpu.addItems(["Yes", "No"])
302
+ grid.addWidget(QLabel(self.tr("Use GPU:")), 1, 0)
303
+ self.cmb_gpu = QComboBox(); self.cmb_gpu.addItems([self.tr("Yes"), self.tr("No")])
301
304
  grid.addWidget(self.cmb_gpu, 1, 1)
302
305
 
303
306
  # Sharpen block
@@ -364,8 +367,8 @@ class CosmicClarityDialogPro(QDialog):
364
367
 
365
368
  # Buttons
366
369
  row = QHBoxLayout()
367
- b_run = QPushButton("Execute"); b_run.clicked.connect(self._run_main)
368
- b_close = QPushButton("Close"); b_close.clicked.connect(self.reject)
370
+ b_run = QPushButton(self.tr("Execute")); b_run.clicked.connect(self._run_main)
371
+ b_close = QPushButton(self.tr("Close")); b_close.clicked.connect(self.reject)
369
372
  row.addStretch(1); row.addWidget(b_run); row.addWidget(b_close)
370
373
  v.addLayout(row)
371
374
 
@@ -632,7 +635,53 @@ class CosmicClarityDialogPro(QDialog):
632
635
  if self._wait: self._wait.close(); self._wait = None
633
636
  if self._wait_thread: self._wait_thread.stop(); self._wait_thread = None
634
637
 
635
- # Load processed image we just got
638
+ has_more = bool(self._op_queue)
639
+
640
+ # --- Optimization: Chained Execution Fast Path ---
641
+ # If we have more steps, skip the expensive load/display/save cycle.
642
+ # Just move the output file to be the input for the next step.
643
+ if has_more:
644
+ if not out_path or not os.path.exists(out_path):
645
+ QMessageBox.critical(self, "Cosmic Clarity", "Output file missing during chain execution.")
646
+ self._op_queue.clear()
647
+ return
648
+
649
+ base = self._base_name()
650
+ next_in = os.path.join(self.cosmic_root, "input", f"{base}.tif")
651
+ prev_in = getattr(self, "_current_input", None)
652
+
653
+ try:
654
+ # Direct move/copy instead of decode+encode
655
+ if os.path.abspath(out_path) != os.path.abspath(next_in):
656
+ # Windows cannot atomic replace if target exists via os.rename usually,
657
+ # but shutil.move is generally robust.
658
+ # We remove target first to be sure.
659
+ if os.path.exists(next_in):
660
+ os.remove(next_in)
661
+ shutil.move(out_path, next_in)
662
+
663
+ # Ensure stability of the *new* input
664
+ if not _wait_stable_file(next_in):
665
+ QMessageBox.critical(self, "Cosmic Clarity", "Staged input for next step is unstable.")
666
+ self._op_queue.clear()
667
+ return
668
+
669
+ self._current_input = next_in
670
+
671
+ # Cleanup previous input if distinct
672
+ if prev_in and prev_in != next_in and os.path.exists(prev_in):
673
+ os.remove(prev_in)
674
+
675
+ except Exception as e:
676
+ QMessageBox.critical(self, "Cosmic Clarity", f"Failed to stage next step:\n{e}")
677
+ self._op_queue.clear()
678
+ return
679
+
680
+ # Trigger next step immediately
681
+ QTimer.singleShot(50, self._run_next)
682
+ return
683
+
684
+ # --- Final Step (or Single Step): Load and Display ---
636
685
  try:
637
686
  img, hdr, bd, mono = load_image(out_path)
638
687
  if img is None:
@@ -643,61 +692,34 @@ class CosmicClarityDialogPro(QDialog):
643
692
 
644
693
  dest = img.astype(np.float32, copy=False)
645
694
 
646
- # Apply to document (so the user sees the step result immediately)
695
+ # Apply to document
647
696
  step_title = f"Cosmic Clarity – {mode.title()}"
648
697
  create_new = (self.cmb_target.currentIndex() == 1)
649
698
 
650
699
  if create_new:
651
700
  ok = self._spawn_new_doc_from_numpy(dest, step_title)
652
701
  if not ok:
653
- # fall back to overwriting if we couldn’t spawn a new doc
654
702
  self._apply_to_active(dest, step_title)
655
703
  else:
656
704
  self._apply_to_active(dest, step_title)
657
705
 
658
- # Will we run another step (i.e., we're in "Both")?
659
- has_more = bool(self._op_queue)
660
- base = self._base_name()
661
- next_in = os.path.join(self.cosmic_root, "input", f"{base}.tif")
662
- prev_in = getattr(self, "_current_input", None)
663
-
706
+ # Cleanup final output
707
+ if out_path and os.path.exists(out_path):
708
+ try: os.remove(out_path)
709
+ except OSError: pass
710
+
711
+ # Cleanup final input
712
+ prev_in = getattr(self, "_current_input", None)
713
+ if prev_in and os.path.exists(prev_in):
714
+ try: os.remove(prev_in)
715
+ except OSError: pass
716
+
717
+ # Final purge
664
718
  try:
665
- if has_more:
666
- def _writer(tmp_path, arr=dest):
667
- save_image(arr, tmp_path, "tiff", "32-bit floating point",
668
- getattr(self.doc, "original_header", None),
669
- getattr(self.doc, "is_mono", False))
670
- _atomic_fsync_replace(_writer, next_in)
671
- if not _wait_stable_file(next_in):
672
- QMessageBox.critical(self, "Cosmic Clarity", "Staging for next step failed (not stable).")
673
- self._op_queue.clear()
674
- return
675
- self._current_input = next_in
676
-
677
- # Now it’s safe to clean up the produced output
678
- if out_path and os.path.exists(out_path):
679
- os.remove(out_path)
680
-
681
- # Remove the previous input file if it’s different from the new one
682
- if prev_in and prev_in != next_in and os.path.exists(prev_in):
683
- os.remove(prev_in)
684
-
685
- except Exception as e:
686
- QMessageBox.critical(self, "Cosmic Clarity", f"Failed while staging next step:\n{e}")
687
- self._op_queue.clear()
688
- return
689
-
690
- # Continue or finish
691
- if has_more:
692
- QTimer.singleShot(100, self._run_next)
693
- else:
694
- # Nothing else queued — we're done
695
- try:
696
- # 🔸 Final cleanup: clear both input & output
697
- _purge_cc_io(self.cosmic_root, clear_input=True, clear_output=True)
698
- except Exception:
699
- pass
700
- self.accept()
719
+ _purge_cc_io(self.cosmic_root, clear_input=True, clear_output=True)
720
+ except Exception:
721
+ pass
722
+ self.accept()
701
723
 
702
724
 
703
725
  def _on_wait_error(self, msg: str):
@@ -1009,6 +1031,9 @@ class CosmicClaritySatelliteDialogPro(QDialog):
1009
1031
  def __init__(self, parent, doc=None, icon: QIcon | None = None):
1010
1032
  super().__init__(parent)
1011
1033
  self.setWindowTitle("Cosmic Clarity – Satellite Removal")
1034
+ self.setWindowFlag(Qt.WindowType.Window, True)
1035
+ self.setWindowModality(Qt.WindowModality.NonModal)
1036
+ self.setModal(False)
1012
1037
  if icon:
1013
1038
  try: self.setWindowIcon(icon)
1014
1039
  except Exception as e:
@@ -270,8 +270,17 @@ class CropDialogPro(QDialog):
270
270
 
271
271
  def __init__(self, parent, document):
272
272
  super().__init__(parent)
273
- self.setWindowTitle("Crop Tool")
273
+ self.setWindowTitle(self.tr("Crop Tool"))
274
+ self.setWindowFlag(Qt.WindowType.Window, True)
275
+ self.setWindowModality(Qt.WindowModality.NonModal)
276
+ self.setModal(False)
277
+ self._main = parent
274
278
  self.doc = document
279
+
280
+ # Connect to active document change signal
281
+ if hasattr(self._main, "currentDocumentChanged"):
282
+ self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
283
+
275
284
  self._rect_item: Optional[ResizableRotatableRectItem] = None
276
285
  self._pix_item: Optional[QGraphicsPixmapItem] = None
277
286
  self._drawing = False
@@ -281,19 +290,29 @@ class CropDialogPro(QDialog):
281
290
  # ---------- layout ----------
282
291
  main = QVBoxLayout(self)
283
292
 
284
- info = QLabel(
293
+ info = QLabel(self.tr(
285
294
  "• Click–drag to draw a crop\n"
286
295
  "• Drag corner handles to resize\n"
287
296
  "• Shift + drag on box to rotate"
288
- ); info.setStyleSheet("color: gray; font-style: italic;")
297
+ )); info.setStyleSheet("color: gray; font-style: italic;")
289
298
  main.addWidget(info)
290
299
 
291
300
  # aspect row
292
301
  row = QHBoxLayout()
293
302
  row.addStretch(1)
294
- row.addWidget(QLabel("Aspect Ratio:"))
303
+ row.addWidget(QLabel(self.tr("Aspect Ratio:")))
295
304
  self.cmb_ar = QComboBox()
296
- self.cmb_ar.addItems(["Free", "Original", "1:1", "16:9", "9:16", "4:3"])
305
+ self.cmb_ar.addItems([
306
+ self.tr("Free"), self.tr("Original"),
307
+ "1:1",
308
+ "3:2", "2:3",
309
+ "4:3", "3:4",
310
+ "4:5", "5:4",
311
+ "16:9", "9:16",
312
+ "21:9", "9:21",
313
+ "2:1", "1:2",
314
+ "3:5", "5:3",
315
+ ])
297
316
  row.addWidget(self.cmb_ar)
298
317
  row.addStretch(1)
299
318
  main.addLayout(row)
@@ -301,7 +320,7 @@ class CropDialogPro(QDialog):
301
320
  # typed margins (pixels): Top, Right, Bottom, Left
302
321
  margins_row = QHBoxLayout()
303
322
  margins_row.addStretch(1)
304
- margins_row.addWidget(QLabel("Margins (px):"))
323
+ margins_row.addWidget(QLabel(self.tr("Margins (px):")))
305
324
  self.sb_top = QSpinBox(); self.sb_top.setSuffix(" px")
306
325
  self.sb_right = QSpinBox(); self.sb_right.setSuffix(" px")
307
326
  self.sb_bottom = QSpinBox(); self.sb_bottom.setSuffix(" px")
@@ -312,16 +331,16 @@ class CropDialogPro(QDialog):
312
331
  sb.setRange(0, 1_000_000)
313
332
 
314
333
  # labels inline for clarity
315
- margins_row.addWidget(QLabel("Top"))
334
+ margins_row.addWidget(QLabel(self.tr("Top")))
316
335
  margins_row.addWidget(self.sb_top)
317
336
  margins_row.addSpacing(8)
318
- margins_row.addWidget(QLabel("Right"))
337
+ margins_row.addWidget(QLabel(self.tr("Right")))
319
338
  margins_row.addWidget(self.sb_right)
320
339
  margins_row.addSpacing(8)
321
- margins_row.addWidget(QLabel("Bottom"))
340
+ margins_row.addWidget(QLabel(self.tr("Bottom")))
322
341
  margins_row.addWidget(self.sb_bottom)
323
342
  margins_row.addSpacing(8)
324
- margins_row.addWidget(QLabel("Left"))
343
+ margins_row.addWidget(QLabel(self.tr("Left")))
325
344
  margins_row.addWidget(self.sb_left)
326
345
  margins_row.addStretch(1)
327
346
  main.addLayout(margins_row)
@@ -354,10 +373,10 @@ class CropDialogPro(QDialog):
354
373
  zoom_row = QHBoxLayout()
355
374
  zoom_row.addStretch(1)
356
375
 
357
- self.btn_zoom_out = themed_toolbtn("zoom-out", "Zoom Out")
358
- self.btn_zoom_in = themed_toolbtn("zoom-in", "Zoom In")
359
- self.btn_zoom_100 = themed_toolbtn("zoom-original", "Zoom 100%")
360
- self.btn_zoom_fit = themed_toolbtn("zoom-fit-best", "Fit to View")
376
+ self.btn_zoom_out = themed_toolbtn("zoom-out", self.tr("Zoom Out"))
377
+ self.btn_zoom_in = themed_toolbtn("zoom-in", self.tr("Zoom In"))
378
+ self.btn_zoom_100 = themed_toolbtn("zoom-original", self.tr("Zoom 100%"))
379
+ self.btn_zoom_fit = themed_toolbtn("zoom-fit-best", self.tr("Fit to View"))
361
380
 
362
381
  for b in (self.btn_zoom_out, self.btn_zoom_in, self.btn_zoom_100, self.btn_zoom_fit):
363
382
  zoom_row.addWidget(b)
@@ -367,7 +386,7 @@ class CropDialogPro(QDialog):
367
386
 
368
387
  dim_row = QHBoxLayout()
369
388
  dim_row.addStretch(1)
370
- self.lbl_dims = QLabel("Selection: —")
389
+ self.lbl_dims = QLabel(self.tr("Selection: —"))
371
390
  self.lbl_dims.setStyleSheet("color: gray;")
372
391
  dim_row.addWidget(self.lbl_dims)
373
392
  dim_row.addStretch(1)
@@ -381,11 +400,11 @@ class CropDialogPro(QDialog):
381
400
 
382
401
  # buttons
383
402
  btn_row = QHBoxLayout()
384
- self.btn_autostretch = QPushButton("Toggle Autostretch")
385
- self.btn_prev = QPushButton("Load Previous Crop")
386
- self.btn_apply = QPushButton("Apply")
387
- self.btn_batch = QPushButton("Batch Crop (all open)")
388
- self.btn_close = QToolButton(); self.btn_close.setText("Close")
403
+ self.btn_autostretch = QPushButton(self.tr("Toggle Autostretch"))
404
+ self.btn_prev = QPushButton(self.tr("Load Previous Crop"))
405
+ self.btn_apply = QPushButton(self.tr("Apply"))
406
+ self.btn_batch = QPushButton(self.tr("Batch Crop (all open)"))
407
+ self.btn_close = QToolButton(); self.btn_close.setText(self.tr("Close"))
389
408
  for b in (self.btn_autostretch, self.btn_prev, self.btn_apply, self.btn_batch, self.btn_close):
390
409
  btn_row.addWidget(b)
391
410
  main.addLayout(btn_row)
@@ -463,6 +482,14 @@ class CropDialogPro(QDialog):
463
482
  arr = arr[..., 0]
464
483
  return np.clip(arr, 0.0, 1.0)
465
484
 
485
+ def _on_active_doc_changed(self, doc):
486
+ """Called when user clicks a different image window."""
487
+ if doc is None or getattr(doc, "image", None) is None:
488
+ return
489
+ self.doc = doc
490
+ self._rect_item = None
491
+ self._load_from_doc()
492
+
466
493
  def _load_from_doc(self):
467
494
  self._full01 = self._img01_from_doc()
468
495
  self._orig_h, self._orig_w = self._full01.shape[:2]
@@ -487,7 +514,7 @@ class CropDialogPro(QDialog):
487
514
 
488
515
  def _set_dim_label_none(self):
489
516
  if hasattr(self, "lbl_dims"):
490
- self.lbl_dims.setText("Selection: —")
517
+ self.lbl_dims.setText(self.tr("Selection: —"))
491
518
 
492
519
  def _update_dim_label_from_corners(self, corners_scene):
493
520
  """
@@ -509,7 +536,7 @@ class CropDialogPro(QDialog):
509
536
  height = float(np.linalg.norm(src[3] - src[0]))
510
537
 
511
538
  self.lbl_dims.setText(
512
- f"Selection: {int(round(height))}×{int(round(width))} px"
539
+ self.tr("Selection: {0}×{1} px").format(int(round(height)), int(round(width)))
513
540
  )
514
541
 
515
542
  def _update_dim_label_from_rect_item(self):
@@ -691,17 +718,25 @@ class CropDialogPro(QDialog):
691
718
 
692
719
  def _current_ar_value(self) -> Optional[float]:
693
720
  txt = self.cmb_ar.currentText()
694
- if txt == "Free": return None
695
- if txt == "Original": return self._orig_w / self._orig_h
721
+ if txt == self.tr("Free"): return None
722
+ if txt == self.tr("Original"): return self._orig_w / self._orig_h
696
723
  a, b = map(float, txt.split(":")); return a / b
697
724
 
698
725
  def _apply_ar_to_rect(self, r: QRectF, live: bool, scene_pt: QPointF) -> QRectF:
699
- txt = self.cmb_ar.currentText()
700
- if txt == "Free": return r
701
- ar = self._orig_w / self._orig_h if txt == "Original" else (lambda a,b: a/b)(*map(float, txt.split(":")))
702
- w = r.width(); h = w / ar
703
- if scene_pt.y() < self._origin.y(): r.setTop(r.bottom() - h)
704
- else: r.setBottom(r.top() + h)
726
+ ar = self._current_ar_value()
727
+ if ar is None:
728
+ return r
729
+
730
+ # Calculate height from width using current aspect ratio
731
+ w = r.width()
732
+ h = w / ar
733
+
734
+ # Anchor to the click origin, adjust height based on drag direction
735
+ if scene_pt.y() < self._origin.y():
736
+ r.setTop(r.bottom() - h)
737
+ else:
738
+ r.setBottom(r.top() + h)
739
+
705
740
  return r.normalized()
706
741
 
707
742
  def _draw_live_rect(self, r: QRectF):
@@ -743,7 +778,7 @@ class CropDialogPro(QDialog):
743
778
 
744
779
  def _load_previous(self):
745
780
  if CropDialogPro._prev_rect is None:
746
- QMessageBox.information(self, "No Previous", "No previous crop stored.")
781
+ QMessageBox.information(self, self.tr("No Previous"), self.tr("No previous crop stored."))
747
782
  return
748
783
  if self._rect_item:
749
784
  self.scene.removeItem(self._rect_item)
@@ -770,7 +805,7 @@ class CropDialogPro(QDialog):
770
805
 
771
806
  def _apply_one(self):
772
807
  if not self._rect_item:
773
- QMessageBox.warning(self, "No Selection", "Draw & finalize a crop first.")
808
+ QMessageBox.warning(self, self.tr("No Selection"), self.tr("Draw & finalize a crop first."))
774
809
  return
775
810
 
776
811
  corners = self._corners_scene()
@@ -789,7 +824,7 @@ class CropDialogPro(QDialog):
789
824
  # Pixel-perfect slice
790
825
  bounds = self._int_bounds_from_quad(src, W_img, H_img)
791
826
  if bounds is None:
792
- QMessageBox.critical(self, "Apply failed", "Invalid crop bounds.")
827
+ QMessageBox.critical(self, self.tr("Apply failed"), self.tr("Invalid crop bounds."))
793
828
  return
794
829
  x0, x1, y0, y1 = bounds
795
830
  out = self._full01[y0:y1, x0:x1].copy()
@@ -806,7 +841,7 @@ class CropDialogPro(QDialog):
806
841
  w_out = int(round(width))
807
842
  h_out = int(round(height))
808
843
  if w_out <= 0 or h_out <= 0:
809
- QMessageBox.critical(self, "Apply failed", "Invalid crop size.")
844
+ QMessageBox.critical(self, self.tr("Apply failed"), self.tr("Invalid crop size."))
810
845
  return
811
846
 
812
847
  out = cv2.warpPerspective(
@@ -832,11 +867,11 @@ class CropDialogPro(QDialog):
832
867
  self.crop_applied.emit(out)
833
868
  self.accept()
834
869
  except Exception as e:
835
- QMessageBox.critical(self, "Apply failed", str(e))
870
+ QMessageBox.critical(self, self.tr("Apply failed"), str(e))
836
871
 
837
872
  def _apply_batch(self):
838
873
  if not self._rect_item:
839
- QMessageBox.warning(self, "No Selection", "Draw & finalize a crop first.")
874
+ QMessageBox.warning(self, self.tr("No Selection"), self.tr("Draw & finalize a crop first."))
840
875
  return
841
876
 
842
877
  # Normalize the crop polygon to THIS image size
@@ -855,12 +890,12 @@ class CropDialogPro(QDialog):
855
890
  docs.append(d)
856
891
 
857
892
  if not docs:
858
- QMessageBox.information(self, "No Images", "No open images to crop.")
893
+ QMessageBox.information(self, self.tr("No Images"), self.tr("No open images to crop."))
859
894
  return
860
895
 
861
896
  ok = QMessageBox.question(
862
- self, "Confirm Batch",
863
- f"Apply this crop to {len(docs)} open image(s)?",
897
+ self, self.tr("Confirm Batch"),
898
+ self.tr("Apply this crop to {0} open image(s)?").format(len(docs)),
864
899
  QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
865
900
  QMessageBox.StandardButton.No
866
901
  )
@@ -913,7 +948,7 @@ class CropDialogPro(QDialog):
913
948
  except Exception:
914
949
  pass
915
950
 
916
- QMessageBox.information(self, "Batch Crop", "Applied crop to all open images. Any Astrometric Solutions has been updated.")
951
+ QMessageBox.information(self, self.tr("Batch Crop"), self.tr("Applied crop to all open images. Any Astrometric Solutions has been updated."))
917
952
  if last_cropped is not None:
918
953
  self.crop_applied.emit(last_cropped)
919
954
  self.accept()
@@ -936,13 +971,13 @@ class CropDialogPro(QDialog):
936
971
  size_txt = f"{size[0]}×{size[1]}" if size else "?"
937
972
  extra = f"\n{batch_note}" if batch_note else ""
938
973
  msg = (
939
- "Astrometric solution updated ✔️\n\n"
940
- f"Model: {sip_txt} Image: {size_txt}\n"
941
- f"CRVAL: ({b_ra:.6f}, {b_dec:.6f}) → ({a_ra:.6f}, {a_dec:.6f})\n"
942
- f"Fit residuals: RMS {rms:.3f}\" (p95 {p95:.3f}\")"
974
+ self.tr("Astrometric solution updated ✔️\n\n") +
975
+ self.tr("Model: {0} Image: {1}\n").format(sip_txt, size_txt) +
976
+ self.tr("CRVAL: ({0:.6f}, {1:.6f}) → ({2:.6f}, {3:.6f})\n").format(b_ra, b_dec, a_ra, a_dec) +
977
+ self.tr("Fit residuals: RMS {0:.3f}\" (p95 {1:.3f}\")").format(rms, p95) +
943
978
  f"{extra}"
944
979
  )
945
- QMessageBox.information(self, "WCS Updated", msg)
980
+ QMessageBox.information(self, self.tr("WCS Updated"), msg)
946
981
  except Exception:
947
982
  # Be quiet if formatting fails
948
983
  pass