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,421 @@
|
|
|
1
|
+
# pro/gui/mixins/mask_mixin.py
|
|
2
|
+
"""
|
|
3
|
+
Mask management mixin for AstroSuiteProMainWindow.
|
|
4
|
+
|
|
5
|
+
This mixin contains all functionality for creating, managing, and
|
|
6
|
+
manipulating masks on document images.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from PyQt6.QtWidgets import QMessageBox
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MaskMixin:
|
|
19
|
+
"""
|
|
20
|
+
Mixin for mask management.
|
|
21
|
+
|
|
22
|
+
Provides methods for creating, applying, inverting, and removing masks
|
|
23
|
+
from document images.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def _show_mask_overlay(self):
|
|
27
|
+
"""Show the mask overlay on the active view."""
|
|
28
|
+
vw = self._active_view()
|
|
29
|
+
if not vw:
|
|
30
|
+
return
|
|
31
|
+
# require an active mask on this doc
|
|
32
|
+
doc = getattr(vw, "document", None)
|
|
33
|
+
has_mask = bool(doc and getattr(doc, "active_mask_id", None))
|
|
34
|
+
if not has_mask:
|
|
35
|
+
QMessageBox.information(self, self.tr("Mask Overlay"), self.tr("No active mask on this image."))
|
|
36
|
+
return
|
|
37
|
+
vw.show_mask_overlay = True
|
|
38
|
+
# ensure visuals are up-to-date immediately
|
|
39
|
+
try:
|
|
40
|
+
vw._set_mask_highlight(True)
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
vw._render(rebuild=True)
|
|
44
|
+
self._refresh_mask_action_states()
|
|
45
|
+
|
|
46
|
+
def _hide_mask_overlay(self):
|
|
47
|
+
"""Hide the mask overlay on the active view."""
|
|
48
|
+
vw = self._active_view()
|
|
49
|
+
if not vw:
|
|
50
|
+
return
|
|
51
|
+
vw.show_mask_overlay = False
|
|
52
|
+
vw._render(rebuild=True)
|
|
53
|
+
self._refresh_mask_action_states()
|
|
54
|
+
|
|
55
|
+
def _invert_mask(self):
|
|
56
|
+
"""Invert the active mask on the current document."""
|
|
57
|
+
doc = self._active_doc()
|
|
58
|
+
if not doc:
|
|
59
|
+
return
|
|
60
|
+
mid = getattr(doc, "active_mask_id", None)
|
|
61
|
+
if not mid:
|
|
62
|
+
return
|
|
63
|
+
layer = (getattr(doc, "masks", {}) or {}).get(mid)
|
|
64
|
+
if layer is None or getattr(layer, "data", None) is None:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
m = np.asarray(layer.data)
|
|
68
|
+
if m.size == 0:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
# invert (preserve dtype)
|
|
72
|
+
if m.dtype.kind in "ui":
|
|
73
|
+
maxv = np.iinfo(m.dtype).max
|
|
74
|
+
layer.data = (maxv - m).astype(m.dtype, copy=False)
|
|
75
|
+
else:
|
|
76
|
+
layer.data = (1.0 - m.astype(np.float32, copy=False)).clip(0.0, 1.0)
|
|
77
|
+
|
|
78
|
+
# notify listeners (triggers ImageSubWindow.render via your existing hookup)
|
|
79
|
+
if hasattr(doc, "changed"):
|
|
80
|
+
doc.changed.emit()
|
|
81
|
+
|
|
82
|
+
# and explicitly refresh the active view overlay right now
|
|
83
|
+
vw = self._active_view()
|
|
84
|
+
if vw and hasattr(vw, "refresh_mask_overlay"):
|
|
85
|
+
vw.refresh_mask_overlay()
|
|
86
|
+
|
|
87
|
+
# keep menu states tidy
|
|
88
|
+
if hasattr(self, "_refresh_mask_action_states"):
|
|
89
|
+
self._refresh_mask_action_states()
|
|
90
|
+
|
|
91
|
+
def _action_create_mask(self):
|
|
92
|
+
"""Create a new mask from the current document."""
|
|
93
|
+
from setiastro.saspro.masks_core import create_mask_and_attach
|
|
94
|
+
|
|
95
|
+
doc = self._current_document()
|
|
96
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
97
|
+
QMessageBox.information(self, self.tr("No image"), self.tr("Open an image first."))
|
|
98
|
+
return
|
|
99
|
+
created = create_mask_and_attach(self, doc)
|
|
100
|
+
# Optional toast/log
|
|
101
|
+
if created and hasattr(self, "_log"):
|
|
102
|
+
self._log("Mask created and set active.")
|
|
103
|
+
|
|
104
|
+
def _list_candidate_mask_sources(self, exclude_doc=None):
|
|
105
|
+
"""Return list of open documents that can serve as mask sources."""
|
|
106
|
+
return [d for d in self._list_open_docs() if d is not exclude_doc]
|
|
107
|
+
|
|
108
|
+
def _prepare_mask_array(self, src_img, target_hw, invert=False, feather_px=0.0):
|
|
109
|
+
"""
|
|
110
|
+
Prepare a mask array from source image.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
src_img: Source image array
|
|
114
|
+
target_hw: Target (height, width) tuple
|
|
115
|
+
invert: Whether to invert the mask
|
|
116
|
+
feather_px: Feather radius in pixels
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Prepared mask as float32 array
|
|
120
|
+
"""
|
|
121
|
+
a = np.asarray(src_img)
|
|
122
|
+
if a.ndim == 3:
|
|
123
|
+
a = (0.2126 * a[..., 0] + 0.7152 * a[..., 1] + 0.0722 * a[..., 2])
|
|
124
|
+
elif a.ndim == 3 and a.shape[2] == 1:
|
|
125
|
+
a = a[..., 0]
|
|
126
|
+
a = a.astype(np.float32, copy=False)
|
|
127
|
+
if a.dtype.kind in "ui":
|
|
128
|
+
a /= float(np.iinfo(a.dtype).max)
|
|
129
|
+
else:
|
|
130
|
+
mx = float(a.max()) if a.size else 1.0
|
|
131
|
+
if mx > 1.0:
|
|
132
|
+
a /= mx
|
|
133
|
+
a = np.clip(a, 0.0, 1.0)
|
|
134
|
+
|
|
135
|
+
th, tw = target_hw
|
|
136
|
+
sh, sw = a.shape[:2]
|
|
137
|
+
if (sh, sw) != (th, tw):
|
|
138
|
+
yi = (np.linspace(0, sh - 1, th)).astype(np.int32)
|
|
139
|
+
xi = (np.linspace(0, sw - 1, tw)).astype(np.int32)
|
|
140
|
+
a = a[yi][:, xi]
|
|
141
|
+
if invert:
|
|
142
|
+
a = 1.0 - a
|
|
143
|
+
if feather_px and feather_px > 0.5:
|
|
144
|
+
k = max(1, min(int(round(feather_px)), 64))
|
|
145
|
+
w = np.ones((k,), dtype=np.float32) / float(k)
|
|
146
|
+
a = np.apply_along_axis(lambda r: np.convolve(r, w, mode='same'), 1, a)
|
|
147
|
+
a = np.apply_along_axis(lambda c: np.convolve(c, w, mode='same'), 0, a)
|
|
148
|
+
a = np.clip(a, 0.0, 1.0)
|
|
149
|
+
return a.astype(np.float32, copy=False)
|
|
150
|
+
|
|
151
|
+
def _attach_mask_to_document(self, target_doc, mask_doc, *, name="Mask", mode="replace", invert=False, feather=0.0):
|
|
152
|
+
"""
|
|
153
|
+
Attach a mask from mask_doc to target_doc.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
target_doc: Document to attach mask to
|
|
157
|
+
mask_doc: Document to use as mask source
|
|
158
|
+
name: Name for the mask layer
|
|
159
|
+
mode: Mask blend mode
|
|
160
|
+
invert: Whether to invert the mask
|
|
161
|
+
feather: Feather radius in pixels
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if successful, False otherwise
|
|
165
|
+
"""
|
|
166
|
+
if getattr(target_doc, "image", None) is None or getattr(mask_doc, "image", None) is None:
|
|
167
|
+
return False
|
|
168
|
+
th, tw = target_doc.image.shape[:2]
|
|
169
|
+
mask_arr = self._prepare_mask_array(mask_doc.image, (th, tw), invert=invert, feather_px=feather)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
from setiastro.saspro.masks_core import MaskLayer
|
|
173
|
+
except Exception:
|
|
174
|
+
from uuid import uuid4
|
|
175
|
+
|
|
176
|
+
class MaskLayer:
|
|
177
|
+
def __init__(self, name, data, mode="replace", opacity=1.0):
|
|
178
|
+
self.id = f"mask-{uuid4().hex[:8]}"
|
|
179
|
+
self.name = name
|
|
180
|
+
self.data = data
|
|
181
|
+
self.mode = mode
|
|
182
|
+
self.opacity = opacity
|
|
183
|
+
|
|
184
|
+
layer = MaskLayer(id=name, name=name, data=mask_arr, mode=mode, opacity=1.0)
|
|
185
|
+
try:
|
|
186
|
+
target_doc.add_mask(layer, make_active=True)
|
|
187
|
+
except Exception:
|
|
188
|
+
if not hasattr(target_doc, "masks"):
|
|
189
|
+
target_doc.masks = {}
|
|
190
|
+
target_doc.masks[layer.id] = layer
|
|
191
|
+
target_doc.active_mask_id = layer.id
|
|
192
|
+
target_doc.changed.emit()
|
|
193
|
+
|
|
194
|
+
md = target_doc.metadata.setdefault("masks_meta", {})
|
|
195
|
+
md[layer.id] = {"name": name, "mode": mode, "invert": bool(invert), "feather": float(feather)}
|
|
196
|
+
target_doc.changed.emit()
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
def _apply_mask_menu(self):
|
|
200
|
+
"""Show dialog to apply a mask from another document."""
|
|
201
|
+
target_doc = self._active_doc()
|
|
202
|
+
if not target_doc:
|
|
203
|
+
QMessageBox.information(self, self.tr("Mask"), self.tr("No active document."))
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
candidates = self._list_candidate_mask_sources(exclude_doc=target_doc)
|
|
207
|
+
if not candidates:
|
|
208
|
+
QMessageBox.information(self, self.tr("Mask"), self.tr("Open another image to use as a mask."))
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# If there are multiple, ask which one to use
|
|
212
|
+
mask_doc = None
|
|
213
|
+
if len(candidates) == 1:
|
|
214
|
+
mask_doc = candidates[0]
|
|
215
|
+
else:
|
|
216
|
+
from PyQt6.QtWidgets import QInputDialog
|
|
217
|
+
names = [f"{i + 1}. {d.display_name()}" for i, d in enumerate(candidates)]
|
|
218
|
+
choice, ok = QInputDialog.getItem(self, self.tr("Choose Mask Image"),
|
|
219
|
+
self.tr("Use this image as mask:"), names, 0, False)
|
|
220
|
+
if not ok:
|
|
221
|
+
return
|
|
222
|
+
idx = names.index(choice)
|
|
223
|
+
mask_doc = candidates[idx]
|
|
224
|
+
|
|
225
|
+
name = mask_doc.display_name() or "Mask"
|
|
226
|
+
ok = self._attach_mask_to_document(target_doc, mask_doc,
|
|
227
|
+
name=name, mode="replace",
|
|
228
|
+
invert=False, feather=0.0)
|
|
229
|
+
if ok and hasattr(self, "_log"):
|
|
230
|
+
self._log(f"Mask '{name}' applied to '{target_doc.display_name()}'")
|
|
231
|
+
|
|
232
|
+
# Force views to update title/overlay immediately
|
|
233
|
+
if ok:
|
|
234
|
+
try:
|
|
235
|
+
target_doc.changed.emit()
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
self._refresh_mask_action_states()
|
|
240
|
+
|
|
241
|
+
def _resolve_mask_source_doc_from_payload(self, payload: dict):
|
|
242
|
+
"""
|
|
243
|
+
Robustly resolve the source document for a mask drop using any of:
|
|
244
|
+
- doc_ptr / mask_doc_ptr (legacy pointer)
|
|
245
|
+
- doc_uid / base_doc_uid
|
|
246
|
+
- file_path
|
|
247
|
+
"""
|
|
248
|
+
# 1) Try pointer first, using the existing helper if present
|
|
249
|
+
ptr = payload.get("doc_ptr") or payload.get("mask_doc_ptr")
|
|
250
|
+
if ptr and hasattr(self, "_doc_by_ptr"):
|
|
251
|
+
try:
|
|
252
|
+
doc = self._doc_by_ptr(ptr)
|
|
253
|
+
if doc is not None:
|
|
254
|
+
return doc
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
|
|
258
|
+
# 2) Fall back to uid-based matching
|
|
259
|
+
uid = payload.get("doc_uid") or payload.get("base_doc_uid")
|
|
260
|
+
file_path = payload.get("file_path")
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
open_docs = self._list_open_docs()
|
|
264
|
+
except Exception:
|
|
265
|
+
open_docs = []
|
|
266
|
+
|
|
267
|
+
for d in open_docs:
|
|
268
|
+
# A) uid match
|
|
269
|
+
if uid and getattr(d, "uid", None) == uid:
|
|
270
|
+
return d
|
|
271
|
+
|
|
272
|
+
if file_path:
|
|
273
|
+
# B) file_path match as last resort
|
|
274
|
+
for d in open_docs:
|
|
275
|
+
meta = getattr(d, "metadata", {}) or {}
|
|
276
|
+
if meta.get("file_path") == file_path:
|
|
277
|
+
return d
|
|
278
|
+
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _remove_mask_menu(self):
|
|
283
|
+
"""Remove the active mask from the current document."""
|
|
284
|
+
doc = self._active_doc()
|
|
285
|
+
if not doc:
|
|
286
|
+
return
|
|
287
|
+
mid = getattr(doc, "active_mask_id", None)
|
|
288
|
+
if not mid:
|
|
289
|
+
QMessageBox.information(self, self.tr("Mask"), self.tr("No active mask to remove."))
|
|
290
|
+
return
|
|
291
|
+
try:
|
|
292
|
+
doc.remove_mask(mid)
|
|
293
|
+
doc.changed.emit()
|
|
294
|
+
if hasattr(self, "_log"):
|
|
295
|
+
self._log(f"Removed active mask from '{doc.display_name()}'")
|
|
296
|
+
except Exception:
|
|
297
|
+
...
|
|
298
|
+
# If overlay was on, hide it now
|
|
299
|
+
vw = self._active_view()
|
|
300
|
+
if vw and getattr(vw, "show_mask_overlay", False):
|
|
301
|
+
vw.show_mask_overlay = False
|
|
302
|
+
vw._render(rebuild=True)
|
|
303
|
+
|
|
304
|
+
self._refresh_mask_action_states()
|
|
305
|
+
|
|
306
|
+
def _handle_mask_drop(self, payload: dict, target_sw):
|
|
307
|
+
print("[MainWindow] _handle_mask_drop payload:", payload)
|
|
308
|
+
"""
|
|
309
|
+
Handle mask drag-and-drop from one document to another.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
payload: Dict with source document info
|
|
313
|
+
target_sw: Target QMdiSubWindow
|
|
314
|
+
"""
|
|
315
|
+
from PyQt6.QtCore import Qt, QTimer
|
|
316
|
+
from PyQt6.QtWidgets import QMessageBox
|
|
317
|
+
from setiastro.saspro.subwindow import ImageSubWindow
|
|
318
|
+
|
|
319
|
+
if target_sw is None:
|
|
320
|
+
# applying a mask requires a target view
|
|
321
|
+
print("[MainWindow] _handle_mask_drop: target_sw is None")
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
# --- 1) Resolve source doc pointer ---------------------------------
|
|
325
|
+
src_ptr = payload.get("mask_doc_ptr") or payload.get("doc_ptr")
|
|
326
|
+
if not src_ptr:
|
|
327
|
+
print("[MainWindow] _handle_mask_drop: missing mask_doc_ptr/doc_ptr")
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
src_doc = None
|
|
331
|
+
src_sw = None
|
|
332
|
+
|
|
333
|
+
# Prefer a doc-manager helper if you ever add one
|
|
334
|
+
dm = getattr(self, "docman", None)
|
|
335
|
+
if dm is not None and hasattr(dm, "doc_for_ptr"):
|
|
336
|
+
try:
|
|
337
|
+
src_doc = dm.doc_for_ptr(src_ptr)
|
|
338
|
+
except Exception:
|
|
339
|
+
src_doc = None
|
|
340
|
+
|
|
341
|
+
# Legacy resolver: walk MDI subwindows and compare id(document)
|
|
342
|
+
if src_doc is None and hasattr(self, "_find_doc_by_id"):
|
|
343
|
+
try:
|
|
344
|
+
src_doc, src_sw = self._find_doc_by_id(src_ptr)
|
|
345
|
+
except Exception:
|
|
346
|
+
src_doc, src_sw = None, None
|
|
347
|
+
|
|
348
|
+
if src_doc is None:
|
|
349
|
+
print(f"[MainWindow] _handle_mask_drop: no src_doc for ptr={src_ptr}")
|
|
350
|
+
QMessageBox.warning(self, self.tr("Mask"), self.tr("Could not resolve mask document."))
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
# --- 2) Resolve target view / doc ----------------------------------
|
|
354
|
+
target_view = target_sw.widget()
|
|
355
|
+
if not isinstance(target_view, ImageSubWindow):
|
|
356
|
+
# In case there’s a wrapper widget
|
|
357
|
+
tv = target_sw.widget()
|
|
358
|
+
if tv is not None:
|
|
359
|
+
target_view = tv.findChild(ImageSubWindow)
|
|
360
|
+
else:
|
|
361
|
+
target_view = None
|
|
362
|
+
|
|
363
|
+
if target_view is None:
|
|
364
|
+
print("[MainWindow] _handle_mask_drop: no target_view resolved")
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
target_doc = getattr(target_view, "document", None)
|
|
368
|
+
if target_doc is None:
|
|
369
|
+
print("[MainWindow] _handle_mask_drop: target_view has no document")
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
# Allow DocProxy, but unwrap if it exposes base_document
|
|
373
|
+
real_target = getattr(target_doc, "base_document", None) or target_doc
|
|
374
|
+
|
|
375
|
+
mode = str(payload.get("mode", "replace"))
|
|
376
|
+
invert = bool(payload.get("invert", False))
|
|
377
|
+
feather = float(payload.get("feather", 0.0))
|
|
378
|
+
name = payload.get("name") or src_doc.display_name() or "Mask"
|
|
379
|
+
|
|
380
|
+
print(f"[MainWindow] _handle_mask_drop: src_doc={src_doc}, target_doc={real_target}, "
|
|
381
|
+
f"mode={mode}, invert={invert}, feather={feather}, name={name!r}")
|
|
382
|
+
|
|
383
|
+
# --- 3) Attach mask using the shared helper -------------------------
|
|
384
|
+
ok = self._attach_mask_to_document(
|
|
385
|
+
real_target,
|
|
386
|
+
src_doc,
|
|
387
|
+
name=name,
|
|
388
|
+
mode=mode,
|
|
389
|
+
invert=invert,
|
|
390
|
+
feather=feather,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
if not ok:
|
|
394
|
+
print("[MainWindow] _handle_mask_drop: _attach_mask_to_document() returned False")
|
|
395
|
+
return
|
|
396
|
+
|
|
397
|
+
if hasattr(self, "_log"):
|
|
398
|
+
try:
|
|
399
|
+
self._log(f"Mask '{name}' applied to '{real_target.display_name()}'")
|
|
400
|
+
except Exception:
|
|
401
|
+
pass
|
|
402
|
+
|
|
403
|
+
if hasattr(real_target, "changed"):
|
|
404
|
+
try:
|
|
405
|
+
real_target.changed.emit()
|
|
406
|
+
except Exception:
|
|
407
|
+
pass
|
|
408
|
+
|
|
409
|
+
# Make the drop target the active subwindow immediately (like before)
|
|
410
|
+
def _activate():
|
|
411
|
+
try:
|
|
412
|
+
self.mdi.setActiveSubWindow(target_sw)
|
|
413
|
+
target_sw.activateWindow()
|
|
414
|
+
target_sw.raise_()
|
|
415
|
+
target_sw.widget().setFocus(Qt.FocusReason.MouseFocusReason)
|
|
416
|
+
except Exception:
|
|
417
|
+
pass
|
|
418
|
+
self._refresh_mask_action_states()
|
|
419
|
+
|
|
420
|
+
_activate()
|
|
421
|
+
QTimer.singleShot(0, _activate)
|