1616 reference to consult when making all kinds of figures, not just those made
1717 using PyMOL.
1818
19- The colors are :
19+ The "colorblind" color palette includes :
2020
2121 * cb_black
2222 * cb_orange
2727 * cb_vermillion (also: cb_red, cb_redorange, cb_red_orange)
2828 * cb_reddish_purple (also: cb_rose, cb_violet, cb_magenta)
2929
30+ Also added are two palettes from matplotlib, "viridis" and "magma", which
31+ are designed to be perceptually uniform in both color and black-and-white
32+ printouts. These are available as "viridis[1-11]", "magma[1-11]".
33+
3034USAGE
3135
36+ With the PyMOL Script Repo installed and importable, import the module and
37+ set the colors:
38+
39+ ```
3240 import colorblindfriendly as cbf
3341
3442 # Add the new colors
3543 cbf.set_colors()
3644 color myObject, cb_red
3745
38- # Replace built-in colors with cbf ones
46+ # Replace built-in colors of same names with cbf ones
3947 cbf.set_colors(replace=True)
4048 color myOtherObject, yellow # actually cb_yellow
4149
4250 # Add a `cb_colors` menu item to the OpenGL GUI ([C] menu in the right panel)
4351 cbf.add_menu()
52+ ```
53+
54+ Or, to use without installing, run the script directly from GitHub. This
55+ will add the colors and install GUI palette menus for all three default
56+ color palettes:
57+
58+ ```
59+ run https://github.com/Pymol-Scripts/Pymol-script-repo/blob/master/colorblindfriendly.py
60+ color myObject, cb_red
61+ ```
4462
4563REQUIREMENTS
4664
5371
5472LICENSE
5573
56- Copyright (c) 2014-2017 Jared Sampson
74+ Copyright (c) 2014-2025 Jared Sampson
5775
5876Permission is hereby granted, free of charge, to any person obtaining a copy
5977of this software and associated documentation files (the "Software"), to deal
7593
7694CHANGELOG
7795
96+ 0.4.0 Add Palette and PaletteColor NamedTuples for cleaner declaration of
97+ color palettes. [2025-02-10]
98+
99+ 0.3.0 Generalize the way colors and menus are efined and added, to
100+ enable the use of additional color palettes. Add Viridis and Magma
101+ palettes (contributed by Yehudi Bloch). [2021-10-27]
102+
78103 0.2.0 Complete overhaul for PyMOL 2.0 with conversion to module format.
79104 Now, setting the new `cb_*` color values requires a call to the
80105 `set_colors()` function after import. You can also now add a
83108
84109'''
85110from __future__ import print_function
111+ import math
112+ from typing import NamedTuple , Optional
86113
87114__author__ = 'Jared Sampson'
88- __version__ = '0.2 .0'
115+ __version__ = '0.4 .0'
89116
90117import pymol
91118from pymol import cmd
92119
93120
94- # Color blind-friendly color list based on information found at:
95- # http://jfly.iam.u-tokyo.ac.jp/html/color_blind/#pallet
96- CB_COLORS = {
97- 'black' : {
98- 'rgb' : [0 , 0 , 0 ],
99- 'alt' : None ,
100- },
101- 'orange' : {
102- 'rgb' : [230 , 159 , 0 ],
103- 'alt' : None ,
104- },
105- 'sky_blue' : {
106- 'rgb' : [86 , 180 , 233 ],
107- 'alt' : ['skyblue' , 'light_blue' , 'lightblue' ],
108- },
109- 'bluish_green' : {
110- 'rgb' : [0 , 158 , 115 ],
111- 'alt' : ['bluishgreen' , 'green' ],
112- },
113- 'yellow' : {
114- 'rgb' : [240 , 228 , 66 ],
115- 'alt' : None ,
116- },
117- 'blue' : {
118- 'rgb' : [0 , 114 , 178 ],
119- 'alt' : None ,
120- },
121- 'vermillion' : {
122- 'rgb' : [213 , 94 , 0 ],
123- 'alt' : ['red' , 'red_orange' , 'redorange' ],
124- },
125- 'reddish_purple' : {
126- 'rgb' : [204 , 121 , 167 ],
127- 'alt' : ['reddishpurple' , 'rose' , 'violet' , 'magenta' ],
128- },
129- }
121+ class PaletteColor (NamedTuple ):
122+ '''Named tuple for storing color information.'''
123+ name : str
124+ rgb : tuple [int , int , int ]
125+ alt_names : Optional [list [str ]] = None
126+ # Allow code to be set explicitly in palette definition. This is helpful
127+ # for very dark colors, to allow contrast against the dark menu background.
128+ short_code : Optional [str ] = None # for GUI menu
130129
130+ def all_names (self ):
131+ '''Return a list of all names for this color.'''
132+ names = [self .name ]
133+ if self .alt_names :
134+ names .extend (self .alt_names )
135+ return names
131136
132- def set_colors (replace = False ):
133- '''Add the color blind-friendly colors to PyMOL.'''
134- # Track the added colors
135- added_colors = []
137+ def get_short_code (self ):
138+ '''Return a 3-digit string approximating the RGB color.'''
139+ if self .short_code :
140+ return self .short_code
141+ return '' .join ([str (math .floor (x / 256 * 10 )) for x in self .rgb ])
136142
137- for color , properties in CB_COLORS .items ():
138- # RGB tuple shortcut
139- rgb = properties ['rgb' ]
140143
141- # Get the primary and alternate color names into a single list
142- names = [color ]
143- if properties ['alt' ]:
144- names .extend (properties ['alt' ])
144+ class Palette (NamedTuple ):
145+ '''Named tuple for storing palette information.'''
146+ name : str
147+ colors : list [PaletteColor ]
148+ prefix : str = ''
145149
146- # Set the colors
147- for name in names :
148- # Set the cb_color
149- cb_name = 'cb_{}' .format (name )
150- cmd .set_color (cb_name , rgb )
150+ def install (self ):
151+ '''Install the palette, adding colors and the GUI menu.'''
152+ PALETTES_MAP [self .name ] = self
153+ add_menu (self .name )
151154
152- # Optionally replace built-in colors
153- if replace :
154- cmd .set_color (name , rgb )
155- spacer = (20 - len (name )) * ' '
156- added_colors .append (' {}{}{}' .format (name , spacer , cb_name ))
157- else :
158- added_colors .append (' {}' .format (cb_name ))
159155
160- # Notify user of newly available colors
161- print ('\n Color blind-friendly colors are now available:' )
162- print ('\n ' .join (added_colors ))
163- print ('' )
156+ # Color blind-friendly color list based on information found at:
157+ # http://jfly.iam.u-tokyo.ac.jp/html/color_blind/#pallet
158+ CB_COLORS = [
159+ PaletteColor ('red' , (213 , 94 , 0 ),
160+ ['vermillion' , 'red_orange' , 'redorange' ]),
161+ PaletteColor ('orange' , (230 , 159 , 0 )),
162+ PaletteColor ('yellow' , (240 , 228 , 66 )),
163+ PaletteColor ('green' , (0 , 158 , 115 ),
164+ ['bluish_green' , 'bluishgreen' ]),
165+ PaletteColor ('light_blue' , (86 , 180 , 233 ),
166+ ['lightblue' , 'sky_blue' , 'skyblue' ]),
167+ PaletteColor ('blue' , (0 , 114 , 178 )),
168+ PaletteColor ('violet' , (204 , 121 , 167 ),
169+ ['reddish_purple' , 'reddishpurple' , 'rose' , 'magenta' ]),
170+ PaletteColor ('black' , (0 , 0 , 0 ), short_code = '222' ),
171+ ]
172+ CB_PALETTE = Palette ('colorblind' , CB_COLORS , prefix = 'cb_' )
173+
174+ # Viridis and Magma palettes contributed by Yehudi Bloch, originally
175+ # developed by Stéfan van der Walt and Nathaniel Smith for matplotlib.
176+ # https://matplotlib.org/stable/users/prev_whats_new/whats_new_1.5.html
177+ VIRIDIS_COLORS = [
178+ PaletteColor ('viridis1' , (253 , 231 , 36 )),
179+ PaletteColor ('viridis2' , (186 , 222 , 39 )),
180+ PaletteColor ('viridis3' , (121 , 209 , 81 )),
181+ PaletteColor ('viridis4' , ( 66 , 190 , 113 )),
182+ PaletteColor ('viridis5' , ( 34 , 167 , 132 )),
183+ PaletteColor ('viridis6' , ( 32 , 143 , 140 )),
184+ PaletteColor ('viridis7' , ( 41 , 120 , 142 )),
185+ PaletteColor ('viridis8' , ( 52 , 94 , 141 )),
186+ PaletteColor ('viridis9' , ( 64 , 67 , 135 )),
187+ PaletteColor ('viridis10' , ( 72 , 35 , 116 )),
188+ PaletteColor ('viridis11' , ( 68 , 1 , 84 )),
189+ ]
190+ VIRIDIS_PALETTE = Palette ('viridis' , VIRIDIS_COLORS )
191+
192+ MAGMA_COLORS = [
193+ PaletteColor ('magma1' , (251 , 252 , 191 )),
194+ PaletteColor ('magma2' , (253 , 205 , 114 )),
195+ PaletteColor ('magma3' , (253 , 159 , 108 )),
196+ PaletteColor ('magma4' , (246 , 110 , 91 )),
197+ PaletteColor ('magma5' , (221 , 73 , 104 )),
198+ PaletteColor ('magma6' , (181 , 54 , 121 )),
199+ PaletteColor ('magma7' , (140 , 41 , 128 )),
200+ PaletteColor ('magma8' , ( 99 , 25 , 127 )),
201+ PaletteColor ('magma9' , ( 59 , 15 , 111 )),
202+ PaletteColor ('magma10' , ( 20 , 13 , 53 )),
203+ PaletteColor ('magma11' , ( 0 , 0 , 3 )),
204+ ]
205+ MAGMA_PALETTE = Palette ('magma' , MAGMA_COLORS )
206+
207+ PALETTES_MAP = {
208+ CB_PALETTE .name : CB_PALETTE ,
209+ VIRIDIS_PALETTE .name : VIRIDIS_PALETTE ,
210+ MAGMA_PALETTE .name : MAGMA_PALETTE ,
211+ }
212+
213+
214+ def _get_palettes (palette_name : Optional [str ] = None ):
215+ '''Return the desired Palette(s).'''
216+ if palette_name is None :
217+ return PALETTES_MAP .values ()
218+ if palette_name not in PALETTES_MAP :
219+ raise ValueError (f'Palette "{ palette_name } " not found.' )
220+ else :
221+ return [PALETTES_MAP [palette_name ]]
164222
165223
166- def add_menu ():
167- '''Add a color blind-friendly list of colors to the PyMOL OpenGL menu.'''
224+ def set_colors (palette = None , replace = False ):
225+ '''Add the color blind-friendly colors to PyMOL.'''
226+ palettes = _get_palettes (palette )
227+ for palette in palettes :
228+ added_colors = []
229+ for color in palette .colors :
230+ # RGB tuple shortcut
231+ rgb = color .rgb
232+
233+ # Set the colors
234+ for name in color .all_names ():
235+ if palette .prefix :
236+ use_name = f'{ palette .prefix } { name } '
237+ else :
238+ use_name = name
239+ cmd .set_color (use_name , rgb )
240+
241+ # Optionally replace built-in colors
242+ if replace :
243+ cmd .set_color (name , rgb )
244+ # FIXME hard-coded column width
245+ spacer = (20 - len (name )) * ' '
246+ added_colors .append (f' { name } { spacer } { use_name } ' )
247+ else :
248+ added_colors .append (' {}' .format (use_name ))
249+
250+ # Notify user of newly available colors
251+ print (f'These { palette .name } colors are now available:' )
252+ print ('\n ' .join (added_colors ))
253+
254+
255+ def _add_palette_menu (palette : Palette ):
256+ '''Add a color palette to the PyMOL OpenGL menu.'''
168257
169258 # Make sure cb_colors are installed.
170- print ('Checking for colorblindfriendly colors...' )
259+ print (f 'Checking for { palette . name } colors...' )
171260 try :
172- if cmd .get_color_index ('cb_red' ) == - 1 :
173- # mimic pre-1.7.4 behavior
174- raise pymol .CmdException
261+ for color in palette .colors :
262+ if cmd .get_color_index (color .name ) == - 1 :
263+ # mimic pre-1.7.4 behavior
264+ raise pymol .CmdException
175265 except pymol .CmdException :
176- print ('Adding colorblindfriendly colors...' )
177- set_colors ()
266+ print (f 'Adding { palette . name } palette colors...' )
267+ set_colors (palette = palette . name )
178268
179269 # Abort if PyMOL is too old.
180270 try :
@@ -184,35 +274,50 @@ def add_menu():
184274 return
185275
186276 # Add the menu
187- print ('Adding cb_colors menu...' )
277+ print (f 'Adding { palette . name } menu...' )
188278 # mimic pymol.menu.all_colors_list format
189279 # first color in list is used for menu item color
190- cb_colors = ('cb_colors' , [
191- ('830' , 'cb_red' ),
192- ('064' , 'cb_green' ),
193- ('046' , 'cb_blue' ),
194- ('882' , 'cb_yellow' ),
195- ('746' , 'cb_magenta' ),
196- ('368' , 'cb_skyblue' ),
197- ('860' , 'cb_orange' ),
198- ])
280+
281+ # Menu item for each color in the menu should be a tuple in the form
282+ # ('999', 'color_name')
283+ # where '999' is a string representing the 0-255 RGB color converted to
284+ # a 0-9 integer RGB format (i.e. 1000 colors).
285+ color_tuples = [
286+ (color .get_short_code (), palette .prefix + color .name )
287+ for color in palette .colors
288+ ]
289+ menu_colors = (palette .name , color_tuples )
290+
199291 # First `pymol` is the program instance, second is the Python module
200292 all_colors_list = pymol .pymol .menu .all_colors_list
201- if cb_colors in all_colors_list :
202- print (' Menu was already added!' )
293+ if menu_colors in all_colors_list :
294+ print (f' - Menu for { palette . name } was already added!' )
203295 else :
204- all_colors_list .append (cb_colors )
205- print (' done.' )
296+ all_colors_list .append (menu_colors )
297+ print (' done.\n ' )
298+
206299
300+ def add_menu (palette_name = None ):
301+ '''Add the specified color palettes to the PyMOL OpenGL menu.'''
302+ palettes = _get_palettes (palette_name )
303+ for palette in palettes :
304+ _add_palette_menu (palette )
207305
208- def remove_menu ():
209- '''Remove the cb_colors menu.'''
306+
307+ def remove_menu (palette_name = None ):
308+ '''Remove the color palette menu(s).'''
309+ palettes = _get_palettes (palette_name )
210310 all_colors_list = pymol .pymol .menu .all_colors_list
211- if all_colors_list [- 1 ][0 ] == 'cb_colors' :
212- all_colors_list .pop ()
213- print ('The `cb_colors` menu has been removed.' )
214- else :
215- print ('The `cb_colors` menu was not found! Aborting.' )
311+ for palette in palettes :
312+ initial_length = len (all_colors_list )
313+ all_colors_list [:] = [
314+ color_menu for color_menu in all_colors_list
315+ if color_menu [0 ] != palette .name
316+ ]
317+ if len (all_colors_list ) == initial_length :
318+ print (f'No menu for { palette .name } palette found. Nothing deleted.' )
319+ else :
320+ print (f'Deleted menu for { palette .name } palette.' )
216321
217322
218323if __name__ == "pymol" :
0 commit comments