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
setiastro/saspro/i18n.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Internationalization (i18n) module for Seti Astro Suite Pro.
|
|
4
|
+
|
|
5
|
+
Handles loading and managing translations using PyQt6's QTranslator.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Optional, Dict, List
|
|
11
|
+
|
|
12
|
+
from PyQt6.QtCore import QCoreApplication, QTranslator, QLocale, QSettings
|
|
13
|
+
|
|
14
|
+
# Module-level translator instance (kept alive for the app lifetime)
|
|
15
|
+
_translator: Optional[QTranslator] = None
|
|
16
|
+
_current_language: str = "en"
|
|
17
|
+
|
|
18
|
+
# Available languages with display names
|
|
19
|
+
AVAILABLE_LANGUAGES: Dict[str, str] = {
|
|
20
|
+
"en": "English",
|
|
21
|
+
"it": "Italiano",
|
|
22
|
+
"fr": "Français",
|
|
23
|
+
"es": "Español",
|
|
24
|
+
"zh": "简体中文",
|
|
25
|
+
"de": "Deutsch",
|
|
26
|
+
"pt": "Português",
|
|
27
|
+
"ja": "日本語",
|
|
28
|
+
"hi": "हिन्दी",
|
|
29
|
+
"sw": "Kiswahili",
|
|
30
|
+
"uk": "Українська",
|
|
31
|
+
"ru": "Русский",
|
|
32
|
+
"ar": "العربية",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_translations_dir() -> str:
|
|
37
|
+
"""Get the path to the translations directory."""
|
|
38
|
+
# Source / installed package location
|
|
39
|
+
module_dir = os.path.dirname(os.path.abspath(__file__))
|
|
40
|
+
pkg_dir = os.path.join(module_dir, "translations")
|
|
41
|
+
|
|
42
|
+
# PyInstaller frozen builds
|
|
43
|
+
if hasattr(os.sys, "_MEIPASS"):
|
|
44
|
+
# New bundle layout (preferred)
|
|
45
|
+
frozen_internal = os.path.join(os.sys._MEIPASS, "_internal", "translations")
|
|
46
|
+
if os.path.exists(frozen_internal):
|
|
47
|
+
return frozen_internal
|
|
48
|
+
|
|
49
|
+
# Legacy bundle layout fallback
|
|
50
|
+
frozen_legacy = os.path.join(os.sys._MEIPASS, "translations")
|
|
51
|
+
if os.path.exists(frozen_legacy):
|
|
52
|
+
return frozen_legacy
|
|
53
|
+
|
|
54
|
+
return pkg_dir
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_available_languages() -> Dict[str, str]:
|
|
59
|
+
"""
|
|
60
|
+
Get available languages.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dict mapping language codes to display names (e.g., {"en": "English"})
|
|
64
|
+
"""
|
|
65
|
+
return AVAILABLE_LANGUAGES.copy()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_current_language() -> str:
|
|
69
|
+
"""Get the currently loaded language code."""
|
|
70
|
+
return _current_language
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_saved_language() -> str:
|
|
74
|
+
"""
|
|
75
|
+
Get the language saved in settings.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Language code from settings, defaults to "en"
|
|
79
|
+
"""
|
|
80
|
+
settings = QSettings("SetiAstro", "SetiAstroSuitePro")
|
|
81
|
+
return settings.value("ui/language", "en", type=str) or "en"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def save_language(lang_code: str) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Save the language preference to settings.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
lang_code: Language code (e.g., "it", "fr", "es")
|
|
90
|
+
"""
|
|
91
|
+
settings = QSettings("SetiAstro", "SetiAstroSuitePro")
|
|
92
|
+
settings.setValue("ui/language", lang_code)
|
|
93
|
+
settings.sync()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def load_language(lang_code: str = None, app: QCoreApplication = None) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Load a translation for the specified language.
|
|
99
|
+
|
|
100
|
+
Must be called BEFORE creating any widgets for full effect.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
lang_code: Language code (e.g., "it", "fr", "es").
|
|
104
|
+
If None, reads from settings.
|
|
105
|
+
app: QApplication instance. If None, uses QCoreApplication.instance()
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
True if translation was loaded successfully, False otherwise
|
|
109
|
+
"""
|
|
110
|
+
global _translator, _current_language
|
|
111
|
+
|
|
112
|
+
if app is None:
|
|
113
|
+
app = QCoreApplication.instance()
|
|
114
|
+
|
|
115
|
+
if app is None:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
# Get language from settings if not specified
|
|
119
|
+
if lang_code is None:
|
|
120
|
+
lang_code = get_saved_language()
|
|
121
|
+
|
|
122
|
+
# English is the base language, no translation needed
|
|
123
|
+
if lang_code == "en":
|
|
124
|
+
# Remove any existing translator
|
|
125
|
+
if _translator is not None:
|
|
126
|
+
app.removeTranslator(_translator)
|
|
127
|
+
_translator = None
|
|
128
|
+
_current_language = "en"
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
# Find translation file
|
|
132
|
+
translations_dir = get_translations_dir()
|
|
133
|
+
qm_file = os.path.join(translations_dir, f"saspro_{lang_code}.qm")
|
|
134
|
+
|
|
135
|
+
if not os.path.exists(qm_file):
|
|
136
|
+
# Translation file doesn't exist yet
|
|
137
|
+
_current_language = lang_code
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
# Remove old translator if any
|
|
141
|
+
if _translator is not None:
|
|
142
|
+
app.removeTranslator(_translator)
|
|
143
|
+
|
|
144
|
+
# Load new translator
|
|
145
|
+
_translator = QTranslator()
|
|
146
|
+
if _translator.load(qm_file):
|
|
147
|
+
app.installTranslator(_translator)
|
|
148
|
+
_current_language = lang_code
|
|
149
|
+
return True
|
|
150
|
+
else:
|
|
151
|
+
_translator = None
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def tr(text: str, context: str = "Global") -> str:
|
|
156
|
+
"""
|
|
157
|
+
Translate a string outside of a QObject context.
|
|
158
|
+
|
|
159
|
+
For use in module-level code or functions that aren't methods of QObject.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
text: Text to translate
|
|
163
|
+
context: Context for disambiguation (default: "Global")
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Translated string
|
|
167
|
+
"""
|
|
168
|
+
return QCoreApplication.translate(context, text)
|
|
@@ -91,6 +91,9 @@ class ImageCombineDialog(QDialog):
|
|
|
91
91
|
def __init__(self, main_window):
|
|
92
92
|
super().__init__(main_window)
|
|
93
93
|
self.setWindowTitle("Image Combine")
|
|
94
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
95
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
96
|
+
self.setModal(False)
|
|
94
97
|
self.mw = main_window
|
|
95
98
|
self.dm = getattr(main_window, "doc_manager", None) or getattr(main_window, "dm", None)
|
|
96
99
|
self.zoom = 1.0
|
|
@@ -94,14 +94,14 @@ class PreviewPane(QWidget):
|
|
|
94
94
|
self.zoom_slider.setValue(100)
|
|
95
95
|
self.zoom_slider.valueChanged.connect(self.on_zoom_changed)
|
|
96
96
|
|
|
97
|
-
self.zoom_in_btn = themed_toolbtn("zoom-in", "Zoom In")
|
|
98
|
-
self.zoom_out_btn = themed_toolbtn("zoom-out", "Zoom Out")
|
|
99
|
-
self.fit_btn = themed_toolbtn("zoom-fit-best", "Fit to Preview")
|
|
97
|
+
self.zoom_in_btn = themed_toolbtn("zoom-in", self.tr("Zoom In"))
|
|
98
|
+
self.zoom_out_btn = themed_toolbtn("zoom-out", self.tr("Zoom Out"))
|
|
99
|
+
self.fit_btn = themed_toolbtn("zoom-fit-best", self.tr("Fit to Preview"))
|
|
100
100
|
self.zoom_in_btn.clicked .connect(lambda: self.adjust_zoom(10))
|
|
101
101
|
self.zoom_out_btn.clicked.connect(lambda: self.adjust_zoom(-10))
|
|
102
102
|
self.fit_btn.clicked .connect(self.fit_to_view)
|
|
103
103
|
|
|
104
|
-
self.stretch_btn = QPushButton("AutoStretch")
|
|
104
|
+
self.stretch_btn = QPushButton(self.tr("AutoStretch"))
|
|
105
105
|
self.stretch_btn.clicked.connect(self.toggle_stretch)
|
|
106
106
|
|
|
107
107
|
zl = QHBoxLayout()
|
|
@@ -632,14 +632,14 @@ class TiltDialog(QDialog):
|
|
|
632
632
|
min_d, max_d = min(corner_deltas), max(corner_deltas)
|
|
633
633
|
|
|
634
634
|
# 2) now build a more meaningful label:
|
|
635
|
-
range_label = QLabel(
|
|
635
|
+
range_label = QLabel(self.tr("Tilt span: {0:.1f} µm … {1:.1f} µm").format(min_d, max_d))
|
|
636
636
|
for name, (x, y) in corners.items():
|
|
637
637
|
# how far above/below the center plane
|
|
638
638
|
delta = a*(x - cx) + b*(y - cy)
|
|
639
639
|
rows.append((name, f"{delta:.1f}"))
|
|
640
640
|
|
|
641
641
|
table = QTableWidget(len(rows), 2, self)
|
|
642
|
-
table.setHorizontalHeaderLabels(["Corner", "Δ µm"])
|
|
642
|
+
table.setHorizontalHeaderLabels([self.tr("Corner"), self.tr("Δ µm")])
|
|
643
643
|
# hide the vertical header
|
|
644
644
|
table.verticalHeader().setVisible(False)
|
|
645
645
|
for i, (name, val) in enumerate(rows):
|
|
@@ -653,7 +653,7 @@ class TiltDialog(QDialog):
|
|
|
653
653
|
layout.addWidget(range_label, 0) # stretch = 0
|
|
654
654
|
if table:
|
|
655
655
|
layout.addWidget(table, 0)
|
|
656
|
-
close_btn = QPushButton("Close", self)
|
|
656
|
+
close_btn = QPushButton(self.tr("Close"), self)
|
|
657
657
|
close_btn.clicked.connect(self.accept)
|
|
658
658
|
layout.addWidget(close_btn, 0)
|
|
659
659
|
|
|
@@ -897,9 +897,9 @@ class SurfaceDialog(QDialog):
|
|
|
897
897
|
h.addWidget(view, 1)
|
|
898
898
|
h.addWidget(lbl_cb, 0)
|
|
899
899
|
|
|
900
|
-
btn = QPushButton("Close")
|
|
900
|
+
btn = QPushButton(self.tr("Close"))
|
|
901
901
|
btn.clicked.connect(self.accept)
|
|
902
|
-
lbl_span = QLabel(
|
|
902
|
+
lbl_span = QLabel(self.tr("Span: {0:.2f} … {1:.2f} {2}").format(vmin, vmax, units))
|
|
903
903
|
|
|
904
904
|
v = QVBoxLayout(self)
|
|
905
905
|
v.addLayout(h)
|
|
@@ -1057,7 +1057,7 @@ class DistortionGridDialog(QDialog):
|
|
|
1057
1057
|
amplify: float = 20.0,
|
|
1058
1058
|
parent=None):
|
|
1059
1059
|
super().__init__(parent)
|
|
1060
|
-
self.setWindowTitle("Astrometric Distortion & Histogram")
|
|
1060
|
+
self.setWindowTitle(self.tr("Astrometric Distortion & Histogram"))
|
|
1061
1061
|
|
|
1062
1062
|
# — 1) detect stars —
|
|
1063
1063
|
gray = img.mean(-1).astype(np.float32) if img.ndim==3 else img.astype(np.float32)
|
|
@@ -1065,7 +1065,7 @@ class DistortionGridDialog(QDialog):
|
|
|
1065
1065
|
bkg = sep.Background(data)
|
|
1066
1066
|
stars = sep.extract(data - bkg.back(), thresh=5.0, err=bkg.globalrms)
|
|
1067
1067
|
if stars is None or len(stars) < 10:
|
|
1068
|
-
QMessageBox.warning(self, "Distortion", "Not enough stars found.")
|
|
1068
|
+
QMessageBox.warning(self, self.tr("Distortion"), self.tr("Not enough stars found."))
|
|
1069
1069
|
self.reject()
|
|
1070
1070
|
return
|
|
1071
1071
|
|
|
@@ -1098,7 +1098,7 @@ class DistortionGridDialog(QDialog):
|
|
|
1098
1098
|
label_font = QFont("Arial", 12, QFont.Weight.Bold)
|
|
1099
1099
|
|
|
1100
1100
|
# title above the grid
|
|
1101
|
-
title = QLabel("Astrometric Distortion Grid")
|
|
1101
|
+
title = QLabel(self.tr("Astrometric Distortion Grid"))
|
|
1102
1102
|
title.setFont(QFont("Arial", 16, QFont.Weight.Bold))
|
|
1103
1103
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
1104
1104
|
title.setStyleSheet("color: white;")
|
|
@@ -1163,9 +1163,9 @@ class DistortionGridDialog(QDialog):
|
|
|
1163
1163
|
canvas = FigureCanvas(fig)
|
|
1164
1164
|
ax = fig.add_subplot(111)
|
|
1165
1165
|
ax.hist(disp_star_arcsec, bins=30, edgecolor='black')
|
|
1166
|
-
ax.set_xlabel("Distortion (″)")
|
|
1167
|
-
ax.set_ylabel("Number of stars")
|
|
1168
|
-
ax.set_title("Residual histogram")
|
|
1166
|
+
ax.set_xlabel(self.tr("Distortion (″)"))
|
|
1167
|
+
ax.set_ylabel(self.tr("Number of stars"))
|
|
1168
|
+
ax.set_title(self.tr("Residual histogram"))
|
|
1169
1169
|
fig.tight_layout()
|
|
1170
1170
|
|
|
1171
1171
|
# side-by-side layout
|
|
@@ -1174,7 +1174,7 @@ class DistortionGridDialog(QDialog):
|
|
|
1174
1174
|
hl.addWidget(canvas, 1)
|
|
1175
1175
|
|
|
1176
1176
|
# close button
|
|
1177
|
-
btn = QPushButton("Close")
|
|
1177
|
+
btn = QPushButton(self.tr("Close"))
|
|
1178
1178
|
btn.clicked.connect(self.accept)
|
|
1179
1179
|
|
|
1180
1180
|
# final
|
|
@@ -1313,7 +1313,10 @@ def _arcsec_per_pix_from_header(hdr: fits.Header, fallback_px_um: float|None=Non
|
|
|
1313
1313
|
class ImagePeekerDialogPro(QDialog):
|
|
1314
1314
|
def __init__(self, parent, document, settings):
|
|
1315
1315
|
super().__init__(parent)
|
|
1316
|
-
self.setWindowTitle("Image Peeker")
|
|
1316
|
+
self.setWindowTitle(self.tr("Image Peeker"))
|
|
1317
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
1318
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
1319
|
+
self.setModal(False)
|
|
1317
1320
|
self.document = self._coerce_doc(document) # <- ensure we hold a real doc
|
|
1318
1321
|
self.settings = settings
|
|
1319
1322
|
# status / progress line
|
|
@@ -1321,7 +1324,7 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1321
1324
|
self.status_lbl.setStyleSheet("color:#bbb;")
|
|
1322
1325
|
|
|
1323
1326
|
|
|
1324
|
-
self.params = QGroupBox("Grid parameters")
|
|
1327
|
+
self.params = QGroupBox(self.tr("Grid parameters"))
|
|
1325
1328
|
self.params.setMinimumWidth(180)
|
|
1326
1329
|
self.params.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
|
1327
1330
|
gl = QGridLayout(self.params)
|
|
@@ -1343,28 +1346,31 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1343
1346
|
self.pixel_size_input.setValue(px); self.focal_length_input.setValue(fl); self.aperture_input.setValue(ap)
|
|
1344
1347
|
|
|
1345
1348
|
row = 0
|
|
1346
|
-
gl.addWidget(QLabel("Grid size:"), row, 0); gl.addWidget(self.grid_spin, row, 1); row += 1
|
|
1347
|
-
gl.addWidget(QLabel("Panel size:"), row, 0)
|
|
1349
|
+
gl.addWidget(QLabel(self.tr("Grid size:")), row, 0); gl.addWidget(self.grid_spin, row, 1); row += 1
|
|
1350
|
+
gl.addWidget(QLabel(self.tr("Panel size:")), row, 0)
|
|
1348
1351
|
pr = QHBoxLayout(); pr.addWidget(self.panel_slider, 1); pr.addWidget(self.panel_value_label)
|
|
1349
1352
|
gl.addLayout(pr, row, 1); row += 1
|
|
1350
|
-
gl.addWidget(QLabel("Separation:"), row, 0)
|
|
1353
|
+
gl.addWidget(QLabel(self.tr("Separation:")), row, 0)
|
|
1351
1354
|
sr = QHBoxLayout(); sr.addWidget(self.sep_slider, 1); sr.addWidget(self.sep_value_label)
|
|
1352
1355
|
gl.addLayout(sr, row, 1); row += 1
|
|
1353
|
-
gl.addWidget(QLabel("Pixel size (µm):"), row, 0); gl.addWidget(self.pixel_size_input, row, 1); row += 1
|
|
1354
|
-
gl.addWidget(QLabel("Focal length (mm):"), row, 0); gl.addWidget(self.focal_length_input, row, 1); row += 1
|
|
1355
|
-
gl.addWidget(QLabel("Aperture (mm):"), row, 0); gl.addWidget(self.aperture_input, row, 1); row += 1
|
|
1356
|
+
gl.addWidget(QLabel(self.tr("Pixel size (µm):")), row, 0); gl.addWidget(self.pixel_size_input, row, 1); row += 1
|
|
1357
|
+
gl.addWidget(QLabel(self.tr("Focal length (mm):")), row, 0); gl.addWidget(self.focal_length_input, row, 1); row += 1
|
|
1358
|
+
gl.addWidget(QLabel(self.tr("Aperture (mm):")), row, 0); gl.addWidget(self.aperture_input, row, 1); row += 1
|
|
1356
1359
|
|
|
1357
1360
|
# Right side
|
|
1358
1361
|
from PyQt6.QtWidgets import QTabWidget
|
|
1359
1362
|
self.preview_pane = PreviewPane()
|
|
1360
1363
|
analysis_row = QHBoxLayout()
|
|
1361
|
-
analysis_row.addWidget(QLabel("Analysis:"))
|
|
1364
|
+
analysis_row.addWidget(QLabel(self.tr("Analysis:")))
|
|
1362
1365
|
self.analysis_combo = QComboBox()
|
|
1363
|
-
self.analysis_combo.
|
|
1366
|
+
self.analysis_combo.addItem(self.tr("None"), "None")
|
|
1367
|
+
self.analysis_combo.addItem(self.tr("Tilt Analysis"), "Tilt Analysis")
|
|
1368
|
+
self.analysis_combo.addItem(self.tr("Focal Plane Analysis"), "Focal Plane Analysis")
|
|
1369
|
+
self.analysis_combo.addItem(self.tr("Astrometric Distortion Analysis"), "Astrometric Distortion Analysis")
|
|
1364
1370
|
analysis_row.addWidget(self.analysis_combo); analysis_row.addStretch(1)
|
|
1365
1371
|
|
|
1366
1372
|
btns = QHBoxLayout(); btns.addStretch(1)
|
|
1367
|
-
ok_btn = QPushButton("Save Settings && Exit"); cancel_btn = QPushButton("Exit without Saving")
|
|
1373
|
+
ok_btn = QPushButton(self.tr("Save Settings && Exit")); cancel_btn = QPushButton(self.tr("Exit without Saving"))
|
|
1368
1374
|
btns.addWidget(ok_btn); btns.addWidget(cancel_btn)
|
|
1369
1375
|
|
|
1370
1376
|
main = QHBoxLayout(self)
|
|
@@ -1381,7 +1387,8 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1381
1387
|
|
|
1382
1388
|
QTimer.singleShot(0, self._refresh_mosaic)
|
|
1383
1389
|
|
|
1384
|
-
def _set_busy(self, on: bool, text: str = "
|
|
1390
|
+
def _set_busy(self, on: bool, text: str = ""):
|
|
1391
|
+
if not text: text = self.tr("Processing…")
|
|
1385
1392
|
self.status_lbl.setText(text if on else "")
|
|
1386
1393
|
for w in (self.params, self.analysis_combo):
|
|
1387
1394
|
w.setEnabled(not on)
|
|
@@ -1404,13 +1411,14 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1404
1411
|
|
|
1405
1412
|
|
|
1406
1413
|
def _run_analysis(self, *_):
|
|
1407
|
-
|
|
1408
|
-
|
|
1414
|
+
mode_key = self.analysis_combo.currentData()
|
|
1415
|
+
mode_disp = self.analysis_combo.currentText()
|
|
1416
|
+
if mode_key == "None":
|
|
1409
1417
|
self._set_busy(False, "")
|
|
1410
1418
|
self._refresh_mosaic()
|
|
1411
1419
|
return
|
|
1412
|
-
self._set_busy(True,
|
|
1413
|
-
QTimer.singleShot(0, lambda: self._run_analysis_dispatch(
|
|
1420
|
+
self._set_busy(True, self.tr("Running {0}…").format(mode_disp))
|
|
1421
|
+
QTimer.singleShot(0, lambda: self._run_analysis_dispatch(mode_key))
|
|
1414
1422
|
|
|
1415
1423
|
def _run_analysis_dispatch(self, mode: str):
|
|
1416
1424
|
try:
|
|
@@ -1427,15 +1435,15 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1427
1435
|
arr, pixel_size_um=ps_um, focal_length_mm=fl_mm, aperture_mm=ap_mm,
|
|
1428
1436
|
sigma_clip=2.5, thresh_sigma=snr_th
|
|
1429
1437
|
)
|
|
1430
|
-
TiltDialog("Sensor Tilt (µm)", norm_plane, (a,b,c), (H,W), ps_um, parent=self).show()
|
|
1438
|
+
TiltDialog(self.tr("Sensor Tilt (µm)"), norm_plane, (a,b,c), (H,W), ps_um, parent=self).show()
|
|
1431
1439
|
|
|
1432
1440
|
elif mode == "Focal Plane Analysis":
|
|
1433
1441
|
fwhm_heat, (mn_f, mx_f) = compute_fwhm_surface(arr, ps_um, thresh_sigma=snr_th, deg=3)
|
|
1434
|
-
SurfaceDialog("FWHM Heatmap", fwhm_heat, mn_f, mx_f, "µm", "viridis", parent=self).show()
|
|
1442
|
+
SurfaceDialog(self.tr("FWHM Heatmap"), fwhm_heat, mn_f, mx_f, "µm", "viridis", parent=self).show()
|
|
1435
1443
|
ecc_heat, (mn_e, mx_e) = compute_eccentricity_surface(arr, ps_um, thresh_sigma=snr_th, deg=3)
|
|
1436
|
-
SurfaceDialog("Eccentricity Map", ecc_heat, mn_e, mx_e, "e = 1−b/a", "magma", parent=self).show()
|
|
1444
|
+
SurfaceDialog(self.tr("Eccentricity Map"), ecc_heat, mn_e, mx_e, "e = 1−b/a", "magma", parent=self).show()
|
|
1437
1445
|
ori_heat, (mn_o, mx_o) = compute_orientation_surface(arr, thresh_sigma=snr_th, deg=3)
|
|
1438
|
-
SurfaceDialog("Orientation Map", ori_heat, mn_o, mx_o, "rad", "hsv", parent=self).show()
|
|
1446
|
+
SurfaceDialog(self.tr("Orientation Map"), ori_heat, mn_o, mx_o, "rad", "hsv", parent=self).show()
|
|
1439
1447
|
|
|
1440
1448
|
elif mode == "Astrometric Distortion Analysis":
|
|
1441
1449
|
hdr = _header_from_meta(meta)
|
|
@@ -1446,7 +1454,7 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1446
1454
|
parent=self, doc=self._coerce_doc(self.document), settings=self.settings
|
|
1447
1455
|
)
|
|
1448
1456
|
if not ok:
|
|
1449
|
-
QMessageBox.warning(self, "Plate Solve",
|
|
1457
|
+
QMessageBox.warning(self, self.tr("Plate Solve"), self.tr("ASTAP/Astrometry failed:\n{0}").format(hdr_or_err))
|
|
1450
1458
|
return
|
|
1451
1459
|
|
|
1452
1460
|
# IMPORTANT: if solver returned a Header, store it
|
|
@@ -1465,22 +1473,22 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1465
1473
|
|
|
1466
1474
|
# Now WCS exists, but do we have SIP?
|
|
1467
1475
|
if hdr is None:
|
|
1468
|
-
QMessageBox.critical(self, "WCS Error", "Plate solve did not produce a readable WCS header.")
|
|
1476
|
+
QMessageBox.critical(self, self.tr("WCS Error"), self.tr("Plate solve did not produce a readable WCS header."))
|
|
1469
1477
|
return
|
|
1470
1478
|
|
|
1471
1479
|
has_sip = any(k.startswith("A_") for k in hdr.keys()) and any(k.startswith("B_") for k in hdr.keys())
|
|
1472
1480
|
if not has_sip:
|
|
1473
1481
|
QMessageBox.warning(
|
|
1474
|
-
self, "No Distortion Model",
|
|
1475
|
-
"This image has a valid WCS, but no SIP distortion terms (A_*, B_*).\n"
|
|
1482
|
+
self, self.tr("No Distortion Model"),
|
|
1483
|
+
self.tr("This image has a valid WCS, but no SIP distortion terms (A_*, B_*).\n"
|
|
1476
1484
|
"Astrometric distortion analysis requires a SIP-enabled solve.\n\n"
|
|
1477
|
-
"Re-solve with distortion fitting enabled in ASTAP."
|
|
1485
|
+
"Re-solve with distortion fitting enabled in ASTAP.")
|
|
1478
1486
|
)
|
|
1479
1487
|
return
|
|
1480
1488
|
|
|
1481
1489
|
asp = _arcsec_per_pix_from_header(hdr, fallback_px_um=ps_um, fallback_fl_mm=fl_mm)
|
|
1482
1490
|
if asp is None:
|
|
1483
|
-
QMessageBox.critical(self, "WCS Error", "Cannot determine pixel scale.")
|
|
1491
|
+
QMessageBox.critical(self, self.tr("WCS Error"), self.tr("Cannot determine pixel scale."))
|
|
1484
1492
|
return
|
|
1485
1493
|
|
|
1486
1494
|
DistortionGridDialog(
|
|
@@ -1526,7 +1534,7 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1526
1534
|
self.sep_color_btn.setIcon(QIcon(pix))
|
|
1527
1535
|
|
|
1528
1536
|
def _choose_sep_color(self):
|
|
1529
|
-
col = QColorDialog.getColor(self._sep_color, self, "Choose separation color")
|
|
1537
|
+
col = QColorDialog.getColor(self._sep_color, self, self.tr("Choose separation color"))
|
|
1530
1538
|
if col.isValid():
|
|
1531
1539
|
self._sep_color = col
|
|
1532
1540
|
self._update_sep_color_button()
|
|
@@ -1554,7 +1562,7 @@ class ImagePeekerDialogPro(QDialog):
|
|
|
1554
1562
|
# fetch the currently loaded image (you’ll adapt to your image_manager API)
|
|
1555
1563
|
img = self.image_manager.current_qimage()
|
|
1556
1564
|
if img is None:
|
|
1557
|
-
QMessageBox.warning(self, "No image", "No image loaded to peek at!")
|
|
1565
|
+
QMessageBox.warning(self, self.tr("No image"), self.tr("No image loaded to peek at!"))
|
|
1558
1566
|
return
|
|
1559
1567
|
|
|
1560
1568
|
mosaic = self._build_mosaic(img, n, panel_sz, sep, sep_col)
|
|
@@ -95,19 +95,11 @@ def apply_curves_adjustment(image: np.ndarray,
|
|
|
95
95
|
xvals, yvals = _calculate_curve_points(target_median, curves_boost)
|
|
96
96
|
|
|
97
97
|
# Apply the 1D LUT per channel using np.interp (piecewise linear)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
out = np.empty_like(img, dtype=np.float32)
|
|
104
|
-
# Apply same curve to each color channel
|
|
105
|
-
for ch in range(c):
|
|
106
|
-
flat = img[..., ch].ravel()
|
|
107
|
-
out[..., ch] = np.interp(flat, xvals, yvals).reshape(h, w)
|
|
108
|
-
else:
|
|
109
|
-
# Fallback: just return clamped image
|
|
110
|
-
out = img
|
|
98
|
+
# Apply the 1D LUT per channel using np.interp (piecewise linear)
|
|
99
|
+
# Optimization: np.interp can handle N-D 'x' array directly.
|
|
100
|
+
# No need to loop over channels or flatten/reshape if we pass the whole array.
|
|
101
|
+
|
|
102
|
+
out = np.interp(img, xvals, yvals).astype(np.float32, copy=False)
|
|
111
103
|
|
|
112
104
|
return np.clip(out, 0.0, 1.0)
|
|
113
105
|
|
setiastro/saspro/isophote.py
CHANGED
|
@@ -360,6 +360,9 @@ class IsophoteModelerDialog(QDialog):
|
|
|
360
360
|
def __init__(self, mono_image: np.ndarray, parent: Optional[QWidget] = None,
|
|
361
361
|
title_hint: Optional[str] = None, image_manager=None, doc_manager=None):
|
|
362
362
|
super().__init__(parent)
|
|
363
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
364
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
365
|
+
self.setModal(False)
|
|
363
366
|
self.image_manager = image_manager
|
|
364
367
|
self.doc_manager = doc_manager
|
|
365
368
|
|
|
@@ -802,39 +802,46 @@ def kappa_sigma_clip_weighted_3d(stack, weights, kappa=2.5, iterations=3):
|
|
|
802
802
|
pixel_weights = weights[:]
|
|
803
803
|
else:
|
|
804
804
|
pixel_weights = weights[:, i, j].copy()
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
current_idx[f] = f
|
|
809
|
-
current_vals = pixel_values
|
|
810
|
-
current_w = pixel_weights
|
|
811
|
-
current_indices = current_idx
|
|
805
|
+
|
|
806
|
+
valid_mask = pixel_values != 0
|
|
807
|
+
|
|
812
808
|
med = 0.0
|
|
813
809
|
for _ in range(iterations):
|
|
814
|
-
|
|
810
|
+
count = 0
|
|
811
|
+
for k in range(num_frames):
|
|
812
|
+
if valid_mask[k]:
|
|
813
|
+
count += 1
|
|
814
|
+
|
|
815
|
+
if count == 0:
|
|
815
816
|
break
|
|
817
|
+
|
|
818
|
+
current_vals = pixel_values[valid_mask]
|
|
819
|
+
|
|
816
820
|
med = np.median(current_vals)
|
|
817
821
|
std = np.std(current_vals)
|
|
818
822
|
lower_bound = med - kappa * std
|
|
819
823
|
upper_bound = med + kappa * std
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
824
|
+
|
|
825
|
+
for k in range(num_frames):
|
|
826
|
+
if valid_mask[k]:
|
|
827
|
+
val = pixel_values[k]
|
|
828
|
+
if val < lower_bound or val > upper_bound:
|
|
829
|
+
valid_mask[k] = False
|
|
830
|
+
|
|
825
831
|
for f in range(num_frames):
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
832
|
+
rej_mask[f, i, j] = not valid_mask[f]
|
|
833
|
+
|
|
834
|
+
wsum = 0.0
|
|
835
|
+
vsum = 0.0
|
|
836
|
+
for k in range(num_frames):
|
|
837
|
+
if valid_mask[k]:
|
|
838
|
+
w = pixel_weights[k]
|
|
839
|
+
v = pixel_values[k]
|
|
840
|
+
wsum += w
|
|
841
|
+
vsum += v * w
|
|
842
|
+
|
|
843
|
+
if wsum > 0:
|
|
844
|
+
clipped[i, j] = vsum / wsum
|
|
838
845
|
else:
|
|
839
846
|
clipped[i, j] = med
|
|
840
847
|
return clipped, rej_mask
|
|
@@ -859,36 +866,46 @@ def kappa_sigma_clip_weighted_4d(stack, weights, kappa=2.5, iterations=3):
|
|
|
859
866
|
pixel_weights = weights[:]
|
|
860
867
|
else:
|
|
861
868
|
pixel_weights = weights[:, i, j, c].copy()
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
current_vals = pixel_values
|
|
866
|
-
current_w = pixel_weights
|
|
867
|
-
current_indices = current_idx
|
|
869
|
+
|
|
870
|
+
valid_mask = pixel_values != 0
|
|
871
|
+
|
|
868
872
|
med = 0.0
|
|
869
873
|
for _ in range(iterations):
|
|
870
|
-
|
|
874
|
+
count = 0
|
|
875
|
+
for k in range(num_frames):
|
|
876
|
+
if valid_mask[k]:
|
|
877
|
+
count += 1
|
|
878
|
+
|
|
879
|
+
if count == 0:
|
|
871
880
|
break
|
|
881
|
+
|
|
882
|
+
current_vals = pixel_values[valid_mask]
|
|
883
|
+
|
|
872
884
|
med = np.median(current_vals)
|
|
873
885
|
std = np.std(current_vals)
|
|
874
886
|
lower_bound = med - kappa * std
|
|
875
887
|
upper_bound = med + kappa * std
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
888
|
+
|
|
889
|
+
for k in range(num_frames):
|
|
890
|
+
if valid_mask[k]:
|
|
891
|
+
val = pixel_values[k]
|
|
892
|
+
if val < lower_bound or val > upper_bound:
|
|
893
|
+
valid_mask[k] = False
|
|
894
|
+
|
|
880
895
|
for f in range(num_frames):
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
896
|
+
rej_mask[f, i, j, c] = not valid_mask[f]
|
|
897
|
+
|
|
898
|
+
wsum = 0.0
|
|
899
|
+
vsum = 0.0
|
|
900
|
+
for k in range(num_frames):
|
|
901
|
+
if valid_mask[k]:
|
|
902
|
+
w = pixel_weights[k]
|
|
903
|
+
v = pixel_values[k]
|
|
904
|
+
wsum += w
|
|
905
|
+
vsum += v * w
|
|
906
|
+
|
|
907
|
+
if wsum > 0:
|
|
908
|
+
clipped[i, j, c] = vsum / wsum
|
|
892
909
|
else:
|
|
893
910
|
clipped[i, j, c] = med
|
|
894
911
|
return clipped, rej_mask
|
setiastro/saspro/linear_fit.py
CHANGED
|
@@ -224,6 +224,9 @@ class LinearFitDialog(QDialog):
|
|
|
224
224
|
def __init__(self, parent, doc_manager, active_doc):
|
|
225
225
|
super().__init__(parent)
|
|
226
226
|
self.setWindowTitle("Linear Fit")
|
|
227
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
228
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
229
|
+
self.setModal(False)
|
|
227
230
|
self.dm = doc_manager
|
|
228
231
|
self.doc = active_doc
|
|
229
232
|
self.worker: Optional[_LinearFitWorker] = None
|