16
16
reference to consult when making all kinds of figures, not just those made
17
17
using PyMOL.
18
18
19
- The colors are :
19
+ The "colorblind" color palette includes :
20
20
21
21
* cb_black
22
22
* cb_orange
27
27
* cb_vermillion (also: cb_red, cb_redorange, cb_red_orange)
28
28
* cb_reddish_purple (also: cb_rose, cb_violet, cb_magenta)
29
29
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
+
30
34
USAGE
31
35
36
+ With the PyMOL Script Repo installed and importable, import the module and
37
+ set the colors:
38
+
39
+ ```
32
40
import colorblindfriendly as cbf
33
41
34
42
# Add the new colors
35
43
cbf.set_colors()
36
44
color myObject, cb_red
37
45
38
- # Replace built-in colors with cbf ones
46
+ # Replace built-in colors of same names with cbf ones
39
47
cbf.set_colors(replace=True)
40
48
color myOtherObject, yellow # actually cb_yellow
41
49
42
50
# Add a `cb_colors` menu item to the OpenGL GUI ([C] menu in the right panel)
43
51
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
+ ```
44
62
45
63
REQUIREMENTS
46
64
53
71
54
72
LICENSE
55
73
56
- Copyright (c) 2014-2017 Jared Sampson
74
+ Copyright (c) 2014-2025 Jared Sampson
57
75
58
76
Permission is hereby granted, free of charge, to any person obtaining a copy
59
77
of this software and associated documentation files (the "Software"), to deal
75
93
76
94
CHANGELOG
77
95
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
+
78
103
0.2.0 Complete overhaul for PyMOL 2.0 with conversion to module format.
79
104
Now, setting the new `cb_*` color values requires a call to the
80
105
`set_colors()` function after import. You can also now add a
83
108
84
109
'''
85
110
from __future__ import print_function
111
+ import math
112
+ from typing import NamedTuple , Optional
86
113
87
114
__author__ = 'Jared Sampson'
88
- __version__ = '0.2 .0'
115
+ __version__ = '0.4 .0'
89
116
90
117
import pymol
91
118
from pymol import cmd
92
119
93
120
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
130
129
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
131
136
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 ])
136
142
137
- for color , properties in CB_COLORS .items ():
138
- # RGB tuple shortcut
139
- rgb = properties ['rgb' ]
140
143
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 = ''
145
149
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 )
151
154
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 ))
159
155
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 ]]
164
222
165
223
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.'''
168
257
169
258
# Make sure cb_colors are installed.
170
- print ('Checking for colorblindfriendly colors...' )
259
+ print (f 'Checking for { palette . name } colors...' )
171
260
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
175
265
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 )
178
268
179
269
# Abort if PyMOL is too old.
180
270
try :
@@ -184,35 +274,50 @@ def add_menu():
184
274
return
185
275
186
276
# Add the menu
187
- print ('Adding cb_colors menu...' )
277
+ print (f 'Adding { palette . name } menu...' )
188
278
# mimic pymol.menu.all_colors_list format
189
279
# 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
+
199
291
# First `pymol` is the program instance, second is the Python module
200
292
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!' )
203
295
else :
204
- all_colors_list .append (cb_colors )
205
- print (' done.' )
296
+ all_colors_list .append (menu_colors )
297
+ print (' done.\n ' )
298
+
206
299
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 )
207
305
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 )
210
310
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.' )
216
321
217
322
218
323
if __name__ == "pymol" :
0 commit comments