assets/color_palettes.py

"""
Colorblind-Friendly Color Palettes for Scientific Visualization

This module provides carefully curated color palettes optimized for
scientific publications and accessibility.

Usage:
    from color_palettes import OKABE_ITO, apply_palette
    import matplotlib.pyplot as plt

    apply_palette('okabe_ito')
    plt.plot([1, 2, 3], [1, 4, 9])
"""

# Okabe-Ito Palette (2008)
# The most widely recommended colorblind-friendly palette
OKABE_ITO = {
    'orange': '#E69F00',
    'sky_blue': '#56B4E9',
    'bluish_green': '#009E73',
    'yellow': '#F0E442',
    'blue': '#0072B2',
    'vermillion': '#D55E00',
    'reddish_purple': '#CC79A7',
    'black': '#000000'
}

OKABE_ITO_LIST = ['#E69F00', '#56B4E9', '#009E73', '#F0E442',
                   '#0072B2', '#D55E00', '#CC79A7', '#000000']

# Wong Palette (Nature Methods)
WONG = ['#000000', '#E69F00', '#56B4E9', '#009E73',
        '#F0E442', '#0072B2', '#D55E00', '#CC79A7']

# Paul Tol Palettes (https://personal.sron.nl/~pault/)
TOL_BRIGHT = ['#4477AA', '#EE6677', '#228833', '#CCBB44',
              '#66CCEE', '#AA3377', '#BBBBBB']

TOL_MUTED = ['#332288', '#88CCEE', '#44AA99', '#117733',
             '#999933', '#DDCC77', '#CC6677', '#882255', '#AA4499']

TOL_LIGHT = ['#77AADD', '#EE8866', '#EEDD88', '#FFAABB',
             '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00', '#DDDDDD']

TOL_HIGH_CONTRAST = ['#004488', '#DDAA33', '#BB5566']

# Sequential colormaps (for continuous data)
SEQUENTIAL_COLORMAPS = [
    'viridis',   # Default, perceptually uniform
    'plasma',    # Perceptually uniform
    'inferno',   # Perceptually uniform
    'magma',     # Perceptually uniform
    'cividis',   # Optimized for colorblind viewers
    'YlOrRd',    # Yellow-Orange-Red
    'YlGnBu',    # Yellow-Green-Blue
    'Blues',     # Single hue
    'Greens',    # Single hue
    'Purples',   # Single hue
]

# Diverging colormaps (for data with meaningful center)
DIVERGING_COLORMAPS_SAFE = [
    'RdYlBu',    # Red-Yellow-Blue (reversed is common)
    'RdBu',      # Red-Blue
    'PuOr',      # Purple-Orange (excellent for colorblind)
    'BrBG',      # Brown-Blue-Green (good for colorblind)
    'PRGn',      # Purple-Green (use with caution)
    'PiYG',      # Pink-Yellow-Green (use with caution)
]

# Diverging colormaps to AVOID (red-green combinations)
DIVERGING_COLORMAPS_AVOID = [
    'RdGn',      # Red-Green (problematic!)
    'RdYlGn',    # Red-Yellow-Green (problematic!)
]

# Fluorophore colors (traditional - use with caution)
FLUOROPHORES_TRADITIONAL = {
    'DAPI': '#0000FF',    # Blue
    'GFP': '#00FF00',     # Green (problematic for colorblind)
    'RFP': '#FF0000',     # Red
    'Cy5': '#FF00FF',     # Magenta
    'YFP': '#FFFF00',     # Yellow
}

# Fluorophore colors (colorblind-friendly alternatives)
FLUOROPHORES_ACCESSIBLE = {
    'Channel1': '#0072B2',  # Blue
    'Channel2': '#E69F00',  # Orange (instead of green)
    'Channel3': '#D55E00',  # Vermillion (instead of red)
    'Channel4': '#CC79A7',  # Magenta
    'Channel5': '#F0E442',  # Yellow
}

# Genomics/Bioinformatics
DNA_BASES = {
    'A': '#00CC00',  # Green
    'C': '#0000CC',  # Blue
    'G': '#FFB300',  # Orange
    'T': '#CC0000',  # Red
}

DNA_BASES_ACCESSIBLE = {
    'A': '#009E73',  # Bluish Green
    'C': '#0072B2',  # Blue
    'G': '#E69F00',  # Orange
    'T': '#D55E00',  # Vermillion
}


def apply_palette(palette_name='okabe_ito'):
    """
    Apply a color palette to matplotlib's default color cycle.

    Parameters
    ----------
    palette_name : str
        Name of the palette to apply. Options:
        'okabe_ito', 'wong', 'tol_bright', 'tol_muted',
        'tol_light', 'tol_high_contrast'

    Returns
    -------
    list
        List of colors in the palette

    Examples
    --------
    >>> apply_palette('okabe_ito')
    >>> plt.plot([1, 2, 3], [1, 4, 9])  # Uses Okabe-Ito colors
    """
    try:
        import matplotlib.pyplot as plt
    except ImportError:
        print("matplotlib not installed")
        return None

    palettes = {
        'okabe_ito': OKABE_ITO_LIST,
        'wong': WONG,
        'tol_bright': TOL_BRIGHT,
        'tol_muted': TOL_MUTED,
        'tol_light': TOL_LIGHT,
        'tol_high_contrast': TOL_HIGH_CONTRAST,
    }

    if palette_name not in palettes:
        available = ', '.join(palettes.keys())
        raise ValueError(f"Palette '{palette_name}' not found. Available: {available}")

    colors = palettes[palette_name]
    plt.rcParams['axes.prop_cycle'] = plt.cycler(color=colors)
    return colors


def get_palette(palette_name='okabe_ito'):
    """
    Get a color palette as a list.

    Parameters
    ----------
    palette_name : str
        Name of the palette

    Returns
    -------
    list
        List of color hex codes
    """
    palettes = {
        'okabe_ito': OKABE_ITO_LIST,
        'wong': WONG,
        'tol_bright': TOL_BRIGHT,
        'tol_muted': TOL_MUTED,
        'tol_light': TOL_LIGHT,
        'tol_high_contrast': TOL_HIGH_CONTRAST,
    }

    if palette_name not in palettes:
        available = ', '.join(palettes.keys())
        raise ValueError(f"Palette '{palette_name}' not found. Available: {available}")

    return palettes[palette_name]


if __name__ == "__main__":
    print("Available colorblind-friendly palettes:")
    print(f"  - Okabe-Ito: {len(OKABE_ITO_LIST)} colors")
    print(f"  - Wong: {len(WONG)} colors")
    print(f"  - Tol Bright: {len(TOL_BRIGHT)} colors")
    print(f"  - Tol Muted: {len(TOL_MUTED)} colors")
    print(f"  - Tol Light: {len(TOL_LIGHT)} colors")
    print(f"  - Tol High Contrast: {len(TOL_HIGH_CONTRAST)} colors")

    print("\nOkabe-Ito palette (most recommended):")
    for name, color in OKABE_ITO.items():
        print(f"  {name:15s}: {color}")
← Back to scientific-visualization