setiastrosuitepro 1.6.7__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/acv_icon.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/first_quarter.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/full_moon.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/last_quarter.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/new_moon.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/waning_crescent_1.png +0 -0
- setiastro/images/waning_crescent_2.png +0 -0
- setiastro/images/waning_crescent_3.png +0 -0
- setiastro/images/waning_crescent_4.png +0 -0
- setiastro/images/waning_crescent_5.png +0 -0
- setiastro/images/waning_gibbous_1.png +0 -0
- setiastro/images/waning_gibbous_2.png +0 -0
- setiastro/images/waning_gibbous_3.png +0 -0
- setiastro/images/waning_gibbous_4.png +0 -0
- setiastro/images/waning_gibbous_5.png +0 -0
- setiastro/images/waxing_crescent_1.png +0 -0
- setiastro/images/waxing_crescent_2.png +0 -0
- setiastro/images/waxing_crescent_3.png +0 -0
- setiastro/images/waxing_crescent_4.png +0 -0
- setiastro/images/waxing_crescent_5.png +0 -0
- setiastro/images/waxing_gibbous_1.png +0 -0
- setiastro/images/waxing_gibbous_2.png +0 -0
- setiastro/images/waxing_gibbous_3.png +0 -0
- setiastro/images/waxing_gibbous_4.png +0 -0
- setiastro/images/waxing_gibbous_5.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 +128 -0
- setiastro/saspro/__init__.py +20 -0
- setiastro/saspro/__main__.py +964 -0
- setiastro/saspro/_generated/__init__.py +7 -0
- setiastro/saspro/_generated/build_info.py +3 -0
- setiastro/saspro/abe.py +1379 -0
- setiastro/saspro/abe_preset.py +196 -0
- setiastro/saspro/aberration_ai.py +910 -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/acv_exporter.py +379 -0
- setiastro/saspro/add_stars.py +627 -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 +639 -0
- setiastro/saspro/batch_convert.py +328 -0
- setiastro/saspro/batch_renamer.py +522 -0
- setiastro/saspro/blemish_blaster.py +494 -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 +371 -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 +1620 -0
- setiastro/saspro/convo.py +1403 -0
- setiastro/saspro/convo_preset.py +414 -0
- setiastro/saspro/copyastro.py +190 -0
- setiastro/saspro/cosmicclarity.py +1593 -0
- setiastro/saspro/cosmicclarity_preset.py +407 -0
- setiastro/saspro/crop_dialog_pro.py +1005 -0
- setiastro/saspro/crop_preset.py +189 -0
- setiastro/saspro/curve_editor_pro.py +2608 -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 +2727 -0
- setiastro/saspro/exoplanet_detector.py +2258 -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 +1352 -0
- setiastro/saspro/function_bundle.py +1596 -0
- setiastro/saspro/generate_translations.py +3092 -0
- setiastro/saspro/ghs_dialog_pro.py +728 -0
- setiastro/saspro/ghs_preset.py +284 -0
- setiastro/saspro/graxpert.py +638 -0
- setiastro/saspro/graxpert_preset.py +287 -0
- setiastro/saspro/gui/__init__.py +0 -0
- setiastro/saspro/gui/main_window.py +8928 -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 +391 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +1824 -0
- setiastro/saspro/gui/mixins/update_mixin.py +323 -0
- setiastro/saspro/gui/mixins/view_mixin.py +477 -0
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +492 -0
- setiastro/saspro/header_viewer.py +448 -0
- setiastro/saspro/headless_utils.py +88 -0
- setiastro/saspro/histogram.py +760 -0
- setiastro/saspro/history_explorer.py +941 -0
- setiastro/saspro/i18n.py +168 -0
- setiastro/saspro/image_combine.py +421 -0
- setiastro/saspro/image_peeker_pro.py +1608 -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 +1186 -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 +1090 -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 +411 -0
- setiastro/saspro/multiscale_decomp.py +1751 -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 +2480 -0
- setiastro/saspro/project_io.py +797 -0
- setiastro/saspro/psf_utils.py +136 -0
- setiastro/saspro/psf_viewer.py +631 -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 +570 -0
- setiastro/saspro/rgb_combination.py +208 -0
- setiastro/saspro/rgb_extract.py +19 -0
- setiastro/saspro/rgbalign.py +727 -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 +1614 -0
- setiastro/saspro/sfcc.py +1530 -0
- setiastro/saspro/shortcuts.py +3125 -0
- setiastro/saspro/signature_insert.py +1106 -0
- setiastro/saspro/stacking_suite.py +19069 -0
- setiastro/saspro/star_alignment.py +7383 -0
- setiastro/saspro/star_alignment_preset.py +329 -0
- setiastro/saspro/star_metrics.py +49 -0
- setiastro/saspro/star_spikes.py +769 -0
- setiastro/saspro/star_stretch.py +542 -0
- setiastro/saspro/stat_stretch.py +554 -0
- setiastro/saspro/status_log_dock.py +78 -0
- setiastro/saspro/subwindow.py +3523 -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 +648 -0
- setiastro/saspro/wavescale_hdr_preset.py +101 -0
- setiastro/saspro/wavescalede.py +683 -0
- setiastro/saspro/wavescalede_preset.py +230 -0
- setiastro/saspro/wcs_update.py +374 -0
- setiastro/saspro/whitebalance.py +540 -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 +313 -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 +7367 -0
- setiastro/saspro/wims.py +588 -0
- setiastro/saspro/window_shelf.py +185 -0
- setiastro/saspro/xisf.py +1213 -0
- setiastrosuitepro-1.6.7.dist-info/METADATA +279 -0
- setiastrosuitepro-1.6.7.dist-info/RECORD +394 -0
- setiastrosuitepro-1.6.7.dist-info/WHEEL +4 -0
- setiastrosuitepro-1.6.7.dist-info/entry_points.txt +6 -0
- setiastrosuitepro-1.6.7.dist-info/licenses/LICENSE +674 -0
- setiastrosuitepro-1.6.7.dist-info/licenses/license.txt +2580 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
#src/setiastro/saspro/luminancerecombine.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import numpy as np
|
|
4
|
+
import cv2
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from setiastro.saspro.headless_utils import normalize_headless_main, unwrap_docproxy
|
|
8
|
+
from setiastro.saspro.ops.command_runner import CommandError
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
# Shared utilities
|
|
12
|
+
from setiastro.saspro.widgets.image_utils import (
|
|
13
|
+
extract_mask_from_document as _active_mask_array_from_doc,
|
|
14
|
+
to_float01_strict as _to_float01_strict,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_LUMA_REC709 = np.array([0.2126, 0.7152, 0.0722], dtype=np.float32)
|
|
18
|
+
_LUMA_REC601 = np.array([0.2990, 0.5870, 0.1140], dtype=np.float32)
|
|
19
|
+
_LUMA_REC2020 = np.array([0.2627, 0.6780, 0.0593], dtype=np.float32)
|
|
20
|
+
|
|
21
|
+
# ---- Luma profiles (UI selectable) ----
|
|
22
|
+
# Key = what the UI stores in self.luma_method / preset["mode"]
|
|
23
|
+
# weights must be length-3 (RGB), assumed linear
|
|
24
|
+
LUMA_PROFILES: dict[str, dict] = {
|
|
25
|
+
# --- Standard ---
|
|
26
|
+
"rec709": {"method": "rec709", "weights": _LUMA_REC709, "category": "Standard", "description": "Broadband RGB (Rec.709)"},
|
|
27
|
+
"rec601": {"method": "rec601", "weights": _LUMA_REC601, "category": "Standard", "description": "Rec.601"},
|
|
28
|
+
"rec2020": {"method": "rec2020", "weights": _LUMA_REC2020, "category": "Standard", "description": "Rec.2020"},
|
|
29
|
+
"equal": {"method": "equal", "weights": None, "category": "Standard", "description": "Equal RGB"},
|
|
30
|
+
"max": {"method": "max", "weights": None, "category": "Standard", "description": "Max (Narrowband mappings)"},
|
|
31
|
+
"median": {"method": "median", "weights": None, "category": "Standard", "description": "Median RGB"},
|
|
32
|
+
"snr": {"method": "snr", "weights": None, "category": "Standard", "description": "Unequal Noise (SNR)"},
|
|
33
|
+
|
|
34
|
+
# --- Sensors (examples — paste your whole list here) ---
|
|
35
|
+
"sensor:Sony IMX571 (ASI2600/QHY268)": {
|
|
36
|
+
"method": "custom",
|
|
37
|
+
"weights": np.array([0.2944, 0.5021, 0.2035], dtype=np.float32),
|
|
38
|
+
"category": "Sensors/Sony Modern BSI",
|
|
39
|
+
"description": "Sony IMX571 26MP APS-C BSI (STARVIS)",
|
|
40
|
+
"info": "Gold standard APS-C. Excellent balance for broadband.",
|
|
41
|
+
},
|
|
42
|
+
"sensor:Sony IMX533 (ASI533)": {
|
|
43
|
+
"method": "custom",
|
|
44
|
+
"weights": np.array([0.2910, 0.5072, 0.2018], dtype=np.float32),
|
|
45
|
+
"category": "Sensors/Sony Modern BSI",
|
|
46
|
+
"description": "Sony IMX533 9MP 1\" Square BSI (STARVIS)",
|
|
47
|
+
"info": "Popular square format. Very low noise.",
|
|
48
|
+
},
|
|
49
|
+
"sensor:Sony IMX455 (ASI6200/QHY600)": {
|
|
50
|
+
"weights": (0.2987, 0.5001, 0.2013),
|
|
51
|
+
"description": "Sony IMX455 61MP Full Frame BSI (STARVIS)",
|
|
52
|
+
"info": "Full frame reference sensor.",
|
|
53
|
+
"category": "Sony / Modern BSI",
|
|
54
|
+
},
|
|
55
|
+
"sensor:Sony IMX294 (ASI294)": {
|
|
56
|
+
"weights": (0.3068, 0.5008, 0.1925),
|
|
57
|
+
"description": "Sony IMX294 11.7MP 4/3\" BSI",
|
|
58
|
+
"info": "High sensitivity 4/3 format.",
|
|
59
|
+
"category": "Sony / Modern BSI",
|
|
60
|
+
},
|
|
61
|
+
"sensor:Sony IMX183 (ASI183)": {
|
|
62
|
+
"weights": (0.2967, 0.4983, 0.2050),
|
|
63
|
+
"description": "Sony IMX183 20MP 1\" BSI",
|
|
64
|
+
"info": "High resolution 1-inch sensor.",
|
|
65
|
+
"category": "Sony / Modern BSI",
|
|
66
|
+
},
|
|
67
|
+
"sensor:Sony IMX178 (ASI178)": {
|
|
68
|
+
"weights": (0.2346, 0.5206, 0.2448),
|
|
69
|
+
"description": "Sony IMX178 6.4MP 1/1.8\" BSI",
|
|
70
|
+
"info": "High resolution entry-level sensor.",
|
|
71
|
+
"category": "Sony / Modern BSI",
|
|
72
|
+
},
|
|
73
|
+
"sensor:Sony IMX224 (ASI224)": {
|
|
74
|
+
"weights": (0.3402, 0.4765, 0.1833),
|
|
75
|
+
"description": "Sony IMX224 1.27MP 1/3\" BSI",
|
|
76
|
+
"info": "Classic planetary sensor. High Red response.",
|
|
77
|
+
"category": "Sony / Modern BSI",
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
# --- SONY STARVIS 2 (NIR Optimized) ---
|
|
81
|
+
"sensor:Sony IMX585 (ASI585) - STARVIS 2": {
|
|
82
|
+
"weights": (0.3431, 0.4822, 0.1747),
|
|
83
|
+
"description": "Sony IMX585 8.3MP 1/1.2\" BSI (STARVIS 2)",
|
|
84
|
+
"info": "NIR optimized. Excellent for H-Alpha/Narrowband.",
|
|
85
|
+
"category": "Sony / STARVIS 2",
|
|
86
|
+
},
|
|
87
|
+
"sensor:Sony IMX662 (ASI662) - STARVIS 2": {
|
|
88
|
+
"weights": (0.3430, 0.4821, 0.1749),
|
|
89
|
+
"description": "Sony IMX662 2.1MP 1/2.8\" BSI (STARVIS 2)",
|
|
90
|
+
"info": "Planetary/Guiding. High Red/NIR sensitivity.",
|
|
91
|
+
"category": "Sony / STARVIS 2",
|
|
92
|
+
},
|
|
93
|
+
"sensor:Sony IMX678/715 - STARVIS 2": {
|
|
94
|
+
"weights": (0.3426, 0.4825, 0.1750),
|
|
95
|
+
"description": "Sony IMX678/715 BSI (STARVIS 2)",
|
|
96
|
+
"info": "High resolution planetary/security sensors.",
|
|
97
|
+
"category": "Sony / STARVIS 2",
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
# --- PANASONIC / OTHERS ---
|
|
101
|
+
"sensor:Panasonic MN34230 (ASI1600/QHY163)": {
|
|
102
|
+
"weights": (0.2650, 0.5250, 0.2100),
|
|
103
|
+
"description": "Panasonic MN34230 4/3\" CMOS",
|
|
104
|
+
"info": "Classic Mono/OSC sensor. Optimized weights.",
|
|
105
|
+
"category": "Panasonic",
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
# --- CANON DSLR (Averaged Profiles) ---
|
|
109
|
+
"sensor:Canon EOS (Modern - 60D/6D/R)": {
|
|
110
|
+
"weights": (0.2550, 0.5250, 0.2200),
|
|
111
|
+
"description": "Canon CMOS Profile (Modern)",
|
|
112
|
+
"info": "Balanced profile for most Canon EOS cameras (60D, 6D, 5D, R-series).",
|
|
113
|
+
"category": "Canon",
|
|
114
|
+
},
|
|
115
|
+
"sensor:Canon EOS (Legacy - 300D/40D)": {
|
|
116
|
+
"weights": (0.2400, 0.5400, 0.2200),
|
|
117
|
+
"description": "Canon CMOS Profile (Legacy)",
|
|
118
|
+
"info": "For older Canon models (Digic 2/3 era).",
|
|
119
|
+
"category": "Canon",
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
# --- NIKON DSLR (Averaged Profiles) ---
|
|
123
|
+
"sensor:Nikon DSLR (Modern - D5300/D850)": {
|
|
124
|
+
"weights": (0.2600, 0.5100, 0.2300),
|
|
125
|
+
"description": "Nikon CMOS Profile (Modern)",
|
|
126
|
+
"info": "Balanced profile for Nikon Expeed 4+ cameras.",
|
|
127
|
+
"category": "Nikon",
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
# --- SMART TELESCOPES ---
|
|
131
|
+
"sensor:ZWO Seestar S50": {
|
|
132
|
+
"weights": (0.3333, 0.4866, 0.1801),
|
|
133
|
+
"description": "ZWO Seestar S50 (IMX462)",
|
|
134
|
+
"info": "Specific profile for Seestar S50 smart telescope.",
|
|
135
|
+
"category": "Smart Telescopes",
|
|
136
|
+
},
|
|
137
|
+
"sensor:ZWO Seestar S30": {
|
|
138
|
+
"weights": (0.2928, 0.5053, 0.2019),
|
|
139
|
+
"description": "ZWO Seestar S30",
|
|
140
|
+
"info": "Specific profile for Seestar S30 smart telescope.",
|
|
141
|
+
"category": "Smart Telescopes",
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ---------- helpers ----------
|
|
147
|
+
def resolve_luma_profile_weights(mode: str | None):
|
|
148
|
+
"""
|
|
149
|
+
Returns (resolved_method, weights_or_None, profile_name_or_None)
|
|
150
|
+
|
|
151
|
+
- Standard modes return (mode, None or standard weights, None)
|
|
152
|
+
- Sensor profiles return ("custom", weights, <profile display name>)
|
|
153
|
+
"""
|
|
154
|
+
if mode is None:
|
|
155
|
+
mode = "rec709"
|
|
156
|
+
key = str(mode).strip()
|
|
157
|
+
|
|
158
|
+
# common aliases
|
|
159
|
+
alias = {
|
|
160
|
+
"rec.709": "rec709",
|
|
161
|
+
"rec-709": "rec709",
|
|
162
|
+
"rgb": "rec709",
|
|
163
|
+
"k": "rec709",
|
|
164
|
+
"rec.601": "rec601",
|
|
165
|
+
"rec-601": "rec601",
|
|
166
|
+
"rec.2020": "rec2020",
|
|
167
|
+
"rec-2020": "rec2020",
|
|
168
|
+
"nb_max": "max",
|
|
169
|
+
"narrowband": "max",
|
|
170
|
+
"snr_unequal": "snr",
|
|
171
|
+
"unequal_noise": "snr",
|
|
172
|
+
}
|
|
173
|
+
key = alias.get(key.lower(), key)
|
|
174
|
+
|
|
175
|
+
prof = LUMA_PROFILES.get(key)
|
|
176
|
+
if not prof:
|
|
177
|
+
# fallback
|
|
178
|
+
return ("rec709", _LUMA_REC709, None)
|
|
179
|
+
|
|
180
|
+
method = str(prof.get("method", "rec709")).strip().lower()
|
|
181
|
+
w = prof.get("weights", None)
|
|
182
|
+
if w is not None:
|
|
183
|
+
w = np.asarray(w, dtype=np.float32)
|
|
184
|
+
|
|
185
|
+
if key.startswith("sensor:"):
|
|
186
|
+
# Use "custom" path in compute_luminance by passing weights
|
|
187
|
+
# We'll return resolved_method="rec709" (ignored) and weights=w
|
|
188
|
+
# BUT to keep your API simple: return ("rec709", w, profile_name)
|
|
189
|
+
profile_name = key.split("sensor:", 1)[1].strip()
|
|
190
|
+
return ("rec709", w, profile_name)
|
|
191
|
+
|
|
192
|
+
# Standard modes
|
|
193
|
+
return (key, w, None)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _estimate_noise_sigma_per_channel(img01: np.ndarray) -> np.ndarray:
|
|
197
|
+
# unchanged (but call with strict input)
|
|
198
|
+
a = img01
|
|
199
|
+
if a.ndim == 2:
|
|
200
|
+
a = a[..., None]
|
|
201
|
+
a = a[::4, ::4, :].astype(np.float32, copy=False)
|
|
202
|
+
med = np.median(a, axis=(0,1))
|
|
203
|
+
mad = np.median(np.abs(a - med), axis=(0,1))
|
|
204
|
+
sigma = 1.4826 * mad
|
|
205
|
+
sigma[sigma <= 1e-12] = 1e-12
|
|
206
|
+
return sigma.astype(np.float32)
|
|
207
|
+
|
|
208
|
+
# ---------- luminance compute (linear) ----------
|
|
209
|
+
|
|
210
|
+
def compute_luminance(
|
|
211
|
+
img: np.ndarray,
|
|
212
|
+
method: str | None = "rec709",
|
|
213
|
+
weights: Optional[np.ndarray] = None,
|
|
214
|
+
noise_sigma: Optional[np.ndarray] = None,
|
|
215
|
+
normalize_weights: bool = True
|
|
216
|
+
) -> np.ndarray:
|
|
217
|
+
"""
|
|
218
|
+
Returns 2-D linear luminance Y in [0,1] (float32).
|
|
219
|
+
No per-image normalization. If custom `weights` are supplied and
|
|
220
|
+
`normalize_weights=False`, their absolute sum is respected.
|
|
221
|
+
"""
|
|
222
|
+
f = _to_float01_strict(img)
|
|
223
|
+
|
|
224
|
+
if f.ndim == 2:
|
|
225
|
+
return np.ascontiguousarray(f.astype(np.float32, copy=False))
|
|
226
|
+
if f.ndim != 3:
|
|
227
|
+
raise ValueError("compute_luminance: expected 2-D or 3-D array.")
|
|
228
|
+
|
|
229
|
+
H, W, C = f.shape
|
|
230
|
+
if C == 1:
|
|
231
|
+
return np.ascontiguousarray(f[..., 0].astype(np.float32, copy=False))
|
|
232
|
+
|
|
233
|
+
if weights is not None:
|
|
234
|
+
w = np.asarray(weights, dtype=np.float32)
|
|
235
|
+
if w.ndim != 1 or w.size not in (C, 3):
|
|
236
|
+
raise ValueError("weights must be 1-D with length equal to channel count or 3.")
|
|
237
|
+
if normalize_weights:
|
|
238
|
+
s = float(w.sum())
|
|
239
|
+
if s != 0.0:
|
|
240
|
+
w = w / s
|
|
241
|
+
useC = w.size
|
|
242
|
+
lum = np.tensordot(f[..., :useC], w, axes=([2], [0]))
|
|
243
|
+
elif method == "equal":
|
|
244
|
+
lum = f[..., :3].mean(axis=2)
|
|
245
|
+
elif method == "snr":
|
|
246
|
+
if noise_sigma is None:
|
|
247
|
+
raise ValueError("snr method requires noise_sigma per channel.")
|
|
248
|
+
ns = np.asarray(noise_sigma, dtype=np.float32)
|
|
249
|
+
if ns.ndim != 1 or ns.size not in (C, 3):
|
|
250
|
+
raise ValueError("noise_sigma must be 1-D with length equal to channel count or 3.")
|
|
251
|
+
useC = ns.size
|
|
252
|
+
w = 1.0 / (ns[:useC]**2 + 1e-12)
|
|
253
|
+
w = w / w.sum()
|
|
254
|
+
lum = np.tensordot(f[..., :useC], w, axes=([2],[0]))
|
|
255
|
+
elif method == "max":
|
|
256
|
+
lum = f.max(axis=2)
|
|
257
|
+
elif method == "median":
|
|
258
|
+
lum = np.median(f, axis=2)
|
|
259
|
+
elif method == "rec601":
|
|
260
|
+
lum = np.tensordot(f[..., :3], _LUMA_REC601, axes=([2],[0]))
|
|
261
|
+
elif method == "rec2020":
|
|
262
|
+
lum = np.tensordot(f[..., :3], _LUMA_REC2020, axes=([2],[0]))
|
|
263
|
+
else: # default rec709
|
|
264
|
+
lum = np.tensordot(f[..., :3], _LUMA_REC709, axes=([2],[0]))
|
|
265
|
+
|
|
266
|
+
return np.clip(lum.astype(np.float32, copy=False), 0.0, 1.0)
|
|
267
|
+
|
|
268
|
+
# ---------- luminance recombine (linear scaling) ----------
|
|
269
|
+
|
|
270
|
+
def recombine_luminance_linear_scale(
|
|
271
|
+
target_rgb: np.ndarray,
|
|
272
|
+
new_L: np.ndarray,
|
|
273
|
+
weights: np.ndarray = _LUMA_REC709,
|
|
274
|
+
eps: float = 1e-6,
|
|
275
|
+
blend: float = 1.0, # 0..1, 1=full replace
|
|
276
|
+
highlight_soft_knee: float = 0.0 # 0..1, optional protection
|
|
277
|
+
) -> np.ndarray:
|
|
278
|
+
"""
|
|
279
|
+
Replace linear luminance Y (w·RGB) with `new_L` by per-pixel scaling:
|
|
280
|
+
s = new_L / (Y + eps); RGB' = RGB * s
|
|
281
|
+
This preserves hue/chroma in linear space and round-trips when new_L==Y.
|
|
282
|
+
Optional: blend (mix with original) and highlight soft-knee protection.
|
|
283
|
+
"""
|
|
284
|
+
rgb = _to_float01_strict(target_rgb)
|
|
285
|
+
if rgb.ndim != 3 or rgb.shape[2] != 3:
|
|
286
|
+
raise ValueError("Recombine Luminance requires an RGB target image.")
|
|
287
|
+
|
|
288
|
+
H, W, _ = rgb.shape
|
|
289
|
+
L = new_L.astype(np.float32)
|
|
290
|
+
if L.shape[:2] != (H, W):
|
|
291
|
+
L = cv2.resize(L, (W, H), interpolation=cv2.INTER_LINEAR)
|
|
292
|
+
|
|
293
|
+
w = np.asarray(weights, dtype=np.float32)
|
|
294
|
+
if w.shape != (3,):
|
|
295
|
+
raise ValueError("weights must be length-3 for RGB recombine.")
|
|
296
|
+
|
|
297
|
+
# current Y
|
|
298
|
+
Y = rgb[..., 0]*w[0] + rgb[..., 1]*w[1] + rgb[..., 2]*w[2]
|
|
299
|
+
s = L / (Y + eps)
|
|
300
|
+
|
|
301
|
+
if highlight_soft_knee > 0.0:
|
|
302
|
+
# compress extreme upsizing to avoid blowing out tiny Y
|
|
303
|
+
# knee in [0..1], higher = more protection
|
|
304
|
+
k = np.clip(highlight_soft_knee, 0.0, 1.0)
|
|
305
|
+
s = s / (1.0 + k*(s - 1.0))
|
|
306
|
+
|
|
307
|
+
out = rgb * s[..., None]
|
|
308
|
+
out = np.clip(out, 0.0, 1.0)
|
|
309
|
+
|
|
310
|
+
if 0.0 <= blend < 1.0:
|
|
311
|
+
out = rgb*(1.0 - blend) + out*blend
|
|
312
|
+
|
|
313
|
+
return out.astype(np.float32, copy=False)
|
|
314
|
+
|
|
315
|
+
def _resolve_active_doc_from(main, target_doc=None):
|
|
316
|
+
doc = target_doc
|
|
317
|
+
if doc is None:
|
|
318
|
+
d = getattr(main, "_active_doc", None)
|
|
319
|
+
doc = d() if callable(d) else d
|
|
320
|
+
doc = unwrap_docproxy(doc)
|
|
321
|
+
return doc
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def apply_recombine_to_doc(
|
|
325
|
+
target_doc,
|
|
326
|
+
luminance_source_img: np.ndarray,
|
|
327
|
+
method: str = "rec709",
|
|
328
|
+
weights: Optional[np.ndarray] = None,
|
|
329
|
+
noise_sigma: Optional[np.ndarray] = None,
|
|
330
|
+
blend: float = 1.0,
|
|
331
|
+
soft_knee: float = 0.0
|
|
332
|
+
):
|
|
333
|
+
"""
|
|
334
|
+
Overwrite target_doc.image by recombining with luminance from source (RGB or mono).
|
|
335
|
+
Uses linear scaling recombine; honors destination mask if present.
|
|
336
|
+
"""
|
|
337
|
+
base = _to_float01_strict(np.asarray(target_doc.image))
|
|
338
|
+
|
|
339
|
+
# Resolve profile (sensor profiles return weights w)
|
|
340
|
+
resolved_method, w, profile_name = resolve_luma_profile_weights(method)
|
|
341
|
+
|
|
342
|
+
# Caller override for weights wins (useful for custom UI / scripts)
|
|
343
|
+
if weights is not None:
|
|
344
|
+
w = np.asarray(weights, dtype=np.float32).reshape(-1)
|
|
345
|
+
if w.size != 3:
|
|
346
|
+
raise ValueError("weights must be a 3-element RGB vector")
|
|
347
|
+
elif w is not None:
|
|
348
|
+
w = np.asarray(w, dtype=np.float32).reshape(-1)
|
|
349
|
+
if w.size != 3:
|
|
350
|
+
w = None # ignore bad profile weights defensively
|
|
351
|
+
|
|
352
|
+
# Build L (mono source passes through; RGB is weighted)
|
|
353
|
+
src = _to_float01_strict(luminance_source_img)
|
|
354
|
+
if src.ndim == 2 or (src.ndim == 3 and src.shape[2] == 1):
|
|
355
|
+
L = src if src.ndim == 2 else src[..., 0]
|
|
356
|
+
# For mono L sources, we still want recombine weights to match the selected method/profile.
|
|
357
|
+
else:
|
|
358
|
+
# Noise sigma: if caller provided, use it; otherwise estimate when needed
|
|
359
|
+
ns = None
|
|
360
|
+
if resolved_method == "snr":
|
|
361
|
+
if noise_sigma is not None:
|
|
362
|
+
ns = np.asarray(noise_sigma, dtype=np.float32).reshape(-1)
|
|
363
|
+
else:
|
|
364
|
+
ns = _estimate_noise_sigma_per_channel(src)
|
|
365
|
+
|
|
366
|
+
# compute_luminance respects weights override; for sensor/custom profiles w is used
|
|
367
|
+
L = compute_luminance(src, method=resolved_method, weights=w, noise_sigma=ns)
|
|
368
|
+
|
|
369
|
+
# For scaling recombine, we need an actual RGB weight vector.
|
|
370
|
+
# If we don't have one from the chosen mode/profile, fall back sensibly.
|
|
371
|
+
if w is not None and w.size == 3:
|
|
372
|
+
recombine_w = w
|
|
373
|
+
else:
|
|
374
|
+
# If your resolver returns w=None for rec709/rec601/rec2020, fill explicitly here:
|
|
375
|
+
if resolved_method == "rec601":
|
|
376
|
+
recombine_w = _LUMA_REC601
|
|
377
|
+
elif resolved_method == "rec2020":
|
|
378
|
+
recombine_w = _LUMA_REC2020
|
|
379
|
+
else:
|
|
380
|
+
recombine_w = _LUMA_REC709
|
|
381
|
+
|
|
382
|
+
replaced = recombine_luminance_linear_scale(
|
|
383
|
+
base,
|
|
384
|
+
L,
|
|
385
|
+
weights=recombine_w,
|
|
386
|
+
blend=float(blend),
|
|
387
|
+
highlight_soft_knee=float(soft_knee),
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# Metadata
|
|
391
|
+
md = {"step_name": "Recombine Luminance", "luma_method": resolved_method}
|
|
392
|
+
if profile_name:
|
|
393
|
+
md["luma_profile"] = profile_name
|
|
394
|
+
if w is not None:
|
|
395
|
+
md["luma_weights"] = np.asarray(w, dtype=np.float32).tolist()
|
|
396
|
+
|
|
397
|
+
target_doc.apply_edit(replaced.astype(np.float32, copy=False), metadata=md, step_name="Recombine Luminance")
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def run_recombine_luminance_via_preset(main_or_ctx, preset=None, target_doc=None):
|
|
401
|
+
"""
|
|
402
|
+
Headless entrypoint for recombine_luminance.
|
|
403
|
+
|
|
404
|
+
preset supports:
|
|
405
|
+
- source_doc_ptr: int (id(doc)) [highest priority]
|
|
406
|
+
- source_title: str [next priority]
|
|
407
|
+
- method, weights, blend, soft_knee (existing)
|
|
408
|
+
If neither source_* is given, first eligible non-target open doc is used.
|
|
409
|
+
"""
|
|
410
|
+
from setiastro.saspro.luminancerecombine import apply_recombine_to_doc
|
|
411
|
+
|
|
412
|
+
p = dict(preset or {})
|
|
413
|
+
main, doc, dm = normalize_headless_main(main_or_ctx, target_doc)
|
|
414
|
+
|
|
415
|
+
# ---- Validate target ----
|
|
416
|
+
if doc is None or getattr(doc, "image", None) is None:
|
|
417
|
+
raise CommandError("recombine_luminance: no active RGB ImageDocument. Load an image first.")
|
|
418
|
+
|
|
419
|
+
# ---- Collect open docs (unwrapped) ----
|
|
420
|
+
open_docs = []
|
|
421
|
+
if dm is not None:
|
|
422
|
+
try:
|
|
423
|
+
if hasattr(dm, "all_documents") and callable(dm.all_documents):
|
|
424
|
+
open_docs = [unwrap_docproxy(d) for d in dm.all_documents()]
|
|
425
|
+
elif hasattr(dm, "_docs"):
|
|
426
|
+
open_docs = [unwrap_docproxy(d) for d in dm._docs]
|
|
427
|
+
except Exception:
|
|
428
|
+
open_docs = []
|
|
429
|
+
|
|
430
|
+
# Filter to docs that look like images
|
|
431
|
+
def _has_image(d):
|
|
432
|
+
return d is not None and getattr(d, "image", None) is not None
|
|
433
|
+
|
|
434
|
+
open_docs = [d for d in open_docs if _has_image(d)]
|
|
435
|
+
|
|
436
|
+
# ---- Resolve luminance source ----
|
|
437
|
+
src_doc = None
|
|
438
|
+
|
|
439
|
+
# 1) source_doc_ptr
|
|
440
|
+
src_ptr = p.get("source_doc_ptr", None)
|
|
441
|
+
if src_ptr is not None:
|
|
442
|
+
try:
|
|
443
|
+
src_ptr = int(src_ptr)
|
|
444
|
+
for d in open_docs:
|
|
445
|
+
if id(d) == src_ptr:
|
|
446
|
+
src_doc = d
|
|
447
|
+
break
|
|
448
|
+
except Exception:
|
|
449
|
+
src_doc = None
|
|
450
|
+
|
|
451
|
+
# 2) source_title
|
|
452
|
+
if src_doc is None:
|
|
453
|
+
st = p.get("source_title", None)
|
|
454
|
+
if st:
|
|
455
|
+
st_low = str(st).strip().lower()
|
|
456
|
+
|
|
457
|
+
def _title_of(d):
|
|
458
|
+
# prefer display_name() if available
|
|
459
|
+
try:
|
|
460
|
+
if hasattr(d, "display_name") and callable(d.display_name):
|
|
461
|
+
return str(d.display_name())
|
|
462
|
+
except Exception:
|
|
463
|
+
pass
|
|
464
|
+
# fallback to metadata display_name or file basename
|
|
465
|
+
try:
|
|
466
|
+
md = getattr(d, "metadata", {}) or {}
|
|
467
|
+
if md.get("display_name"):
|
|
468
|
+
return str(md["display_name"])
|
|
469
|
+
fp = md.get("file_path")
|
|
470
|
+
if fp:
|
|
471
|
+
import os
|
|
472
|
+
return os.path.basename(fp)
|
|
473
|
+
except Exception:
|
|
474
|
+
pass
|
|
475
|
+
return ""
|
|
476
|
+
|
|
477
|
+
for d in open_docs:
|
|
478
|
+
if d is doc:
|
|
479
|
+
continue
|
|
480
|
+
if _title_of(d).lower() == st_low:
|
|
481
|
+
src_doc = d
|
|
482
|
+
break
|
|
483
|
+
|
|
484
|
+
# 3) auto-pick first eligible non-target doc
|
|
485
|
+
if src_doc is None:
|
|
486
|
+
for d in open_docs:
|
|
487
|
+
if d is doc:
|
|
488
|
+
continue
|
|
489
|
+
src_doc = d
|
|
490
|
+
break
|
|
491
|
+
|
|
492
|
+
if src_doc is None:
|
|
493
|
+
raise CommandError(
|
|
494
|
+
"recombine_luminance: no luminance source found. "
|
|
495
|
+
"Open another image, or pass preset {'source_title': ...} "
|
|
496
|
+
"or {'source_doc_ptr': id(doc)}."
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
# ---- Execute recombine ----
|
|
500
|
+
src_img = np.asarray(src_doc.image)
|
|
501
|
+
|
|
502
|
+
apply_recombine_to_doc(
|
|
503
|
+
doc,
|
|
504
|
+
src_img,
|
|
505
|
+
method=p.get("method", "rec709"),
|
|
506
|
+
weights=p.get("weights", None),
|
|
507
|
+
blend=float(p.get("blend", 1.0)),
|
|
508
|
+
soft_knee=float(p.get("soft_knee", 0.0)),
|
|
509
|
+
)
|
|
510
|
+
|