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
|
@@ -60,7 +60,7 @@ class _AstrobinIdDelegate(QStyledItemDelegate):
|
|
|
60
60
|
|
|
61
61
|
def createEditor(self, parent, option, index):
|
|
62
62
|
editor = QLineEdit(parent)
|
|
63
|
-
editor.setPlaceholderText("e.g. 4408")
|
|
63
|
+
editor.setPlaceholderText(self.tr("e.g. 4408"))
|
|
64
64
|
editor.setValidator(QIntValidator(1, 999_999_999, editor))
|
|
65
65
|
if self._completer is not None:
|
|
66
66
|
editor.setCompleter(self._completer)
|
|
@@ -83,7 +83,7 @@ class FilterIdDialog(QDialog):
|
|
|
83
83
|
current_map: Optional[Dict[str, str]] = None,
|
|
84
84
|
offline_csv_default: Optional[str] = None):
|
|
85
85
|
super().__init__(parent)
|
|
86
|
-
self.setWindowTitle("AstroBin Filter IDs")
|
|
86
|
+
self.setWindowTitle(self.tr("AstroBin Filter IDs"))
|
|
87
87
|
self.settings = settings
|
|
88
88
|
self._offline_csv_default = offline_csv_default
|
|
89
89
|
base_names = sorted({f for f in (filters_in_data or []) if f and f != "Unknown"}, key=str.lower)
|
|
@@ -96,14 +96,14 @@ class FilterIdDialog(QDialog):
|
|
|
96
96
|
|
|
97
97
|
# Help row
|
|
98
98
|
help_row = QHBoxLayout()
|
|
99
|
-
help_label = QLabel("Edit filter names and their AstroBin numeric IDs.")
|
|
99
|
+
help_label = QLabel(self.tr("Edit filter names and their AstroBin numeric IDs."))
|
|
100
100
|
help_btn = QToolButton(self)
|
|
101
101
|
help_btn.setText("?")
|
|
102
|
-
help_btn.setToolTip("Open AstroBin Equipment Explorer (Filters)")
|
|
102
|
+
help_btn.setToolTip(self.tr("Open AstroBin Equipment Explorer (Filters)"))
|
|
103
103
|
help_btn.clicked.connect(lambda: webbrowser.open(ASTROBIN_FILTER_URL))
|
|
104
104
|
|
|
105
105
|
self.load_db_btn = QPushButton(self)
|
|
106
|
-
self.load_db_btn.setToolTip("Search or load the offline filters database.")
|
|
106
|
+
self.load_db_btn.setToolTip(self.tr("Search or load the offline filters database."))
|
|
107
107
|
self.load_db_btn.clicked.connect(self._on_offline_action)
|
|
108
108
|
|
|
109
109
|
help_row.addWidget(help_label)
|
|
@@ -115,7 +115,7 @@ class FilterIdDialog(QDialog):
|
|
|
115
115
|
# Table
|
|
116
116
|
self.table = QTableWidget(self)
|
|
117
117
|
self.table.setColumnCount(2)
|
|
118
|
-
self.table.setHorizontalHeaderLabels(["Filter name", "AstroBin ID"])
|
|
118
|
+
self.table.setHorizontalHeaderLabels([self.tr("Filter name"), self.tr("AstroBin ID")])
|
|
119
119
|
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
|
120
120
|
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
|
|
121
121
|
self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
|
@@ -154,8 +154,8 @@ class FilterIdDialog(QDialog):
|
|
|
154
154
|
|
|
155
155
|
# Row actions
|
|
156
156
|
row_actions = QHBoxLayout()
|
|
157
|
-
self.btn_add = QPushButton("Add row"); self.btn_add.clicked.connect(self._add_row)
|
|
158
|
-
self.btn_del = QPushButton("Delete selected"); self.btn_del.clicked.connect(self._delete_selected_rows)
|
|
157
|
+
self.btn_add = QPushButton(self.tr("Add row")); self.btn_add.clicked.connect(self._add_row)
|
|
158
|
+
self.btn_del = QPushButton(self.tr("Delete selected")); self.btn_del.clicked.connect(self._delete_selected_rows)
|
|
159
159
|
row_actions.addWidget(self.btn_add); row_actions.addWidget(self.btn_del); row_actions.addStretch(1)
|
|
160
160
|
root.addLayout(row_actions)
|
|
161
161
|
|
|
@@ -238,7 +238,7 @@ class FilterIdDialog(QDialog):
|
|
|
238
238
|
return None
|
|
239
239
|
|
|
240
240
|
def _update_offline_button_text(self):
|
|
241
|
-
self.load_db_btn.setText("Search offline DB…" if getattr(self, "_offline_rows", None) else "Load offline DB…")
|
|
241
|
+
self.load_db_btn.setText(self.tr("Search offline DB…") if getattr(self, "_offline_rows", None) else self.tr("Load offline DB…"))
|
|
242
242
|
|
|
243
243
|
def _on_offline_action(self):
|
|
244
244
|
if getattr(self, "_offline_rows", None):
|
|
@@ -250,7 +250,7 @@ class FilterIdDialog(QDialog):
|
|
|
250
250
|
self._update_offline_button_text()
|
|
251
251
|
|
|
252
252
|
def _browse_offline_db(self):
|
|
253
|
-
path, _ = QFileDialog.getOpenFileName(self, "Select AstroBin Filters CSV", "", "CSV files (*.csv);;All files (*)")
|
|
253
|
+
path, _ = QFileDialog.getOpenFileName(self, self.tr("Select AstroBin Filters CSV"), "", self.tr("CSV files (*.csv);;All files (*)"))
|
|
254
254
|
if not path:
|
|
255
255
|
return
|
|
256
256
|
self._load_offline_db(path)
|
|
@@ -302,16 +302,16 @@ class FilterIdDialog(QDialog):
|
|
|
302
302
|
|
|
303
303
|
def _open_offline_search(self):
|
|
304
304
|
if not getattr(self, "_offline_rows", None):
|
|
305
|
-
QMessageBox.information(self, "No DB", "Offline filters database not loaded yet.")
|
|
305
|
+
QMessageBox.information(self, self.tr("No DB"), self.tr("Offline filters database not loaded yet."))
|
|
306
306
|
return
|
|
307
307
|
|
|
308
|
-
dlg = QDialog(self); dlg.setWindowTitle("Search AstroBin Filters (offline)")
|
|
308
|
+
dlg = QDialog(self); dlg.setWindowTitle(self.tr("Search AstroBin Filters (offline)"))
|
|
309
309
|
v = QVBoxLayout(dlg)
|
|
310
|
-
q = QLineEdit(dlg); q.setPlaceholderText("Search ID, brand, or name…")
|
|
310
|
+
q = QLineEdit(dlg); q.setPlaceholderText(self.tr("Search ID, brand, or name…"))
|
|
311
311
|
v.addWidget(q)
|
|
312
312
|
|
|
313
313
|
tbl = QTableWidget(dlg); tbl.setColumnCount(3)
|
|
314
|
-
tbl.setHorizontalHeaderLabels(["ID", "Brand", "Name"])
|
|
314
|
+
tbl.setHorizontalHeaderLabels([self.tr("ID"), self.tr("Brand"), self.tr("Name")])
|
|
315
315
|
hdr = tbl.horizontalHeader()
|
|
316
316
|
hdr.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
|
|
317
317
|
hdr.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
|
|
@@ -386,20 +386,20 @@ class AstrobinExportTab(QWidget):
|
|
|
386
386
|
|
|
387
387
|
# LEFT
|
|
388
388
|
left = QWidget(self); lyt = QVBoxLayout(left)
|
|
389
|
-
self.info_lbl = QLabel("Load FITS via 'Select Folder…' or 'Add Files…' to begin.")
|
|
389
|
+
self.info_lbl = QLabel(self.tr("Load FITS via 'Select Folder…' or 'Add Files…' to begin."))
|
|
390
390
|
lyt.addWidget(self.info_lbl)
|
|
391
391
|
|
|
392
392
|
btn_row = QHBoxLayout()
|
|
393
|
-
self.btn_open = QPushButton("Select Folder…"); self.btn_open.clicked.connect(self.open_directory)
|
|
394
|
-
self.btn_add_files = QPushButton("Add Files…"); self.btn_add_files.clicked.connect(self.open_files)
|
|
395
|
-
self.btn_clear = QPushButton("Clear"); self.btn_clear.clicked.connect(self.clear_images)
|
|
393
|
+
self.btn_open = QPushButton(self.tr("Select Folder…")); self.btn_open.clicked.connect(self.open_directory)
|
|
394
|
+
self.btn_add_files = QPushButton(self.tr("Add Files…")); self.btn_add_files.clicked.connect(self.open_files)
|
|
395
|
+
self.btn_clear = QPushButton(self.tr("Clear")); self.btn_clear.clicked.connect(self.clear_images)
|
|
396
396
|
btn_row.addWidget(self.btn_open); btn_row.addWidget(self.btn_add_files); btn_row.addWidget(self.btn_clear)
|
|
397
397
|
btn_row.addStretch(1)
|
|
398
398
|
lyt.addLayout(btn_row)
|
|
399
399
|
|
|
400
400
|
self.tree = QTreeWidget(self)
|
|
401
401
|
self.tree.setColumnCount(1)
|
|
402
|
-
self.tree.setHeaderLabels(["Files (Object → Filter → Exposure)"])
|
|
402
|
+
self.tree.setHeaderLabels([self.tr("Files (Object → Filter → Exposure)")])
|
|
403
403
|
hdr = self.tree.header()
|
|
404
404
|
hdr.setSectionResizeMode(0, QHeaderView.ResizeMode.Interactive)
|
|
405
405
|
hdr.setStretchLastSection(False)
|
|
@@ -415,50 +415,50 @@ class AstrobinExportTab(QWidget):
|
|
|
415
415
|
# RIGHT
|
|
416
416
|
right = QWidget(self); rlyt = QVBoxLayout(right)
|
|
417
417
|
|
|
418
|
-
form_box = QGroupBox("Global inputs (used if FITS headers are missing/zero)")
|
|
418
|
+
form_box = QGroupBox(self.tr("Global inputs (used if FITS headers are missing/zero)"))
|
|
419
419
|
grid = QGridLayout(form_box)
|
|
420
420
|
|
|
421
421
|
# row 0
|
|
422
|
-
grid.addWidget(QLabel("f/number"), 0, 0)
|
|
423
|
-
self.fnum_edit = QLineEdit(self); self.fnum_edit.setPlaceholderText("e.g. 4.0")
|
|
422
|
+
grid.addWidget(QLabel(self.tr("f/number")), 0, 0)
|
|
423
|
+
self.fnum_edit = QLineEdit(self); self.fnum_edit.setPlaceholderText(self.tr("e.g. 4.0"))
|
|
424
424
|
self.fnum_edit.setValidator(QDoubleValidator(0.0, 999.0, 2, self)); self.fnum_edit.textChanged.connect(self._recompute)
|
|
425
425
|
grid.addWidget(self.fnum_edit, 0, 1)
|
|
426
426
|
|
|
427
|
-
grid.addWidget(QLabel("Darks (#)"), 0, 2)
|
|
427
|
+
grid.addWidget(QLabel(self.tr("Darks (#)")), 0, 2)
|
|
428
428
|
self.darks_edit = QLineEdit(self); self._setup_int_line(self.darks_edit, 0, 999999)
|
|
429
429
|
grid.addWidget(self.darks_edit, 0, 3)
|
|
430
430
|
|
|
431
|
-
grid.addWidget(QLabel("Flats (#)"), 0, 4)
|
|
431
|
+
grid.addWidget(QLabel(self.tr("Flats (#)")), 0, 4)
|
|
432
432
|
self.flats_edit = QLineEdit(self); self._setup_int_line(self.flats_edit, 0, 999999)
|
|
433
433
|
grid.addWidget(self.flats_edit, 0, 5)
|
|
434
434
|
|
|
435
435
|
# row 1
|
|
436
|
-
grid.addWidget(QLabel("Flat-darks (#)"), 1, 0)
|
|
436
|
+
grid.addWidget(QLabel(self.tr("Flat-darks (#)")), 1, 0)
|
|
437
437
|
self.flatdarks_edit = QLineEdit(self); self._setup_int_line(self.flatdarks_edit, 0, 999999)
|
|
438
438
|
grid.addWidget(self.flatdarks_edit, 1, 1)
|
|
439
439
|
|
|
440
|
-
grid.addWidget(QLabel("Bias (#)"), 1, 2)
|
|
440
|
+
grid.addWidget(QLabel(self.tr("Bias (#)")), 1, 2)
|
|
441
441
|
self.bias_edit = QLineEdit(self); self._setup_int_line(self.bias_edit, 0, 999999)
|
|
442
442
|
grid.addWidget(self.bias_edit, 1, 3)
|
|
443
443
|
|
|
444
|
-
grid.addWidget(QLabel("Bortle"), 1, 4)
|
|
445
|
-
self.bortle_edit = QLineEdit(self); self.bortle_edit.setPlaceholderText("0–9")
|
|
444
|
+
grid.addWidget(QLabel(self.tr("Bortle")), 1, 4)
|
|
445
|
+
self.bortle_edit = QLineEdit(self); self.bortle_edit.setPlaceholderText(self.tr("0–9"))
|
|
446
446
|
self.bortle_edit.setValidator(QIntValidator(0, 9, self)); self.bortle_edit.textChanged.connect(self._recompute)
|
|
447
447
|
grid.addWidget(self.bortle_edit, 1, 5)
|
|
448
448
|
|
|
449
449
|
# row 2
|
|
450
|
-
grid.addWidget(QLabel("Mean SQM"), 2, 0)
|
|
451
|
-
self.mean_sqm_edit = QLineEdit(self); self.mean_sqm_edit.setPlaceholderText("e.g. 21.30")
|
|
450
|
+
grid.addWidget(QLabel(self.tr("Mean SQM")), 2, 0)
|
|
451
|
+
self.mean_sqm_edit = QLineEdit(self); self.mean_sqm_edit.setPlaceholderText(self.tr("e.g. 21.30"))
|
|
452
452
|
self.mean_sqm_edit.setValidator(QDoubleValidator(0.0, 25.0, 2, self)); self.mean_sqm_edit.textChanged.connect(self._recompute)
|
|
453
453
|
grid.addWidget(self.mean_sqm_edit, 2, 1)
|
|
454
454
|
|
|
455
|
-
grid.addWidget(QLabel("Mean FWHM"), 2, 2)
|
|
456
|
-
self.mean_fwhm_edit = QLineEdit(self); self.mean_fwhm_edit.setPlaceholderText("e.g. 2.10")
|
|
455
|
+
grid.addWidget(QLabel(self.tr("Mean FWHM")), 2, 2)
|
|
456
|
+
self.mean_fwhm_edit = QLineEdit(self); self.mean_fwhm_edit.setPlaceholderText(self.tr("e.g. 2.10"))
|
|
457
457
|
self.mean_fwhm_edit.setValidator(QDoubleValidator(0.0, 50.0, 2, self)); self.mean_fwhm_edit.textChanged.connect(self._recompute)
|
|
458
458
|
grid.addWidget(self.mean_fwhm_edit, 2, 3)
|
|
459
459
|
|
|
460
|
-
self.noon_cb = QCheckBox("Group nights noon → noon (local time)")
|
|
461
|
-
self.noon_cb.setToolTip("Prevents splitting a single observing night at midnight.")
|
|
460
|
+
self.noon_cb = QCheckBox(self.tr("Group nights noon → noon (local time)"))
|
|
461
|
+
self.noon_cb.setToolTip(self.tr("Prevents splitting a single observing night at midnight."))
|
|
462
462
|
self.noon_cb.setChecked(self.settings.value("astrobin_exporter/noon_to_noon", True, type=bool))
|
|
463
463
|
self.noon_cb.toggled.connect(self._recompute)
|
|
464
464
|
grid.addWidget(self.noon_cb, 2, 4, 1, 2)
|
|
@@ -468,10 +468,10 @@ class AstrobinExportTab(QWidget):
|
|
|
468
468
|
# Filter mapping row
|
|
469
469
|
map_row = QHBoxLayout()
|
|
470
470
|
self.filter_summary = QLabel(self._filters_summary_text()); map_row.addWidget(self.filter_summary)
|
|
471
|
-
self.btn_edit_filters = QPushButton("Manage Filter IDs…"); self.btn_edit_filters.clicked.connect(self._edit_filters)
|
|
471
|
+
self.btn_edit_filters = QPushButton(self.tr("Manage Filter IDs…")); self.btn_edit_filters.clicked.connect(self._edit_filters)
|
|
472
472
|
map_row.addWidget(self.btn_edit_filters)
|
|
473
473
|
qmark = QToolButton(self); qmark.setText("?")
|
|
474
|
-
qmark.setToolTip("Open AstroBin Equipment Explorer (Filters)")
|
|
474
|
+
qmark.setToolTip(self.tr("Open AstroBin Equipment Explorer (Filters)"))
|
|
475
475
|
qmark.clicked.connect(lambda: webbrowser.open(ASTROBIN_FILTER_URL))
|
|
476
476
|
map_row.addWidget(qmark); map_row.addStretch(1)
|
|
477
477
|
map_wrap = QWidget(self); map_wrap.setLayout(map_row)
|
|
@@ -481,8 +481,8 @@ class AstrobinExportTab(QWidget):
|
|
|
481
481
|
|
|
482
482
|
# Aggregated table
|
|
483
483
|
self.table = QTableWidget(self)
|
|
484
|
-
cols = ['date','filter','number','duration','gain','iso','binning','sensorCooling',
|
|
485
|
-
'fNumber','darks','flats','flatDarks','bias','bortle','meanSqm','meanFwhm','temperature']
|
|
484
|
+
cols = [self.tr('date'), self.tr('filter'), self.tr('number'), self.tr('duration'), self.tr('gain'), self.tr('iso'), self.tr('binning'), self.tr('sensorCooling'),
|
|
485
|
+
self.tr('fNumber'), self.tr('darks'), self.tr('flats'), self.tr('flatDarks'), self.tr('bias'), self.tr('bortle'), self.tr('meanSqm'), self.tr('meanFwhm'), self.tr('temperature')]
|
|
486
486
|
self.table.setColumnCount(len(cols)); self.table.setHorizontalHeaderLabels(cols)
|
|
487
487
|
hdr_tbl = self.table.horizontalHeader()
|
|
488
488
|
hdr_tbl.setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
|
|
@@ -499,8 +499,8 @@ class AstrobinExportTab(QWidget):
|
|
|
499
499
|
|
|
500
500
|
# Actions
|
|
501
501
|
act_row = QHBoxLayout()
|
|
502
|
-
self.btn_refresh = QPushButton("Recompute"); self.btn_refresh.clicked.connect(self._recompute)
|
|
503
|
-
self.btn_copy_csv = QPushButton("Copy CSV"); self.btn_copy_csv.clicked.connect(self._copy_csv_to_clipboard)
|
|
502
|
+
self.btn_refresh = QPushButton(self.tr("Recompute")); self.btn_refresh.clicked.connect(self._recompute)
|
|
503
|
+
self.btn_copy_csv = QPushButton(self.tr("Copy CSV")); self.btn_copy_csv.clicked.connect(self._copy_csv_to_clipboard)
|
|
504
504
|
act_row.addWidget(self.btn_refresh); act_row.addWidget(self.btn_copy_csv); act_row.addStretch(1)
|
|
505
505
|
rlyt.addLayout(act_row)
|
|
506
506
|
|
|
@@ -508,7 +508,7 @@ class AstrobinExportTab(QWidget):
|
|
|
508
508
|
splitter.setSizes([360, 680])
|
|
509
509
|
self.setLayout(root)
|
|
510
510
|
|
|
511
|
-
self.info_lbl.setText("Load FITS/XISF via 'Select Folder…' or 'Add Files…' to begin.")
|
|
511
|
+
self.info_lbl.setText(self.tr("Load FITS/XISF via 'Select Folder…' or 'Add Files…' to begin."))
|
|
512
512
|
|
|
513
513
|
def _setup_int_line(self, line: QLineEdit, lo: int, hi: int):
|
|
514
514
|
line.setValidator(QIntValidator(lo, hi, self))
|
|
@@ -558,11 +558,11 @@ class AstrobinExportTab(QWidget):
|
|
|
558
558
|
self.file_paths.clear(); self.records.clear(); self.rows.clear()
|
|
559
559
|
self.tree.blockSignals(True); self.tree.clear(); self.tree.blockSignals(False)
|
|
560
560
|
self.table.setRowCount(0); self.csv_view.clear()
|
|
561
|
-
self.info_lbl.setText("Cleared. Load FITS via 'Select Folder…' or 'Add Files…' to begin.")
|
|
561
|
+
self.info_lbl.setText(self.tr("Cleared. Load FITS via 'Select Folder…' or 'Add Files…' to begin."))
|
|
562
562
|
|
|
563
563
|
def open_directory(self):
|
|
564
564
|
start = self._get_last_dir() or ""
|
|
565
|
-
directory = QFileDialog.getExistingDirectory(self, "Select Folder Containing FITS/XISF Files", start)
|
|
565
|
+
directory = QFileDialog.getExistingDirectory(self, self.tr("Select Folder Containing FITS/XISF Files"), start)
|
|
566
566
|
if not directory: return
|
|
567
567
|
self._save_last_dir(directory)
|
|
568
568
|
|
|
@@ -573,7 +573,7 @@ class AstrobinExportTab(QWidget):
|
|
|
573
573
|
paths.append(os.path.join(root, fn))
|
|
574
574
|
paths.sort(key=self._natural_key)
|
|
575
575
|
if not paths:
|
|
576
|
-
QMessageBox.information(self, "No Images", "No .fit/.fits/.xisf files found.")
|
|
576
|
+
QMessageBox.information(self, self.tr("No Images"), self.tr("No .fit/.fits/.xisf files found."))
|
|
577
577
|
return
|
|
578
578
|
self.file_paths = paths
|
|
579
579
|
self._read_headers(); self._build_tree(); self._recompute()
|
|
@@ -581,14 +581,14 @@ class AstrobinExportTab(QWidget):
|
|
|
581
581
|
def open_files(self):
|
|
582
582
|
start = self._get_last_dir() or ""
|
|
583
583
|
paths, _ = QFileDialog.getOpenFileNames(
|
|
584
|
-
self, "Select FITS/XISF Files", start, "FITS/XISF (*.fit *.fits *.xisf);;All Files (*)"
|
|
584
|
+
self, self.tr("Select FITS/XISF Files"), start, self.tr("FITS/XISF (*.fit *.fits *.xisf);;All Files (*)")
|
|
585
585
|
)
|
|
586
586
|
if not paths: return
|
|
587
587
|
self._save_last_dir(os.path.dirname(paths[0]))
|
|
588
588
|
|
|
589
589
|
new_paths = [p for p in paths if p not in self.file_paths]
|
|
590
590
|
if not new_paths:
|
|
591
|
-
QMessageBox.information(self, "No New Files", "All selected files are already in the list.")
|
|
591
|
+
QMessageBox.information(self, self.tr("No New Files"), self.tr("All selected files are already in the list."))
|
|
592
592
|
return
|
|
593
593
|
|
|
594
594
|
self.file_paths = sorted(set(self.file_paths + new_paths), key=self._natural_key)
|
|
@@ -709,9 +709,9 @@ class AstrobinExportTab(QWidget):
|
|
|
709
709
|
print(f"[WARN] Failed to read {fp}: {e}")
|
|
710
710
|
bad += 1
|
|
711
711
|
|
|
712
|
-
msg =
|
|
713
|
-
if bad: msg +=
|
|
714
|
-
if skipped_xisf: msg +=
|
|
712
|
+
msg = self.tr("Loaded {0} file(s)").format(ok)
|
|
713
|
+
if bad: msg += self.tr(" ({0} failed)").format(bad)
|
|
714
|
+
if skipped_xisf: msg += self.tr(" — skipped {0} XISF (reader unavailable)").format(skipped_xisf)
|
|
715
715
|
self.info_lbl.setText(msg + ".")
|
|
716
716
|
|
|
717
717
|
# ---------- helpers ----------
|
|
@@ -775,19 +775,19 @@ class AstrobinExportTab(QWidget):
|
|
|
775
775
|
by_obj[obj][filt][exp] = lst
|
|
776
776
|
|
|
777
777
|
for obj in sorted(by_obj, key=str.lower):
|
|
778
|
-
obj_item = QTreeWidgetItem([
|
|
778
|
+
obj_item = QTreeWidgetItem([self.tr("Object: {0}").format(obj)])
|
|
779
779
|
obj_item.setFlags(obj_item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
|
780
780
|
obj_item.setCheckState(0, Qt.CheckState.Checked)
|
|
781
781
|
self.tree.addTopLevelItem(obj_item); obj_item.setExpanded(True)
|
|
782
782
|
|
|
783
783
|
for filt in sorted(by_obj[obj], key=str.lower):
|
|
784
|
-
filt_item = QTreeWidgetItem([
|
|
784
|
+
filt_item = QTreeWidgetItem([self.tr("Filter: {0}").format(filt)])
|
|
785
785
|
filt_item.setFlags(filt_item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
|
786
786
|
filt_item.setCheckState(0, Qt.CheckState.Checked)
|
|
787
787
|
obj_item.addChild(filt_item); filt_item.setExpanded(True)
|
|
788
788
|
|
|
789
789
|
for exp in sorted(by_obj[obj][filt].keys(), key=lambda e: str(e)):
|
|
790
|
-
exp_item = QTreeWidgetItem([
|
|
790
|
+
exp_item = QTreeWidgetItem([self.tr("Exposure: {0}").format(exp)])
|
|
791
791
|
exp_item.setData(0, Qt.ItemDataRole.UserRole, float(exp))
|
|
792
792
|
exp_item.setFlags(exp_item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
|
793
793
|
exp_item.setCheckState(0, Qt.CheckState.Checked)
|
|
@@ -915,8 +915,8 @@ class AstrobinExportTab(QWidget):
|
|
|
915
915
|
self._refresh_table(); self._refresh_csv_text()
|
|
916
916
|
|
|
917
917
|
def _refresh_table(self):
|
|
918
|
-
cols = ['date','filter','number','duration','gain','iso','binning','sensorCooling',
|
|
919
|
-
'fNumber','darks','flats','flatDarks','bias','bortle','meanSqm','meanFwhm','temperature']
|
|
918
|
+
cols = [self.tr('date'), self.tr('filter'), self.tr('number'), self.tr('duration'), self.tr('gain'), self.tr('iso'), self.tr('binning'), self.tr('sensorCooling'),
|
|
919
|
+
self.tr('fNumber'), self.tr('darks'), self.tr('flats'), self.tr('flatDarks'), self.tr('bias'), self.tr('bortle'), self.tr('meanSqm'), self.tr('meanFwhm'), self.tr('temperature')]
|
|
920
920
|
self.table.setRowCount(len(self.rows))
|
|
921
921
|
self.table.setColumnCount(len(cols))
|
|
922
922
|
self.table.setHorizontalHeaderLabels(cols)
|
|
@@ -945,10 +945,10 @@ class AstrobinExportTab(QWidget):
|
|
|
945
945
|
def _copy_csv_to_clipboard(self):
|
|
946
946
|
txt = self._rows_to_csv_str()
|
|
947
947
|
if not txt.strip():
|
|
948
|
-
QMessageBox.information(self, "Nothing to copy", "There is no CSV content yet.")
|
|
948
|
+
QMessageBox.information(self, self.tr("Nothing to copy"), self.tr("There is no CSV content yet."))
|
|
949
949
|
return
|
|
950
950
|
QGuiApplication.clipboard().setText(txt)
|
|
951
|
-
QMessageBox.information(self, "Copied", "CSV copied to clipboard.")
|
|
951
|
+
QMessageBox.information(self, self.tr("Copied"), self.tr("CSV copied to clipboard."))
|
|
952
952
|
|
|
953
953
|
# ---------- filter map ----------
|
|
954
954
|
def _load_filter_map(self) -> Dict[str, str]:
|
|
@@ -972,7 +972,7 @@ class AstrobinExportTab(QWidget):
|
|
|
972
972
|
return mapping
|
|
973
973
|
|
|
974
974
|
def _filters_summary_text(self) -> str:
|
|
975
|
-
if not self._filter_map: return "No mappings set"
|
|
975
|
+
if not self._filter_map: return self.tr("No mappings set")
|
|
976
976
|
pairs = sorted(self._filter_map.items(), key=lambda kv: kv[0].lower())
|
|
977
977
|
return ", ".join(f"{k}→{v}" for k, v in pairs)
|
|
978
978
|
|
|
@@ -996,7 +996,10 @@ class AstrobinExportTab(QWidget):
|
|
|
996
996
|
class AstrobinExporterDialog(QDialog):
|
|
997
997
|
def __init__(self, parent=None, offline_filters_csv: Optional[str] = None):
|
|
998
998
|
super().__init__(parent)
|
|
999
|
-
self.setWindowTitle("AstroBin Exporter")
|
|
999
|
+
self.setWindowTitle(self.tr("AstroBin Exporter"))
|
|
1000
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
1001
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
1002
|
+
self.setModal(False)
|
|
1000
1003
|
self.resize(980, 640)
|
|
1001
1004
|
v = QVBoxLayout(self)
|
|
1002
1005
|
self.tab = AstrobinExportTab(self, offline_filters_csv=offline_filters_csv)
|
|
@@ -1040,7 +1040,9 @@ class AstroSpikeWindow(QDialog):
|
|
|
1040
1040
|
def __init__(self, image_data_255: np.ndarray, image_data_float: np.ndarray, ctx):
|
|
1041
1041
|
super().__init__()
|
|
1042
1042
|
self.setWindowTitle("AstroSpike - Star Diffraction Spikes")
|
|
1043
|
-
self.
|
|
1043
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
1044
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
1045
|
+
self.setModal(False)
|
|
1044
1046
|
|
|
1045
1047
|
self.ctx = ctx
|
|
1046
1048
|
self.image_data = image_data_255 # uint8 for detection
|
setiastro/saspro/autostretch.py
CHANGED
|
@@ -180,8 +180,10 @@ def autostretch(
|
|
|
180
180
|
lut = _compute_lut_from_sample(lum, target_median, sigma, maxv)
|
|
181
181
|
|
|
182
182
|
out = np.empty_like(u, dtype=np.float32)
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
# Vectorized LUT application: apply to all RGB channels at once
|
|
184
|
+
# lut is 1D array of float32; u[..., :3] selects RGB indices
|
|
185
|
+
out[..., :3] = lut[u[..., :3]]
|
|
186
|
+
|
|
185
187
|
if C > 3: # pass-through non-RGB channels
|
|
186
188
|
out[..., 3:] = u[..., 3:] / float(maxv)
|
|
187
189
|
return out
|
|
@@ -42,12 +42,23 @@ def background_neutralize_rgb(img: np.ndarray, rect_xywh: tuple[int, int, int, i
|
|
|
42
42
|
|
|
43
43
|
out = img.copy()
|
|
44
44
|
eps = 1e-8
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
|
|
46
|
+
# Vectorized neutralization
|
|
47
|
+
# diff shape: (3,) -> (1, 1, 3)
|
|
48
|
+
diffs = (medians - avg_med).reshape(1, 1, 3)
|
|
49
|
+
|
|
50
|
+
# denom shape: (1, 1, 3)
|
|
51
|
+
denoms = 1.0 - diffs
|
|
52
|
+
|
|
53
|
+
# Avoid div-by-zero (vectorized)
|
|
54
|
+
# logic: if abs(denom) < eps, set to eps (sign matched)
|
|
55
|
+
# We can do this efficiently:
|
|
56
|
+
small_mask = np.abs(denoms) < eps
|
|
57
|
+
denoms[small_mask] = np.where(denoms[small_mask] >= 0, eps, -eps)
|
|
58
|
+
|
|
59
|
+
# Apply formula: (pixel - diff) / denom
|
|
60
|
+
out = (out - diffs) / denoms
|
|
61
|
+
out = np.clip(out, 0.0, 1.0)
|
|
51
62
|
|
|
52
63
|
return out.astype(np.float32, copy=False)
|
|
53
64
|
|
|
@@ -237,14 +248,21 @@ def apply_background_neutral_to_doc(doc, preset: dict | None = None):
|
|
|
237
248
|
class BackgroundNeutralizationDialog(QDialog):
|
|
238
249
|
def __init__(self, parent, doc, icon: QIcon | None = None):
|
|
239
250
|
super().__init__(parent)
|
|
251
|
+
self._main = parent
|
|
240
252
|
self.doc = doc
|
|
253
|
+
|
|
254
|
+
# Connect to active document change signal
|
|
255
|
+
if hasattr(self._main, "currentDocumentChanged"):
|
|
256
|
+
self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
|
|
257
|
+
|
|
241
258
|
if icon:
|
|
242
259
|
self.setWindowIcon(icon)
|
|
243
|
-
self.setWindowTitle("Background Neutralization")
|
|
260
|
+
self.setWindowTitle(self.tr("Background Neutralization"))
|
|
244
261
|
self.resize(900, 600)
|
|
245
262
|
|
|
246
263
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
247
|
-
|
|
264
|
+
# Non-modal: allow user to switch between images while dialog is open
|
|
265
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
248
266
|
self.setModal(False)
|
|
249
267
|
#self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
250
268
|
|
|
@@ -271,10 +289,10 @@ class BackgroundNeutralizationDialog(QDialog):
|
|
|
271
289
|
|
|
272
290
|
# Buttons row
|
|
273
291
|
btn_row = QHBoxLayout()
|
|
274
|
-
self.btn_apply = QPushButton("Apply Neutralization")
|
|
275
|
-
self.btn_cancel = QPushButton("Cancel")
|
|
276
|
-
self.btn_toggle_stretch = QPushButton("Enable Auto-Stretch")
|
|
277
|
-
self.btn_find_bg = QPushButton("Find Background")
|
|
292
|
+
self.btn_apply = QPushButton(self.tr("Apply Neutralization"))
|
|
293
|
+
self.btn_cancel = QPushButton(self.tr("Cancel"))
|
|
294
|
+
self.btn_toggle_stretch = QPushButton(self.tr("Enable Auto-Stretch"))
|
|
295
|
+
self.btn_find_bg = QPushButton(self.tr("Find Background"))
|
|
278
296
|
btn_row.addWidget(self.btn_apply)
|
|
279
297
|
btn_row.addWidget(self.btn_cancel)
|
|
280
298
|
btn_row.addWidget(self.btn_toggle_stretch)
|
|
@@ -313,7 +331,14 @@ class BackgroundNeutralizationDialog(QDialog):
|
|
|
313
331
|
|
|
314
332
|
self._load_image()
|
|
315
333
|
|
|
316
|
-
|
|
334
|
+
# ---- active document change ------------------------------------
|
|
335
|
+
def _on_active_doc_changed(self, doc):
|
|
336
|
+
"""Called when user clicks a different image window."""
|
|
337
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
338
|
+
return
|
|
339
|
+
self.doc = doc
|
|
340
|
+
self.selection_item = None
|
|
341
|
+
self._load_image()
|
|
317
342
|
|
|
318
343
|
# ---------- image display ----------
|
|
319
344
|
def _doc_image_normalized(self) -> np.ndarray:
|
|
@@ -509,9 +534,35 @@ class BackgroundNeutralizationDialog(QDialog):
|
|
|
509
534
|
metadata=meta,
|
|
510
535
|
step_name="Background Neutralization",
|
|
511
536
|
)
|
|
512
|
-
|
|
537
|
+
# Dialog stays open so user can apply to other images
|
|
538
|
+
# Refresh to use the now-active document for next operation
|
|
539
|
+
self.accept() # or: self.close()
|
|
540
|
+
|
|
541
|
+
def closeEvent(self, e):
|
|
542
|
+
try:
|
|
543
|
+
if hasattr(self._main, "currentDocumentChanged"):
|
|
544
|
+
self._main.currentDocumentChanged.disconnect(self._on_active_doc_changed)
|
|
545
|
+
except Exception:
|
|
546
|
+
pass
|
|
547
|
+
super().closeEvent(e)
|
|
513
548
|
|
|
514
549
|
|
|
550
|
+
def _refresh_document_from_active(self):
|
|
551
|
+
"""
|
|
552
|
+
Refresh the dialog's document reference to the currently active document.
|
|
553
|
+
This allows reusing the same dialog on different images.
|
|
554
|
+
"""
|
|
555
|
+
try:
|
|
556
|
+
main = self.parent()
|
|
557
|
+
if main and hasattr(main, "_active_doc"):
|
|
558
|
+
new_doc = main._active_doc()
|
|
559
|
+
if new_doc is not None and new_doc is not self.doc:
|
|
560
|
+
self.doc = new_doc
|
|
561
|
+
# Refresh the preview image
|
|
562
|
+
self._load_preview()
|
|
563
|
+
except Exception:
|
|
564
|
+
pass
|
|
565
|
+
|
|
515
566
|
def _zoom(self, factor: float):
|
|
516
567
|
self._user_zoomed = True
|
|
517
568
|
cur = self.graphics_view.transform().m11()
|
|
@@ -168,7 +168,10 @@ class _BatchWorker(QThread):
|
|
|
168
168
|
class BatchConvertDialog(QDialog):
|
|
169
169
|
def __init__(self, parent=None):
|
|
170
170
|
super().__init__(parent)
|
|
171
|
-
self.setWindowTitle("Batch Convert")
|
|
171
|
+
self.setWindowTitle(self.tr("Batch Convert"))
|
|
172
|
+
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
173
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
174
|
+
self.setModal(False)
|
|
172
175
|
self.setMinimumWidth(560)
|
|
173
176
|
self.worker: _BatchWorker | None = None
|
|
174
177
|
|
|
@@ -208,10 +211,10 @@ class BatchConvertDialog(QDialog):
|
|
|
208
211
|
self.depth.addItem("Auto") # always first
|
|
209
212
|
# choices will be populated based on fmt
|
|
210
213
|
|
|
211
|
-
fmt_row.addWidget(QLabel("Output format:"))
|
|
214
|
+
fmt_row.addWidget(QLabel(self.tr("Output format:")))
|
|
212
215
|
fmt_row.addWidget(self.fmt)
|
|
213
216
|
fmt_row.addSpacing(16)
|
|
214
|
-
fmt_row.addWidget(QLabel("Bit depth:"))
|
|
217
|
+
fmt_row.addWidget(QLabel(self.tr("Bit depth:")))
|
|
215
218
|
fmt_row.addWidget(self.depth)
|
|
216
219
|
fmt_row.addStretch(1)
|
|
217
220
|
lay.addLayout(fmt_row)
|
|
@@ -222,8 +225,8 @@ class BatchConvertDialog(QDialog):
|
|
|
222
225
|
self.bar.setRange(0, 100)
|
|
223
226
|
|
|
224
227
|
# buttons
|
|
225
|
-
self.start_btn = QPushButton("Start")
|
|
226
|
-
self.cancel_btn = QPushButton("Cancel")
|
|
228
|
+
self.start_btn = QPushButton(self.tr("Start"))
|
|
229
|
+
self.cancel_btn = QPushButton(self.tr("Cancel"))
|
|
227
230
|
self.cancel_btn.setEnabled(False)
|
|
228
231
|
btns = QHBoxLayout()
|
|
229
232
|
btns.addStretch(1)
|