setiastrosuitepro 1.6.2__py3-none-any.whl → 1.6.12__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 (162) hide show
  1. setiastro/images/abeicon.svg +16 -0
  2. setiastro/images/acv_icon.png +0 -0
  3. setiastro/images/colorwheel.svg +97 -0
  4. setiastro/images/cosmic.svg +40 -0
  5. setiastro/images/cosmicsat.svg +24 -0
  6. setiastro/images/first_quarter.png +0 -0
  7. setiastro/images/full_moon.png +0 -0
  8. setiastro/images/graxpert.svg +19 -0
  9. setiastro/images/last_quarter.png +0 -0
  10. setiastro/images/linearfit.svg +32 -0
  11. setiastro/images/new_moon.png +0 -0
  12. setiastro/images/pixelmath.svg +42 -0
  13. setiastro/images/rotatearbitrary.png +0 -0
  14. setiastro/images/waning_crescent_1.png +0 -0
  15. setiastro/images/waning_crescent_2.png +0 -0
  16. setiastro/images/waning_crescent_3.png +0 -0
  17. setiastro/images/waning_crescent_4.png +0 -0
  18. setiastro/images/waning_crescent_5.png +0 -0
  19. setiastro/images/waning_gibbous_1.png +0 -0
  20. setiastro/images/waning_gibbous_2.png +0 -0
  21. setiastro/images/waning_gibbous_3.png +0 -0
  22. setiastro/images/waning_gibbous_4.png +0 -0
  23. setiastro/images/waning_gibbous_5.png +0 -0
  24. setiastro/images/waxing_crescent_1.png +0 -0
  25. setiastro/images/waxing_crescent_2.png +0 -0
  26. setiastro/images/waxing_crescent_3.png +0 -0
  27. setiastro/images/waxing_crescent_4.png +0 -0
  28. setiastro/images/waxing_crescent_5.png +0 -0
  29. setiastro/images/waxing_gibbous_1.png +0 -0
  30. setiastro/images/waxing_gibbous_2.png +0 -0
  31. setiastro/images/waxing_gibbous_3.png +0 -0
  32. setiastro/images/waxing_gibbous_4.png +0 -0
  33. setiastro/images/waxing_gibbous_5.png +0 -0
  34. setiastro/qml/ResourceMonitor.qml +84 -82
  35. setiastro/saspro/__main__.py +20 -1
  36. setiastro/saspro/_generated/build_info.py +2 -2
  37. setiastro/saspro/abe.py +37 -4
  38. setiastro/saspro/aberration_ai.py +237 -21
  39. setiastro/saspro/acv_exporter.py +379 -0
  40. setiastro/saspro/add_stars.py +33 -6
  41. setiastro/saspro/backgroundneutral.py +114 -37
  42. setiastro/saspro/blemish_blaster.py +4 -1
  43. setiastro/saspro/blink_comparator_pro.py +548 -275
  44. setiastro/saspro/clahe.py +4 -1
  45. setiastro/saspro/continuum_subtract.py +4 -1
  46. setiastro/saspro/convo.py +13 -7
  47. setiastro/saspro/cosmicclarity.py +129 -18
  48. setiastro/saspro/crop_dialog_pro.py +134 -8
  49. setiastro/saspro/curve_editor_pro.py +109 -42
  50. setiastro/saspro/doc_manager.py +246 -16
  51. setiastro/saspro/exoplanet_detector.py +120 -28
  52. setiastro/saspro/frequency_separation.py +1158 -204
  53. setiastro/saspro/function_bundle.py +16 -16
  54. setiastro/saspro/ghs_dialog_pro.py +81 -16
  55. setiastro/saspro/graxpert.py +1 -0
  56. setiastro/saspro/gui/main_window.py +519 -289
  57. setiastro/saspro/gui/mixins/dock_mixin.py +276 -42
  58. setiastro/saspro/gui/mixins/geometry_mixin.py +105 -5
  59. setiastro/saspro/gui/mixins/menu_mixin.py +28 -1
  60. setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
  61. setiastro/saspro/gui/mixins/toolbar_mixin.py +416 -27
  62. setiastro/saspro/gui/mixins/update_mixin.py +138 -36
  63. setiastro/saspro/gui/mixins/view_mixin.py +42 -0
  64. setiastro/saspro/halobgon.py +4 -0
  65. setiastro/saspro/histogram.py +5 -1
  66. setiastro/saspro/image_combine.py +4 -0
  67. setiastro/saspro/image_peeker_pro.py +4 -0
  68. setiastro/saspro/imageops/starbasedwhitebalance.py +23 -52
  69. setiastro/saspro/imageops/stretch.py +582 -62
  70. setiastro/saspro/isophote.py +4 -0
  71. setiastro/saspro/layers.py +13 -9
  72. setiastro/saspro/layers_dock.py +183 -3
  73. setiastro/saspro/legacy/image_manager.py +154 -20
  74. setiastro/saspro/legacy/numba_utils.py +67 -47
  75. setiastro/saspro/legacy/xisf.py +240 -98
  76. setiastro/saspro/live_stacking.py +180 -79
  77. setiastro/saspro/luminancerecombine.py +228 -27
  78. setiastro/saspro/mask_creation.py +174 -15
  79. setiastro/saspro/mfdeconv.py +113 -35
  80. setiastro/saspro/mfdeconvcudnn.py +119 -70
  81. setiastro/saspro/mfdeconvsport.py +112 -35
  82. setiastro/saspro/morphology.py +4 -0
  83. setiastro/saspro/multiscale_decomp.py +748 -255
  84. setiastro/saspro/numba_utils.py +72 -57
  85. setiastro/saspro/ops/commands.py +18 -18
  86. setiastro/saspro/ops/script_editor.py +10 -2
  87. setiastro/saspro/ops/scripts.py +122 -0
  88. setiastro/saspro/perfect_palette_picker.py +37 -3
  89. setiastro/saspro/plate_solver.py +84 -49
  90. setiastro/saspro/psf_viewer.py +119 -37
  91. setiastro/saspro/remove_stars_preset.py +55 -13
  92. setiastro/saspro/resources.py +97 -11
  93. setiastro/saspro/rgbalign.py +4 -0
  94. setiastro/saspro/selective_color.py +83 -21
  95. setiastro/saspro/sfcc.py +364 -152
  96. setiastro/saspro/shortcuts.py +253 -49
  97. setiastro/saspro/signature_insert.py +692 -33
  98. setiastro/saspro/stacking_suite.py +1610 -574
  99. setiastro/saspro/star_alignment.py +522 -453
  100. setiastro/saspro/star_spikes.py +4 -0
  101. setiastro/saspro/star_stretch.py +38 -3
  102. setiastro/saspro/stat_stretch.py +743 -128
  103. setiastro/saspro/status_log_dock.py +1 -1
  104. setiastro/saspro/subwindow.py +786 -360
  105. setiastro/saspro/supernovaasteroidhunter.py +1 -1
  106. setiastro/saspro/swap_manager.py +77 -42
  107. setiastro/saspro/translations/all_source_strings.json +1588 -516
  108. setiastro/saspro/translations/ar_translations.py +915 -684
  109. setiastro/saspro/translations/de_translations.py +442 -463
  110. setiastro/saspro/translations/es_translations.py +277 -47
  111. setiastro/saspro/translations/fr_translations.py +279 -47
  112. setiastro/saspro/translations/hi_translations.py +253 -21
  113. setiastro/saspro/translations/integrate_translations.py +3 -2
  114. setiastro/saspro/translations/it_translations.py +1211 -161
  115. setiastro/saspro/translations/ja_translations.py +3340 -3107
  116. setiastro/saspro/translations/pt_translations.py +3315 -3337
  117. setiastro/saspro/translations/ru_translations.py +351 -117
  118. setiastro/saspro/translations/saspro_ar.qm +0 -0
  119. setiastro/saspro/translations/saspro_ar.ts +15902 -138
  120. setiastro/saspro/translations/saspro_de.qm +0 -0
  121. setiastro/saspro/translations/saspro_de.ts +14428 -133
  122. setiastro/saspro/translations/saspro_es.qm +0 -0
  123. setiastro/saspro/translations/saspro_es.ts +11503 -7821
  124. setiastro/saspro/translations/saspro_fr.qm +0 -0
  125. setiastro/saspro/translations/saspro_fr.ts +11168 -7812
  126. setiastro/saspro/translations/saspro_hi.qm +0 -0
  127. setiastro/saspro/translations/saspro_hi.ts +14733 -135
  128. setiastro/saspro/translations/saspro_it.qm +0 -0
  129. setiastro/saspro/translations/saspro_it.ts +14347 -7821
  130. setiastro/saspro/translations/saspro_ja.qm +0 -0
  131. setiastro/saspro/translations/saspro_ja.ts +14860 -137
  132. setiastro/saspro/translations/saspro_pt.qm +0 -0
  133. setiastro/saspro/translations/saspro_pt.ts +14904 -137
  134. setiastro/saspro/translations/saspro_ru.qm +0 -0
  135. setiastro/saspro/translations/saspro_ru.ts +11766 -168
  136. setiastro/saspro/translations/saspro_sw.qm +0 -0
  137. setiastro/saspro/translations/saspro_sw.ts +15115 -135
  138. setiastro/saspro/translations/saspro_uk.qm +0 -0
  139. setiastro/saspro/translations/saspro_uk.ts +11206 -6729
  140. setiastro/saspro/translations/saspro_zh.qm +0 -0
  141. setiastro/saspro/translations/saspro_zh.ts +10581 -7812
  142. setiastro/saspro/translations/sw_translations.py +282 -56
  143. setiastro/saspro/translations/uk_translations.py +264 -35
  144. setiastro/saspro/translations/zh_translations.py +282 -47
  145. setiastro/saspro/view_bundle.py +17 -17
  146. setiastro/saspro/wavescale_hdr.py +4 -1
  147. setiastro/saspro/wavescalede.py +4 -1
  148. setiastro/saspro/whitebalance.py +84 -12
  149. setiastro/saspro/widgets/common_utilities.py +28 -21
  150. setiastro/saspro/widgets/minigame/game.js +11 -6
  151. setiastro/saspro/widgets/resource_monitor.py +133 -57
  152. setiastro/saspro/widgets/spinboxes.py +28 -13
  153. setiastro/saspro/wimi.py +92 -721
  154. setiastro/saspro/wims.py +46 -36
  155. setiastro/saspro/window_shelf.py +2 -2
  156. setiastro/saspro/xisf.py +101 -11
  157. {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/METADATA +8 -7
  158. {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/RECORD +162 -128
  159. {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/WHEEL +0 -0
  160. {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/entry_points.txt +0 -0
  161. {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/licenses/LICENSE +0 -0
  162. {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/wims.py CHANGED
@@ -38,6 +38,8 @@ def _app_root() -> str:
38
38
  def imgs_path(*parts) -> str:
39
39
  return os.path.join(_app_root(), "imgs", *parts)
40
40
 
41
+ from setiastro.saspro.resources import get_icon_path
42
+
41
43
  getcontext().prec = 24
42
44
  warnings.filterwarnings("ignore")
43
45
 
@@ -289,7 +291,7 @@ def _tz_vs_longitude_hint(tz_name: str, date_str: str, time_str: str, lon_deg: f
289
291
  class WhatsInMySkyDialog(QDialog):
290
292
  def __init__(self, parent=None, wims_path: Optional[str] = None, wrench_path: Optional[str] = None):
291
293
  super().__init__(parent)
292
- self.setWindowTitle("What's In My Sky")
294
+ self.setWindowTitle(self.tr("What's In My Sky"))
293
295
  if wims_path:
294
296
  self.setWindowIcon(QIcon(wims_path))
295
297
 
@@ -316,14 +318,14 @@ class WhatsInMySkyDialog(QDialog):
316
318
  self.timezone_combo.setFixedWidth(fixed_w)
317
319
 
318
320
  r = 0
319
- layout.addWidget(QLabel("Latitude:"), r, 0); layout.addWidget(self.latitude_entry, r, 1); r += 1
320
- layout.addWidget(QLabel("Longitude (E+, W−):"), r, 0); layout.addWidget(self.longitude_entry, r, 1); r += 1
321
- layout.addWidget(QLabel("Date (YYYY-MM-DD):"), r, 0); layout.addWidget(self.date_entry, r, 1); r += 1
322
- layout.addWidget(QLabel("Time (HH:MM):"), r, 0); layout.addWidget(self.time_entry, r, 1); r += 1
323
- layout.addWidget(QLabel("Time Zone:"), r, 0); layout.addWidget(self.timezone_combo, r, 1); r += 1
321
+ layout.addWidget(QLabel(self.tr("Latitude:")), r, 0); layout.addWidget(self.latitude_entry, r, 1); r += 1
322
+ layout.addWidget(QLabel(self.tr("Longitude (E+, W−):")), r, 0); layout.addWidget(self.longitude_entry, r, 1); r += 1
323
+ layout.addWidget(QLabel(self.tr("Date (YYYY-MM-DD):")), r, 0); layout.addWidget(self.date_entry, r, 1); r += 1
324
+ layout.addWidget(QLabel(self.tr("Time (HH:MM):")), r, 0); layout.addWidget(self.time_entry, r, 1); r += 1
325
+ layout.addWidget(QLabel(self.tr("Time Zone:")), r, 0); layout.addWidget(self.timezone_combo, r, 1); r += 1
324
326
 
325
327
  self.min_altitude_entry = QLineEdit(); self.min_altitude_entry.setFixedWidth(fixed_w)
326
- layout.addWidget(QLabel("Min Altitude (0–90°):"), r, 0); layout.addWidget(self.min_altitude_entry, r, 1); r += 1
328
+ layout.addWidget(QLabel(self.tr("Min Altitude (0–90°):")), r, 0); layout.addWidget(self.min_altitude_entry, r, 1); r += 1
327
329
 
328
330
  # catalogs
329
331
  catalog_frame = QScrollArea()
@@ -334,36 +336,36 @@ class WhatsInMySkyDialog(QDialog):
334
336
  cat_layout.addWidget(cb, i // 5, i % 5)
335
337
  self.catalog_vars[name] = cb
336
338
  catalog_frame.setWidget(cat_widget); catalog_frame.setFixedWidth(fixed_w + 250)
337
- layout.addWidget(QLabel("Catalog Filters:"), r, 0); layout.addWidget(catalog_frame, r, 1); r += 1
339
+ layout.addWidget(QLabel(self.tr("Catalog Filters:")), r, 0); layout.addWidget(catalog_frame, r, 1); r += 1
338
340
 
339
341
  # RA/Dec format
340
- self.ra_dec_degrees = QRadioButton("Degrees")
341
- self.ra_dec_hms = QRadioButton("H:M:S / D:M:S")
342
+ self.ra_dec_degrees = QRadioButton(self.tr("Degrees"))
343
+ self.ra_dec_hms = QRadioButton(self.tr("H:M:S / D:M:S"))
342
344
  self.ra_dec_degrees.setChecked(True)
343
345
  g = QButtonGroup(self); g.addButton(self.ra_dec_degrees); g.addButton(self.ra_dec_hms)
344
346
  ra_row = QHBoxLayout(); ra_row.addWidget(self.ra_dec_degrees); ra_row.addWidget(self.ra_dec_hms)
345
- layout.addWidget(QLabel("RA/Dec Format:"), r, 0); layout.addLayout(ra_row, r, 1); r += 1
347
+ layout.addWidget(QLabel(self.tr("RA/Dec Format:")), r, 0); layout.addLayout(ra_row, r, 1); r += 1
346
348
  self.ra_dec_degrees.toggled.connect(self.update_ra_dec_format)
347
349
  self.ra_dec_hms.toggled.connect(self.update_ra_dec_format)
348
350
 
349
351
  # action buttons / status
350
- calc_btn = QPushButton("Calculate"); calc_btn.setFixedWidth(fixed_w); calc_btn.clicked.connect(self.start_calculation)
352
+ calc_btn = QPushButton(self.tr("Calculate")); calc_btn.setFixedWidth(fixed_w); calc_btn.clicked.connect(self.start_calculation)
351
353
  layout.addWidget(calc_btn, r, 0); r += 1
352
354
 
353
- self.status_label = QLabel("Status: Idle"); layout.addWidget(self.status_label, r, 0, 1, 2); r += 1
354
- self.lst_label = QLabel("Local Sidereal Time: 0.000"); layout.addWidget(self.lst_label, r, 0, 1, 2); r += 1
355
+ self.status_label = QLabel(self.tr("Status: Idle")); layout.addWidget(self.status_label, r, 0, 1, 2); r += 1
356
+ self.lst_label = QLabel(self.tr("Local Sidereal Time: 0.000")); layout.addWidget(self.lst_label, r, 0, 1, 2); r += 1
355
357
 
356
358
  # moon phase preview
357
359
  self.lunar_phase_image_label = QLabel()
358
360
  layout.addWidget(self.lunar_phase_image_label, 0, 2, 4, 1)
359
- self.lunar_phase_label = QLabel("Lunar Phase: N/A")
361
+ self.lunar_phase_label = QLabel(self.tr("Lunar Phase: N/A"))
360
362
  layout.addWidget(self.lunar_phase_label, 4, 2)
361
363
 
362
364
  # results tree
363
365
  self.tree = QTreeWidget()
364
366
  self.tree.setHeaderLabels([
365
- "Name","RA","Dec","Altitude","Azimuth","Minutes to Transit","Before/After Transit",
366
- "Degrees from Moon","Alt Name","Type","Magnitude","Size (arcmin)"
367
+ self.tr("Name"),self.tr("RA"),self.tr("Dec"),self.tr("Altitude"),self.tr("Azimuth"),self.tr("Minutes to Transit"),self.tr("Before/After Transit"),
368
+ self.tr("Degrees from Moon"),self.tr("Alt Name"),self.tr("Type"),self.tr("Magnitude"),self.tr("Size (arcmin)")
367
369
  ])
368
370
  self.tree.setSortingEnabled(True)
369
371
  hdr = self.tree.header()
@@ -374,10 +376,10 @@ class WhatsInMySkyDialog(QDialog):
374
376
  layout.addWidget(self.tree, r, 0, 1, 3); r += 1
375
377
 
376
378
  # bottom row
377
- add_btn = QPushButton("Add Custom Object"); add_btn.setFixedWidth(fixed_w); add_btn.clicked.connect(self.add_custom_object)
379
+ add_btn = QPushButton(self.tr("Add Custom Object")); add_btn.setFixedWidth(fixed_w); add_btn.clicked.connect(self.add_custom_object)
378
380
  layout.addWidget(add_btn, r, 0)
379
381
 
380
- save_btn = QPushButton("Save to CSV"); save_btn.setFixedWidth(fixed_w); save_btn.clicked.connect(self.save_to_csv)
382
+ save_btn = QPushButton(self.tr("Save to CSV")); save_btn.setFixedWidth(fixed_w); save_btn.clicked.connect(self.save_to_csv)
381
383
  layout.addWidget(save_btn, r, 1)
382
384
 
383
385
  settings_btn = QPushButton(); settings_btn.setFixedWidth(fixed_w)
@@ -434,7 +436,7 @@ class WhatsInMySkyDialog(QDialog):
434
436
  tz_str = self.timezone_combo.currentText()
435
437
  min_alt = float(self.min_altitude_entry.text())
436
438
  except ValueError as e:
437
- self.update_status(f"Invalid input: {e}")
439
+ self.update_status(self.tr("Invalid input: {}").format(e))
438
440
  return
439
441
 
440
442
  # Heuristic warning (and gentle auto-fix if user probably forgot the suffix)
@@ -450,9 +452,9 @@ class WhatsInMySkyDialog(QDialog):
450
452
  self.longitude_entry.setText(_format_with_suffix(longitude, "lon"))
451
453
  self.update_status(f"{msg} → Assuming you meant {_format_with_suffix(longitude, 'lon')} (auto-corrected).")
452
454
  else:
453
- self.update_status(msg + " Please verify your longitude/timezone.")
455
+ self.update_status(msg + self.tr(" Please verify your longitude/timezone."))
454
456
  else:
455
- self.update_status("Inputs look consistent.")
457
+ self.update_status(self.tr("Inputs look consistent."))
456
458
 
457
459
  # Persist settings (numeric)
458
460
  self._save_settings(latitude, longitude, date_str, time_str, tz_str, min_alt)
@@ -467,16 +469,24 @@ class WhatsInMySkyDialog(QDialog):
467
469
  self.calc_thread.lst_calculated.connect(self.update_lst)
468
470
  self.calc_thread.status_update.connect(self.update_status)
469
471
 
470
- self.update_status("Calculating…")
472
+ self.update_status(self.tr("Calculating…"))
471
473
  self.calc_thread.start()
472
474
 
473
475
  def update_lunar_phase(self, phase_percentage: int, phase_image_name: str):
474
- self.lunar_phase_label.setText(f"Lunar Phase: {phase_percentage}% illuminated")
475
- pth = imgs_path(phase_image_name)
476
+ self.lunar_phase_label.setText(self.tr("Lunar Phase: {}% illuminated").format(phase_percentage))
477
+
478
+ pth = get_icon_path(phase_image_name) # phase_image_name already includes .png
476
479
  if os.path.exists(pth):
477
- pm = QPixmap(pth).scaled(100, 100, Qt.AspectRatioMode.KeepAspectRatio,
478
- Qt.TransformationMode.SmoothTransformation)
480
+ pm = QPixmap(pth).scaled(
481
+ 100, 100,
482
+ Qt.AspectRatioMode.KeepAspectRatio,
483
+ Qt.TransformationMode.SmoothTransformation
484
+ )
479
485
  self.lunar_phase_image_label.setPixmap(pm)
486
+ else:
487
+ # super helpful while debugging
488
+ self.lunar_phase_image_label.clear()
489
+ self.update_status(self.tr("Moon icon missing: {}").format(pth))
480
490
 
481
491
  def on_calculation_complete(self, df: pd.DataFrame, message: str):
482
492
  self.update_status(message)
@@ -509,13 +519,13 @@ class WhatsInMySkyDialog(QDialog):
509
519
  self.tree.addTopLevelItem(SortableTreeWidgetItem(vals))
510
520
 
511
521
  def update_status(self, msg: str):
512
- self.status_label.setText(f"Status: {msg}")
522
+ self.status_label.setText(self.tr("Status: {}").format(msg))
513
523
 
514
524
  def update_lst(self, msg: str):
515
525
  self.lst_label.setText(msg)
516
526
 
517
527
  def open_settings(self):
518
- n, ok = QInputDialog.getInt(self, "Settings", "Enter number of objects to display:",
528
+ n, ok = QInputDialog.getInt(self, self.tr("Settings"), self.tr("Enter number of objects to display:"),
519
529
  value=int(self.object_limit), min=1, max=1000)
520
530
  if ok:
521
531
  self.object_limit = int(n)
@@ -526,12 +536,12 @@ class WhatsInMySkyDialog(QDialog):
526
536
  webbrowser.open(f"https://www.astrobin.com/search/?q={name}")
527
537
 
528
538
  def add_custom_object(self):
529
- name, ok = QInputDialog.getText(self, "Add Custom Object", "Enter object name:")
539
+ name, ok = QInputDialog.getText(self, self.tr("Add Custom Object"), self.tr("Enter object name:"))
530
540
  if not ok or not name:
531
541
  return
532
- ra, ok = QInputDialog.getDouble(self, "Add Custom Object", "Enter RA (deg):", decimals=3)
542
+ ra, ok = QInputDialog.getDouble(self, self.tr("Add Custom Object"), self.tr("Enter RA (deg):"), decimals=3)
533
543
  if not ok: return
534
- dec, ok = QInputDialog.getDouble(self, "Add Custom Object", "Enter Dec (deg):", decimals=3)
544
+ dec, ok = QInputDialog.getDouble(self, self.tr("Add Custom Object"), self.tr("Enter Dec (deg):"), decimals=3)
535
545
  if not ok: return
536
546
 
537
547
  entry = {"Name": name, "RA": ra, "Dec": dec, "Catalog": "User",
@@ -542,9 +552,9 @@ class WhatsInMySkyDialog(QDialog):
542
552
  df = pd.read_csv(catalog_csv, encoding="ISO-8859-1") if os.path.exists(catalog_csv) else pd.DataFrame()
543
553
  df = pd.concat([df, pd.DataFrame([entry])], ignore_index=True)
544
554
  df.to_csv(catalog_csv, index=False, encoding="ISO-8859-1")
545
- self.update_status(f"Added custom object: {name}")
555
+ self.update_status(self.tr("Added custom object: {}").format(name))
546
556
  except Exception as e:
547
- QMessageBox.warning(self, "Add Custom Object", f"Could not update catalog:\n{e}")
557
+ QMessageBox.warning(self, self.tr("Add Custom Object"), self.tr("Could not update catalog:\n{}").format(e))
548
558
 
549
559
  def update_ra_dec_format(self):
550
560
  use_deg = self.ra_dec_degrees.isChecked()
@@ -566,7 +576,7 @@ class WhatsInMySkyDialog(QDialog):
566
576
  pass
567
577
 
568
578
  def save_to_csv(self):
569
- path, _ = QFileDialog.getSaveFileName(self, "Save CSV File", "", "CSV files (*.csv);;All Files (*)")
579
+ path, _ = QFileDialog.getSaveFileName(self, self.tr("Save CSV File"), "", self.tr("CSV files (*.csv);;All Files (*)"))
570
580
  if not path:
571
581
  return
572
582
  cols = [self.tree.headerItem().text(i) for i in range(self.tree.columnCount())]
@@ -575,4 +585,4 @@ class WhatsInMySkyDialog(QDialog):
575
585
  it = self.tree.topLevelItem(i)
576
586
  rows.append([it.text(j) for j in range(self.tree.columnCount())])
577
587
  pd.DataFrame(rows, columns=cols).to_csv(path, index=False)
578
- self.update_status(f"Data saved to {path}")
588
+ self.update_status(self.tr("Data saved to {}").format(path))
@@ -17,7 +17,7 @@ def _dbg(owner, msg: str):
17
17
 
18
18
  class WindowShelf(QDockWidget):
19
19
  def __init__(self, parent=None):
20
- super().__init__("Minimized Views", parent)
20
+ super().__init__(self.tr("Minimized Views"), parent)
21
21
 
22
22
  # PyQt6 dock area enum
23
23
  self.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas)
@@ -53,7 +53,7 @@ class WindowShelf(QDockWidget):
53
53
  if sub is None or sub.widget() is None:
54
54
  return
55
55
 
56
- title = sub.windowTitle() or "Untitled"
56
+ title = sub.windowTitle() or self.tr("Untitled")
57
57
  # strip leading dot and Active prefix for the shelf display text only
58
58
 
59
59
  # Remove any number of leading glyphs like ■ ● ◆ ▲ etc.
setiastro/saspro/xisf.py CHANGED
@@ -34,7 +34,14 @@ import sys
34
34
  from datetime import datetime
35
35
  import ast
36
36
 
37
- __version__ = "1.0.0"
37
+ __version__ = "1.0.1"
38
+
39
+ def _is_attached_or_inline_property(p_dict):
40
+ return "location" in p_dict # location implies inline/embedded/attachment
41
+
42
+ def _make_lazy(p_dict):
43
+ p_dict["_lazy"] = True
44
+ return p_dict
38
45
 
39
46
  class XISF:
40
47
  """Implements an baseline XISF Decoder and a simple baseline Encoder.
@@ -340,7 +347,7 @@ class XISF:
340
347
  def _read_embedded_data_block(elem):
341
348
  assert elem["location"][0] == "embedded"
342
349
  data_elem = ET.fromstring(elem["value"])
343
- encoding, data = data.attrib["encoding"], data_elem.text
350
+ encoding, data = data_elem.attrib["encoding"], data_elem.text
344
351
  return XISF._decode_inline_or_embedded_data(encoding, data, elem)
345
352
 
346
353
  @staticmethod
@@ -741,11 +748,13 @@ class XISF:
741
748
  # Keep original string value if parsing fails
742
749
  p_dict["datetime"] = None
743
750
  elif p_dict["type"] == "String":
744
- p_dict["value"] = p_et.text
751
+ # NOTE: currently does: p_dict["value"] = p_et.text; then if location -> read block now
752
+ p_dict["value"] = p_et.text # may be None
745
753
  if "location" in p_dict:
746
- # Process location and compression attributes to find data block
747
754
  self._process_location_compression(p_dict)
748
- p_dict["value"] = self._read_data_block(p_dict).decode("utf-8")
755
+ # LAZY: do NOT read block here
756
+ return _make_lazy(p_dict)
757
+ return p_dict
749
758
  elif p_dict["type"] == "Boolean":
750
759
  # Boolean valid values are "true" and "false"
751
760
  p_dict["value"] = p_dict["value"] == "true"
@@ -757,24 +766,105 @@ class XISF:
757
766
  p_dict["length"] = int(p_dict["length"])
758
767
  p_dict["dtype"] = self._parse_vector_dtype(p_dict["type"])
759
768
  self._process_location_compression(p_dict)
760
- raw_data = self._read_data_block(p_dict)
761
- p_dict["value"] = np.frombuffer(raw_data, dtype=p_dict["dtype"], count=p_dict["length"])
769
+ # LAZY: do NOT read block here
770
+ return _make_lazy(p_dict)
771
+
762
772
  elif "Matrix" in p_dict["type"]:
763
773
  p_dict["value"] = p_et.text
764
774
  p_dict["rows"] = int(p_dict["rows"])
765
775
  p_dict["columns"] = int(p_dict["columns"])
766
- length = p_dict["rows"] * p_dict["columns"]
767
776
  p_dict["dtype"] = self._parse_vector_dtype(p_dict["type"])
768
777
  self._process_location_compression(p_dict)
769
- raw_data = self._read_data_block(p_dict)
770
- p_dict["value"] = np.frombuffer(raw_data, dtype=p_dict["dtype"], count=length)
771
- p_dict["value"] = p_dict["value"].reshape((p_dict["rows"], p_dict["columns"]))
778
+ # LAZY: do NOT read block here
779
+ return _make_lazy(p_dict)
772
780
  else:
773
781
  print(f"Unsupported Property type {p_dict['type']}: {p_et}")
774
782
  p_dict = False
775
783
 
776
784
  return p_dict
777
785
 
786
+ def resolve_property(self, p_dict):
787
+ """
788
+ Resolve a lazy property (String/Vector/Matrix with a data block).
789
+ Mutates p_dict in place and returns decoded 'value'.
790
+ """
791
+ if not p_dict.get("_lazy"):
792
+ return p_dict.get("value")
793
+
794
+ raw = self._read_data_block(p_dict)
795
+
796
+ t = p_dict["type"]
797
+ if t == "String":
798
+ val = raw.decode("utf-8")
799
+ elif "Vector" in t:
800
+ val = np.frombuffer(raw, dtype=p_dict["dtype"], count=p_dict["length"])
801
+ elif "Matrix" in t:
802
+ length = p_dict["rows"] * p_dict["columns"]
803
+ val = np.frombuffer(raw, dtype=p_dict["dtype"], count=length).reshape((p_dict["rows"], p_dict["columns"]))
804
+ else:
805
+ # if something else ever gets marked lazy
806
+ val = raw
807
+
808
+ p_dict["value"] = val
809
+ p_dict["_lazy"] = False
810
+ return val
811
+
812
+ def can_partial_read_image(self, n=0):
813
+ meta = self._images_meta[n]
814
+ if meta["location"][0] != "attachment":
815
+ return False
816
+ if "compression" in meta:
817
+ return False
818
+ return True
819
+
820
+ def read_image_roi(self, n=0, x0=0, y0=0, x1=None, y1=None, channels=None, data_format="channels_last"):
821
+ meta = self._images_meta[n]
822
+ if meta["location"][0] != "attachment":
823
+ raise NotImplementedError("ROI read only supported for attachment blocks")
824
+ if "compression" in meta:
825
+ raise NotImplementedError("ROI read not supported for compressed image blocks")
826
+
827
+ w, h, chc = meta["geometry"]
828
+ dtype = meta["dtype"]
829
+ itemsize = dtype.itemsize
830
+
831
+ if x1 is None: x1 = w
832
+ if y1 is None: y1 = h
833
+ x0 = max(0, min(w, x0)); x1 = max(0, min(w, x1))
834
+ y0 = max(0, min(h, y0)); y1 = max(0, min(h, y1))
835
+ if x1 <= x0 or y1 <= y0:
836
+ raise ValueError("Empty ROI")
837
+
838
+ if channels is None:
839
+ channels = list(range(chc))
840
+ else:
841
+ channels = list(channels)
842
+
843
+ _, pos, _size = meta["location"]
844
+ roi_w = x1 - x0
845
+ roi_h = y1 - y0
846
+
847
+ out = np.empty((len(channels), roi_h, roi_w), dtype=dtype)
848
+
849
+ row_bytes = w * itemsize
850
+ roi_bytes = roi_w * itemsize
851
+ plane_bytes = h * row_bytes
852
+
853
+ with open(self._fname, "rb") as f:
854
+ for ci, c in enumerate(channels):
855
+ if c < 0 or c >= chc:
856
+ raise IndexError(f"channel {c} out of range")
857
+ plane_base = pos + c * plane_bytes
858
+ for r, y in enumerate(range(y0, y1)):
859
+ offset = plane_base + y * row_bytes + x0 * itemsize
860
+ f.seek(offset)
861
+ out[ci, r, :] = np.frombuffer(f.read(roi_bytes), dtype=dtype, count=roi_w)
862
+
863
+ if data_format == "channels_last":
864
+ return np.transpose(out, (1, 2, 0))
865
+ return out
866
+
867
+
778
868
  @staticmethod
779
869
  def _process_location_compression(p_dict):
780
870
  p_dict["location"] = XISF._parse_location(p_dict["location"])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: setiastrosuitepro
3
- Version: 1.6.2
3
+ Version: 1.6.12
4
4
  Summary: Seti Astro Suite Pro - Advanced astrophotography toolkit for image calibration, stacking, registration, photometry, and visualization
5
5
  License: GPL-3.0
6
6
  License-File: LICENSE
@@ -25,6 +25,7 @@ Requires-Dist: astroalign
25
25
  Requires-Dist: astropy
26
26
  Requires-Dist: astroquery
27
27
  Requires-Dist: exifread
28
+ Requires-Dist: imagecodecs (>=2025.11.11,<2026.0.0) ; python_version >= "3.11" and sys_platform == "darwin"
28
29
  Requires-Dist: imageio
29
30
  Requires-Dist: jplephem
30
31
  Requires-Dist: lightkurve
@@ -65,18 +66,18 @@ Description-Content-Type: text/markdown
65
66
 
66
67
  ### Other contributors:
67
68
  - [Fabio Tempera](https://github.com/Ft2801) 🥇
68
- - Complete code refactoring of `setiastrosuitepro.py` (20,000+ lines)
69
- - Addition of AstroSpikes tool and 10+ language translations
70
- - Implementation of UI elements, startup window, caching methods, lazy imports, utils functions, app statistics, and other important code optimizations across the entire project
69
+ - Complete code refactoring of `setiastrosuitepro.py` (20,000+ lines), and duplicated code removal across the project
70
+ - Addition of AstroSpikes tool, secret minigame, system resources monitor, app statistics, and 10+ language translations
71
+ - Implementation of UI elements, startup window, caching methods, lazy imports, utils functions, better memory management, and other important code optimizations across the entire project
71
72
  - [Joaquin Rodriguez](https://github.com/jrhuerta)
72
- - Project migration to Poetry and other small optimizations
73
+ - Project migration to Poetry
73
74
  - [Tim Dicke](https://github.com/dickett)
74
75
  - Windows and MacOS installer development
75
76
  - MacOS Wiki instructions maintenance
76
- - App testing
77
+ - App testing and small bugfixes
77
78
  - [Michael Lev](https://github.com/MichaelLevAstro)
78
79
  - Addition of hebrew language
79
- - [awitwicki]()
80
+ - [Andrew Witwicki](https://github.com/awitwicki)
80
81
  - Addition of ukrainian language
81
82
  ---
82
83