Source code for multicolorfits

"""
MultiColorFits
==============

Colorize and combine FITS images to produce visually aesthetic scientific
plots -- with any number of image layers, in any colors.

Scripting quickstart (unchanged from v2.x)::

    import multicolorfits as mcf

    grey = mcf.to_grey_rgb(data, rescalefn='asinh', min_max=[0., 1.2])
    colr = mcf.colorize_image(grey, '#C11B17', colorintype='hex')
    combined = mcf.combine_multicolor([colr, ...], gamma=2.2)

GUIs (all optional):

* ``mcf.gui()`` or the ``mcf-web`` console script -- browser-based GUI
  (requires ``pip install multicolorfits[web]``)
* ``mcf.gui_qt()`` or the ``mcf-qt`` console script -- PySide6 desktop GUI
  (requires ``pip install multicolorfits[qt]``)

New here (human or AI agent)?  Run ``mcf.overview()`` for the mental model,
conventions, and a task index, then ``mcf.recipes('<keyword>')`` for
copy-paste code.  The same catalog is published as ``llms.txt`` /
``llms-full.txt`` for agent ingestion.
"""

__author__ = "Phil Cigan"
__version__ = "3.0.0"

# Agent- / newcomer-facing orientation (mcf.overview() / mcf.recipes()).
from ._overview import overview, recipes  # noqa: E402,F401

# Re-export the entire computational API under the historical flat namespace,
# so that `import multicolorfits as mcf` scripts from v2.x keep working.
from .core import (  # noqa: F401
    # scaling
    stretch_functions,
    SIGNED_STRETCHES,
    adjust_gamma,
    draw_progress_bar,
    nan_percentile_of_score,
    make_norm,
    rescale_image,
    zscale_limits,
    # header / WCS / coordinates / cropping
    McfHeader,
    as_mcfheader,
    save_header,
    load_header,
    force_header_2d,
    force_header_3d,
    force_header_floats,
    deg2dms,
    dms2deg,
    deg2hour,
    hour2deg,
    dec2sex,
    sex2dec,
    angular_distance,
    get_cdelts,
    sky_to_pixel,
    pixel_to_sky,
    make_simple_header,
    get_cd_matrix,
    deg_per_pixel,
    arcsec_per_pixel,
    sterad_per_pixel,
    squeeze_image,
    header_coord_grids,
    beam_params_arcsec,
    pixels_per_beam,
    crop_image,
    crop_cube,
    crop_image_sky,
    crop_cube_sky,
    # reprojection (lazy optional deps)
    reproject_image,
    reproject_cube,
    # Colorize / tint greyscale → colorized layers
    hex_to_rgb,
    rgb_to_hex,
    hex_to_hsv,
    rgb_to_hsv,
    hsv_to_rgb,
    hex_complement,
    to_hex,
    to_grey_rgb,
    colorize_image_direct_rgb,
    colorize_image,
    combine_multicolor,
    smooth_image,
    # Output helpers
    plot_combined_rgb,
    compare_multicolor_vs_rgb,
    save_rgb_fits,
    # Perceptual compositing (Lab supported; HSV/HSL experimental) + accessibility
    rgb_to_hsl,
    hsl_to_rgb,
    rgb_to_lab,
    lab_to_rgb,
    combine_multicolor_colorspace,
    mix_colors_hex,
    greyscale_image,
    simulate_colorblindness,
    # Subtractive RYB/CMYK + background / transparent compositing
    rgb_to_ryb,
    ryb_to_rgb,
    hex_to_ryb,
    ryb_to_hex,
    rgb_to_cmyk,
    cmyk_to_rgb,
    hex_to_cmyk,
    cmyk_to_hex,
    layer_coverage,
    composite_over_background,
    combine_multicolor_alpha,
    # Composite-mode swatch + shared combine dispatch
    combine_colorized_layers,
    swatch_layout,
    combo_swatch,
    # Transparent cutouts for slides / stamps
    luminance,
    alpha_from_intensity,
    make_transparent_cutout,
    save_transparent_cutout,
    make_checkerboard,
    preview_cutout_on_backgrounds,
    batch_transparent_cutouts,
    # Deblend a flattened composite back to RGBA (and re-flatten)
    flatten_rgba,
    deblend_background,
    # Celestial frame conversion (optional reproject)
    header_frame_name,
    convert_header_frame,
    reproject_to_frame,
    reproject_to_galactic,
    optimal_common_header,
    # Stack alignment and interactive preview downsampling
    reproject_stack_to_header,
    reproject_stack_to_reference,
    align_stack,
    downsample_for_preview,
    annotate_provenance_header,
)

# Backward-compatible v2.x names.  Every legacy alias lives in compat.py
# (one place to corral for eventual removal); LEGACY_ALIASES maps old -> new.
from .compat import *  # noqa: F401,F403
from .compat import LEGACY_ALIASES  # noqa: F401

from .session import (  # noqa: F401
    PanelState, ComposeState, McfSession, reproject_available, ALIGN_FRAMES,
    STRETCHES, COMBINE_MODES, DEFAULT_N_PANELS, MIN_PANELS, MAX_PANELS,
)
from .core.colormix import COLORSPACES, BLENDS  # noqa: F401
from . import palettes  # noqa: F401
from .palettes import get_palette, list_palettes, suggest_colors  # noqa: F401
from .pipeline import combine_layers, combine_from_files, save_combined  # noqa: F401
from .figures import (  # noqa: F401
    apply_bare_plot_style, apply_bare_axes, make_component_mosaic,
    make_combined_figure, setup_combined_axes,
)
from . import overlays  # noqa: F401


