setiastrosuitepro 1.6.2.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/__init__.py +2 -0
- 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/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/__init__.py +20 -0
- setiastro/saspro/__main__.py +945 -0
- setiastro/saspro/_generated/__init__.py +7 -0
- setiastro/saspro/_generated/build_info.py +3 -0
- setiastro/saspro/abe.py +1346 -0
- setiastro/saspro/abe_preset.py +196 -0
- setiastro/saspro/aberration_ai.py +694 -0
- setiastro/saspro/aberration_ai_preset.py +224 -0
- setiastro/saspro/accel_installer.py +218 -0
- setiastro/saspro/accel_workers.py +30 -0
- setiastro/saspro/add_stars.py +624 -0
- setiastro/saspro/astrobin_exporter.py +1010 -0
- setiastro/saspro/astrospike.py +153 -0
- setiastro/saspro/astrospike_python.py +1841 -0
- setiastro/saspro/autostretch.py +198 -0
- setiastro/saspro/backgroundneutral.py +602 -0
- setiastro/saspro/batch_convert.py +328 -0
- setiastro/saspro/batch_renamer.py +522 -0
- setiastro/saspro/blemish_blaster.py +491 -0
- setiastro/saspro/blink_comparator_pro.py +2926 -0
- setiastro/saspro/bundles.py +61 -0
- setiastro/saspro/bundles_dock.py +114 -0
- setiastro/saspro/cheat_sheet.py +213 -0
- setiastro/saspro/clahe.py +368 -0
- setiastro/saspro/comet_stacking.py +1442 -0
- setiastro/saspro/common_tr.py +107 -0
- setiastro/saspro/config.py +38 -0
- setiastro/saspro/config_bootstrap.py +40 -0
- setiastro/saspro/config_manager.py +316 -0
- setiastro/saspro/continuum_subtract.py +1617 -0
- setiastro/saspro/convo.py +1400 -0
- setiastro/saspro/convo_preset.py +414 -0
- setiastro/saspro/copyastro.py +190 -0
- setiastro/saspro/cosmicclarity.py +1589 -0
- setiastro/saspro/cosmicclarity_preset.py +407 -0
- setiastro/saspro/crop_dialog_pro.py +973 -0
- setiastro/saspro/crop_preset.py +189 -0
- setiastro/saspro/curve_editor_pro.py +2562 -0
- setiastro/saspro/curves_preset.py +375 -0
- setiastro/saspro/debayer.py +673 -0
- setiastro/saspro/debug_utils.py +29 -0
- setiastro/saspro/dnd_mime.py +35 -0
- setiastro/saspro/doc_manager.py +2664 -0
- setiastro/saspro/exoplanet_detector.py +2166 -0
- setiastro/saspro/file_utils.py +284 -0
- setiastro/saspro/fitsmodifier.py +748 -0
- setiastro/saspro/fix_bom.py +32 -0
- setiastro/saspro/free_torch_memory.py +48 -0
- setiastro/saspro/frequency_separation.py +1349 -0
- setiastro/saspro/function_bundle.py +1596 -0
- setiastro/saspro/generate_translations.py +3092 -0
- setiastro/saspro/ghs_dialog_pro.py +663 -0
- setiastro/saspro/ghs_preset.py +284 -0
- setiastro/saspro/graxpert.py +637 -0
- setiastro/saspro/graxpert_preset.py +287 -0
- setiastro/saspro/gui/__init__.py +0 -0
- setiastro/saspro/gui/main_window.py +8810 -0
- setiastro/saspro/gui/mixins/__init__.py +33 -0
- setiastro/saspro/gui/mixins/dock_mixin.py +362 -0
- setiastro/saspro/gui/mixins/file_mixin.py +450 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
- setiastro/saspro/gui/mixins/header_mixin.py +441 -0
- setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
- setiastro/saspro/gui/mixins/menu_mixin.py +389 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +1457 -0
- setiastro/saspro/gui/mixins/update_mixin.py +309 -0
- setiastro/saspro/gui/mixins/view_mixin.py +435 -0
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +488 -0
- setiastro/saspro/header_viewer.py +448 -0
- setiastro/saspro/headless_utils.py +88 -0
- setiastro/saspro/histogram.py +756 -0
- setiastro/saspro/history_explorer.py +941 -0
- setiastro/saspro/i18n.py +168 -0
- setiastro/saspro/image_combine.py +417 -0
- setiastro/saspro/image_peeker_pro.py +1604 -0
- setiastro/saspro/imageops/__init__.py +37 -0
- setiastro/saspro/imageops/mdi_snap.py +292 -0
- setiastro/saspro/imageops/scnr.py +36 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
- setiastro/saspro/imageops/stretch.py +236 -0
- setiastro/saspro/isophote.py +1182 -0
- setiastro/saspro/layers.py +208 -0
- setiastro/saspro/layers_dock.py +714 -0
- setiastro/saspro/lazy_imports.py +193 -0
- setiastro/saspro/legacy/__init__.py +2 -0
- setiastro/saspro/legacy/image_manager.py +2226 -0
- setiastro/saspro/legacy/numba_utils.py +3676 -0
- setiastro/saspro/legacy/xisf.py +1071 -0
- setiastro/saspro/linear_fit.py +537 -0
- setiastro/saspro/live_stacking.py +1841 -0
- setiastro/saspro/log_bus.py +5 -0
- setiastro/saspro/logging_config.py +460 -0
- setiastro/saspro/luminancerecombine.py +309 -0
- setiastro/saspro/main_helpers.py +201 -0
- setiastro/saspro/mask_creation.py +931 -0
- setiastro/saspro/masks_core.py +56 -0
- setiastro/saspro/mdi_widgets.py +353 -0
- setiastro/saspro/memory_utils.py +666 -0
- setiastro/saspro/metadata_patcher.py +75 -0
- setiastro/saspro/mfdeconv.py +3831 -0
- setiastro/saspro/mfdeconv_earlystop.py +71 -0
- setiastro/saspro/mfdeconvcudnn.py +3263 -0
- setiastro/saspro/mfdeconvsport.py +2382 -0
- setiastro/saspro/minorbodycatalog.py +567 -0
- setiastro/saspro/morphology.py +407 -0
- setiastro/saspro/multiscale_decomp.py +1293 -0
- setiastro/saspro/nbtorgb_stars.py +541 -0
- setiastro/saspro/numba_utils.py +3145 -0
- setiastro/saspro/numba_warmup.py +141 -0
- setiastro/saspro/ops/__init__.py +9 -0
- setiastro/saspro/ops/command_help_dialog.py +623 -0
- setiastro/saspro/ops/command_runner.py +217 -0
- setiastro/saspro/ops/commands.py +1594 -0
- setiastro/saspro/ops/script_editor.py +1102 -0
- setiastro/saspro/ops/scripts.py +1473 -0
- setiastro/saspro/ops/settings.py +637 -0
- setiastro/saspro/parallel_utils.py +554 -0
- setiastro/saspro/pedestal.py +121 -0
- setiastro/saspro/perfect_palette_picker.py +1071 -0
- setiastro/saspro/pipeline.py +110 -0
- setiastro/saspro/pixelmath.py +1604 -0
- setiastro/saspro/plate_solver.py +2445 -0
- setiastro/saspro/project_io.py +797 -0
- setiastro/saspro/psf_utils.py +136 -0
- setiastro/saspro/psf_viewer.py +549 -0
- setiastro/saspro/pyi_rthook_astroquery.py +95 -0
- setiastro/saspro/remove_green.py +331 -0
- setiastro/saspro/remove_stars.py +1599 -0
- setiastro/saspro/remove_stars_preset.py +404 -0
- setiastro/saspro/resources.py +501 -0
- setiastro/saspro/rgb_combination.py +208 -0
- setiastro/saspro/rgb_extract.py +19 -0
- setiastro/saspro/rgbalign.py +723 -0
- setiastro/saspro/runtime_imports.py +7 -0
- setiastro/saspro/runtime_torch.py +754 -0
- setiastro/saspro/save_options.py +73 -0
- setiastro/saspro/selective_color.py +1552 -0
- setiastro/saspro/sfcc.py +1472 -0
- setiastro/saspro/shortcuts.py +3043 -0
- setiastro/saspro/signature_insert.py +1102 -0
- setiastro/saspro/stacking_suite.py +18470 -0
- setiastro/saspro/star_alignment.py +7435 -0
- setiastro/saspro/star_alignment_preset.py +329 -0
- setiastro/saspro/star_metrics.py +49 -0
- setiastro/saspro/star_spikes.py +765 -0
- setiastro/saspro/star_stretch.py +507 -0
- setiastro/saspro/stat_stretch.py +538 -0
- setiastro/saspro/status_log_dock.py +78 -0
- setiastro/saspro/subwindow.py +3328 -0
- setiastro/saspro/supernovaasteroidhunter.py +1719 -0
- setiastro/saspro/swap_manager.py +99 -0
- setiastro/saspro/torch_backend.py +89 -0
- setiastro/saspro/torch_rejection.py +434 -0
- setiastro/saspro/translations/all_source_strings.json +3654 -0
- setiastro/saspro/translations/ar_translations.py +3865 -0
- setiastro/saspro/translations/de_translations.py +3749 -0
- setiastro/saspro/translations/es_translations.py +3939 -0
- setiastro/saspro/translations/fr_translations.py +3858 -0
- setiastro/saspro/translations/hi_translations.py +3571 -0
- setiastro/saspro/translations/integrate_translations.py +270 -0
- setiastro/saspro/translations/it_translations.py +3678 -0
- setiastro/saspro/translations/ja_translations.py +3601 -0
- setiastro/saspro/translations/pt_translations.py +3869 -0
- setiastro/saspro/translations/ru_translations.py +2848 -0
- setiastro/saspro/translations/saspro_ar.qm +0 -0
- setiastro/saspro/translations/saspro_ar.ts +255 -0
- setiastro/saspro/translations/saspro_de.qm +0 -0
- setiastro/saspro/translations/saspro_de.ts +253 -0
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +12520 -0
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +12514 -0
- setiastro/saspro/translations/saspro_hi.qm +0 -0
- setiastro/saspro/translations/saspro_hi.ts +257 -0
- setiastro/saspro/translations/saspro_it.qm +0 -0
- setiastro/saspro/translations/saspro_it.ts +12520 -0
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +257 -0
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +257 -0
- setiastro/saspro/translations/saspro_ru.qm +0 -0
- setiastro/saspro/translations/saspro_ru.ts +237 -0
- setiastro/saspro/translations/saspro_sw.qm +0 -0
- setiastro/saspro/translations/saspro_sw.ts +257 -0
- setiastro/saspro/translations/saspro_uk.qm +0 -0
- setiastro/saspro/translations/saspro_uk.ts +10771 -0
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +12520 -0
- setiastro/saspro/translations/sw_translations.py +3671 -0
- setiastro/saspro/translations/uk_translations.py +3700 -0
- setiastro/saspro/translations/zh_translations.py +3675 -0
- setiastro/saspro/versioning.py +77 -0
- setiastro/saspro/view_bundle.py +1558 -0
- setiastro/saspro/wavescale_hdr.py +645 -0
- setiastro/saspro/wavescale_hdr_preset.py +101 -0
- setiastro/saspro/wavescalede.py +680 -0
- setiastro/saspro/wavescalede_preset.py +230 -0
- setiastro/saspro/wcs_update.py +374 -0
- setiastro/saspro/whitebalance.py +492 -0
- setiastro/saspro/widgets/__init__.py +48 -0
- setiastro/saspro/widgets/common_utilities.py +306 -0
- setiastro/saspro/widgets/graphics_views.py +122 -0
- setiastro/saspro/widgets/image_utils.py +518 -0
- setiastro/saspro/widgets/minigame/game.js +986 -0
- setiastro/saspro/widgets/minigame/index.html +53 -0
- setiastro/saspro/widgets/minigame/style.css +241 -0
- setiastro/saspro/widgets/preview_dialogs.py +280 -0
- setiastro/saspro/widgets/resource_monitor.py +237 -0
- setiastro/saspro/widgets/spinboxes.py +275 -0
- setiastro/saspro/widgets/themed_buttons.py +13 -0
- setiastro/saspro/widgets/wavelet_utils.py +331 -0
- setiastro/saspro/wimi.py +7996 -0
- setiastro/saspro/wims.py +578 -0
- setiastro/saspro/window_shelf.py +185 -0
- setiastro/saspro/xisf.py +1123 -0
- setiastrosuitepro-1.6.2.post1.dist-info/METADATA +278 -0
- setiastrosuitepro-1.6.2.post1.dist-info/RECORD +367 -0
- setiastrosuitepro-1.6.2.post1.dist-info/WHEEL +4 -0
- setiastrosuitepro-1.6.2.post1.dist-info/entry_points.txt +6 -0
- setiastrosuitepro-1.6.2.post1.dist-info/licenses/LICENSE +674 -0
- setiastrosuitepro-1.6.2.post1.dist-info/licenses/license.txt +2580 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# pro/gui/mixins/geometry_mixin.py
|
|
2
|
+
"""
|
|
3
|
+
Geometry operations mixin for AstroSuiteProMainWindow.
|
|
4
|
+
|
|
5
|
+
This mixin contains all geometry-related functionality: invert, flip,
|
|
6
|
+
rotate, rescale, and WCS transformation handling.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from PyQt6.QtCore import Qt
|
|
13
|
+
from PyQt6.QtGui import QIcon
|
|
14
|
+
from PyQt6.QtWidgets import QMessageBox, QInputDialog, QDialog
|
|
15
|
+
|
|
16
|
+
# Import numba-accelerated functions
|
|
17
|
+
try:
|
|
18
|
+
from setiastro.saspro.legacy.numba_utils import (
|
|
19
|
+
invert_image_numba,
|
|
20
|
+
flip_horizontal_numba,
|
|
21
|
+
flip_vertical_numba,
|
|
22
|
+
rotate_90_clockwise_numba,
|
|
23
|
+
rotate_90_counterclockwise_numba,
|
|
24
|
+
rotate_180_numba,
|
|
25
|
+
rescale_image_numba,
|
|
26
|
+
)
|
|
27
|
+
except ImportError:
|
|
28
|
+
# Fallback stubs if numba_utils is not available
|
|
29
|
+
def invert_image_numba(arr):
|
|
30
|
+
return 1.0 - arr
|
|
31
|
+
|
|
32
|
+
def flip_horizontal_numba(arr):
|
|
33
|
+
return arr[:, ::-1].copy()
|
|
34
|
+
|
|
35
|
+
def flip_vertical_numba(arr):
|
|
36
|
+
return arr[::-1, :].copy()
|
|
37
|
+
|
|
38
|
+
def rotate_90_clockwise_numba(arr):
|
|
39
|
+
return np.rot90(arr, k=-1)
|
|
40
|
+
|
|
41
|
+
def rotate_90_counterclockwise_numba(arr):
|
|
42
|
+
return np.rot90(arr, k=1)
|
|
43
|
+
|
|
44
|
+
def rotate_180_numba(arr):
|
|
45
|
+
return np.rot90(arr, k=2)
|
|
46
|
+
|
|
47
|
+
def rescale_image_numba(arr, factor):
|
|
48
|
+
import cv2
|
|
49
|
+
h, w = arr.shape[:2]
|
|
50
|
+
new_h, new_w = int(h * factor), int(w * factor)
|
|
51
|
+
return cv2.resize(arr, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Try to import WCS update function
|
|
55
|
+
try:
|
|
56
|
+
from setiastro.saspro.wcs_utils import update_wcs_after_crop
|
|
57
|
+
except ImportError:
|
|
58
|
+
update_wcs_after_crop = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if TYPE_CHECKING:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class GeometryMixin:
|
|
66
|
+
"""
|
|
67
|
+
Mixin for geometry operations.
|
|
68
|
+
|
|
69
|
+
Provides methods for inverting, flipping, rotating, and rescaling images,
|
|
70
|
+
with automatic WCS (World Coordinate System) updates when applicable.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def _apply_geom_with_wcs(self, doc, out_image: np.ndarray,
|
|
74
|
+
M_src_to_dst: np.ndarray | None,
|
|
75
|
+
step_name: str):
|
|
76
|
+
"""
|
|
77
|
+
Apply a geometry transform to `doc` and update WCS (if present)
|
|
78
|
+
using the same machinery as crop (update_wcs_after_crop).
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
doc: Document to apply transform to
|
|
82
|
+
out_image: Transformed image array
|
|
83
|
+
M_src_to_dst: 3x3 transformation matrix (source to destination)
|
|
84
|
+
step_name: Name of the operation for history
|
|
85
|
+
"""
|
|
86
|
+
out_h, out_w = out_image.shape[:2]
|
|
87
|
+
meta = dict(getattr(doc, "metadata", {}) or {})
|
|
88
|
+
|
|
89
|
+
if update_wcs_after_crop is not None and M_src_to_dst is not None:
|
|
90
|
+
try:
|
|
91
|
+
meta = update_wcs_after_crop(
|
|
92
|
+
meta,
|
|
93
|
+
M_src_to_dst=M_src_to_dst,
|
|
94
|
+
out_w=out_w,
|
|
95
|
+
out_h=out_h,
|
|
96
|
+
)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(f"[WCS-GEOM] WCS update failed for {step_name}: {e}")
|
|
99
|
+
|
|
100
|
+
# Push the image + updated metadata back into the document
|
|
101
|
+
if hasattr(doc, "apply_edit"):
|
|
102
|
+
doc.apply_edit(
|
|
103
|
+
out_image,
|
|
104
|
+
metadata={**meta, "step_name": step_name},
|
|
105
|
+
step_name=step_name,
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
doc.image = out_image
|
|
109
|
+
try:
|
|
110
|
+
setattr(doc, "metadata", {**meta, "step_name": step_name})
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
if hasattr(doc, "changed"):
|
|
114
|
+
try:
|
|
115
|
+
doc.changed.emit()
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
# If WCS was successfully refit, update_wcs_after_crop
|
|
120
|
+
# will have stashed a '__wcs_debug__' payload in metadata.
|
|
121
|
+
dbg = meta.get("__wcs_debug__")
|
|
122
|
+
if isinstance(dbg, dict):
|
|
123
|
+
try:
|
|
124
|
+
self._show_wcs_update_popup(dbg, step_name=step_name)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"[WCS-GEOM] Failed to show WCS popup for {step_name}: {e}")
|
|
127
|
+
|
|
128
|
+
def _exec_geom_invert(self):
|
|
129
|
+
"""Execute invert operation on active view."""
|
|
130
|
+
sw = self.mdi.activeSubWindow() if hasattr(self, "mdi") else None
|
|
131
|
+
view = sw.widget() if sw else None
|
|
132
|
+
doc = getattr(view, "document", None)
|
|
133
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
134
|
+
QMessageBox.information(self, self.tr("Invert"), self.tr("Active view has no image."))
|
|
135
|
+
return
|
|
136
|
+
try:
|
|
137
|
+
self._apply_geom_invert_to_doc(doc)
|
|
138
|
+
self._log("Invert applied to active view")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
QMessageBox.critical(self, "Invert", str(e))
|
|
141
|
+
|
|
142
|
+
def _exec_geom_flip_h(self):
|
|
143
|
+
"""Execute horizontal flip on active view."""
|
|
144
|
+
sw = self.mdi.activeSubWindow() if hasattr(self, "mdi") else None
|
|
145
|
+
view = sw.widget() if sw else None
|
|
146
|
+
doc = getattr(view, "document", None)
|
|
147
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
148
|
+
QMessageBox.information(self, self.tr("Flip Horizontal"), self.tr("Active view has no image."))
|
|
149
|
+
return
|
|
150
|
+
try:
|
|
151
|
+
self._apply_geom_flip_h_to_doc(doc)
|
|
152
|
+
self._log("Flip Horizontal applied to active view")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
QMessageBox.critical(self, "Flip Horizontal", str(e))
|
|
155
|
+
|
|
156
|
+
def _exec_geom_flip_v(self):
|
|
157
|
+
"""Execute vertical flip on active view."""
|
|
158
|
+
sw = self.mdi.activeSubWindow() if hasattr(self, "mdi") else None
|
|
159
|
+
view = sw.widget() if sw else None
|
|
160
|
+
doc = getattr(view, "document", None)
|
|
161
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
162
|
+
QMessageBox.information(self, self.tr("Flip Vertical"), self.tr("Active view has no image."))
|
|
163
|
+
return
|
|
164
|
+
try:
|
|
165
|
+
self._apply_geom_flip_v_to_doc(doc)
|
|
166
|
+
self._log("Flip Vertical applied to active view")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
QMessageBox.critical(self, "Flip Vertical", str(e))
|
|
169
|
+
|
|
170
|
+
def _exec_geom_rot_cw(self):
|
|
171
|
+
"""Execute 90 degree clockwise rotation on active view."""
|
|
172
|
+
sw = self.mdi.activeSubWindow() if hasattr(self, "mdi") else None
|
|
173
|
+
view = sw.widget() if sw else None
|
|
174
|
+
doc = getattr(view, "document", None)
|
|
175
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
176
|
+
QMessageBox.information(self, self.tr("Rotate 90° CW"), self.tr("Active view has no image."))
|
|
177
|
+
return
|
|
178
|
+
try:
|
|
179
|
+
self._apply_geom_rot_cw_to_doc(doc)
|
|
180
|
+
self._log("Rotate 90° CW applied to active view")
|
|
181
|
+
except Exception as e:
|
|
182
|
+
QMessageBox.critical(self, "Rotate 90° CW", str(e))
|
|
183
|
+
|
|
184
|
+
def _exec_geom_rot_ccw(self):
|
|
185
|
+
"""Execute 90 degree counterclockwise rotation on active view."""
|
|
186
|
+
sw = self.mdi.activeSubWindow() if hasattr(self, "mdi") else None
|
|
187
|
+
view = sw.widget() if sw else None
|
|
188
|
+
doc = getattr(view, "document", None)
|
|
189
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
190
|
+
QMessageBox.information(self, self.tr("Rotate 90° CCW"), self.tr("Active view has no image."))
|
|
191
|
+
return
|
|
192
|
+
try:
|
|
193
|
+
self._apply_geom_rot_ccw_to_doc(doc)
|
|
194
|
+
self._log("Rotate 90° CCW applied to active view")
|
|
195
|
+
except Exception as e:
|
|
196
|
+
QMessageBox.critical(self, "Rotate 90° CCW", str(e))
|
|
197
|
+
|
|
198
|
+
def _exec_geom_rot_180(self):
|
|
199
|
+
"""Execute 180 degree rotation on active view."""
|
|
200
|
+
sw = self.mdi.activeSubWindow() if hasattr(self, "mdi") else None
|
|
201
|
+
view = sw.widget() if sw else None
|
|
202
|
+
doc = getattr(view, "document", None)
|
|
203
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
204
|
+
QMessageBox.information(self, self.tr("Rotate 180°"), self.tr("Active view has no image."))
|
|
205
|
+
return
|
|
206
|
+
try:
|
|
207
|
+
self._apply_geom_rot_180_to_doc(doc)
|
|
208
|
+
self._log("Rotate 180° applied to active view")
|
|
209
|
+
except Exception as e:
|
|
210
|
+
QMessageBox.critical(self, "Rotate 180°", str(e))
|
|
211
|
+
|
|
212
|
+
def _exec_geom_rescale(self):
|
|
213
|
+
"""Execute rescale operation on active view with dialog."""
|
|
214
|
+
sw = self.mdi.activeSubWindow() if hasattr(self, "mdi") else None
|
|
215
|
+
view = sw.widget() if sw else None
|
|
216
|
+
doc = getattr(view, "document", None)
|
|
217
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
218
|
+
QMessageBox.information(self, self.tr("Rescale Image"), self.tr("Active view has no image."))
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
# remember last value
|
|
222
|
+
if not hasattr(self, "_last_rescale_factor"):
|
|
223
|
+
self._last_rescale_factor = 1.0
|
|
224
|
+
|
|
225
|
+
dlg = QInputDialog(self)
|
|
226
|
+
dlg.setWindowTitle(self.tr("Rescale Image"))
|
|
227
|
+
dlg.setLabelText(self.tr("Enter scaling factor (e.g., 0.5 for 50%, 2 for 200%):"))
|
|
228
|
+
dlg.setInputMode(QInputDialog.InputMode.DoubleInput)
|
|
229
|
+
dlg.setDoubleRange(0.1, 10.0)
|
|
230
|
+
dlg.setDoubleDecimals(2)
|
|
231
|
+
dlg.setDoubleValue(self._last_rescale_factor)
|
|
232
|
+
|
|
233
|
+
# make sure it's a true window so the icon shows on all platforms
|
|
234
|
+
dlg.setWindowFlag(Qt.WindowType.Window, True)
|
|
235
|
+
|
|
236
|
+
# set the icon from rescale_path if available
|
|
237
|
+
try:
|
|
238
|
+
from setiastro.saspro.resources import rescale_path
|
|
239
|
+
dlg.setWindowIcon(QIcon(rescale_path))
|
|
240
|
+
except Exception:
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
if dlg.exec() != QDialog.DialogCode.Accepted:
|
|
244
|
+
return
|
|
245
|
+
factor = dlg.doubleValue()
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
self._apply_geom_rescale_to_doc(doc, factor=factor)
|
|
249
|
+
self._last_rescale_factor = factor
|
|
250
|
+
self._log(f"Rescale ({factor:g}×) applied to active view")
|
|
251
|
+
except Exception as e:
|
|
252
|
+
QMessageBox.critical(self, self.tr("Rescale Image"), str(e))
|
|
253
|
+
|
|
254
|
+
# --- Geometry: headless apply-to-doc helpers ---
|
|
255
|
+
|
|
256
|
+
def _apply_geom_invert_to_doc(self, doc):
|
|
257
|
+
"""Apply invert to document."""
|
|
258
|
+
arr = np.asarray(doc.image, dtype=np.float32)
|
|
259
|
+
out = invert_image_numba(arr)
|
|
260
|
+
if hasattr(doc, "set_image"):
|
|
261
|
+
doc.set_image(out, step_name="Invert")
|
|
262
|
+
else:
|
|
263
|
+
doc.image = out
|
|
264
|
+
|
|
265
|
+
def _apply_geom_flip_h_to_doc(self, doc):
|
|
266
|
+
"""Apply horizontal flip to document with WCS update."""
|
|
267
|
+
arr = np.asarray(doc.image, dtype=np.float32)
|
|
268
|
+
h, w = arr.shape[:2]
|
|
269
|
+
out = flip_horizontal_numba(arr)
|
|
270
|
+
|
|
271
|
+
M = np.array([
|
|
272
|
+
[-1.0, 0.0, w - 1.0],
|
|
273
|
+
[0.0, 1.0, 0.0],
|
|
274
|
+
[0.0, 0.0, 1.0],
|
|
275
|
+
], dtype=float)
|
|
276
|
+
|
|
277
|
+
self._apply_geom_with_wcs(doc, out, M_src_to_dst=M, step_name="Flip Horizontal")
|
|
278
|
+
|
|
279
|
+
def _apply_geom_flip_v_to_doc(self, doc):
|
|
280
|
+
"""Apply vertical flip to document with WCS update."""
|
|
281
|
+
arr = np.asarray(doc.image, dtype=np.float32)
|
|
282
|
+
h, w = arr.shape[:2]
|
|
283
|
+
out = flip_vertical_numba(arr)
|
|
284
|
+
|
|
285
|
+
M = np.array([
|
|
286
|
+
[1.0, 0.0, 0.0],
|
|
287
|
+
[0.0, -1.0, h - 1.0],
|
|
288
|
+
[0.0, 0.0, 1.0],
|
|
289
|
+
], dtype=float)
|
|
290
|
+
|
|
291
|
+
self._apply_geom_with_wcs(doc, out, M_src_to_dst=M, step_name="Flip Vertical")
|
|
292
|
+
|
|
293
|
+
def _apply_geom_rot_cw_to_doc(self, doc):
|
|
294
|
+
"""Apply 90° clockwise rotation to document with WCS update."""
|
|
295
|
+
arr = np.asarray(doc.image, dtype=np.float32)
|
|
296
|
+
h, w = arr.shape[:2]
|
|
297
|
+
out = rotate_90_clockwise_numba(arr) # out shape: (w, h)
|
|
298
|
+
|
|
299
|
+
M = np.array([
|
|
300
|
+
[0.0, -1.0, h - 1.0],
|
|
301
|
+
[1.0, 0.0, 0.0],
|
|
302
|
+
[0.0, 0.0, 1.0],
|
|
303
|
+
], dtype=float)
|
|
304
|
+
|
|
305
|
+
self._apply_geom_with_wcs(doc, out, M_src_to_dst=M, step_name="Rotate 90° Clockwise")
|
|
306
|
+
|
|
307
|
+
def _apply_geom_rot_ccw_to_doc(self, doc):
|
|
308
|
+
"""Apply 90° counterclockwise rotation to document with WCS update."""
|
|
309
|
+
arr = np.asarray(doc.image, dtype=np.float32)
|
|
310
|
+
h, w = arr.shape[:2]
|
|
311
|
+
out = rotate_90_counterclockwise_numba(arr) # out shape: (w, h)
|
|
312
|
+
|
|
313
|
+
M = np.array([
|
|
314
|
+
[0.0, 1.0, 0.0],
|
|
315
|
+
[-1.0, 0.0, w - 1.0],
|
|
316
|
+
[0.0, 0.0, 1.0],
|
|
317
|
+
], dtype=float)
|
|
318
|
+
|
|
319
|
+
self._apply_geom_with_wcs(doc, out, M_src_to_dst=M, step_name="Rotate 90° Counterclockwise")
|
|
320
|
+
|
|
321
|
+
def _apply_geom_rot_180_to_doc(self, doc):
|
|
322
|
+
"""Apply 180° rotation to document with WCS update."""
|
|
323
|
+
arr = np.asarray(doc.image, dtype=np.float32)
|
|
324
|
+
h, w = arr.shape[:2]
|
|
325
|
+
out = rotate_180_numba(arr) # out shape: (h, w)
|
|
326
|
+
|
|
327
|
+
# 180° rotation around the image center:
|
|
328
|
+
# (x, y) -> (w-1 - x, h-1 - y)
|
|
329
|
+
M = np.array([
|
|
330
|
+
[-1.0, 0.0, w - 1.0],
|
|
331
|
+
[0.0, -1.0, h - 1.0],
|
|
332
|
+
[0.0, 0.0, 1.0],
|
|
333
|
+
], dtype=float)
|
|
334
|
+
|
|
335
|
+
self._apply_geom_with_wcs(doc, out, M_src_to_dst=M, step_name="Rotate 180°")
|
|
336
|
+
|
|
337
|
+
def _apply_geom_rescale_to_doc(self, doc, *, factor: float):
|
|
338
|
+
"""Apply rescale to document with WCS update."""
|
|
339
|
+
factor = float(max(0.1, min(10.0, factor)))
|
|
340
|
+
arr = np.asarray(doc.image, dtype=np.float32)
|
|
341
|
+
h, w = arr.shape[:2]
|
|
342
|
+
out = rescale_image_numba(arr, factor)
|
|
343
|
+
|
|
344
|
+
M = np.array([
|
|
345
|
+
[factor, 0.0, 0.0],
|
|
346
|
+
[0.0, factor, 0.0],
|
|
347
|
+
[0.0, 0.0, 1.0],
|
|
348
|
+
], dtype=float)
|
|
349
|
+
|
|
350
|
+
self._apply_geom_with_wcs(doc, out, M_src_to_dst=M,
|
|
351
|
+
step_name=f"Rescale ({factor:g}×)")
|
|
352
|
+
|
|
353
|
+
def _apply_geom_rescale_preset_to_doc(self, doc, preset):
|
|
354
|
+
"""
|
|
355
|
+
Accepts flexible presets:
|
|
356
|
+
- dict with 'factor' or 'scale'
|
|
357
|
+
- a lone float/int
|
|
358
|
+
- a '0.5x'/'2x' string
|
|
359
|
+
- (factor, ...) tuple/list
|
|
360
|
+
Falls back to 1.0 if unparsable.
|
|
361
|
+
"""
|
|
362
|
+
factor = None
|
|
363
|
+
try:
|
|
364
|
+
if isinstance(preset, dict):
|
|
365
|
+
factor = preset.get("factor", preset.get("scale", None))
|
|
366
|
+
elif isinstance(preset, (float, int)):
|
|
367
|
+
factor = float(preset)
|
|
368
|
+
elif isinstance(preset, str):
|
|
369
|
+
s = preset.strip().lower().replace("×", "x")
|
|
370
|
+
if s.endswith("x"):
|
|
371
|
+
s = s[:-1]
|
|
372
|
+
factor = float(s)
|
|
373
|
+
elif isinstance(preset, (list, tuple)) and preset:
|
|
374
|
+
factor = float(preset[0])
|
|
375
|
+
except Exception:
|
|
376
|
+
factor = None
|
|
377
|
+
|
|
378
|
+
if factor is None:
|
|
379
|
+
factor = getattr(self, "_last_rescale_factor", 1.0) or 1.0
|
|
380
|
+
|
|
381
|
+
self._apply_geom_rescale_to_doc(doc, factor=factor)
|
|
382
|
+
|
|
383
|
+
def _apply_rescale_preset_to_doc(self, doc, preset: dict):
|
|
384
|
+
"""
|
|
385
|
+
Headless rescale for drag-and-drop / shortcut preset application.
|
|
386
|
+
Expects preset like {"factor": 1.25}.
|
|
387
|
+
"""
|
|
388
|
+
factor = float(preset.get("factor", 1.0))
|
|
389
|
+
if not (0.10 <= factor <= 10.0):
|
|
390
|
+
raise ValueError("Rescale factor must be between 0.10 and 10.0")
|
|
391
|
+
|
|
392
|
+
if getattr(doc, "image", None) is None:
|
|
393
|
+
raise RuntimeError("Target document has no image")
|
|
394
|
+
|
|
395
|
+
src = np.asarray(doc.image, dtype=np.float32, order="C")
|
|
396
|
+
out = rescale_image_numba(src, factor)
|
|
397
|
+
|
|
398
|
+
if hasattr(doc, "set_image"):
|
|
399
|
+
doc.set_image(out, step_name=f"Rescale ×{factor:.2f}")
|
|
400
|
+
elif hasattr(doc, "apply_numpy"):
|
|
401
|
+
doc.apply_numpy(out, step_name=f"Rescale ×{factor:.2f}")
|
|
402
|
+
else:
|
|
403
|
+
doc.image = out
|