setiastrosuitepro 1.6.5.post3__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.
- 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/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/__init__.py +20 -0
- setiastro/saspro/__main__.py +958 -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 +698 -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 +611 -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 +3149 -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 +983 -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 +8792 -0
- setiastro/saspro/gui/mixins/__init__.py +33 -0
- setiastro/saspro/gui/mixins/dock_mixin.py +375 -0
- setiastro/saspro/gui/mixins/file_mixin.py +450 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +503 -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 +390 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +1619 -0
- setiastro/saspro/gui/mixins/update_mixin.py +323 -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 +2360 -0
- setiastro/saspro/legacy/numba_utils.py +3676 -0
- setiastro/saspro/legacy/xisf.py +1213 -0
- setiastro/saspro/linear_fit.py +537 -0
- setiastro/saspro/live_stacking.py +1854 -0
- setiastro/saspro/log_bus.py +5 -0
- setiastro/saspro/logging_config.py +460 -0
- setiastro/saspro/luminancerecombine.py +510 -0
- setiastro/saspro/main_helpers.py +201 -0
- setiastro/saspro/mask_creation.py +1086 -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 +3909 -0
- setiastro/saspro/mfdeconv_earlystop.py +71 -0
- setiastro/saspro/mfdeconvcudnn.py +3312 -0
- setiastro/saspro/mfdeconvsport.py +2459 -0
- setiastro/saspro/minorbodycatalog.py +567 -0
- setiastro/saspro/morphology.py +407 -0
- setiastro/saspro/multiscale_decomp.py +1747 -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 +1105 -0
- setiastro/saspro/ops/scripts.py +1476 -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 +1105 -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 +446 -0
- setiastro/saspro/resources.py +503 -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 +1611 -0
- setiastro/saspro/sfcc.py +1472 -0
- setiastro/saspro/shortcuts.py +3116 -0
- setiastro/saspro/signature_insert.py +1102 -0
- setiastro/saspro/stacking_suite.py +19066 -0
- setiastro/saspro/star_alignment.py +7380 -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 +3407 -0
- setiastro/saspro/supernovaasteroidhunter.py +1719 -0
- setiastro/saspro/swap_manager.py +134 -0
- setiastro/saspro/torch_backend.py +89 -0
- setiastro/saspro/torch_rejection.py +434 -0
- 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 +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 +513 -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 +991 -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 +263 -0
- setiastro/saspro/widgets/spinboxes.py +290 -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 +1213 -0
- setiastrosuitepro-1.6.5.post3.dist-info/METADATA +278 -0
- setiastrosuitepro-1.6.5.post3.dist-info/RECORD +368 -0
- setiastrosuitepro-1.6.5.post3.dist-info/WHEEL +4 -0
- setiastrosuitepro-1.6.5.post3.dist-info/entry_points.txt +6 -0
- setiastrosuitepro-1.6.5.post3.dist-info/licenses/LICENSE +674 -0
- setiastrosuitepro-1.6.5.post3.dist-info/licenses/license.txt +2580 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
# pro/widgets/image_utils.py
|
|
2
|
+
"""
|
|
3
|
+
Centralized image conversion utilities for Seti Astro Suite Pro.
|
|
4
|
+
|
|
5
|
+
Provides common numpy <-> QImage conversion functions.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from PyQt6.QtGui import QImage, QPixmap
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def ensure_contiguous(arr: np.ndarray) -> np.ndarray:
|
|
14
|
+
"""
|
|
15
|
+
Ensure array is C-contiguous without unnecessary copy.
|
|
16
|
+
|
|
17
|
+
This is an optimization over np.ascontiguousarray()
|
|
18
|
+
which always copies if the array is not contiguous.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
arr: Input array
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
C-contiguous array (same object if already contiguous)
|
|
25
|
+
"""
|
|
26
|
+
if arr is None:
|
|
27
|
+
return arr
|
|
28
|
+
if arr.flags.c_contiguous:
|
|
29
|
+
return arr
|
|
30
|
+
return np.ascontiguousarray(arr)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def numpy_to_qimage(arr: np.ndarray, normalize: bool = True) -> QImage:
|
|
34
|
+
"""
|
|
35
|
+
Convert a numpy array to QImage.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
arr: Image array. Can be:
|
|
39
|
+
- 2D grayscale (H, W)
|
|
40
|
+
- 3D grayscale (H, W, 1)
|
|
41
|
+
- 3D RGB (H, W, 3)
|
|
42
|
+
- float32 [0, 1] or uint8 [0, 255]
|
|
43
|
+
normalize: If True, clip and scale float arrays to [0, 1]
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
QImage in appropriate format
|
|
47
|
+
"""
|
|
48
|
+
if arr is None:
|
|
49
|
+
raise ValueError("Input array is None")
|
|
50
|
+
|
|
51
|
+
arr = ensure_contiguous(arr)
|
|
52
|
+
|
|
53
|
+
# Handle float vs uint8
|
|
54
|
+
if arr.dtype in (np.float32, np.float64):
|
|
55
|
+
if normalize:
|
|
56
|
+
arr = np.clip(arr, 0.0, 1.0)
|
|
57
|
+
arr = (arr * 255).astype(np.uint8)
|
|
58
|
+
elif arr.dtype != np.uint8:
|
|
59
|
+
# Convert other integer types
|
|
60
|
+
arr = arr.astype(np.uint8)
|
|
61
|
+
|
|
62
|
+
# Ensure contiguous
|
|
63
|
+
arr = ensure_contiguous(arr)
|
|
64
|
+
|
|
65
|
+
# Handle dimensions
|
|
66
|
+
if arr.ndim == 2:
|
|
67
|
+
# Grayscale
|
|
68
|
+
h, w = arr.shape
|
|
69
|
+
return QImage(arr.data, w, h, w, QImage.Format.Format_Grayscale8).copy()
|
|
70
|
+
|
|
71
|
+
elif arr.ndim == 3:
|
|
72
|
+
h, w, c = arr.shape
|
|
73
|
+
|
|
74
|
+
if c == 1:
|
|
75
|
+
# Grayscale with channel dim
|
|
76
|
+
arr = arr.squeeze()
|
|
77
|
+
return QImage(arr.data, w, h, w, QImage.Format.Format_Grayscale8).copy()
|
|
78
|
+
|
|
79
|
+
elif c == 3:
|
|
80
|
+
# RGB
|
|
81
|
+
bytes_per_line = 3 * w
|
|
82
|
+
return QImage(arr.data, w, h, bytes_per_line, QImage.Format.Format_RGB888).copy()
|
|
83
|
+
|
|
84
|
+
elif c == 4:
|
|
85
|
+
# RGBA
|
|
86
|
+
bytes_per_line = 4 * w
|
|
87
|
+
return QImage(arr.data, w, h, bytes_per_line, QImage.Format.Format_RGBA8888).copy()
|
|
88
|
+
|
|
89
|
+
else:
|
|
90
|
+
raise ValueError(f"Unsupported number of channels: {c}")
|
|
91
|
+
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(f"Unsupported array dimensions: {arr.ndim}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def numpy_to_qpixmap(arr: np.ndarray, normalize: bool = True) -> QPixmap:
|
|
97
|
+
"""
|
|
98
|
+
Convert a numpy array to QPixmap.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
arr: Image array (see numpy_to_qimage for formats)
|
|
102
|
+
normalize: If True, clip and scale float arrays to [0, 1]
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
QPixmap
|
|
106
|
+
"""
|
|
107
|
+
return QPixmap.fromImage(numpy_to_qimage(arr, normalize))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def float_to_qimage_rgb8(arr: np.ndarray) -> QImage:
|
|
111
|
+
"""
|
|
112
|
+
Convert float32 [0, 1] array to QImage RGB888 format.
|
|
113
|
+
|
|
114
|
+
This is a shared implementation replacing duplicates in:
|
|
115
|
+
- pro/pixelmath.py (_float_to_qimage_rgb8)
|
|
116
|
+
- pro/curve_editor_pro.py (_float_to_qimage_rgb8)
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
arr: float32 array in [0, 1], can be:
|
|
120
|
+
- 2D grayscale (H, W) - expanded to RGB
|
|
121
|
+
- 3D with 1 channel (H, W, 1) - expanded to RGB
|
|
122
|
+
- 3D RGB (H, W, 3)
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
QImage in RGB888 format
|
|
126
|
+
"""
|
|
127
|
+
f = np.asarray(arr, dtype=np.float32)
|
|
128
|
+
if f.ndim == 2:
|
|
129
|
+
f = np.stack([f, f, f], axis=-1)
|
|
130
|
+
elif f.ndim == 3 and f.shape[2] == 1:
|
|
131
|
+
f = np.repeat(f, 3, axis=2)
|
|
132
|
+
|
|
133
|
+
buf8 = (np.clip(f, 0.0, 1.0) * 255.0 + 0.5).astype(np.uint8)
|
|
134
|
+
buf8 = ensure_contiguous(buf8)
|
|
135
|
+
h, w, _ = buf8.shape
|
|
136
|
+
img = QImage(buf8.data, w, h, 3 * w, QImage.Format.Format_RGB888)
|
|
137
|
+
# Keep reference so bytes stay alive
|
|
138
|
+
img._buf = buf8
|
|
139
|
+
return img
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def qimage_to_numpy(qimg: QImage) -> np.ndarray:
|
|
143
|
+
"""
|
|
144
|
+
Convert a QImage to numpy array.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
qimg: QImage to convert
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
numpy array in RGB format (H, W, 3) or grayscale (H, W)
|
|
151
|
+
"""
|
|
152
|
+
# Convert to a standard format
|
|
153
|
+
fmt = qimg.format()
|
|
154
|
+
|
|
155
|
+
if fmt == QImage.Format.Format_Grayscale8:
|
|
156
|
+
w, h = qimg.width(), qimg.height()
|
|
157
|
+
ptr = qimg.bits()
|
|
158
|
+
ptr.setsize(h * w)
|
|
159
|
+
arr = np.frombuffer(ptr, dtype=np.uint8).reshape((h, w))
|
|
160
|
+
return arr.copy()
|
|
161
|
+
|
|
162
|
+
# Convert to RGB888 for other formats
|
|
163
|
+
if fmt != QImage.Format.Format_RGB888:
|
|
164
|
+
qimg = qimg.convertToFormat(QImage.Format.Format_RGB888)
|
|
165
|
+
|
|
166
|
+
w, h = qimg.width(), qimg.height()
|
|
167
|
+
ptr = qimg.bits()
|
|
168
|
+
ptr.setsize(h * w * 3)
|
|
169
|
+
arr = np.frombuffer(ptr, dtype=np.uint8).reshape((h, w, 3))
|
|
170
|
+
return arr.copy()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def create_preview_image(arr: np.ndarray, max_size: int = 1024) -> np.ndarray:
|
|
174
|
+
"""
|
|
175
|
+
Create a preview-sized version of an image.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
arr: Full resolution image array
|
|
179
|
+
max_size: Maximum dimension for preview
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Downscaled image if needed, original otherwise
|
|
183
|
+
"""
|
|
184
|
+
if arr is None:
|
|
185
|
+
return arr
|
|
186
|
+
|
|
187
|
+
h = arr.shape[0]
|
|
188
|
+
w = arr.shape[1]
|
|
189
|
+
|
|
190
|
+
if max(h, w) <= max_size:
|
|
191
|
+
return arr
|
|
192
|
+
|
|
193
|
+
# Calculate scale factor
|
|
194
|
+
scale = max_size / max(h, w)
|
|
195
|
+
new_h = int(h * scale)
|
|
196
|
+
new_w = int(w * scale)
|
|
197
|
+
|
|
198
|
+
# Use simple slicing for speed (subsampling)
|
|
199
|
+
step_h = max(1, h // new_h)
|
|
200
|
+
step_w = max(1, w // new_w)
|
|
201
|
+
|
|
202
|
+
if arr.ndim == 2:
|
|
203
|
+
return arr[::step_h, ::step_w]
|
|
204
|
+
else:
|
|
205
|
+
return arr[::step_h, ::step_w, :]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def normalize_image(arr: np.ndarray, target_max: float = 1.0) -> np.ndarray:
|
|
209
|
+
"""
|
|
210
|
+
Normalize image to [0, target_max] range.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
arr: Image array
|
|
214
|
+
target_max: Maximum value after normalization
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Normalized float32 array
|
|
218
|
+
"""
|
|
219
|
+
arr = np.asarray(arr, dtype=np.float32)
|
|
220
|
+
vmin = np.nanmin(arr)
|
|
221
|
+
vmax = np.nanmax(arr)
|
|
222
|
+
|
|
223
|
+
if vmax > vmin:
|
|
224
|
+
arr = (arr - vmin) / (vmax - vmin) * target_max
|
|
225
|
+
else:
|
|
226
|
+
arr = np.zeros_like(arr)
|
|
227
|
+
|
|
228
|
+
return arr
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
# Shared float normalization (replaces 10+ duplicate implementations)
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
def to_float01(arr: np.ndarray) -> np.ndarray:
|
|
236
|
+
"""
|
|
237
|
+
Convert image to float32 in [0, 1] range.
|
|
238
|
+
|
|
239
|
+
Handles:
|
|
240
|
+
- uint8: divides by 255
|
|
241
|
+
- uint16: divides by 65535
|
|
242
|
+
- float: clips to [0, 1]
|
|
243
|
+
- Already float32 in [0, 1]: returns as-is
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
arr: Input image array
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
float32 array normalized to [0, 1]
|
|
250
|
+
"""
|
|
251
|
+
if arr is None:
|
|
252
|
+
return None
|
|
253
|
+
arr = np.asarray(arr)
|
|
254
|
+
if arr.dtype == np.uint8:
|
|
255
|
+
return arr.astype(np.float32) / 255.0
|
|
256
|
+
elif arr.dtype == np.uint16:
|
|
257
|
+
return arr.astype(np.float32) / 65535.0
|
|
258
|
+
else:
|
|
259
|
+
arr = arr.astype(np.float32, copy=False)
|
|
260
|
+
if arr.max() > 1.0 or arr.min() < 0.0:
|
|
261
|
+
return np.clip(arr, 0.0, 1.0)
|
|
262
|
+
return arr
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def to_float01_strict(arr: np.ndarray) -> np.ndarray:
|
|
266
|
+
"""
|
|
267
|
+
Strictly convert image to float32 in [0, 1] with explicit scaling.
|
|
268
|
+
|
|
269
|
+
Always clips output to ensure [0, 1] range.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
arr: Input image array
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
float32 array strictly in [0, 1]
|
|
276
|
+
"""
|
|
277
|
+
if arr is None:
|
|
278
|
+
return None
|
|
279
|
+
arr = np.asarray(arr)
|
|
280
|
+
if arr.dtype == np.uint8:
|
|
281
|
+
return arr.astype(np.float32) / 255.0
|
|
282
|
+
elif arr.dtype == np.uint16:
|
|
283
|
+
return arr.astype(np.float32) / 65535.0
|
|
284
|
+
else:
|
|
285
|
+
return np.clip(arr.astype(np.float32), 0.0, 1.0)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def ensure_rgb(img: np.ndarray) -> np.ndarray:
|
|
289
|
+
"""
|
|
290
|
+
Ensure image is 3-channel RGB format.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
img: Input image (grayscale or RGB)
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
3-channel RGB array (H, W, 3)
|
|
297
|
+
"""
|
|
298
|
+
if img is None:
|
|
299
|
+
return None
|
|
300
|
+
img = np.asarray(img)
|
|
301
|
+
if img.ndim == 2:
|
|
302
|
+
return np.stack([img, img, img], axis=-1)
|
|
303
|
+
elif img.ndim == 3 and img.shape[2] == 1:
|
|
304
|
+
return np.concatenate([img, img, img], axis=-1)
|
|
305
|
+
elif img.ndim == 3 and img.shape[2] == 3:
|
|
306
|
+
return img
|
|
307
|
+
elif img.ndim == 3 and img.shape[2] == 4:
|
|
308
|
+
return img[..., :3] # Drop alpha
|
|
309
|
+
else:
|
|
310
|
+
raise ValueError(f"Cannot convert shape {img.shape} to RGB")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def ensure_grayscale(img: np.ndarray) -> np.ndarray:
|
|
314
|
+
"""
|
|
315
|
+
Ensure image is grayscale format.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
img: Input image (grayscale or RGB)
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
2D grayscale array (H, W)
|
|
322
|
+
"""
|
|
323
|
+
if img is None:
|
|
324
|
+
return None
|
|
325
|
+
img = np.asarray(img)
|
|
326
|
+
if img.ndim == 2:
|
|
327
|
+
return img
|
|
328
|
+
elif img.ndim == 3 and img.shape[2] == 1:
|
|
329
|
+
return img[..., 0]
|
|
330
|
+
elif img.ndim == 3 and img.shape[2] in (3, 4):
|
|
331
|
+
# Luminance weights: 0.2126 R + 0.7152 G + 0.0722 B
|
|
332
|
+
return (0.2126 * img[..., 0] + 0.7152 * img[..., 1] + 0.0722 * img[..., 2]).astype(img.dtype)
|
|
333
|
+
else:
|
|
334
|
+
raise ValueError(f"Cannot convert shape {img.shape} to grayscale")
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# ---------------------------------------------------------------------------
|
|
338
|
+
# Mask extraction helper (replaces 4+ duplicate implementations)
|
|
339
|
+
# ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
import cv2 as _cv2
|
|
343
|
+
except ImportError:
|
|
344
|
+
_cv2 = None
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# ---------------------------------------------------------------------------
|
|
348
|
+
# Resize utilities (replaces 4+ duplicate implementations)
|
|
349
|
+
# ---------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
def nearest_resize_2d(m: np.ndarray, H: int, W: int) -> np.ndarray:
|
|
352
|
+
"""
|
|
353
|
+
Resize a 2D array to (H, W) using nearest neighbor interpolation.
|
|
354
|
+
|
|
355
|
+
This is a shared implementation replacing duplicates in:
|
|
356
|
+
- pro/clahe.py
|
|
357
|
+
- pro/morphology.py
|
|
358
|
+
- pro/pixelmath.py
|
|
359
|
+
- pro/stacking_suite.py
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
m: 2D input array
|
|
363
|
+
H: Target height
|
|
364
|
+
W: Target width
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Resized float32 array (H, W)
|
|
368
|
+
"""
|
|
369
|
+
m = np.asarray(m, dtype=np.float32)
|
|
370
|
+
if m.shape == (H, W):
|
|
371
|
+
return m
|
|
372
|
+
if _cv2 is not None:
|
|
373
|
+
try:
|
|
374
|
+
return _cv2.resize(m, (W, H), interpolation=_cv2.INTER_NEAREST)
|
|
375
|
+
except Exception:
|
|
376
|
+
pass
|
|
377
|
+
# Fallback without cv2
|
|
378
|
+
yi = np.linspace(0, m.shape[0] - 1, H).astype(np.int32)
|
|
379
|
+
xi = np.linspace(0, m.shape[1] - 1, W).astype(np.int32)
|
|
380
|
+
return m[yi][:, xi].astype(np.float32, copy=False)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def extract_mask_resized(doc, H: int, W: int) -> np.ndarray | None:
|
|
384
|
+
"""
|
|
385
|
+
Extract active mask from document and resize to (H, W).
|
|
386
|
+
|
|
387
|
+
This is a shared implementation replacing duplicates in:
|
|
388
|
+
- pro/clahe.py (_get_active_mask_resized)
|
|
389
|
+
- pro/morphology.py (_get_active_mask_resized)
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
doc: Document object with active_mask_id and masks attributes
|
|
393
|
+
H: Target height
|
|
394
|
+
W: Target width
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Resized mask (H, W) float32 in [0, 1], or None if not found
|
|
398
|
+
"""
|
|
399
|
+
if doc is None:
|
|
400
|
+
return None
|
|
401
|
+
mid = getattr(doc, "active_mask_id", None)
|
|
402
|
+
if not mid:
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
masks = getattr(doc, "masks", {}) or {}
|
|
406
|
+
layer = masks.get(mid)
|
|
407
|
+
if layer is None:
|
|
408
|
+
return None
|
|
409
|
+
|
|
410
|
+
# Extract data from layer (object, dict, or raw ndarray)
|
|
411
|
+
data = None
|
|
412
|
+
for attr in ("data", "mask", "image", "array"):
|
|
413
|
+
if hasattr(layer, attr):
|
|
414
|
+
val = getattr(layer, attr)
|
|
415
|
+
if val is not None:
|
|
416
|
+
data = val
|
|
417
|
+
break
|
|
418
|
+
if data is None and isinstance(layer, dict):
|
|
419
|
+
for key in ("data", "mask", "image", "array"):
|
|
420
|
+
if key in layer and layer[key] is not None:
|
|
421
|
+
data = layer[key]
|
|
422
|
+
break
|
|
423
|
+
if data is None and isinstance(layer, np.ndarray):
|
|
424
|
+
data = layer
|
|
425
|
+
if data is None:
|
|
426
|
+
return None
|
|
427
|
+
|
|
428
|
+
m = np.asarray(data)
|
|
429
|
+
if m.ndim == 3: # collapse RGB(A) → gray
|
|
430
|
+
m = m.mean(axis=2)
|
|
431
|
+
m = m.astype(np.float32, copy=False)
|
|
432
|
+
|
|
433
|
+
# Normalize to [0, 1]
|
|
434
|
+
mx = float(m.max()) if m.size else 1.0
|
|
435
|
+
if mx > 1.0:
|
|
436
|
+
m = m / mx
|
|
437
|
+
m = np.clip(m, 0.0, 1.0)
|
|
438
|
+
|
|
439
|
+
return nearest_resize_2d(m, H, W)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def blend_with_mask(base: np.ndarray, out: np.ndarray, mask: np.ndarray) -> np.ndarray:
|
|
443
|
+
"""
|
|
444
|
+
Blend base and out arrays using mask weights.
|
|
445
|
+
|
|
446
|
+
result = base * (1 - mask) + out * mask
|
|
447
|
+
|
|
448
|
+
This is a shared implementation replacing duplicates in:
|
|
449
|
+
- pro/morphology.py (_blend_with_mask)
|
|
450
|
+
- pro/ghs_preset.py (_blend_with_mask)
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
base: Original image (2D or 3D)
|
|
454
|
+
out: Processed image (2D or 3D)
|
|
455
|
+
mask: 2D mask in [0, 1] range
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
Blended image in [0, 1]
|
|
459
|
+
"""
|
|
460
|
+
base = np.asarray(base, dtype=np.float32)
|
|
461
|
+
out = np.asarray(out, dtype=np.float32)
|
|
462
|
+
mask = np.clip(np.asarray(mask, dtype=np.float32), 0.0, 1.0)
|
|
463
|
+
|
|
464
|
+
if out.ndim == 3:
|
|
465
|
+
# Ensure base is 3D
|
|
466
|
+
if base.ndim == 2:
|
|
467
|
+
base = base[:, :, None].repeat(out.shape[2], axis=2)
|
|
468
|
+
elif base.ndim == 3 and base.shape[2] == 1:
|
|
469
|
+
base = base.repeat(out.shape[2], axis=2)
|
|
470
|
+
# Expand mask to 3D
|
|
471
|
+
M = mask[:, :, None].repeat(out.shape[2], axis=2)
|
|
472
|
+
return np.clip(base * (1.0 - M) + out * M, 0.0, 1.0)
|
|
473
|
+
|
|
474
|
+
# 2D output
|
|
475
|
+
if base.ndim == 3 and base.shape[2] == 1:
|
|
476
|
+
base = base.squeeze(axis=2)
|
|
477
|
+
return np.clip(base * (1.0 - mask) + out * mask, 0.0, 1.0)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
# ---------------------------------------------------------------------------
|
|
481
|
+
# Mask extraction helper (replaces 4+ duplicate implementations)
|
|
482
|
+
# ---------------------------------------------------------------------------
|
|
483
|
+
|
|
484
|
+
def extract_mask_from_document(doc) -> np.ndarray | None:
|
|
485
|
+
"""
|
|
486
|
+
Extract active mask (H, W) float32 in [0, 1] from a document.
|
|
487
|
+
|
|
488
|
+
This is a shared implementation replacing duplicates in:
|
|
489
|
+
- pro/add_stars.py
|
|
490
|
+
- pro/remove_stars.py
|
|
491
|
+
- pro/remove_green.py
|
|
492
|
+
- pro/image_combine.py
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
doc: Document object with active_mask_id and masks attributes
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
Mask array (H, W) float32 in [0, 1], or None if not found
|
|
499
|
+
"""
|
|
500
|
+
try:
|
|
501
|
+
mid = getattr(doc, "active_mask_id", None)
|
|
502
|
+
if not mid:
|
|
503
|
+
return None
|
|
504
|
+
masks = getattr(doc, "masks", {}) or {}
|
|
505
|
+
layer = masks.get(mid)
|
|
506
|
+
data = getattr(layer, "data", None) if layer is not None else None
|
|
507
|
+
if data is None:
|
|
508
|
+
return None
|
|
509
|
+
a = np.asarray(data)
|
|
510
|
+
if a.ndim == 3:
|
|
511
|
+
if _cv2 is not None:
|
|
512
|
+
a = _cv2.cvtColor(a, _cv2.COLOR_BGR2GRAY)
|
|
513
|
+
else:
|
|
514
|
+
a = a.mean(axis=2)
|
|
515
|
+
a = a.astype(np.float32, copy=False)
|
|
516
|
+
return np.clip(a, 0.0, 1.0)
|
|
517
|
+
except Exception:
|
|
518
|
+
return None
|