[docs] def overlays_available(): """True when the optional skyplothelper overlays extra is installed.""" from .overlays import overlays_available as _fn return _fn()
[docs] def gui(host='127.0.0.1', port=8321, open_browser=True, browser=None, session=None, state=None, files=None, colors=None, labels=None): """ Launch the (browser-based) multicolorfits GUI. Parameters ---------- host, port Local uvicorn bind address (default ``127.0.0.1:8321``; port auto- increments if busy). open_browser : bool If True (default), open a browser tab when the server is ready. browser : str or None Which browser to open when ``open_browser`` is True. Names understood by :func:`webbrowser.get` work (e.g. ``'firefox'``, ``'google-chrome'``, ``'chromium'``, ``'safari'``), as does a full path to an executable. ``None`` uses the system default; on Unix the ``BROWSER`` environment variable is also consulted by the stdlib. Ignored when ``open_browser=False``. session : McfSession or None Optional pre-configured session (panels + compose state). state : str or dict or None JSON session file path or state dict to restore on startup (see :meth:`McfSession.save_state` / :meth:`McfSession.load_state`). files : sequence of str or None FITS paths to load into successive panels on startup. colors, labels : sequences or None Optional per-file colors and channel labels (with ``files``). Examples -------- >>> mcf.gui() # doctest: +SKIP >>> mcf.gui(browser='firefox') # doctest: +SKIP >>> mcf.gui(open_browser=False) # then open the printed URL yourself CLI equivalents: ``mcf-web --browser firefox``, ``mcf-web --no-browser``. Requires the web extra: pip install multicolorfits[web] """ try: from .gui_web.launcher import run except ImportError as exc: raise ImportError( "The browser GUI requires the 'web' extra dependencies. " "Install them with: pip install multicolorfits[web]" ) from exc run(host=host, port=port, open_browser=open_browser, browser=browser, session=session, state=state, files=files, colors=colors, labels=labels)
[docs] def start_gui_server(host='127.0.0.1', port=8321, session=None, state=None, files=None, colors=None, labels=None, **kwargs): """ Start the web GUI in a background thread (non-blocking). Returns a :class:`~multicolorfits.gui_web.embed.GuiServer` with ``url``, ``port``, and ``host``. See :func:`gui_embed` for notebook embedding. Requires the web extra: pip install multicolorfits[web] """ try: from .gui_web.embed import start_gui_server as _start except ImportError as exc: raise ImportError( "The browser GUI requires the 'web' extra dependencies. " "Install them with: pip install multicolorfits[web]" ) from exc return _start(host=host, port=port, session=session, state=state, files=files, colors=colors, labels=labels, **kwargs)
[docs] def gui_embed(host='127.0.0.1', port=8321, session=None, state=None, files=None, colors=None, labels=None, **kwargs): """ Embed the browser GUI in Jupyter, Colab, VS Code notebooks, or Binder. Starts uvicorn in a background thread and displays an inline iframe (or Colab's port proxy). Use ``display='url'`` to print the URL only, or ``return_url=True`` to get the URL string without displaying. Parameters ---------- host, port, session, state, files, colors, labels Same as :func:`gui`. display : {'auto', 'iframe', 'colab', 'url', 'none'} Embedding mode; ``auto`` picks Colab vs Jupyter vs URL print. height, width iframe size (``height`` also used for Colab). proxy_url : str or None Override iframe ``src`` for custom JupyterHub proxy layouts. return_url, print_url URL-only helpers for scripting and debugging. Examples -------- :: import multicolorfits as mcf s = mcf.McfSession() s.load_files(['a.fits', 'b.fits'], colors=['#f00', '#0ff']) mcf.gui_embed(session=s, height=950) Requires the web extra: pip install multicolorfits[web] """ try: from .gui_web.embed import gui_embed as _embed except ImportError as exc: raise ImportError( "The browser GUI requires the 'web' extra dependencies. " "Install them with: pip install multicolorfits[web]" ) from exc return _embed(host=host, port=port, session=session, state=state, files=files, colors=colors, labels=labels, **kwargs)
[docs] def detect_notebook_environment(): """ Detect Jupyter / Colab / VS Code / Binder, or ``None`` outside IPython. Used by :func:`gui_embed` when ``display='auto'``. """ try: from .gui_web.embed import detect_notebook_environment as _detect except ImportError as exc: raise ImportError( "Notebook helpers require the 'web' extra: pip install multicolorfits[web]" ) from exc return _detect()
[docs] def mcf_gui(*args, **kwargs): """Alias for :func:`gui` (backward compatible with v2.x scripts).""" gui(*args, **kwargs)
[docs] def gui_qt(session=None, state=None, files=None, colors=None, labels=None): """ Launch the PySide6 desktop GUI. Parameters ---------- session : McfSession or None Optional pre-configured session. When ``state`` or ``files`` is given, a fresh session is built unless ``session`` is also passed. state : str or dict or None JSON session file or dict to restore on startup. files : sequence of str or None FITS paths to load into successive panels on startup. colors, labels : sequences or None Optional per-file colors and labels. Requires the qt extra: pip install multicolorfits[qt] """ try: from .qt_gui.app import main as qt_main except ImportError as exc: raise ImportError( "The desktop GUI requires the 'qt' extra dependencies. " "Install them with: pip install multicolorfits[qt]" ) from exc if session is None: session = McfSession() if state: session.load_state(state) elif files: session.load_files(files, colors=colors, labels=labels) return qt_main(session=session)