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.
- setiastro/data/SASP_data.fits +0 -0
- setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
- setiastro/data/catalogs/astrobin_filters.csv +890 -0
- setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
- setiastro/data/catalogs/cali2.csv +63 -0
- setiastro/data/catalogs/cali2color.csv +65 -0
- setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
- setiastro/data/catalogs/celestial_catalog.csv +24031 -0
- setiastro/data/catalogs/detected_stars.csv +24784 -0
- setiastro/data/catalogs/fits_header_data.csv +46 -0
- setiastro/data/catalogs/test.csv +8 -0
- setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
- setiastro/images/Astro_Spikes.png +0 -0
- setiastro/images/Background_startup.jpg +0 -0
- setiastro/images/HRDiagram.png +0 -0
- setiastro/images/LExtract.png +0 -0
- setiastro/images/LInsert.png +0 -0
- setiastro/images/Oxygenation-atm-2.svg.png +0 -0
- setiastro/images/RGB080604.png +0 -0
- setiastro/images/abeicon.png +0 -0
- setiastro/images/aberration.png +0 -0
- setiastro/images/andromedatry.png +0 -0
- setiastro/images/andromedatry_satellited.png +0 -0
- setiastro/images/annotated.png +0 -0
- setiastro/images/aperture.png +0 -0
- setiastro/images/astrosuite.ico +0 -0
- setiastro/images/astrosuite.png +0 -0
- setiastro/images/astrosuitepro.icns +0 -0
- setiastro/images/astrosuitepro.ico +0 -0
- setiastro/images/astrosuitepro.png +0 -0
- setiastro/images/background.png +0 -0
- setiastro/images/background2.png +0 -0
- setiastro/images/benchmark.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
- setiastro/images/blaster.png +0 -0
- setiastro/images/blink.png +0 -0
- setiastro/images/clahe.png +0 -0
- setiastro/images/collage.png +0 -0
- setiastro/images/colorwheel.png +0 -0
- setiastro/images/contsub.png +0 -0
- setiastro/images/convo.png +0 -0
- setiastro/images/copyslot.png +0 -0
- setiastro/images/cosmic.png +0 -0
- setiastro/images/cosmicsat.png +0 -0
- setiastro/images/crop1.png +0 -0
- setiastro/images/cropicon.png +0 -0
- setiastro/images/curves.png +0 -0
- setiastro/images/cvs.png +0 -0
- setiastro/images/debayer.png +0 -0
- setiastro/images/denoise_cnn_custom.png +0 -0
- setiastro/images/denoise_cnn_graph.png +0 -0
- setiastro/images/disk.png +0 -0
- setiastro/images/dse.png +0 -0
- setiastro/images/exoicon.png +0 -0
- setiastro/images/eye.png +0 -0
- setiastro/images/fliphorizontal.png +0 -0
- setiastro/images/flipvertical.png +0 -0
- setiastro/images/font.png +0 -0
- setiastro/images/freqsep.png +0 -0
- setiastro/images/functionbundle.png +0 -0
- setiastro/images/graxpert.png +0 -0
- setiastro/images/green.png +0 -0
- setiastro/images/gridicon.png +0 -0
- setiastro/images/halo.png +0 -0
- setiastro/images/hdr.png +0 -0
- setiastro/images/histogram.png +0 -0
- setiastro/images/hubble.png +0 -0
- setiastro/images/imagecombine.png +0 -0
- setiastro/images/invert.png +0 -0
- setiastro/images/isophote.png +0 -0
- setiastro/images/isophote_demo_figure.png +0 -0
- setiastro/images/isophote_demo_image.png +0 -0
- setiastro/images/isophote_demo_model.png +0 -0
- setiastro/images/isophote_demo_residual.png +0 -0
- setiastro/images/jwstpupil.png +0 -0
- setiastro/images/linearfit.png +0 -0
- setiastro/images/livestacking.png +0 -0
- setiastro/images/mask.png +0 -0
- setiastro/images/maskapply.png +0 -0
- setiastro/images/maskcreate.png +0 -0
- setiastro/images/maskremove.png +0 -0
- setiastro/images/morpho.png +0 -0
- setiastro/images/mosaic.png +0 -0
- setiastro/images/multiscale_decomp.png +0 -0
- setiastro/images/nbtorgb.png +0 -0
- setiastro/images/neutral.png +0 -0
- setiastro/images/nuke.png +0 -0
- setiastro/images/openfile.png +0 -0
- setiastro/images/pedestal.png +0 -0
- setiastro/images/pen.png +0 -0
- setiastro/images/pixelmath.png +0 -0
- setiastro/images/platesolve.png +0 -0
- setiastro/images/ppp.png +0 -0
- setiastro/images/pro.png +0 -0
- setiastro/images/project.png +0 -0
- setiastro/images/psf.png +0 -0
- setiastro/images/redo.png +0 -0
- setiastro/images/redoicon.png +0 -0
- setiastro/images/rescale.png +0 -0
- setiastro/images/rgbalign.png +0 -0
- setiastro/images/rgbcombo.png +0 -0
- setiastro/images/rgbextract.png +0 -0
- setiastro/images/rotate180.png +0 -0
- setiastro/images/rotatearbitrary.png +0 -0
- setiastro/images/rotateclockwise.png +0 -0
- setiastro/images/rotatecounterclockwise.png +0 -0
- setiastro/images/satellite.png +0 -0
- setiastro/images/script.png +0 -0
- setiastro/images/selectivecolor.png +0 -0
- setiastro/images/simbad.png +0 -0
- setiastro/images/slot0.png +0 -0
- setiastro/images/slot1.png +0 -0
- setiastro/images/slot2.png +0 -0
- setiastro/images/slot3.png +0 -0
- setiastro/images/slot4.png +0 -0
- setiastro/images/slot5.png +0 -0
- setiastro/images/slot6.png +0 -0
- setiastro/images/slot7.png +0 -0
- setiastro/images/slot8.png +0 -0
- setiastro/images/slot9.png +0 -0
- setiastro/images/spcc.png +0 -0
- setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
- setiastro/images/spinner.gif +0 -0
- setiastro/images/stacking.png +0 -0
- setiastro/images/staradd.png +0 -0
- setiastro/images/staralign.png +0 -0
- setiastro/images/starnet.png +0 -0
- setiastro/images/starregistration.png +0 -0
- setiastro/images/starspike.png +0 -0
- setiastro/images/starstretch.png +0 -0
- setiastro/images/statstretch.png +0 -0
- setiastro/images/supernova.png +0 -0
- setiastro/images/uhs.png +0 -0
- setiastro/images/undoicon.png +0 -0
- setiastro/images/upscale.png +0 -0
- setiastro/images/viewbundle.png +0 -0
- setiastro/images/whitebalance.png +0 -0
- setiastro/images/wimi_icon_256x256.png +0 -0
- setiastro/images/wimilogo.png +0 -0
- setiastro/images/wims.png +0 -0
- setiastro/images/wrench_icon.png +0 -0
- setiastro/images/xisfliberator.png +0 -0
- setiastro/qml/ResourceMonitor.qml +126 -0
- setiastro/saspro/__main__.py +228 -67
- setiastro/saspro/_generated/build_info.py +2 -1
- setiastro/saspro/abe.py +76 -25
- setiastro/saspro/aberration_ai.py +14 -14
- setiastro/saspro/add_stars.py +15 -12
- setiastro/saspro/astrobin_exporter.py +61 -58
- setiastro/saspro/astrospike_python.py +3 -1
- setiastro/saspro/autostretch.py +4 -2
- setiastro/saspro/backgroundneutral.py +65 -14
- setiastro/saspro/batch_convert.py +8 -5
- setiastro/saspro/batch_renamer.py +39 -36
- setiastro/saspro/blemish_blaster.py +15 -12
- setiastro/saspro/blink_comparator_pro.py +605 -379
- setiastro/saspro/cheat_sheet.py +62 -17
- setiastro/saspro/clahe.py +34 -8
- setiastro/saspro/comet_stacking.py +103 -38
- setiastro/saspro/common_tr.py +107 -0
- setiastro/saspro/continuum_subtract.py +7 -7
- setiastro/saspro/convo.py +12 -9
- setiastro/saspro/copyastro.py +3 -0
- setiastro/saspro/cosmicclarity.py +77 -52
- setiastro/saspro/crop_dialog_pro.py +80 -45
- setiastro/saspro/curve_editor_pro.py +51 -33
- setiastro/saspro/debayer.py +6 -3
- setiastro/saspro/doc_manager.py +49 -19
- setiastro/saspro/exoplanet_detector.py +11 -11
- setiastro/saspro/fitsmodifier.py +48 -44
- setiastro/saspro/fix_bom.py +32 -0
- setiastro/saspro/frequency_separation.py +18 -12
- setiastro/saspro/function_bundle.py +18 -16
- setiastro/saspro/generate_translations.py +3092 -0
- setiastro/saspro/ghs_dialog_pro.py +19 -16
- setiastro/saspro/graxpert.py +3 -0
- setiastro/saspro/gui/main_window.py +471 -126
- setiastro/saspro/gui/mixins/dock_mixin.py +123 -11
- setiastro/saspro/gui/mixins/file_mixin.py +25 -20
- setiastro/saspro/gui/mixins/geometry_mixin.py +115 -15
- setiastro/saspro/gui/mixins/header_mixin.py +6 -6
- setiastro/saspro/gui/mixins/mask_mixin.py +8 -8
- setiastro/saspro/gui/mixins/menu_mixin.py +62 -33
- setiastro/saspro/gui/mixins/toolbar_mixin.py +382 -226
- setiastro/saspro/gui/mixins/update_mixin.py +26 -26
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +29 -3
- setiastro/saspro/header_viewer.py +21 -18
- setiastro/saspro/histogram.py +29 -26
- setiastro/saspro/history_explorer.py +2 -0
- setiastro/saspro/i18n.py +168 -0
- setiastro/saspro/image_combine.py +3 -0
- setiastro/saspro/image_peeker_pro.py +52 -44
- setiastro/saspro/imageops/stretch.py +5 -13
- setiastro/saspro/isophote.py +3 -0
- setiastro/saspro/legacy/numba_utils.py +64 -47
- setiastro/saspro/linear_fit.py +3 -0
- setiastro/saspro/live_stacking.py +13 -2
- setiastro/saspro/mask_creation.py +180 -22
- setiastro/saspro/mfdeconv.py +5 -0
- setiastro/saspro/morphology.py +38 -13
- setiastro/saspro/multiscale_decomp.py +713 -256
- setiastro/saspro/nbtorgb_stars.py +12 -2
- setiastro/saspro/numba_utils.py +149 -48
- setiastro/saspro/ops/scripts.py +77 -17
- setiastro/saspro/ops/settings.py +177 -100
- setiastro/saspro/perfect_palette_picker.py +25 -7
- setiastro/saspro/pixelmath.py +114 -110
- setiastro/saspro/plate_solver.py +118 -108
- setiastro/saspro/remove_green.py +24 -7
- setiastro/saspro/remove_stars.py +136 -162
- setiastro/saspro/remove_stars_preset.py +55 -13
- setiastro/saspro/resources.py +46 -15
- setiastro/saspro/rgb_combination.py +19 -18
- setiastro/saspro/rgbalign.py +11 -11
- setiastro/saspro/save_options.py +5 -4
- setiastro/saspro/selective_color.py +84 -25
- setiastro/saspro/sfcc.py +119 -72
- setiastro/saspro/shortcuts.py +345 -36
- setiastro/saspro/signature_insert.py +4 -1
- setiastro/saspro/stacking_suite.py +2066 -1119
- setiastro/saspro/star_alignment.py +291 -331
- setiastro/saspro/star_spikes.py +137 -53
- setiastro/saspro/star_stretch.py +47 -10
- setiastro/saspro/stat_stretch.py +52 -16
- setiastro/saspro/status_log_dock.py +1 -1
- setiastro/saspro/subwindow.py +97 -36
- setiastro/saspro/supernovaasteroidhunter.py +68 -61
- setiastro/saspro/swap_manager.py +77 -42
- setiastro/saspro/translations/all_source_strings.json +4726 -0
- setiastro/saspro/translations/ar_translations.py +4096 -0
- setiastro/saspro/translations/de_translations.py +3728 -0
- setiastro/saspro/translations/es_translations.py +4169 -0
- setiastro/saspro/translations/fr_translations.py +4090 -0
- setiastro/saspro/translations/hi_translations.py +3803 -0
- setiastro/saspro/translations/integrate_translations.py +271 -0
- setiastro/saspro/translations/it_translations.py +4728 -0
- setiastro/saspro/translations/ja_translations.py +3834 -0
- setiastro/saspro/translations/pt_translations.py +3847 -0
- setiastro/saspro/translations/ru_translations.py +3082 -0
- setiastro/saspro/translations/saspro_ar.qm +0 -0
- setiastro/saspro/translations/saspro_ar.ts +16019 -0
- setiastro/saspro/translations/saspro_de.qm +0 -0
- setiastro/saspro/translations/saspro_de.ts +14548 -0
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +16202 -0
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +15870 -0
- setiastro/saspro/translations/saspro_hi.qm +0 -0
- setiastro/saspro/translations/saspro_hi.ts +14855 -0
- setiastro/saspro/translations/saspro_it.qm +0 -0
- setiastro/saspro/translations/saspro_it.ts +19046 -0
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +14980 -0
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +15024 -0
- setiastro/saspro/translations/saspro_ru.qm +0 -0
- setiastro/saspro/translations/saspro_ru.ts +11835 -0
- setiastro/saspro/translations/saspro_sw.qm +0 -0
- setiastro/saspro/translations/saspro_sw.ts +15237 -0
- setiastro/saspro/translations/saspro_uk.qm +0 -0
- setiastro/saspro/translations/saspro_uk.ts +15248 -0
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +15289 -0
- setiastro/saspro/translations/sw_translations.py +3897 -0
- setiastro/saspro/translations/uk_translations.py +3929 -0
- setiastro/saspro/translations/zh_translations.py +3910 -0
- setiastro/saspro/versioning.py +77 -0
- setiastro/saspro/view_bundle.py +20 -17
- setiastro/saspro/wavescale_hdr.py +54 -33
- setiastro/saspro/wavescale_hdr_preset.py +6 -5
- setiastro/saspro/wavescalede.py +54 -31
- setiastro/saspro/wavescalede_preset.py +9 -7
- setiastro/saspro/whitebalance.py +58 -22
- setiastro/saspro/widgets/common_utilities.py +12 -11
- setiastro/saspro/widgets/minigame/game.js +991 -0
- setiastro/saspro/widgets/minigame/index.html +53 -0
- setiastro/saspro/widgets/minigame/style.css +241 -0
- setiastro/saspro/widgets/preview_dialogs.py +8 -8
- setiastro/saspro/widgets/resource_monitor.py +263 -0
- setiastro/saspro/widgets/spinboxes.py +18 -0
- setiastro/saspro/widgets/wavelet_utils.py +52 -20
- setiastro/saspro/wimi.py +7996 -0
- setiastro/saspro/wims.py +578 -0
- setiastro/saspro/window_shelf.py +2 -2
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/METADATA +15 -3
- setiastrosuitepro-1.6.4.post1.dist-info/RECORD +368 -0
- setiastrosuitepro-1.6.0.dist-info/RECORD +0 -174
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/license.txt +0 -0
|
@@ -71,7 +71,7 @@ class UpdateMixin:
|
|
|
71
71
|
def check_for_updates_now(self):
|
|
72
72
|
"""Check for updates interactively (show result to user)."""
|
|
73
73
|
if self.statusBar():
|
|
74
|
-
self.statusBar().showMessage("Checking for updates...")
|
|
74
|
+
self.statusBar().showMessage(self.tr("Checking for updates..."))
|
|
75
75
|
self._kick_update_check(interactive=True)
|
|
76
76
|
|
|
77
77
|
def check_for_updates_startup(self):
|
|
@@ -109,8 +109,8 @@ class UpdateMixin:
|
|
|
109
109
|
if self.statusBar():
|
|
110
110
|
self.statusBar().showMessage("Update check failed.", 5000)
|
|
111
111
|
if interactive:
|
|
112
|
-
QMessageBox.warning(self, "Update Check Failed",
|
|
113
|
-
|
|
112
|
+
QMessageBox.warning(self, self.tr("Update Check Failed"),
|
|
113
|
+
self.tr("Unable to check for updates.\n\n{err}").replace("{err}", err))
|
|
114
114
|
else:
|
|
115
115
|
print(f"[updates] check failed: {err}")
|
|
116
116
|
return
|
|
@@ -122,8 +122,8 @@ class UpdateMixin:
|
|
|
122
122
|
if self.statusBar():
|
|
123
123
|
self.statusBar().showMessage("Update check failed (bad JSON).", 5000)
|
|
124
124
|
if interactive:
|
|
125
|
-
QMessageBox.warning(self, "Update Check Failed",
|
|
126
|
-
|
|
125
|
+
QMessageBox.warning(self, self.tr("Update Check Failed"),
|
|
126
|
+
self.tr("Update JSON is invalid.\n\n{je}").replace("{je}", str(je)))
|
|
127
127
|
else:
|
|
128
128
|
print(f"[updates] bad JSON: {je}")
|
|
129
129
|
return
|
|
@@ -136,8 +136,8 @@ class UpdateMixin:
|
|
|
136
136
|
if self.statusBar():
|
|
137
137
|
self.statusBar().showMessage("Update check failed (no 'version').", 5000)
|
|
138
138
|
if interactive:
|
|
139
|
-
QMessageBox.warning(self, "Update Check Failed",
|
|
140
|
-
"Update JSON missing the 'version' field.")
|
|
139
|
+
QMessageBox.warning(self, self.tr("Update Check Failed"),
|
|
140
|
+
self.tr("Update JSON missing the 'version' field."))
|
|
141
141
|
else:
|
|
142
142
|
print("[updates] JSON missing 'version'")
|
|
143
143
|
return
|
|
@@ -148,13 +148,13 @@ class UpdateMixin:
|
|
|
148
148
|
|
|
149
149
|
if available:
|
|
150
150
|
if self.statusBar():
|
|
151
|
-
self.statusBar().showMessage(
|
|
151
|
+
self.statusBar().showMessage(self.tr("Update available: {0}").format(latest_str), 5000)
|
|
152
152
|
msg_box = QMessageBox(self)
|
|
153
153
|
msg_box.setIcon(QMessageBox.Icon.Information)
|
|
154
|
-
msg_box.setWindowTitle("Update Available")
|
|
155
|
-
msg_box.setText(
|
|
154
|
+
msg_box.setWindowTitle(self.tr("Update Available"))
|
|
155
|
+
msg_box.setText(self.tr("A new version ({0}) is available!").format(latest_str))
|
|
156
156
|
if notes:
|
|
157
|
-
msg_box.setInformativeText(
|
|
157
|
+
msg_box.setInformativeText(self.tr("Release Notes:\n{0}").format(notes))
|
|
158
158
|
msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
159
159
|
msg_box.setDefaultButton(QMessageBox.StandardButton.Yes)
|
|
160
160
|
|
|
@@ -170,7 +170,7 @@ class UpdateMixin:
|
|
|
170
170
|
"Linux" if plat.startswith("linux") else "", ""
|
|
171
171
|
)
|
|
172
172
|
if not link:
|
|
173
|
-
QMessageBox.warning(self, "Download", "No download link available for this platform.")
|
|
173
|
+
QMessageBox.warning(self, self.tr("Download"), self.tr("No download link available for this platform."))
|
|
174
174
|
return
|
|
175
175
|
|
|
176
176
|
if plat.startswith("win"):
|
|
@@ -183,8 +183,8 @@ class UpdateMixin:
|
|
|
183
183
|
if self.statusBar():
|
|
184
184
|
self.statusBar().showMessage("You're up to date.", 3000)
|
|
185
185
|
if interactive:
|
|
186
|
-
QMessageBox.information(self, "Up to Date",
|
|
187
|
-
"You're already running the latest version.")
|
|
186
|
+
QMessageBox.information(self, self.tr("Up to Date"),
|
|
187
|
+
self.tr("You're already running the latest version."))
|
|
188
188
|
finally:
|
|
189
189
|
reply.deleteLater()
|
|
190
190
|
|
|
@@ -229,7 +229,7 @@ class UpdateMixin:
|
|
|
229
229
|
|
|
230
230
|
reply.downloadProgress.connect(
|
|
231
231
|
lambda rec, tot: self.statusBar().showMessage(
|
|
232
|
-
|
|
232
|
+
self.tr("Downloading update... {0:.1f} KB / {1:.1f} KB").format(rec / 1024, tot / 1024) if tot > 0 else self.tr("Downloading update...")
|
|
233
233
|
)
|
|
234
234
|
)
|
|
235
235
|
|
|
@@ -244,8 +244,8 @@ class UpdateMixin:
|
|
|
244
244
|
target_path = Path(reply.property("target_path"))
|
|
245
245
|
|
|
246
246
|
if reply.error() != QNetworkReply.NetworkError.NoError:
|
|
247
|
-
QMessageBox.warning(self, "Update Failed",
|
|
248
|
-
|
|
247
|
+
QMessageBox.warning(self, self.tr("Update Failed"),
|
|
248
|
+
self.tr("Could not download update:\n{0}").format(reply.errorString()))
|
|
249
249
|
return
|
|
250
250
|
|
|
251
251
|
# Write the .zip
|
|
@@ -254,8 +254,8 @@ class UpdateMixin:
|
|
|
254
254
|
with open(target_path, "wb") as f:
|
|
255
255
|
f.write(data)
|
|
256
256
|
except Exception as e:
|
|
257
|
-
QMessageBox.warning(self, "Update Failed",
|
|
258
|
-
|
|
257
|
+
QMessageBox.warning(self, self.tr("Update Failed"),
|
|
258
|
+
self.tr("Could not save update to disk:\n{0}").format(e))
|
|
259
259
|
return
|
|
260
260
|
|
|
261
261
|
self.statusBar().showMessage(f"Update downloaded to {target_path}", 5000)
|
|
@@ -267,8 +267,8 @@ class UpdateMixin:
|
|
|
267
267
|
with zipfile.ZipFile(target_path, "r") as zf:
|
|
268
268
|
zf.extractall(extract_dir)
|
|
269
269
|
except Exception as e:
|
|
270
|
-
QMessageBox.warning(self, "Update Failed",
|
|
271
|
-
|
|
270
|
+
QMessageBox.warning(self, self.tr("Update Failed"),
|
|
271
|
+
self.tr("Could not extract update zip:\n{0}").format(e))
|
|
272
272
|
return
|
|
273
273
|
|
|
274
274
|
# Look recursively for an .exe
|
|
@@ -277,7 +277,7 @@ class UpdateMixin:
|
|
|
277
277
|
QMessageBox.warning(
|
|
278
278
|
self,
|
|
279
279
|
"Update Failed",
|
|
280
|
-
|
|
280
|
+
self.tr("Downloaded ZIP did not contain an .exe installer.\nFolder: {0}").format(extract_dir)
|
|
281
281
|
)
|
|
282
282
|
return
|
|
283
283
|
|
|
@@ -289,8 +289,8 @@ class UpdateMixin:
|
|
|
289
289
|
# Ask to run
|
|
290
290
|
ok = QMessageBox.question(
|
|
291
291
|
self,
|
|
292
|
-
"Run Installer",
|
|
293
|
-
"The update has been downloaded.\n\nRun the installer now? (SAS will close.)",
|
|
292
|
+
self.tr("Run Installer"),
|
|
293
|
+
self.tr("The update has been downloaded.\n\nRun the installer now? (SAS will close.)"),
|
|
294
294
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
295
295
|
QMessageBox.StandardButton.Yes,
|
|
296
296
|
)
|
|
@@ -301,8 +301,8 @@ class UpdateMixin:
|
|
|
301
301
|
try:
|
|
302
302
|
subprocess.Popen([str(installer_path)], shell=False)
|
|
303
303
|
except Exception as e:
|
|
304
|
-
QMessageBox.warning(self, "Update Failed",
|
|
305
|
-
|
|
304
|
+
QMessageBox.warning(self, self.tr("Update Failed"),
|
|
305
|
+
self.tr("Could not start installer:\n{0}").format(e))
|
|
306
306
|
return
|
|
307
307
|
|
|
308
308
|
# Close app so the installer can overwrite files
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QFormLayout, QPushButton
|
|
2
|
+
from PyQt6.QtCore import Qt, QSettings
|
|
3
|
+
from PyQt6.QtGui import QIcon
|
|
4
|
+
|
|
5
|
+
class StatisticsDialog(QDialog):
|
|
6
|
+
def __init__(self, parent=None):
|
|
7
|
+
super().__init__(parent)
|
|
8
|
+
self.setWindowTitle(self.tr("App Statistics"))
|
|
9
|
+
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint)
|
|
10
|
+
self.resize(300, 200)
|
|
11
|
+
|
|
12
|
+
# Settings to read stats
|
|
13
|
+
self.settings = QSettings("SetiAstro", "SetiAstroSuitePro")
|
|
14
|
+
|
|
15
|
+
layout = QVBoxLayout(self)
|
|
16
|
+
|
|
17
|
+
form_layout = QFormLayout()
|
|
18
|
+
|
|
19
|
+
# Time Spent
|
|
20
|
+
total_seconds = self.settings.value("stats/total_time_seconds", 0, type=float)
|
|
21
|
+
days = int(total_seconds // 86400)
|
|
22
|
+
hours = int((total_seconds % 86400) // 3600)
|
|
23
|
+
minutes = int((total_seconds % 3600) // 60)
|
|
24
|
+
|
|
25
|
+
time_str = f"{days} {self.tr('Days')}, {hours} {self.tr('Hours')}, {minutes} {self.tr('Minutes')}"
|
|
26
|
+
if days == 0:
|
|
27
|
+
time_str = f"{hours} {self.tr('Hours')}, {minutes} {self.tr('Minutes')}"
|
|
28
|
+
|
|
29
|
+
self.lbl_time = QLabel(time_str)
|
|
30
|
+
form_layout.addRow(self.tr("Time Spent:"), self.lbl_time)
|
|
31
|
+
|
|
32
|
+
# Images Opened
|
|
33
|
+
images_count = self.settings.value("stats/opened_images_count", 0, type=int)
|
|
34
|
+
self.lbl_images = QLabel(str(images_count))
|
|
35
|
+
form_layout.addRow(self.tr("Images Opened:"), self.lbl_images)
|
|
36
|
+
|
|
37
|
+
# Tools Opened
|
|
38
|
+
tools_count = self.settings.value("stats/opened_tools_count", 0, type=int)
|
|
39
|
+
self.lbl_tools = QLabel(str(tools_count))
|
|
40
|
+
form_layout.addRow(self.tr("Tools Opened:"), self.lbl_tools)
|
|
41
|
+
|
|
42
|
+
layout.addLayout(form_layout)
|
|
43
|
+
|
|
44
|
+
# Close button
|
|
45
|
+
btn_close = QPushButton(self.tr("Close"))
|
|
46
|
+
btn_close.clicked.connect(self.accept)
|
|
47
|
+
layout.addWidget(btn_close, alignment=Qt.AlignmentFlag.AlignRight)
|
setiastro/saspro/halobgon.py
CHANGED
|
@@ -249,6 +249,9 @@ class HaloBGonDialogPro(QDialog):
|
|
|
249
249
|
def __init__(self, parent, doc, icon: Optional[QIcon] = None):
|
|
250
250
|
super().__init__(parent)
|
|
251
251
|
self.setWindowTitle("Halo-B-Gon")
|
|
252
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
253
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
254
|
+
self.setModal(False)
|
|
252
255
|
if icon:
|
|
253
256
|
try: self.setWindowIcon(icon)
|
|
254
257
|
except Exception as e:
|
|
@@ -427,7 +430,8 @@ class HaloBGonDialogPro(QDialog):
|
|
|
427
430
|
except Exception:
|
|
428
431
|
pass
|
|
429
432
|
|
|
430
|
-
|
|
433
|
+
# Dialog stays open - refresh document for next operation
|
|
434
|
+
self._refresh_document_from_active()
|
|
431
435
|
return
|
|
432
436
|
else:
|
|
433
437
|
# Fallback: try legacy spawner if present; else warn and overwrite.
|
|
@@ -437,7 +441,8 @@ class HaloBGonDialogPro(QDialog):
|
|
|
437
441
|
if callable(spawner):
|
|
438
442
|
title = self.doc.display_name() if hasattr(self.doc, "display_name") else "Image"
|
|
439
443
|
spawner(out, f"{title} [Halo-B-Gon]")
|
|
440
|
-
|
|
444
|
+
# Dialog stays open - refresh document for next operation
|
|
445
|
+
self._refresh_document_from_active()
|
|
441
446
|
return
|
|
442
447
|
else:
|
|
443
448
|
QMessageBox.warning(
|
|
@@ -448,11 +453,32 @@ class HaloBGonDialogPro(QDialog):
|
|
|
448
453
|
|
|
449
454
|
# Overwrite current (original behavior)
|
|
450
455
|
self._apply_overwrite(out)
|
|
451
|
-
|
|
456
|
+
# Dialog stays open - refresh document for next operation
|
|
457
|
+
self._refresh_document_from_active()
|
|
452
458
|
|
|
453
459
|
except Exception as e:
|
|
454
460
|
QMessageBox.critical(self, "Halo-B-Gon", f"Failed to apply:\n{e}")
|
|
455
461
|
|
|
462
|
+
def _refresh_document_from_active(self):
|
|
463
|
+
"""
|
|
464
|
+
Refresh the dialog's document reference to the currently active document.
|
|
465
|
+
This allows reusing the same dialog on different images.
|
|
466
|
+
"""
|
|
467
|
+
try:
|
|
468
|
+
main = self.parent()
|
|
469
|
+
if main and hasattr(main, "_active_doc"):
|
|
470
|
+
new_doc = main._active_doc()
|
|
471
|
+
if new_doc is not None and new_doc is not self.doc:
|
|
472
|
+
self.doc = new_doc
|
|
473
|
+
# Refresh preview for new document
|
|
474
|
+
self.orig = np.clip(np.asarray(new_doc.image, dtype=np.float32), 0.0, 1.0)
|
|
475
|
+
disp = self.orig
|
|
476
|
+
if disp.ndim == 2: disp = disp[..., None].repeat(3, axis=2)
|
|
477
|
+
elif disp.ndim == 3 and disp.shape[2] == 1: disp = disp.repeat(3, axis=2)
|
|
478
|
+
self._disp_base = disp
|
|
479
|
+
self._update_preview()
|
|
480
|
+
except Exception:
|
|
481
|
+
pass
|
|
456
482
|
|
|
457
483
|
|
|
458
484
|
def _reset(self):
|
|
@@ -30,15 +30,15 @@ class HeaderViewerDock(QDockWidget):
|
|
|
30
30
|
Supports FITS headers and XISF file & image metadata.
|
|
31
31
|
"""
|
|
32
32
|
def __init__(self, parent=None):
|
|
33
|
-
super().__init__("Header Viewer", parent)
|
|
33
|
+
super().__init__(self.tr("Header Viewer"), parent)
|
|
34
34
|
self._doc: Optional[ImageDocument] = None
|
|
35
35
|
self._doc_conn = False
|
|
36
36
|
|
|
37
37
|
self._tree = QTreeWidget()
|
|
38
|
-
self._tree.setHeaderLabels(["Key", "Value"])
|
|
38
|
+
self._tree.setHeaderLabels([self.tr("Key"), self.tr("Value")])
|
|
39
39
|
self._tree.setColumnWidth(0, 220)
|
|
40
40
|
|
|
41
|
-
self._save_btn = QPushButton("Save Metadata…")
|
|
41
|
+
self._save_btn = QPushButton(self.tr("Save Metadata…"))
|
|
42
42
|
self._save_btn.clicked.connect(self._save_metadata)
|
|
43
43
|
self._dm = None # <-- NEW: DocManager to query "active"
|
|
44
44
|
self._follow_hover = False # <-- optional toggle if you ever want hover-follow
|
|
@@ -174,6 +174,9 @@ class HeaderViewerDock(QDockWidget):
|
|
|
174
174
|
|
|
175
175
|
# --- helpers ---------------------------------------------------------
|
|
176
176
|
def _populate_header_dict(self, d: dict, title="Header (dict)"):
|
|
177
|
+
# We translate the default title if it's the default, but often title is passed in.
|
|
178
|
+
# If title is passed in English from other methods, we should translate it at the call site or here if possible.
|
|
179
|
+
# Since title is variable, we'll leave it as is, but ensure call sites pass translated strings.
|
|
177
180
|
root = QTreeWidgetItem([title])
|
|
178
181
|
self._tree.addTopLevelItem(root)
|
|
179
182
|
for k, v in d.items():
|
|
@@ -196,11 +199,11 @@ class HeaderViewerDock(QDockWidget):
|
|
|
196
199
|
pass
|
|
197
200
|
self._populate_fits_header(hdr)
|
|
198
201
|
elif fmt == "dict":
|
|
199
|
-
self._populate_header_dict(snap.get("items") or {}, "Header (snapshot)")
|
|
202
|
+
self._populate_header_dict(snap.get("items") or {}, self.tr("Header (snapshot)"))
|
|
200
203
|
else:
|
|
201
204
|
# generic repr fallback
|
|
202
205
|
txt = (snap or {}).get("text", "")
|
|
203
|
-
node = QTreeWidgetItem(["Header (snapshot)"])
|
|
206
|
+
node = QTreeWidgetItem([self.tr("Header (snapshot)")])
|
|
204
207
|
self._tree.addTopLevelItem(node)
|
|
205
208
|
node.addChild(QTreeWidgetItem(["repr", str(txt)]))
|
|
206
209
|
|
|
@@ -219,7 +222,7 @@ class HeaderViewerDock(QDockWidget):
|
|
|
219
222
|
|
|
220
223
|
# 2) dict-style header (e.g., XISF-style properties captured as dict)
|
|
221
224
|
if isinstance(hdr, dict):
|
|
222
|
-
self._populate_header_dict(hdr, "Header (dict from document)")
|
|
225
|
+
self._populate_header_dict(hdr, self.tr("Header (dict from document)"))
|
|
223
226
|
return True
|
|
224
227
|
|
|
225
228
|
# 3) JSON-safe snapshot captured by DocManager
|
|
@@ -231,7 +234,7 @@ class HeaderViewerDock(QDockWidget):
|
|
|
231
234
|
# 4) XISF properties stored in metadata (common keys)
|
|
232
235
|
for k in ("xisf_header", "xisf_properties"):
|
|
233
236
|
if isinstance(meta.get(k), dict):
|
|
234
|
-
self._populate_header_dict(meta[k], "XISF Properties (document)")
|
|
237
|
+
self._populate_header_dict(meta[k], self.tr("XISF Properties (document)"))
|
|
235
238
|
return True
|
|
236
239
|
|
|
237
240
|
return False
|
|
@@ -264,7 +267,7 @@ class HeaderViewerDock(QDockWidget):
|
|
|
264
267
|
xisf = XISF(path)
|
|
265
268
|
props = getattr(xisf, "properties", None)
|
|
266
269
|
if isinstance(props, dict):
|
|
267
|
-
self._populate_header_dict(props, "XISF Properties")
|
|
270
|
+
self._populate_header_dict(props, self.tr("XISF Properties"))
|
|
268
271
|
return True
|
|
269
272
|
except Exception:
|
|
270
273
|
pass
|
|
@@ -277,14 +280,14 @@ class HeaderViewerDock(QDockWidget):
|
|
|
277
280
|
self._tree.clear()
|
|
278
281
|
base_doc = self._unwrap_base_doc(self._doc)
|
|
279
282
|
if not base_doc:
|
|
280
|
-
self.setWindowTitle("Header Viewer")
|
|
283
|
+
self.setWindowTitle(self.tr("Header Viewer"))
|
|
281
284
|
return
|
|
282
285
|
self._doc = base_doc
|
|
283
286
|
|
|
284
287
|
meta = self._doc.metadata or {}
|
|
285
288
|
path = (meta.get("file_path") or "") if isinstance(meta.get("file_path"), str) else ""
|
|
286
|
-
base = os.path.basename(path) if path else (meta.get("display_name") or "Untitled")
|
|
287
|
-
self.setWindowTitle(
|
|
289
|
+
base = os.path.basename(path) if path else (meta.get("display_name") or self.tr("Untitled"))
|
|
290
|
+
self.setWindowTitle(self.tr("Header: {0}").format(base))
|
|
288
291
|
|
|
289
292
|
try:
|
|
290
293
|
# 1) Prefer header data already stored with the document
|
|
@@ -304,7 +307,7 @@ class HeaderViewerDock(QDockWidget):
|
|
|
304
307
|
pass
|
|
305
308
|
|
|
306
309
|
# 4) Always show remaining lightweight metadata (skip heavy blobs we already rendered)
|
|
307
|
-
info_root = QTreeWidgetItem(["Metadata"])
|
|
310
|
+
info_root = QTreeWidgetItem([self.tr("Metadata")])
|
|
308
311
|
self._tree.addTopLevelItem(info_root)
|
|
309
312
|
for k, v in meta.items():
|
|
310
313
|
if k in ("original_header", "fits_header", "header", "wcs", "__header_snapshot__", "xisf_header", "xisf_properties"):
|
|
@@ -320,7 +323,7 @@ class HeaderViewerDock(QDockWidget):
|
|
|
320
323
|
|
|
321
324
|
# ---- population helpers ----
|
|
322
325
|
def _populate_fits_header(self, header: Any):
|
|
323
|
-
root = QTreeWidgetItem(["FITS Header"])
|
|
326
|
+
root = QTreeWidgetItem([self.tr("FITS Header")])
|
|
324
327
|
self._tree.addTopLevelItem(root)
|
|
325
328
|
|
|
326
329
|
# FITS Header: sanitize and iterate cards defensively
|
|
@@ -354,7 +357,7 @@ class HeaderViewerDock(QDockWidget):
|
|
|
354
357
|
|
|
355
358
|
def _populate_wcs(self, wcs_obj):
|
|
356
359
|
"""Show a real astropy.wcs.WCS as header-like key/values."""
|
|
357
|
-
root = QTreeWidgetItem(["WCS"])
|
|
360
|
+
root = QTreeWidgetItem([self.tr("WCS")])
|
|
358
361
|
self._tree.addTopLevelItem(root)
|
|
359
362
|
try:
|
|
360
363
|
# Use relax=True so SIP/etc. are included if present.
|
|
@@ -381,14 +384,14 @@ class HeaderViewerDock(QDockWidget):
|
|
|
381
384
|
img_meta: Dict[str, Any] = img_meta_list[0] if img_meta_list else {}
|
|
382
385
|
|
|
383
386
|
# File-level metadata
|
|
384
|
-
froot = QTreeWidgetItem(["XISF File Metadata"])
|
|
387
|
+
froot = QTreeWidgetItem([self.tr("XISF File Metadata")])
|
|
385
388
|
self._tree.addTopLevelItem(froot)
|
|
386
389
|
for k, v in file_meta.items():
|
|
387
390
|
vstr = v.get("value", "") if isinstance(v, dict) else v
|
|
388
391
|
froot.addChild(QTreeWidgetItem([str(k), str(vstr)]))
|
|
389
392
|
|
|
390
393
|
# Image-level metadata
|
|
391
|
-
iroot = QTreeWidgetItem(["XISF Image Metadata"])
|
|
394
|
+
iroot = QTreeWidgetItem([self.tr("XISF Image Metadata")])
|
|
392
395
|
self._tree.addTopLevelItem(iroot)
|
|
393
396
|
|
|
394
397
|
# FITS-like keywords (nested)
|
|
@@ -418,7 +421,7 @@ class HeaderViewerDock(QDockWidget):
|
|
|
418
421
|
def _save_metadata(self):
|
|
419
422
|
if not self._doc:
|
|
420
423
|
return
|
|
421
|
-
path, _ = QFileDialog.getSaveFileName(self, "Save Metadata", "", "CSV (*.csv)")
|
|
424
|
+
path, _ = QFileDialog.getSaveFileName(self, self.tr("Save Metadata"), "", self.tr("CSV (*.csv)"))
|
|
422
425
|
if not path:
|
|
423
426
|
return
|
|
424
427
|
|
|
@@ -442,4 +445,4 @@ class HeaderViewerDock(QDockWidget):
|
|
|
442
445
|
w.writerow(["Key", "Value"])
|
|
443
446
|
w.writerows(rows)
|
|
444
447
|
except Exception as e:
|
|
445
|
-
QMessageBox.critical(self, "Save Metadata",
|
|
448
|
+
QMessageBox.critical(self, self.tr("Save Metadata"), self.tr("Failed to save:\n{0}").format(e))
|
setiastro/saspro/histogram.py
CHANGED
|
@@ -29,7 +29,10 @@ class HistogramDialog(QDialog):
|
|
|
29
29
|
pivotPicked = pyqtSignal(float) # normalized [0..1] x position for GHS pivot
|
|
30
30
|
def __init__(self, parent, document):
|
|
31
31
|
super().__init__(parent)
|
|
32
|
-
self.setWindowTitle("Histogram")
|
|
32
|
+
self.setWindowTitle(self.tr("Histogram"))
|
|
33
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
34
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
35
|
+
self.setModal(False)
|
|
33
36
|
self.doc = document
|
|
34
37
|
self.image = _to_float_preserve(document.image)
|
|
35
38
|
|
|
@@ -110,10 +113,10 @@ class HistogramDialog(QDialog):
|
|
|
110
113
|
self.hist_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
111
114
|
self.scroll_area.setWidget(self.hist_label)
|
|
112
115
|
self.hist_label.installEventFilter(self)
|
|
113
|
-
self.hist_label.setToolTip(
|
|
116
|
+
self.hist_label.setToolTip(self.tr(
|
|
114
117
|
"Ctrl+Click on the histogram to send that intensity as the "
|
|
115
118
|
"pivot to Hyperbolic Stretch (if open)."
|
|
116
|
-
)
|
|
119
|
+
))
|
|
117
120
|
self.scroll_area.viewport().installEventFilter(self)
|
|
118
121
|
|
|
119
122
|
splitter.addWidget(self.scroll_area)
|
|
@@ -123,8 +126,8 @@ class HistogramDialog(QDialog):
|
|
|
123
126
|
self.stats_table.setRowCount(7)
|
|
124
127
|
self.stats_table.setColumnCount(1)
|
|
125
128
|
self.stats_table.setVerticalHeaderLabels([
|
|
126
|
-
"Min", "Max", "Median", "StdDev",
|
|
127
|
-
"MAD", "Low Clipped", "High Clipped"
|
|
129
|
+
self.tr("Min"), self.tr("Max"), self.tr("Median"), self.tr("StdDev"),
|
|
130
|
+
self.tr("MAD"), self.tr("Low Clipped"), self.tr("High Clipped")
|
|
128
131
|
])
|
|
129
132
|
|
|
130
133
|
# Let it grow/shrink with the splitter
|
|
@@ -159,31 +162,31 @@ class HistogramDialog(QDialog):
|
|
|
159
162
|
self.zoom_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
|
|
160
163
|
self.zoom_slider.valueChanged.connect(self._on_zoom_changed)
|
|
161
164
|
|
|
162
|
-
ctl.addWidget(QLabel("Zoom:"))
|
|
165
|
+
ctl.addWidget(QLabel(self.tr("Zoom:")))
|
|
163
166
|
ctl.addWidget(self.zoom_slider)
|
|
164
167
|
|
|
165
|
-
self.btn_logx = QPushButton("Toggle Log X-Axis", self)
|
|
168
|
+
self.btn_logx = QPushButton(self.tr("Toggle Log X-Axis"), self)
|
|
166
169
|
self.btn_logx.setCheckable(True)
|
|
167
170
|
self.btn_logx.toggled.connect(self._toggle_log_x)
|
|
168
171
|
ctl.addWidget(self.btn_logx)
|
|
169
172
|
|
|
170
|
-
self.btn_logy = QPushButton("Toggle Log Y-Axis", self)
|
|
173
|
+
self.btn_logy = QPushButton(self.tr("Toggle Log Y-Axis"), self)
|
|
171
174
|
self.btn_logy.setCheckable(True)
|
|
172
175
|
self.btn_logy.toggled.connect(self._toggle_log_y)
|
|
173
176
|
ctl.addWidget(self.btn_logy)
|
|
174
177
|
|
|
175
178
|
self.btn_sensor_max = QToolButton(self)
|
|
176
179
|
self.btn_sensor_max.setText("?")
|
|
177
|
-
self.btn_sensor_max.setToolTip(
|
|
180
|
+
self.btn_sensor_max.setToolTip(self.tr(
|
|
178
181
|
"Set your camera's true saturation level for clipping warnings.\n"
|
|
179
182
|
"Tip: take an overexposed frame and see its max ADU."
|
|
180
|
-
)
|
|
183
|
+
))
|
|
181
184
|
self.btn_sensor_max.clicked.connect(self._prompt_sensor_max)
|
|
182
185
|
ctl.addWidget(self.btn_sensor_max)
|
|
183
186
|
|
|
184
187
|
main_layout.addLayout(ctl)
|
|
185
188
|
|
|
186
|
-
btn_close = QPushButton("Close", self)
|
|
189
|
+
btn_close = QPushButton(self.tr("Close"), self)
|
|
187
190
|
btn_close.clicked.connect(self.accept)
|
|
188
191
|
main_layout.addWidget(btn_close)
|
|
189
192
|
|
|
@@ -408,7 +411,7 @@ class HistogramDialog(QDialog):
|
|
|
408
411
|
p.setPen(QPen(QColor(220, 0, 0), 2, Qt.PenStyle.DashLine))
|
|
409
412
|
p.drawLine(x, top_margin, x, axis_y)
|
|
410
413
|
p.drawText(min(x + 4, width - 80), top_margin + 12,
|
|
411
|
-
|
|
414
|
+
self.tr("True Max {0:.4f}").format(self.sensor_max01))
|
|
412
415
|
# store mapping info for Ctrl+click → normalized x
|
|
413
416
|
try:
|
|
414
417
|
self._click_mapping = {
|
|
@@ -567,13 +570,13 @@ class HistogramDialog(QDialog):
|
|
|
567
570
|
eps = 1e-6 # tolerance for "exactly 0/1" after float ops
|
|
568
571
|
|
|
569
572
|
row_defs = [
|
|
570
|
-
("Min", lambda c: float(np.min(c)), "{:.4f}"),
|
|
571
|
-
("Max", lambda c: float(np.max(c)), "{:.4f}"),
|
|
572
|
-
("Median", lambda c: float(np.median(c)), "{:.4f}"),
|
|
573
|
-
("StdDev", lambda c: float(np.std(c)), "{:.4f}"),
|
|
574
|
-
("MAD", lambda c: float(np.median(np.abs(c - np.median(c)))), "{:.4f}"),
|
|
575
|
-
("Low Clipped", lambda c: _clip_fmt(c, low=True, eps=eps), "{}"),
|
|
576
|
-
("High Clipped", lambda c: _clip_fmt(c, low=False, eps=eps), "{}"),
|
|
573
|
+
(self.tr("Min"), lambda c: float(np.min(c)), "{:.4f}"),
|
|
574
|
+
(self.tr("Max"), lambda c: float(np.max(c)), "{:.4f}"),
|
|
575
|
+
(self.tr("Median"), lambda c: float(np.median(c)), "{:.4f}"),
|
|
576
|
+
(self.tr("StdDev"), lambda c: float(np.std(c)), "{:.4f}"),
|
|
577
|
+
(self.tr("MAD"), lambda c: float(np.median(np.abs(c - np.median(c)))), "{:.4f}"),
|
|
578
|
+
(self.tr("Low Clipped"), lambda c: _clip_fmt(c, low=True, eps=eps), "{}"),
|
|
579
|
+
(self.tr("High Clipped"), lambda c: _clip_fmt(c, low=False, eps=eps), "{}"),
|
|
577
580
|
]
|
|
578
581
|
|
|
579
582
|
def _clip_fmt(c, low: bool, eps: float):
|
|
@@ -600,7 +603,7 @@ class HistogramDialog(QDialog):
|
|
|
600
603
|
it.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
601
604
|
|
|
602
605
|
# --- visual pop for non-trivial clipping ---
|
|
603
|
-
if lab in ("Low Clipped", "High Clipped"):
|
|
606
|
+
if lab in (self.tr("Low Clipped"), self.tr("High Clipped")):
|
|
604
607
|
# text looks like: "123 (0.456%)"
|
|
605
608
|
try:
|
|
606
609
|
pct_str = text.split("(")[1].split("%")[0]
|
|
@@ -674,11 +677,11 @@ class HistogramDialog(QDialog):
|
|
|
674
677
|
|
|
675
678
|
val, ok = QInputDialog.getInt(
|
|
676
679
|
self,
|
|
677
|
-
"Sensor True Max (ADU)",
|
|
678
|
-
|
|
679
|
-
|
|
680
|
+
self.tr("Sensor True Max (ADU)"),
|
|
681
|
+
self.tr("Enter your sensor's true saturation value in native ADU.\n"
|
|
682
|
+
"(Typical max for this file type is {0})\n\n"
|
|
680
683
|
"You can measure this by taking a deliberately overexposed frame\n"
|
|
681
|
-
"and reading its maximum pixel value.",
|
|
684
|
+
"and reading its maximum pixel value.").format(self.native_theoretical_max),
|
|
682
685
|
int(current),
|
|
683
686
|
1,
|
|
684
687
|
int(self.native_theoretical_max)
|
|
@@ -690,8 +693,8 @@ class HistogramDialog(QDialog):
|
|
|
690
693
|
# float images / unknown depth: allow normalized max
|
|
691
694
|
val, ok = QInputDialog.getDouble(
|
|
692
695
|
self,
|
|
693
|
-
"Histogram Effective Max",
|
|
694
|
-
"Enter effective maximum for clipping (normalized units).",
|
|
696
|
+
self.tr("Histogram Effective Max"),
|
|
697
|
+
self.tr("Enter effective maximum for clipping (normalized units)."),
|
|
695
698
|
float(self.sensor_max01),
|
|
696
699
|
1e-6,
|
|
697
700
|
1.0,
|
|
@@ -436,6 +436,8 @@ class HistoryExplorerDialog(QDialog):
|
|
|
436
436
|
def __init__(self, document, parent=None):
|
|
437
437
|
super().__init__(parent)
|
|
438
438
|
self.setWindowTitle("History Explorer")
|
|
439
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
440
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
439
441
|
self.setModal(False)
|
|
440
442
|
self.doc = document
|
|
441
443
|
|