├── .gitignore ├── LICENSE ├── README.md ├── cividis_disclaimer_pnnl.docx ├── cmaputil ├── __init__.py ├── cmaputil.py └── cvdutil.py ├── colormaps ├── cividis.lut ├── cividis.txt ├── cividisHexValues.txt └── cividis_COMSOL.txt ├── examples ├── README.md ├── example1_optimizeMap.py ├── example2_cdpsPlots.py ├── example3_simCVD.py └── example_nanosims_image.txt ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Battelle Memorial Institute 2 | 3 | 1. Battelle Memorial Institute (hereinafter Battelle) hereby grants 4 | permission to any person or entity lawfully obtaining a copy of this software 5 | and associated documentation files (hereinafter “the Software”) to 6 | redistribute and use the Software in source and binary forms, with or without 7 | modification. Such person or entity may use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and may permit 9 | others to do so, subject to the following conditions: 10 | 11 | + Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimers. 13 | 14 | + Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | + Other than as used herein, neither the name Battelle Memorial Institute or 19 | Battelle may be used in any form whatsoever without the express written 20 | consent of Battelle. 21 | 22 | 2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 24 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | ARE DISCLAIMED. IN NO EVENT SHALL BATTELLE OR CONTRIBUTORS BE LIABLE FOR ANY 26 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmaputil 2 | 3 | Colormap utility for manipulating, analyzing, and viewing colormaps 4 | 5 | Please see the following papers for more details: 6 | - [Optimizing colormaps with consideration for color vision deficiency to enable accurate interpretation of scientific data](http://journals.plos.org/plosone/article/comments?id=10.1371/journal.pone.0199239) 7 | - [NanoSIMS for biological applications: Current practices and analyses](http://avs.scitation.org/doi/full/10.1116/1.4993628) 8 | -------------------------------------------------------------------------------- /cividis_disclaimer_pnnl.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnnl/cmaputil/a153df50156b3a7bf93d44abae6696a7b01cd47a/cividis_disclaimer_pnnl.docx -------------------------------------------------------------------------------- /cmaputil/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module containing functions to process, evaluate, and manipulate 3 | colormaps. 4 | 5 | """ 6 | 7 | from .cmaputil import * 8 | 9 | 10 | __author__ = 'Jamie R. Nunez' 11 | __copyright__ = 'Copyright (c) 2017 PNNL' 12 | __version__ = '1.0' 13 | -------------------------------------------------------------------------------- /cmaputil/cmaputil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Utilities for working with standard colormaps 4 | 5 | @author: Jamie R. Nunez 6 | (C) 2017 - Pacific Northwest National Laboratory 7 | """ 8 | #%% Imports 9 | from __future__ import print_function 10 | from math import floor, sqrt, ceil 11 | from os.path import exists 12 | 13 | import matplotlib.cm as cm # Used with eval() 14 | import matplotlib.patches as mpatches 15 | import matplotlib.pyplot as plt 16 | from mpl_toolkits.mplot3d import Axes3D # Used to make 3D axes 17 | import numpy as np 18 | import scipy 19 | 20 | from colorspacious import cspace_convert 21 | 22 | #%% Global Variables 23 | CMAPS = ['Accent', 'Blues', 'BrBG', 'BuGn', 'BuPu', 'CMRmap', 'Dark2', 'GnBu', 24 | 'Greens', 'Greys', 'OrRd', 'Oranges', 'PRGn', 'Paired', 'Pastel1', 25 | 'Pastel2', 'PiYG', 'PuBu', 'PuBuGn', 'PuOr', 'PuRd', 'Purples', 26 | 'RdBu', 'RdGy', 'RdPu', 'RdYlBu', 'RdYlGn', 'Reds', 'Set1', 'Set2', 27 | 'Set3', 'Spectral', 'Wistia', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd', 28 | 'afmhot', 'autumn', 'binary', 'bone', 'brg', 'bwr', 'cool', 29 | 'coolwarm', 'copper', 'cubehelix', 'flag', 'gist_earth', 'gist_gray', 30 | 'gist_ncar', 'gist_stern', 'gist_yarg', 'gnuplot', 31 | 'gnuplot2', 'gray', 'hot', 'hsv', 'inferno', 'jet', 'magma', 32 | 'nipy_spectral', 'ocean', 'pink', 'plasma', 'prism', 'rainbow', 33 | 'seismic', 'spectral', 'spring', 'summer', 'terrain', 'viridis', 34 | 'winter'] 35 | CSPACE1 = 'sRGB1' 36 | CSPACE2 = 'CAM02-UCS' 37 | FLABEL = 20 # font size for labels (title and axis labels) 38 | FAX = 16 # font size for numbers on axes 39 | 40 | #%% Colormap Processing Functions 41 | 42 | 43 | def _check_cmap(cmap): 44 | ''' 45 | Checks passed cmap name. If it is invalid, throws ValueError. 46 | Otherwise, it does nothing. 47 | 48 | To use a custom-made colormap, save it to a .npy file then use the 49 | path and file name (e.g. 'path\example' for path\example.npy') as 50 | the cmap variable name. 51 | 52 | A name is considered invalid when not included in the CMAPS 53 | variable and a .npy file with its name could not be found. This 54 | list is manually created so it could be out of date depending on 55 | the time of this code's release. 56 | ''' 57 | 58 | if cmap is None or cmap not in CMAPS: 59 | if not exists(cmap + '.npy'): 60 | raise ValueError(cmap + ' not a valid colormap name.') 61 | return 62 | 63 | 64 | def create_isoluminant_map(data): 65 | ''' 66 | Takes in a colormap (name or RGB values) and cycles through all 67 | possible intensity values that are valid for all a',b' pairs 68 | present in the original colormap. Full matrix returned can be 69 | plotted to view range of intensities valid for that colormap as a 70 | whole or a single colormap within the returned matrix (selected 71 | using m[:, :, x], which represents one isoluminant colormap) can be 72 | plotted alone. 73 | 74 | Parameters 75 | ----------- 76 | data: str or ndarray 77 | Colormap name or RGB values 78 | 79 | Returns 80 | ----------- 81 | rgb: ndarray, dimensions = (3, 256, X) 82 | Isoluminant colormap matrix containing X isoluminant colormaps 83 | 84 | ''' 85 | 86 | _, jab = get_rgb_jab(data) 87 | minJ, maxJ = find_J_bounds(jab, report=False) 88 | 89 | # Exit if isoluminant map failed 90 | if minJ is None: 91 | return None 92 | 93 | # Continue if passed 94 | h = int(maxJ) - int(minJ) 95 | rgb = np.zeros((jab.shape[0], jab.shape[1], h)) 96 | for j in range(h): 97 | jab[0, :] = int(minJ) + j 98 | rgb[:, :, j] = convert(jab, CSPACE2, CSPACE1) 99 | 100 | return rgb 101 | 102 | 103 | def _find_J_bounds(j, a, b): 104 | ''' 105 | Tests J' values against a'b' pairs to determine the max range, 106 | within the range given by [j, maxj], of J's that can be used with 107 | this pair. Recursive function. 108 | 109 | All J'a'b' pairs will convert to a RGB value but not all of these 110 | values fall within normal color space so they are tested to be 111 | between 0 and 255. 112 | ''' 113 | 114 | test_rgb = cspace_convert([j, a, b], CSPACE2, CSPACE1) 115 | 116 | if _valid_rgb(test_rgb[0]) and _valid_rgb(test_rgb[1]) and \ 117 | _valid_rgb(test_rgb[2]): 118 | return True 119 | return False 120 | 121 | 122 | def find_J_bounds(data, report=True): 123 | ''' 124 | Takes in colormap name or J'a'b' values and finds the maximum and 125 | minumum J' values that all a'b' pairs fit with. This has to be 126 | done since the CAM02-UCS colorspace is not a perfect square. 127 | 128 | Invalid colormap names throw a ValueError. Refer to 129 | _check_cmap for more information. 130 | 131 | Parameters 132 | ----------- 133 | data: str or ndarray 134 | Colormap name or J'a'b' ndarray generated by get_rgb_jab 135 | report: boolean 136 | Decides whether results should be printed to the console. 137 | Default value is True. 138 | 139 | Returns 140 | ----------- 141 | minJ : int 142 | Lowest J' value that works with all a'b' pairs. 143 | maxJ : int 144 | Highest J' value that works with all a'b' pairs 145 | ''' 146 | 147 | # Read in J'a'b' values from variable directly or generate them 148 | if type(data) == str: 149 | _, m = get_rgb_jab(data) 150 | else: 151 | m = np.copy(data) 152 | 153 | # Test each a'b' pair for their max and min J' 154 | minJ = 0 155 | maxJ = 100 156 | 157 | if len(m.shape) > 1 and m.shape[1] > 3: 158 | for i in range(m.shape[1]): 159 | a = m[1, i] 160 | b = m[2, i] 161 | passed = [] 162 | J = minJ 163 | while J <= maxJ: 164 | if _find_J_bounds(J, a, b): 165 | passed.append(J) 166 | while _find_J_bounds(J + 5, a, b): 167 | passed.append(J) 168 | J += 5 169 | J += 0.1 170 | if len(passed) > 0: 171 | minJ = max(minJ, min(passed)) 172 | maxJ = min(maxJ, max(passed)) 173 | 174 | else: 175 | a = m[1] 176 | b = m[2] 177 | passed = [] 178 | J = 0.5 179 | while J <= 100: 180 | if _find_J_bounds(J, a, b): 181 | passed.append(J) 182 | while _find_J_bounds(J + 5, a, b): 183 | passed.append(J) 184 | J += 5 185 | J += 0.1 186 | minJ = min(passed) 187 | maxJ = max(passed) 188 | 189 | if report: 190 | print('Passed: ' + str([minJ, maxJ])) 191 | return minJ, maxJ 192 | 193 | 194 | def convert(data, from_space, to_space): 195 | ''' 196 | Takes a single color value or matrix of values and converts to the 197 | desired colorspace 198 | 199 | Parameters 200 | ----------- 201 | data: 3 x COL array 202 | Colormap name OR array with complete color data. Invalid 203 | colormap names throw a ValueError. Refer to _check_cmap for 204 | more information. 205 | from_space: str 206 | Colorspace the current color value(s) reside(s) in 207 | to_space: str 208 | Colorspace to convert the color value(s) to 209 | 210 | Returns 211 | ----------- 212 | n : 3 x COL ndarray 213 | RGB values for each converted color value 214 | ''' 215 | if from_space == CSPACE1: 216 | data = np.clip(data, 0, 1) 217 | new = cspace_convert(data.T, from_space, to_space).T 218 | if to_space == CSPACE1: 219 | new = np.clip(new, 0, 1) 220 | return new 221 | 222 | 223 | def _get_rgb(cmap): 224 | # Get RGB Values 225 | if cmap in CMAPS: 226 | rgb = np.zeros((3, 256)) 227 | c = eval('cm.' + cmap) 228 | for i in range(256): 229 | rgb[:, i] = c(i)[:-1] 230 | else: 231 | rgb = np.load(cmap + '.npy') 232 | if rgb.shape[0] != 3: 233 | rgb = rgb.T 234 | return rgb 235 | 236 | 237 | def get_rgb_jab(data, calc_jab=True): 238 | ''' 239 | Accepts cmap name or data and creates its corresponding RGB and J'a'b' 240 | matrices. 241 | 242 | Parameters 243 | ----------- 244 | data: string or 3 x 256 array 245 | Colormap name OR array with complete color data. Invalid 246 | colormap names throw a ValueError. Refer to _check_cmap for 247 | more information. 248 | 249 | Returns 250 | ----------- 251 | rgb : 3 x 256 ndarray 252 | RGB values for each value in the colormap 253 | jab : 3 x 256 ndarray 254 | J'a'b' values corresponding to each RGB value 255 | ''' 256 | 257 | # Colormap name passed in - get RGB values 258 | if type(data) == str: 259 | cmap = data 260 | _check_cmap(cmap) 261 | rgb = _get_rgb(cmap) 262 | 263 | # RGB values passed in 264 | else: 265 | rgb = data 266 | 267 | rgb = np.clip(rgb, 0, 1) 268 | 269 | if calc_jab: 270 | # Convert RGB -> J'a'b' 271 | jab = convert(rgb, CSPACE1, CSPACE2) 272 | 273 | # Ensure J' is valid (between 0 and 100) 274 | jab[0, :] = np.clip(jab[0, :], 0, 100) 275 | else: 276 | jab = None 277 | 278 | return rgb, jab 279 | 280 | 281 | #%% Image Processing Functions 282 | def _adjust_bounds(a, minimum, maximum): 283 | ''' 284 | Takes in an array and adjusts all values to be between the min and 285 | max values. Relative magnitude does not change (i.e., numbers still 286 | keep their place in the overall order of min to max values). 287 | 288 | Parameters 289 | ----------- 290 | a : array 291 | Array to be adjusted 292 | minimum : int 293 | Minimum value for new array 294 | maximum : int 295 | Maximum value for new array 296 | 297 | Returns 298 | ----------- 299 | new_array : ndarray 300 | Returns new array (same size as the original) with all values 301 | adjusted to be between the min and max value. 302 | ''' 303 | r = maximum - minimum 304 | new_array = np.copy(a) 305 | mult = float(r) / float(np.max(new_array) - np.min(new_array)) 306 | new_array *= mult # Change data range 307 | new_array += minimum - np.min(new_array) # Start at min value 308 | return new_array 309 | 310 | 311 | def bound(a, high, low): 312 | ''' 313 | Forces all values within an array to be between a specified high 314 | and low value. Values that are smaller than the low value are set 315 | to equal the low value and values that are larger than the high 316 | value are set to equal the high value. Numbers already in this 317 | range are not changed. 318 | 319 | It helps to use the normalize function first to have values based 320 | on their std. dev. so outliers can be easily dealt with. 321 | 322 | Parameters 323 | ----------- 324 | a : array 325 | Array to be bounded 326 | high : float 327 | Upper bound 328 | low : float 329 | Lower bound 330 | 331 | Returns 332 | ----------- 333 | new_array : array 334 | Bounded array. Same size as the original array. 335 | ''' 336 | 337 | new_array = np.copy(a) 338 | new_array[new_array > high] = high 339 | new_array[new_array < low] = low 340 | return new_array 341 | 342 | 343 | def mix_images(img1, img2, cmap, high, low, name=None, maprevolve=False): 344 | ''' 345 | First, checks if passed colormap is valid for mixing images. It is 346 | not valid if the colormap itself is not valid or if each a'b' pair 347 | can not cycle through a range of J' values. See find_J_bounds for 348 | more information. 349 | 350 | Next, plots the colormap with all possible J' values for each a'b' 351 | pair. Can do a full 3D rotation if needed but this makes 352 | computing time about 10X longer. 353 | 354 | Lastly, creates a new image with colors corresponding to img1 355 | values and lightness values corresponding to img2. 356 | 357 | Invalid colormap names throw a ValueError. Refer to 358 | _check_cmap for more information. 359 | 360 | Parameters 361 | ----------- 362 | img1 : array 363 | Image to set color 364 | img2 : array 365 | Image to set lightness 366 | cmap : string 367 | Name of colormap to be used. Note: not all colormaps will work. 368 | high : float 369 | Max cutoff for img1 values (all values higher will be set to 370 | this). Keeps outliers from affecting the quality of the output 371 | low : float 372 | Min cutoff for img1 values (all values lower will be set to 373 | this and then colored black in the final image (as background) 374 | name : string 375 | Name of file to be saved to. Make sure to include the file type 376 | (e.g. .png, .pdf). If name is None, the file will not be saved. 377 | Default value is None. 378 | maprevolve : boolean 379 | Decides whether full 3D rotation images will be plotted and 380 | saved for the 3D colormap plot. Extends total computation time 381 | about 10X. Is considered False is name is None. Default value 382 | is False. 383 | 384 | Returns 385 | ----------- 386 | img1_rgb : ndarray 387 | Img1 colored using the colormap 388 | img1_iso : ndarray 389 | Img1 colored using the isoluminant colormap generated 390 | new_rgb : ndarray 391 | Mixed image 392 | ''' 393 | 394 | # Find J bounds to use on image (if possible) 395 | cmap_rgb, cmap_jab = get_rgb_jab(cmap) 396 | minJ, maxJ = find_J_bounds(cmap_jab, report=False) 397 | 398 | # Case 1: Colormap failed. 399 | if minJ is None: 400 | print('Colormap failed. Try a different one!') 401 | return None, None, None, None 402 | 403 | # Case 2: Colormap passed! Mixin' time! 404 | else: 405 | 406 | plot_3D_colormap(cmap_jab, minJ, maxJ, name=name, 407 | maprevolve=maprevolve) 408 | 409 | # Initialize 410 | img1_rgb = np.zeros((img1.shape[0], img1.shape[1], 3), dtype=np.uint8) 411 | img1_jab = np.zeros((img1.shape[0], img1.shape[1], 2)) 412 | img1_iso = np.zeros(img1_rgb.shape, dtype=np.uint8) 413 | new_rgb = np.zeros(img1_rgb.shape, dtype=np.uint8) 414 | 415 | # Save RGB & J'a'b' values corresponding to colormap 416 | for i in range(img1.shape[0]): 417 | for j in range(img1.shape[1]): 418 | value = int(round((img1[i, j] - low) * 255 / (high - low))) 419 | trgb = cmap_rgb[:, value] 420 | tjab = cmap_jab[:, value] 421 | img1_rgb[i, j, :] = [trgb[0] * 255, trgb[1] * 255, 422 | trgb[2] * 255] 423 | img1_jab[i, j, :] = [tjab[1], tjab[2]] 424 | 425 | # Combine a' & b' values of Img1 with J' of Img2 426 | j_values = _adjust_bounds(img2, minJ, maxJ) 427 | for i in range(img1.shape[0]): 428 | for j in range(img1.shape[1]): 429 | if img1[i, j] > low: 430 | a = img1_jab[i, j, 0] 431 | b = img1_jab[i, j, 1] 432 | img1_iso[i, j, :] = convert([(maxJ + minJ) / 2, a, b], 433 | CSPACE2, CSPACE1) * 255 434 | new_rgb[i, j, :] = convert([j_values[i, j], a, b], 435 | CSPACE2, CSPACE1) * 255 436 | 437 | return img1_rgb, img1_iso, new_rgb 438 | 439 | 440 | def normalize(a): 441 | ''' 442 | Normalize array so the average is at 0 and the std. dev. is 1. 443 | 444 | Parameters 445 | ----------- 446 | a : array 447 | Array to be normalized 448 | 449 | Returns 450 | ----------- 451 | new_array : array 452 | Normalized array. Same size as the original array. 453 | ''' 454 | 455 | new_array = np.copy(a) 456 | return (new_array - np.average(new_array)) / np.std(new_array) 457 | 458 | 459 | #%% Math Functions 460 | def _find_distance(p1, p2): 461 | ''' 462 | Finds the distance between two points. Must be the same length. 463 | ''' 464 | 465 | if len(p1) != len(p2): 466 | return ValueError 467 | val = 0 468 | for i in range(len(p1)): 469 | val += (p2[i] - p1[i]) ** 2 470 | return sqrt(val) 471 | 472 | 473 | def _rnt(num, shift='None'): 474 | ''' 475 | Rounds number to the nearest 10 digit. Returned as int. 476 | ''' 477 | if shift.lower() == 'upper': 478 | return int(ceil(num / 10.0) * 10) 479 | elif shift.lower() == 'lower': 480 | return int(floor(num / 10.0) * 10) 481 | else: 482 | return int(round(num / 10.0) * 10) 483 | 484 | 485 | def _valid_rgb(num, e=0.001): 486 | ''' 487 | Returns whether or not the number is both finite and between 0 and 1 488 | (within allowed error) to test whether it is valid RGB value. 489 | ''' 490 | return np.isfinite(num) and num < (1 + e) and num > -e 491 | 492 | 493 | #%% Plotting Functions 494 | def _plot_3D(ax, m, rgb, lims, ticks): 495 | ''' 496 | Plots J'a'b' values of colormap in 3D space. Points are colored 497 | with their corresponding RGB value. 498 | ''' 499 | 500 | # Plot 501 | for i in range(m.shape[1]): 502 | c = tuple(rgb[:, i]) 503 | ax.scatter(m[1, i], m[2, i], m[0, i], c=c, alpha=0.3, s=80, lw=0) 504 | 505 | # Format 506 | labels = ['J\'', 'a\'', 'b\''] 507 | ax.set_xlabel(labels[1], fontsize=FLABEL) 508 | ax.set_ylabel(labels[2], fontsize=FLABEL) 509 | ax.set_zlabel(labels[0], fontsize=FLABEL) 510 | ax.set_xlim(left=lims[0], right=lims[1]) 511 | ax.set_ylim(bottom=lims[2], top=lims[3]) 512 | ax.set_zlim(bottom=lims[4], top=lims[5]) 513 | ax.set_xticks([ticks[0], ticks[1]]) 514 | ax.set_yticks([ticks[2], ticks[3]]) 515 | ax.set_zticks([ticks[4], ticks[5]]) 516 | plt.xticks(fontsize=FAX) 517 | plt.yticks(fontsize=FAX) 518 | plt.axis('off') 519 | return 520 | 521 | 522 | def plot_3D_colormap(jab, minJ, maxJ, name=None, maprevolve=False): 523 | ''' 524 | Plots colormap, showing all available J'a'b' values through 525 | CAM02-UCS space. 526 | 527 | Parameters 528 | ------------- 529 | jab : ndarray 530 | J'a'b' set generated from get_rgb_jab. J' values do not really 531 | matter. 532 | minJ : int 533 | Minimum J value found to work with all a'b' pairs 534 | maxJ : int 535 | Maximum J value found to work with all a'b' pairs 536 | name : string 537 | Name of file to be saved to. Make sure to include the file type 538 | (e.g. .png, .pdf). If name is None, the file will not be saved. 539 | Default value is None. 540 | maprevolve : boolean 541 | Decides whether full 3D rotation images will be plotted and 542 | saved for the 3D colormap plot. Extends total computation time 543 | about 10X. Is considered False is name is None. Default value 544 | is False. 545 | 546 | Returns 547 | ----------- 548 | None. 549 | ''' 550 | 551 | m = np.copy(jab) 552 | 553 | # Set Up 554 | fig = plt.figure(figsize=(4, 4)) 555 | ax = fig.add_subplot(111, projection='3d') 556 | topJ = _rnt(maxJ) 557 | botJ = _rnt(minJ) 558 | topa = _rnt(max(m[1, :]), shift='upper') 559 | bota = _rnt(min(m[1, :]), shift='lower') 560 | topb = _rnt(max(m[2, :]), shift='upper') 561 | botb = _rnt(min(m[2, :]), shift='lower') 562 | lims = [bota, topa, botb, topb, botJ, topJ] 563 | 564 | # Find RGBs for each possible J 565 | for J in range(minJ, maxJ + 1): 566 | m[0, :] = J 567 | rgb = np.zeros(m.shape) 568 | for col in range(m.shape[1]): 569 | rgb[:, col] = convert(m[:, col], CSPACE2, CSPACE1) 570 | _plot_3D(ax, m, rgb, lims, lims) 571 | 572 | # Plot and save rotating image 573 | if name is not None and maprevolve: 574 | for a in np.arange(0, 360, 30): 575 | ax.view_init(45, a) 576 | plt.draw() 577 | plt.savefig(name + 'angle=' + str(a) + '.png') 578 | plt.show() 579 | return 580 | 581 | 582 | def plot_colormap(data, iso=False, ax=None): 583 | ''' 584 | Plots the colorbar of a given colormap. 585 | 586 | Invalid colormap names throw a ValueError. Refer to 587 | _check_cmap for more information. 588 | 589 | Parameters 590 | ----------- 591 | data: string OR 2-3D array 592 | Colormap name OR Colormap RGB values to use 593 | iso: boolean 594 | Whether or not to generate the isolum manp first. Default is 595 | False. Ignored if RGB values passed in directly. 596 | ax: matplotlib ax object (optional) 597 | Used for adding cmap as a subplot to larger plot. Default is to 598 | create a new plot. 599 | 600 | Returns 601 | ------- 602 | ''' 603 | 604 | # Colormap name passed in - get RGB values 605 | if type(data) == str: 606 | cmap = data 607 | _check_cmap(cmap) 608 | 609 | if iso: 610 | rgb = create_isoluminant_map(cmap) 611 | else: 612 | rgb, _ = get_rgb_jab(cmap) 613 | 614 | # RGB values passed in 615 | else: 616 | rgb = data 617 | 618 | if rgb is None: 619 | return None 620 | 621 | # Create plot 622 | newfig = ax == None 623 | if newfig: 624 | plt.figure(figsize=(6, 6)) 625 | ax = plt.subplot(111) 626 | 627 | # RGB values generated, now plot 628 | if len(rgb.shape) == 2: 629 | for i in range(rgb.shape[1]): 630 | c = tuple(rgb[:, i]) 631 | plt.scatter([i] * 100, np.linspace(0, 10, 100), c=c, lw=0) 632 | else: 633 | k = 10.0 / rgb.shape[2] 634 | y = max(50 / rgb.shape[2], 5) 635 | for j in range(rgb.shape[2]): 636 | for i in range(rgb.shape[1]): 637 | c = tuple(rgb[:, i, j]) 638 | plt.scatter([i] * y, np.linspace(k * j, k * (j + 1), y), c=c, lw=0) 639 | 640 | # Final formatting 641 | ax.set_aspect(5) 642 | plt.axis([0, rgb.shape[1], 0, 10]) 643 | plt.axis('off') 644 | 645 | return rgb 646 | 647 | 648 | def plot_colormap_info(fig, data, sp=[5, 1, 1], name=None, show=True, leg=False): 649 | ''' 650 | Takes in the name of a colormap and plots its colorbar, distance 651 | between each point on the map, and its 3D J'a'b' map with each 652 | point colored with its corresponding RGB value. 653 | 654 | Invalid colormap names throw a ValueError. Refer to 655 | _check_cmap for more information. 656 | 657 | Parameters 658 | ----------- 659 | cmap: string 660 | Colormap name 661 | name : string 662 | Name of file to be saved to. Make sure to include the file type 663 | (e.g. .png, .pdf). If name is None, the file will not be saved. 664 | Default value is None. 665 | 666 | Returns 667 | ----------- 668 | None. 669 | ''' 670 | 671 | rgb, m = get_rgb_jab(data) 672 | 673 | # Set up for figure 674 | if fig is None: 675 | fig = plt.figure(figsize=(6, 12)) 676 | 677 | # Show colorbar 678 | ax = plt.subplot(sp[0], sp[1], sp[2]) 679 | plot_colormap(rgb, ax=ax) 680 | 681 | # Show colorbar test 682 | ax = plt.subplot(sp[0], sp[1], sp[2] + sp[1]) 683 | plt.imshow(test_colormap(rgb, ax=ax)) 684 | plt.axis('off') 685 | ax.set_aspect(1.2) 686 | 687 | # 2D Plot - J'a'b' values 688 | ax = fig.add_subplot(sp[0], sp[1], sp[2] + sp[1] * 2) 689 | _plot_jab(m, leg=leg) 690 | ax.spines['top'].set_visible(False) 691 | ax.spines['right'].set_visible(False) 692 | 693 | # 2D Plot - Distance Between Points (Perceptual Deltas) 694 | ax = fig.add_subplot(sp[0], sp[1], sp[2] + sp[1] * 3) 695 | _plot_pd(m) 696 | ax.spines['top'].set_visible(False) 697 | ax.spines['right'].set_visible(False) 698 | 699 | # 3D Plot - Colormap Representation in CAM02-UCS 700 | ax = fig.add_subplot(sp[0], sp[1], sp[2] + + sp[1] * 4, projection='3d') 701 | lims = [-32.3, 41.6, -39.3, 35.7, 0, 100] 702 | ticks = [-30, 30, -30, 30, 0, 100] 703 | _plot_3D(ax, m, rgb, lims, ticks) 704 | 705 | # Save figure 706 | if name is not None: 707 | plt.savefig(name, transparent=True) 708 | if show: 709 | plt.show() 710 | return 711 | 712 | 713 | def _plot_jab(m, leg=False): 714 | ''' 715 | Plots J'a'b' values 716 | ''' 717 | # plt.title('CAM02-UCS Colorspace', fontsize=FLABEL) 718 | c1 = (45 / 255.0, 145 / 255.0, 167 / 255.0, 1) 719 | c2 = (220 / 255.0, 41 / 255.0, 12 / 255.0, 1) 720 | j, = plt.plot(m[0, :], 'k', lw=3, label='J\'') 721 | a, = plt.plot(m[1, :], c=c1, lw=3, label='a\'') 722 | b, = plt.plot(m[2, :], c=c2, lw=3, label='b\'') 723 | plt.tick_params(which='both', left='off', right='off') 724 | plt.xticks([]) 725 | plt.yticks([-40, 0, 40, 80], fontsize=FAX) 726 | plt.axis([0, m.shape[1], -40, 100]) 727 | 728 | if leg: 729 | plt.legend(handles=[j, a, b], loc='upper left', fontsize=FAX) 730 | 731 | return 732 | 733 | 734 | def _plot_pd(m, show=True): 735 | ''' 736 | Plots perceptual deltas as shown in https://bids.github.io/colormap 737 | ''' 738 | # plt.title('Perceptual Deltas', fontsize=FLABEL) 739 | d = np.zeros(m.shape[1] - 1) 740 | for i in range(m.shape[1] - 1): 741 | d[i] = _find_distance(m[:, i], m[:, i+1]) 742 | 743 | if show: 744 | ymax = max(3, np.max(d)) 745 | plt.xlim(left=1, right=m.shape[1]-1) 746 | plt.ylim(bottom=0, top=ymax) 747 | plt.xticks([]) 748 | plt.yticks([0, floor(ymax)], fontsize=FAX) 749 | plt.plot(d, 'k', lw=3) 750 | plt.tick_params(which='both', bottom='off', right='off', top='off') 751 | # plt.ylabel('Dist. to Next Pt.', fontsize=FLABEL) 752 | # plt.axis('off') 753 | return d 754 | 755 | 756 | def plot_linear_Js(low, high, j1, j2, name=None): 757 | plt.figure(figsize=(6, 6)) 758 | c1 = (99 / 255.0, 198 / 255.0, 10 / 255.0) 759 | c2 = (184 / 255.0, 156 / 255.0, 239 / 255.0) 760 | plt.plot(high, 'k', lw=4) 761 | plt.plot(low, 'k', lw=4) 762 | if j1 is not None: 763 | plt.plot(j1[0, :], c='k', lw=4.8) 764 | plt.plot(j1[0, :], c=c1, lw=4) 765 | if j2 is not None: 766 | plt.plot(j2[0, :], 'k', lw=4.8) 767 | plt.plot(j2[0, :], c=c2, lw=4) 768 | plt.axis([0, 256, 0, 100]) 769 | plt.xticks([]) 770 | plt.yticks([0, 50, 100], fontsize=FAX) 771 | plt.ylabel('J\'', fontsize=FLABEL) 772 | plt.title('J\' Fits Used', fontsize=FLABEL) 773 | p1 = mpatches.Patch(color='k', label='Bounds') 774 | p2 = mpatches.Patch(color=c1, label='Fit to Original') 775 | p3 = mpatches.Patch(color=c2, label='Maximize Range') 776 | patches = [p1, p2, p3] 777 | plt.legend(handles=patches, loc='lower right', fontsize=FAX) 778 | if name is not None: 779 | plt.savefig(name, dpi=300) 780 | plt.show() 781 | 782 | 783 | def _correct_J(low, high, line, m): 784 | test_upper = [x for x in high - line if x < 0] 785 | test_lower = [x for x in line - low if x < 0] 786 | if len(test_upper) + len(test_lower) == 0: 787 | m = np.copy(m) 788 | m[0, :] = line 789 | return m 790 | else: 791 | return None 792 | 793 | 794 | def lin_fit(y): 795 | x = range(256) 796 | x = np.vstack([x, np.ones(len(x))]).T 797 | a, _, _, _ = np.linalg.lstsq(x, y) 798 | return (a * x)[:, 0] 799 | 800 | 801 | def correct_J(m, name=None, delta_slope=1, delta_b=1): 802 | 803 | # Get max and min boundaries for each a, b pair 804 | l = m.shape[1] 805 | high = np.zeros((l)) 806 | low = np.zeros((l)) 807 | for i in range(l): 808 | l, h = find_J_bounds(m[:, i], report=False) 809 | low[i], high[i] = l, h 810 | 811 | # Method 1: Fit to existing line 812 | m1 = np.copy(m) 813 | slope, b = np.polyfit(range(256), m[0, :], 1) 814 | line_fit = slope * np.copy(range(256)) + b 815 | if max(line_fit) > 99: 816 | temp = list(m[0, :] - 99) 817 | line_fit = lin_fit(temp) + (99 - np.max(lin_fit(temp))) 818 | if min(line_fit) < 1: 819 | if line_fit[0] < line_fit[-1]: 820 | line_fit = np.linspace(1, 99, 256) 821 | else: 822 | line_fit = np.linspace(99, 1, 256) 823 | m1[0, :] = line_fit 824 | 825 | # Method 2: Maximize change in J 826 | m2 = None 827 | # if [x for x in (high - min(low)) if x < 0] 828 | if m[0, 0] <= m[0, -1]: 829 | delta_slope = -abs(delta_slope) 830 | slope = high[-1] - low[0] 831 | if slope < 0: 832 | plot_linear_Js(low, high, m1, None, name=name) 833 | return m1, None 834 | else: 835 | delta_slope = abs(delta_slope) 836 | slope = low[-1] - high[0] 837 | if slope > 0: 838 | plot_linear_Js(low, high, m1, None, name=name) 839 | return m1, None 840 | 841 | max_b = max(high[0], high[-1]) 842 | while slope != 0: 843 | b = low[0] 844 | while b <= max_b: 845 | line_fit = (slope / 256.0) * np.asarray(range(256)) + b 846 | # plot_linear_Js(low, high, m1, m2) 847 | # test_upper = [x for x in high - line_fit2 if x < 0] 848 | # test_lower = [x for x in line_fit2 - low if x < 0] 849 | # if len(test_upper) + len(test_lower) == 0: 850 | # plot_linear_Js(low, high, line_fit1, line_fit2, name=name) 851 | # m2 = np.copy(m) 852 | # m2[0, :] = line_fit2 853 | # return m1, m2 854 | m2 = _correct_J(low, high, line_fit, m) 855 | if m2 is not None: 856 | plot_linear_Js(low, high, m1, m2, name=name) 857 | return m1, m2 858 | elif line_fit[-1] > high[-1] or line_fit[0] > high[0]: 859 | b = 100 860 | else: 861 | b += delta_b 862 | slope += delta_slope 863 | plot_linear_Js(low, high, m1, None, name=name) 864 | return m1, None 865 | 866 | 867 | # Make jab perceptually uniform 868 | def make_linear(jab, l=10000): 869 | 870 | jab = np.copy(jab) 871 | 872 | # Interpolate 873 | old_x = range(256) 874 | new_x = np.linspace(0, 255, l) 875 | long_a = np.interp(new_x, old_x, jab[1, :]) 876 | long_b = np.interp(new_x, old_x, jab[2, :]) 877 | 878 | total_length = 0 879 | for i in range(1, l): 880 | total_length += _find_distance([long_a[i - 1], long_b[i - 1]], 881 | [long_a[i], long_b[i]]) 882 | d = total_length / 255 # Desired distance between points 883 | 884 | # Modify a & b 885 | jab_ind = 0 886 | long_ind = 1 887 | d_this = 0 888 | while jab_ind < 254 and long_ind < l - 1: 889 | test_d1 = _find_distance([long_a[long_ind - 1], long_b[long_ind - 1]], 890 | [long_a[long_ind], long_b[long_ind]]) 891 | test_d2 = _find_distance([long_a[long_ind], long_b[long_ind]], 892 | [long_a[long_ind + 1], long_b[long_ind + 1]]) 893 | 894 | d_this += test_d1 895 | d_next = d_this + test_d2 896 | # If the distance to this point is a minima, add. Else, move onto next 897 | if abs(d * (jab_ind + 1) - d_this) < abs(d * (jab_ind + 1) - d_next): 898 | jab_ind += 1 899 | jab[1, jab_ind] = long_a[long_ind] 900 | jab[2, jab_ind] = long_b[long_ind] 901 | long_ind += 1 902 | 903 | return jab 904 | 905 | 906 | def overlay_colormap(img, cmap_rgb, ax=None, name=None, plot_ready=True): 907 | # newfig = ax == None 908 | # if newfig: 909 | # plt.figure(figsize=(8, 4)) 910 | # ax = plt.subplot(1, 1, 1) 911 | img = np.copy(img) 912 | if plot_ready: 913 | img_rgb = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8) 914 | else: 915 | img_rgb = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.float16) 916 | max_val = cmap_rgb.shape[1] 917 | for i in range(img.shape[0]): 918 | for j in range(img.shape[1]): 919 | value = int(round(((img[i, j] - np.min(img)) * (max_val - 1)) / (np.max(img) - np.min(img)))) 920 | trgb = cmap_rgb[:, value] 921 | if plot_ready: 922 | img_rgb[i, j, :] = [trgb[0] * 255, trgb[1] * 255, trgb[2] * 255] 923 | else: 924 | img_rgb[i, j, :] = [trgb[0], trgb[1], trgb[2]] 925 | # if newfig: 926 | # plt.imshow(img_rgb) 927 | # plt.axis('off') 928 | # ax.set_aspect(1.2) 929 | 930 | return img_rgb 931 | 932 | 933 | def test_colormap(cmap, ax=None, name=None): 934 | sin_mag = 8.0 935 | rgb, _ = get_rgb_jab(cmap) 936 | h = 45 937 | w = 256 938 | x = range(w) 939 | img = np.zeros((h, w)) 940 | 941 | for i in range(h): 942 | img[i, :] = (sin_mag - sin_mag * i / h) * np.sin(x) + x 943 | img = normalize(img) 944 | img_rgb = overlay_colormap(img, rgb, ax=ax, name=name) 945 | return img_rgb 946 | 947 | 948 | def cdps_plot(img, cmap, rgb, num, gslope): 949 | 950 | plt.figure(figsize=(4, 4)) 951 | 952 | img_overlay = overlay_colormap(img, rgb, plot_ready=False)[0, :, :] 953 | slice_jab = convert(img_overlay.T, CSPACE1, CSPACE2) 954 | 955 | overlay_pd = _plot_pd(slice_jab, show=False) 956 | 957 | data_pd = np.zeros(img_overlay.shape[0] - 1) 958 | for i in range(data_pd.shape[0]): 959 | data_pd[i] = img[0, i + 1] - img[0, i] 960 | 961 | # Get x and y 962 | x = abs(data_pd) 963 | y = overlay_pd / gslope 964 | 965 | # R^2 966 | coeffs = np.polyfit(x, y, 1) 967 | coeffs[1] = 0 968 | p = np.poly1d(coeffs) 969 | yhat = p(x) 970 | ybar = np.sum(y)/len(y) 971 | ssreg = np.sum((yhat-ybar)**2) 972 | sstot = np.sum((y - ybar)**2) 973 | 974 | # Plot fitted line 975 | plt.plot(x, yhat, 'gray', lw=2, zorder=-32) 976 | plt.scatter(x, y, c='k', s=40, zorder=32) 977 | 978 | # Final formatting 979 | plt.xticks([]) 980 | plt.yticks([]) 981 | plt.axis([0, 1, 0, 2]) 982 | plt.title('y = %.2fx + %.2f, R2 = %.3f' % (coeffs[0], coeffs[1], ssreg / sstot)) 983 | plt.show() 984 | -------------------------------------------------------------------------------- /cmaputil/cvdutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ETo be renamed cvdutil 4 | Utilities for working in color vision deficiency (CVD) space 5 | @author: Jamie R. Nunez 6 | (C) 2017 - Pacific Northwest National Laboratory 7 | """ 8 | #%% Imports 9 | import numpy as np 10 | 11 | import cmaputil as cmu 12 | 13 | #%% Global Variables 14 | SEV = 100 15 | CSPACE1 = cmu.CSPACE1 16 | CSPACE2 = cmu.CSPACE2 17 | CVD_TYPE = 'deuteranomaly' 18 | 19 | #%% Functions 20 | 21 | def get_cvd(data, cvd_type=CVD_TYPE, severity=100): 22 | ''' 23 | Converts RGB values to CVD space RGB values. 24 | 25 | Parameters 26 | ---------- 27 | data: string or 3 x 256 array 28 | Colormap name OR array with complete color data. Invalid 29 | colormap names throw a ValueError. Refer to _check_cmap for 30 | more information. 31 | cvd_type: string 32 | Type of CVD to be simulated. Options: deuteranomaly or 33 | protanomaly. Default: deuteranomaly. 34 | severity: int 35 | Severity of CVD to be simulated. Can be any integer between 0 36 | and 100. Default: 100 37 | Returns 38 | ---------- 39 | cvd: 3 x 256 array 40 | Colormap data in CVD space 41 | ''' 42 | 43 | rgb,_ = cmu.get_rgb_jab(data, calc_jab=False) 44 | cvd_space = {'name': 'sRGB1+CVD', 'cvd_type': cvd_type, 45 | 'severity': severity} 46 | cvd = cmu.convert(rgb, cvd_space, CSPACE1) 47 | return cvd 48 | 49 | def _iter_make_linear(jab): 50 | 51 | # Linearize a' and b' changes 52 | jab1 = cmu.make_linear(np.copy(jab)) 53 | 54 | # Convert J'a'b' values to RGB values and clip 55 | rgb1 = np.clip(cmu.convert(jab1, CSPACE2, CSPACE1), 0, 1) 56 | 57 | # Convert RGB to CVD RGB space 58 | rgb2 = get_cvd(rgb1) 59 | 60 | # Bring back to to J'a'b' 61 | jab2 = cmu.convert(rgb2, CSPACE1, CSPACE2) 62 | 63 | # Force to have same J' as original J'a'b' array 64 | jab2[0, :] = jab1[0, :] 65 | 66 | return jab2 67 | 68 | def iter_make_linear(jab, l=100000): 69 | ''' 70 | Takes J'a'b' array and performs the following two times: linearizes 71 | the change of a' and b' then converts to CVD space and back (to 72 | ensure colormap is still CVD compatible). 73 | 74 | Parameters 75 | ---------- 76 | data: 3 x 256 array 77 | J'a'b' values for colormap 78 | Returns 79 | ---------- 80 | rgb: 3 x 256 array 81 | RGB data 82 | jab: 3 x 256 array 83 | J'a'b' data 84 | ''' 85 | 86 | jab = np.copy(jab) 87 | jab = _iter_make_linear(_iter_make_linear(jab)) 88 | rgb = cmu.convert(jab, CSPACE2, CSPACE1) 89 | return rgb, jab -------------------------------------------------------------------------------- /colormaps/cividis.lut: -------------------------------------------------------------------------------- 1 | 0 32 76 2 | 0 32 78 3 | 0 33 80 4 | 0 34 81 5 | 0 35 83 6 | 0 35 85 7 | 0 36 86 8 | 0 37 88 9 | 0 38 90 10 | 0 38 91 11 | 0 39 93 12 | 0 40 95 13 | 0 40 97 14 | 0 41 99 15 | 0 42 100 16 | 0 42 102 17 | 0 43 104 18 | 0 44 106 19 | 0 45 108 20 | 0 45 109 21 | 0 46 110 22 | 0 46 111 23 | 0 47 111 24 | 0 47 111 25 | 0 48 111 26 | 0 49 111 27 | 0 49 111 28 | 0 50 110 29 | 0 51 110 30 | 0 52 110 31 | 0 52 110 32 | 1 53 110 33 | 6 54 110 34 | 10 55 109 35 | 14 55 109 36 | 18 56 109 37 | 21 57 109 38 | 23 57 109 39 | 26 58 108 40 | 28 59 108 41 | 30 60 108 42 | 32 60 108 43 | 34 61 108 44 | 36 62 108 45 | 38 62 108 46 | 39 63 108 47 | 41 64 107 48 | 43 65 107 49 | 44 65 107 50 | 46 66 107 51 | 47 67 107 52 | 49 68 107 53 | 50 68 107 54 | 51 69 107 55 | 53 70 107 56 | 54 70 107 57 | 55 71 107 58 | 56 72 107 59 | 58 73 107 60 | 59 73 107 61 | 60 74 107 62 | 61 75 107 63 | 62 75 107 64 | 64 76 107 65 | 65 77 107 66 | 66 78 107 67 | 67 78 107 68 | 68 79 107 69 | 69 80 107 70 | 70 80 107 71 | 71 81 107 72 | 72 82 107 73 | 73 83 107 74 | 74 83 107 75 | 75 84 107 76 | 76 85 107 77 | 77 85 107 78 | 78 86 107 79 | 79 87 108 80 | 80 88 108 81 | 81 88 108 82 | 82 89 108 83 | 83 90 108 84 | 84 90 108 85 | 85 91 108 86 | 86 92 108 87 | 87 93 109 88 | 88 93 109 89 | 89 94 109 90 | 90 95 109 91 | 91 95 109 92 | 92 96 109 93 | 93 97 110 94 | 94 98 110 95 | 95 98 110 96 | 95 99 110 97 | 96 100 110 98 | 97 101 111 99 | 98 101 111 100 | 99 102 111 101 | 100 103 111 102 | 101 103 111 103 | 102 104 112 104 | 103 105 112 105 | 104 106 112 106 | 104 106 112 107 | 105 107 113 108 | 106 108 113 109 | 107 109 113 110 | 108 109 114 111 | 109 110 114 112 | 110 111 114 113 | 111 111 114 114 | 111 112 115 115 | 112 113 115 116 | 113 114 115 117 | 114 114 116 118 | 115 115 116 119 | 116 116 117 120 | 117 117 117 121 | 117 117 117 122 | 118 118 118 123 | 119 119 118 124 | 120 120 118 125 | 121 120 119 126 | 122 121 119 127 | 123 122 119 128 | 123 123 120 129 | 124 123 120 130 | 125 124 120 131 | 126 125 120 132 | 127 126 120 133 | 128 126 120 134 | 129 127 120 135 | 130 128 120 136 | 131 129 120 137 | 132 129 120 138 | 133 130 120 139 | 134 131 120 140 | 135 132 120 141 | 136 133 120 142 | 137 133 120 143 | 138 134 120 144 | 139 135 120 145 | 140 136 120 146 | 141 136 120 147 | 142 137 120 148 | 143 138 120 149 | 144 139 120 150 | 145 140 120 151 | 146 140 120 152 | 147 141 120 153 | 148 142 120 154 | 149 143 120 155 | 150 143 119 156 | 151 144 119 157 | 152 145 119 158 | 153 146 119 159 | 154 147 119 160 | 155 147 119 161 | 156 148 119 162 | 157 149 119 163 | 158 150 118 164 | 159 151 118 165 | 160 152 118 166 | 161 152 118 167 | 162 153 118 168 | 163 154 117 169 | 164 155 117 170 | 165 156 117 171 | 166 156 117 172 | 167 157 117 173 | 168 158 116 174 | 169 159 116 175 | 170 160 116 176 | 171 161 116 177 | 172 161 115 178 | 173 162 115 179 | 174 163 115 180 | 175 164 115 181 | 176 165 114 182 | 177 166 114 183 | 178 166 114 184 | 180 167 113 185 | 181 168 113 186 | 182 169 113 187 | 183 170 112 188 | 184 171 112 189 | 185 171 112 190 | 186 172 111 191 | 187 173 111 192 | 188 174 110 193 | 189 175 110 194 | 190 176 110 195 | 191 177 109 196 | 192 177 109 197 | 193 178 108 198 | 194 179 108 199 | 195 180 108 200 | 197 181 107 201 | 198 182 107 202 | 199 183 106 203 | 200 184 106 204 | 201 184 105 205 | 202 185 105 206 | 203 186 104 207 | 204 187 104 208 | 205 188 103 209 | 206 189 103 210 | 208 190 102 211 | 209 191 102 212 | 210 192 101 213 | 211 192 101 214 | 212 193 100 215 | 213 194 99 216 | 214 195 99 217 | 215 196 98 218 | 216 197 97 219 | 217 198 97 220 | 219 199 96 221 | 220 200 96 222 | 221 201 95 223 | 222 202 94 224 | 223 203 93 225 | 224 203 93 226 | 225 204 92 227 | 227 205 91 228 | 228 206 91 229 | 229 207 90 230 | 230 208 89 231 | 231 209 88 232 | 232 210 87 233 | 233 211 86 234 | 235 212 86 235 | 236 213 85 236 | 237 214 84 237 | 238 215 83 238 | 239 216 82 239 | 240 217 81 240 | 241 218 80 241 | 243 219 79 242 | 244 220 78 243 | 245 221 77 244 | 246 222 76 245 | 247 223 75 246 | 249 224 73 247 | 250 224 72 248 | 251 225 71 249 | 252 226 70 250 | 253 227 69 251 | 255 228 67 252 | 255 229 66 253 | 255 230 66 254 | 255 231 67 255 | 255 232 68 256 | 255 233 69 257 | -------------------------------------------------------------------------------- /colormaps/cividis.txt: -------------------------------------------------------------------------------- 1 | 0.0000 0.1262 0.3015 2 | 0.0000 0.1292 0.3077 3 | 0.0000 0.1321 0.3142 4 | 0.0000 0.1350 0.3205 5 | 0.0000 0.1379 0.3269 6 | 0.0000 0.1408 0.3334 7 | 0.0000 0.1437 0.3400 8 | 0.0000 0.1465 0.3467 9 | 0.0000 0.1492 0.3537 10 | 0.0000 0.1519 0.3606 11 | 0.0000 0.1546 0.3676 12 | 0.0000 0.1574 0.3746 13 | 0.0000 0.1601 0.3817 14 | 0.0000 0.1629 0.3888 15 | 0.0000 0.1657 0.3960 16 | 0.0000 0.1685 0.4031 17 | 0.0000 0.1714 0.4102 18 | 0.0000 0.1743 0.4172 19 | 0.0000 0.1773 0.4241 20 | 0.0000 0.1798 0.4307 21 | 0.0000 0.1817 0.4347 22 | 0.0000 0.1834 0.4363 23 | 0.0000 0.1852 0.4368 24 | 0.0000 0.1872 0.4368 25 | 0.0000 0.1901 0.4365 26 | 0.0000 0.1930 0.4361 27 | 0.0000 0.1958 0.4356 28 | 0.0000 0.1987 0.4349 29 | 0.0000 0.2015 0.4343 30 | 0.0000 0.2044 0.4336 31 | 0.0000 0.2073 0.4329 32 | 0.0055 0.2101 0.4322 33 | 0.0236 0.2130 0.4314 34 | 0.0416 0.2158 0.4308 35 | 0.0576 0.2187 0.4301 36 | 0.0710 0.2215 0.4293 37 | 0.0827 0.2244 0.4287 38 | 0.0932 0.2272 0.4280 39 | 0.1030 0.2300 0.4274 40 | 0.1120 0.2329 0.4268 41 | 0.1204 0.2357 0.4262 42 | 0.1283 0.2385 0.4256 43 | 0.1359 0.2414 0.4251 44 | 0.1431 0.2442 0.4245 45 | 0.1500 0.2470 0.4241 46 | 0.1566 0.2498 0.4236 47 | 0.1630 0.2526 0.4232 48 | 0.1692 0.2555 0.4228 49 | 0.1752 0.2583 0.4224 50 | 0.1811 0.2611 0.4220 51 | 0.1868 0.2639 0.4217 52 | 0.1923 0.2667 0.4214 53 | 0.1977 0.2695 0.4212 54 | 0.2030 0.2723 0.4209 55 | 0.2082 0.2751 0.4207 56 | 0.2133 0.2780 0.4205 57 | 0.2183 0.2808 0.4204 58 | 0.2232 0.2836 0.4203 59 | 0.2281 0.2864 0.4202 60 | 0.2328 0.2892 0.4201 61 | 0.2375 0.2920 0.4200 62 | 0.2421 0.2948 0.4200 63 | 0.2466 0.2976 0.4200 64 | 0.2511 0.3004 0.4201 65 | 0.2556 0.3032 0.4201 66 | 0.2599 0.3060 0.4202 67 | 0.2643 0.3088 0.4203 68 | 0.2686 0.3116 0.4205 69 | 0.2728 0.3144 0.4206 70 | 0.2770 0.3172 0.4208 71 | 0.2811 0.3200 0.4210 72 | 0.2853 0.3228 0.4212 73 | 0.2894 0.3256 0.4215 74 | 0.2934 0.3284 0.4218 75 | 0.2974 0.3312 0.4221 76 | 0.3014 0.3340 0.4224 77 | 0.3054 0.3368 0.4227 78 | 0.3093 0.3396 0.4231 79 | 0.3132 0.3424 0.4236 80 | 0.3170 0.3453 0.4240 81 | 0.3209 0.3481 0.4244 82 | 0.3247 0.3509 0.4249 83 | 0.3285 0.3537 0.4254 84 | 0.3323 0.3565 0.4259 85 | 0.3361 0.3593 0.4264 86 | 0.3398 0.3622 0.4270 87 | 0.3435 0.3650 0.4276 88 | 0.3472 0.3678 0.4282 89 | 0.3509 0.3706 0.4288 90 | 0.3546 0.3734 0.4294 91 | 0.3582 0.3763 0.4302 92 | 0.3619 0.3791 0.4308 93 | 0.3655 0.3819 0.4316 94 | 0.3691 0.3848 0.4322 95 | 0.3727 0.3876 0.4331 96 | 0.3763 0.3904 0.4338 97 | 0.3798 0.3933 0.4346 98 | 0.3834 0.3961 0.4355 99 | 0.3869 0.3990 0.4364 100 | 0.3905 0.4018 0.4372 101 | 0.3940 0.4047 0.4381 102 | 0.3975 0.4075 0.4390 103 | 0.4010 0.4104 0.4400 104 | 0.4045 0.4132 0.4409 105 | 0.4080 0.4161 0.4419 106 | 0.4114 0.4189 0.4430 107 | 0.4149 0.4218 0.4440 108 | 0.4183 0.4247 0.4450 109 | 0.4218 0.4275 0.4462 110 | 0.4252 0.4304 0.4473 111 | 0.4286 0.4333 0.4485 112 | 0.4320 0.4362 0.4496 113 | 0.4354 0.4390 0.4508 114 | 0.4388 0.4419 0.4521 115 | 0.4422 0.4448 0.4534 116 | 0.4456 0.4477 0.4547 117 | 0.4489 0.4506 0.4561 118 | 0.4523 0.4535 0.4575 119 | 0.4556 0.4564 0.4589 120 | 0.4589 0.4593 0.4604 121 | 0.4622 0.4622 0.4620 122 | 0.4656 0.4651 0.4635 123 | 0.4689 0.4680 0.4650 124 | 0.4722 0.4709 0.4665 125 | 0.4756 0.4738 0.4679 126 | 0.4790 0.4767 0.4691 127 | 0.4825 0.4797 0.4701 128 | 0.4861 0.4826 0.4707 129 | 0.4897 0.4856 0.4714 130 | 0.4934 0.4886 0.4719 131 | 0.4971 0.4915 0.4723 132 | 0.5008 0.4945 0.4727 133 | 0.5045 0.4975 0.4730 134 | 0.5083 0.5005 0.4732 135 | 0.5121 0.5035 0.4734 136 | 0.5158 0.5065 0.4736 137 | 0.5196 0.5095 0.4737 138 | 0.5234 0.5125 0.4738 139 | 0.5272 0.5155 0.4739 140 | 0.5310 0.5186 0.4739 141 | 0.5349 0.5216 0.4738 142 | 0.5387 0.5246 0.4739 143 | 0.5425 0.5277 0.4738 144 | 0.5464 0.5307 0.4736 145 | 0.5502 0.5338 0.4735 146 | 0.5541 0.5368 0.4733 147 | 0.5579 0.5399 0.4732 148 | 0.5618 0.5430 0.4729 149 | 0.5657 0.5461 0.4727 150 | 0.5696 0.5491 0.4723 151 | 0.5735 0.5522 0.4720 152 | 0.5774 0.5553 0.4717 153 | 0.5813 0.5584 0.4714 154 | 0.5852 0.5615 0.4709 155 | 0.5892 0.5646 0.4705 156 | 0.5931 0.5678 0.4701 157 | 0.5970 0.5709 0.4696 158 | 0.6010 0.5740 0.4691 159 | 0.6050 0.5772 0.4685 160 | 0.6089 0.5803 0.4680 161 | 0.6129 0.5835 0.4673 162 | 0.6168 0.5866 0.4668 163 | 0.6208 0.5898 0.4662 164 | 0.6248 0.5929 0.4655 165 | 0.6288 0.5961 0.4649 166 | 0.6328 0.5993 0.4641 167 | 0.6368 0.6025 0.4632 168 | 0.6408 0.6057 0.4625 169 | 0.6449 0.6089 0.4617 170 | 0.6489 0.6121 0.4609 171 | 0.6529 0.6153 0.4600 172 | 0.6570 0.6185 0.4591 173 | 0.6610 0.6217 0.4583 174 | 0.6651 0.6250 0.4573 175 | 0.6691 0.6282 0.4562 176 | 0.6732 0.6315 0.4553 177 | 0.6773 0.6347 0.4543 178 | 0.6813 0.6380 0.4532 179 | 0.6854 0.6412 0.4521 180 | 0.6895 0.6445 0.4511 181 | 0.6936 0.6478 0.4499 182 | 0.6977 0.6511 0.4487 183 | 0.7018 0.6544 0.4475 184 | 0.7060 0.6577 0.4463 185 | 0.7101 0.6610 0.4450 186 | 0.7142 0.6643 0.4437 187 | 0.7184 0.6676 0.4424 188 | 0.7225 0.6710 0.4409 189 | 0.7267 0.6743 0.4396 190 | 0.7308 0.6776 0.4382 191 | 0.7350 0.6810 0.4368 192 | 0.7392 0.6844 0.4352 193 | 0.7434 0.6877 0.4338 194 | 0.7476 0.6911 0.4322 195 | 0.7518 0.6945 0.4307 196 | 0.7560 0.6979 0.4290 197 | 0.7602 0.7013 0.4273 198 | 0.7644 0.7047 0.4258 199 | 0.7686 0.7081 0.4241 200 | 0.7729 0.7115 0.4223 201 | 0.7771 0.7150 0.4205 202 | 0.7814 0.7184 0.4188 203 | 0.7856 0.7218 0.4168 204 | 0.7899 0.7253 0.4150 205 | 0.7942 0.7288 0.4129 206 | 0.7985 0.7322 0.4111 207 | 0.8027 0.7357 0.4090 208 | 0.8070 0.7392 0.4070 209 | 0.8114 0.7427 0.4049 210 | 0.8157 0.7462 0.4028 211 | 0.8200 0.7497 0.4007 212 | 0.8243 0.7532 0.3984 213 | 0.8287 0.7568 0.3961 214 | 0.8330 0.7603 0.3938 215 | 0.8374 0.7639 0.3915 216 | 0.8417 0.7674 0.3892 217 | 0.8461 0.7710 0.3869 218 | 0.8505 0.7745 0.3843 219 | 0.8548 0.7781 0.3818 220 | 0.8592 0.7817 0.3793 221 | 0.8636 0.7853 0.3766 222 | 0.8681 0.7889 0.3739 223 | 0.8725 0.7926 0.3712 224 | 0.8769 0.7962 0.3684 225 | 0.8813 0.7998 0.3657 226 | 0.8858 0.8035 0.3627 227 | 0.8902 0.8071 0.3599 228 | 0.8947 0.8108 0.3569 229 | 0.8992 0.8145 0.3538 230 | 0.9037 0.8182 0.3507 231 | 0.9082 0.8219 0.3474 232 | 0.9127 0.8256 0.3442 233 | 0.9172 0.8293 0.3409 234 | 0.9217 0.8330 0.3374 235 | 0.9262 0.8367 0.3340 236 | 0.9308 0.8405 0.3306 237 | 0.9353 0.8442 0.3268 238 | 0.9399 0.8480 0.3232 239 | 0.9444 0.8518 0.3195 240 | 0.9490 0.8556 0.3155 241 | 0.9536 0.8593 0.3116 242 | 0.9582 0.8632 0.3076 243 | 0.9628 0.8670 0.3034 244 | 0.9674 0.8708 0.2990 245 | 0.9721 0.8746 0.2947 246 | 0.9767 0.8785 0.2901 247 | 0.9814 0.8823 0.2856 248 | 0.9860 0.8862 0.2807 249 | 0.9907 0.8901 0.2759 250 | 0.9954 0.8940 0.2708 251 | 1.0000 0.8979 0.2655 252 | 1.0000 0.9018 0.2600 253 | 1.0000 0.9057 0.2593 254 | 1.0000 0.9094 0.2634 255 | 1.0000 0.9131 0.2680 256 | 1.0000 0.9169 0.2731 257 | -------------------------------------------------------------------------------- /colormaps/cividisHexValues.txt: -------------------------------------------------------------------------------- 1 | #00204c 2 | #00204e 3 | #002150 4 | #002251 5 | #002353 6 | #002355 7 | #002456 8 | #002558 9 | #00265a 10 | #00265b 11 | #00275d 12 | #00285f 13 | #002861 14 | #002963 15 | #002a64 16 | #002a66 17 | #002b68 18 | #002c6a 19 | #002d6c 20 | #002d6d 21 | #002e6e 22 | #002e6f 23 | #002f6f 24 | #002f6f 25 | #00306f 26 | #00316f 27 | #00316f 28 | #00326e 29 | #00336e 30 | #00346e 31 | #00346e 32 | #01356e 33 | #06366e 34 | #0a376d 35 | #0e376d 36 | #12386d 37 | #15396d 38 | #17396d 39 | #1a3a6c 40 | #1c3b6c 41 | #1e3c6c 42 | #203c6c 43 | #223d6c 44 | #243e6c 45 | #263e6c 46 | #273f6c 47 | #29406b 48 | #2b416b 49 | #2c416b 50 | #2e426b 51 | #2f436b 52 | #31446b 53 | #32446b 54 | #33456b 55 | #35466b 56 | #36466b 57 | #37476b 58 | #38486b 59 | #3a496b 60 | #3b496b 61 | #3c4a6b 62 | #3d4b6b 63 | #3e4b6b 64 | #404c6b 65 | #414d6b 66 | #424e6b 67 | #434e6b 68 | #444f6b 69 | #45506b 70 | #46506b 71 | #47516b 72 | #48526b 73 | #49536b 74 | #4a536b 75 | #4b546b 76 | #4c556b 77 | #4d556b 78 | #4e566b 79 | #4f576c 80 | #50586c 81 | #51586c 82 | #52596c 83 | #535a6c 84 | #545a6c 85 | #555b6c 86 | #565c6c 87 | #575d6d 88 | #585d6d 89 | #595e6d 90 | #5a5f6d 91 | #5b5f6d 92 | #5c606d 93 | #5d616e 94 | #5e626e 95 | #5f626e 96 | #5f636e 97 | #60646e 98 | #61656f 99 | #62656f 100 | #63666f 101 | #64676f 102 | #65676f 103 | #666870 104 | #676970 105 | #686a70 106 | #686a70 107 | #696b71 108 | #6a6c71 109 | #6b6d71 110 | #6c6d72 111 | #6d6e72 112 | #6e6f72 113 | #6f6f72 114 | #6f7073 115 | #707173 116 | #717273 117 | #727274 118 | #737374 119 | #747475 120 | #757575 121 | #757575 122 | #767676 123 | #777776 124 | #787876 125 | #797877 126 | #7a7977 127 | #7b7a77 128 | #7b7b78 129 | #7c7b78 130 | #7d7c78 131 | #7e7d78 132 | #7f7e78 133 | #807e78 134 | #817f78 135 | #828078 136 | #838178 137 | #848178 138 | #858278 139 | #868378 140 | #878478 141 | #888578 142 | #898578 143 | #8a8678 144 | #8b8778 145 | #8c8878 146 | #8d8878 147 | #8e8978 148 | #8f8a78 149 | #908b78 150 | #918c78 151 | #928c78 152 | #938d78 153 | #948e78 154 | #958f78 155 | #968f77 156 | #979077 157 | #989177 158 | #999277 159 | #9a9377 160 | #9b9377 161 | #9c9477 162 | #9d9577 163 | #9e9676 164 | #9f9776 165 | #a09876 166 | #a19876 167 | #a29976 168 | #a39a75 169 | #a49b75 170 | #a59c75 171 | #a69c75 172 | #a79d75 173 | #a89e74 174 | #a99f74 175 | #aaa074 176 | #aba174 177 | #aca173 178 | #ada273 179 | #aea373 180 | #afa473 181 | #b0a572 182 | #b1a672 183 | #b2a672 184 | #b4a771 185 | #b5a871 186 | #b6a971 187 | #b7aa70 188 | #b8ab70 189 | #b9ab70 190 | #baac6f 191 | #bbad6f 192 | #bcae6e 193 | #bdaf6e 194 | #beb06e 195 | #bfb16d 196 | #c0b16d 197 | #c1b26c 198 | #c2b36c 199 | #c3b46c 200 | #c5b56b 201 | #c6b66b 202 | #c7b76a 203 | #c8b86a 204 | #c9b869 205 | #cab969 206 | #cbba68 207 | #ccbb68 208 | #cdbc67 209 | #cebd67 210 | #d0be66 211 | #d1bf66 212 | #d2c065 213 | #d3c065 214 | #d4c164 215 | #d5c263 216 | #d6c363 217 | #d7c462 218 | #d8c561 219 | #d9c661 220 | #dbc760 221 | #dcc860 222 | #ddc95f 223 | #deca5e 224 | #dfcb5d 225 | #e0cb5d 226 | #e1cc5c 227 | #e3cd5b 228 | #e4ce5b 229 | #e5cf5a 230 | #e6d059 231 | #e7d158 232 | #e8d257 233 | #e9d356 234 | #ebd456 235 | #ecd555 236 | #edd654 237 | #eed753 238 | #efd852 239 | #f0d951 240 | #f1da50 241 | #f3db4f 242 | #f4dc4e 243 | #f5dd4d 244 | #f6de4c 245 | #f7df4b 246 | #f9e049 247 | #fae048 248 | #fbe147 249 | #fce246 250 | #fde345 251 | #ffe443 252 | #ffe542 253 | #ffe642 254 | #ffe743 255 | #ffe844 256 | #ffe945 257 | -------------------------------------------------------------------------------- /colormaps/cividis_COMSOL.txt: -------------------------------------------------------------------------------- 1 | % Continuous 2 | 0.0000 0.1262 0.3015 3 | 0.0000 0.1292 0.3077 4 | 0.0000 0.1321 0.3142 5 | 0.0000 0.1350 0.3205 6 | 0.0000 0.1379 0.3269 7 | 0.0000 0.1408 0.3334 8 | 0.0000 0.1437 0.3400 9 | 0.0000 0.1465 0.3467 10 | 0.0000 0.1492 0.3537 11 | 0.0000 0.1519 0.3606 12 | 0.0000 0.1546 0.3676 13 | 0.0000 0.1574 0.3746 14 | 0.0000 0.1601 0.3817 15 | 0.0000 0.1629 0.3888 16 | 0.0000 0.1657 0.3960 17 | 0.0000 0.1685 0.4031 18 | 0.0000 0.1714 0.4102 19 | 0.0000 0.1743 0.4172 20 | 0.0000 0.1773 0.4241 21 | 0.0000 0.1798 0.4307 22 | 0.0000 0.1817 0.4347 23 | 0.0000 0.1834 0.4363 24 | 0.0000 0.1852 0.4368 25 | 0.0000 0.1872 0.4368 26 | 0.0000 0.1901 0.4365 27 | 0.0000 0.1930 0.4361 28 | 0.0000 0.1958 0.4356 29 | 0.0000 0.1987 0.4349 30 | 0.0000 0.2015 0.4343 31 | 0.0000 0.2044 0.4336 32 | 0.0000 0.2073 0.4329 33 | 0.0055 0.2101 0.4322 34 | 0.0236 0.2130 0.4314 35 | 0.0416 0.2158 0.4308 36 | 0.0576 0.2187 0.4301 37 | 0.0710 0.2215 0.4293 38 | 0.0827 0.2244 0.4287 39 | 0.0932 0.2272 0.4280 40 | 0.1030 0.2300 0.4274 41 | 0.1120 0.2329 0.4268 42 | 0.1204 0.2357 0.4262 43 | 0.1283 0.2385 0.4256 44 | 0.1359 0.2414 0.4251 45 | 0.1431 0.2442 0.4245 46 | 0.1500 0.2470 0.4241 47 | 0.1566 0.2498 0.4236 48 | 0.1630 0.2526 0.4232 49 | 0.1692 0.2555 0.4228 50 | 0.1752 0.2583 0.4224 51 | 0.1811 0.2611 0.4220 52 | 0.1868 0.2639 0.4217 53 | 0.1923 0.2667 0.4214 54 | 0.1977 0.2695 0.4212 55 | 0.2030 0.2723 0.4209 56 | 0.2082 0.2751 0.4207 57 | 0.2133 0.2780 0.4205 58 | 0.2183 0.2808 0.4204 59 | 0.2232 0.2836 0.4203 60 | 0.2281 0.2864 0.4202 61 | 0.2328 0.2892 0.4201 62 | 0.2375 0.2920 0.4200 63 | 0.2421 0.2948 0.4200 64 | 0.2466 0.2976 0.4200 65 | 0.2511 0.3004 0.4201 66 | 0.2556 0.3032 0.4201 67 | 0.2599 0.3060 0.4202 68 | 0.2643 0.3088 0.4203 69 | 0.2686 0.3116 0.4205 70 | 0.2728 0.3144 0.4206 71 | 0.2770 0.3172 0.4208 72 | 0.2811 0.3200 0.4210 73 | 0.2853 0.3228 0.4212 74 | 0.2894 0.3256 0.4215 75 | 0.2934 0.3284 0.4218 76 | 0.2974 0.3312 0.4221 77 | 0.3014 0.3340 0.4224 78 | 0.3054 0.3368 0.4227 79 | 0.3093 0.3396 0.4231 80 | 0.3132 0.3424 0.4236 81 | 0.3170 0.3453 0.4240 82 | 0.3209 0.3481 0.4244 83 | 0.3247 0.3509 0.4249 84 | 0.3285 0.3537 0.4254 85 | 0.3323 0.3565 0.4259 86 | 0.3361 0.3593 0.4264 87 | 0.3398 0.3622 0.4270 88 | 0.3435 0.3650 0.4276 89 | 0.3472 0.3678 0.4282 90 | 0.3509 0.3706 0.4288 91 | 0.3546 0.3734 0.4294 92 | 0.3582 0.3763 0.4302 93 | 0.3619 0.3791 0.4308 94 | 0.3655 0.3819 0.4316 95 | 0.3691 0.3848 0.4322 96 | 0.3727 0.3876 0.4331 97 | 0.3763 0.3904 0.4338 98 | 0.3798 0.3933 0.4346 99 | 0.3834 0.3961 0.4355 100 | 0.3869 0.3990 0.4364 101 | 0.3905 0.4018 0.4372 102 | 0.3940 0.4047 0.4381 103 | 0.3975 0.4075 0.4390 104 | 0.4010 0.4104 0.4400 105 | 0.4045 0.4132 0.4409 106 | 0.4080 0.4161 0.4419 107 | 0.4114 0.4189 0.4430 108 | 0.4149 0.4218 0.4440 109 | 0.4183 0.4247 0.4450 110 | 0.4218 0.4275 0.4462 111 | 0.4252 0.4304 0.4473 112 | 0.4286 0.4333 0.4485 113 | 0.4320 0.4362 0.4496 114 | 0.4354 0.4390 0.4508 115 | 0.4388 0.4419 0.4521 116 | 0.4422 0.4448 0.4534 117 | 0.4456 0.4477 0.4547 118 | 0.4489 0.4506 0.4561 119 | 0.4523 0.4535 0.4575 120 | 0.4556 0.4564 0.4589 121 | 0.4589 0.4593 0.4604 122 | 0.4622 0.4622 0.4620 123 | 0.4656 0.4651 0.4635 124 | 0.4689 0.4680 0.4650 125 | 0.4722 0.4709 0.4665 126 | 0.4756 0.4738 0.4679 127 | 0.4790 0.4767 0.4691 128 | 0.4825 0.4797 0.4701 129 | 0.4861 0.4826 0.4707 130 | 0.4897 0.4856 0.4714 131 | 0.4934 0.4886 0.4719 132 | 0.4971 0.4915 0.4723 133 | 0.5008 0.4945 0.4727 134 | 0.5045 0.4975 0.4730 135 | 0.5083 0.5005 0.4732 136 | 0.5121 0.5035 0.4734 137 | 0.5158 0.5065 0.4736 138 | 0.5196 0.5095 0.4737 139 | 0.5234 0.5125 0.4738 140 | 0.5272 0.5155 0.4739 141 | 0.5310 0.5186 0.4739 142 | 0.5349 0.5216 0.4738 143 | 0.5387 0.5246 0.4739 144 | 0.5425 0.5277 0.4738 145 | 0.5464 0.5307 0.4736 146 | 0.5502 0.5338 0.4735 147 | 0.5541 0.5368 0.4733 148 | 0.5579 0.5399 0.4732 149 | 0.5618 0.5430 0.4729 150 | 0.5657 0.5461 0.4727 151 | 0.5696 0.5491 0.4723 152 | 0.5735 0.5522 0.4720 153 | 0.5774 0.5553 0.4717 154 | 0.5813 0.5584 0.4714 155 | 0.5852 0.5615 0.4709 156 | 0.5892 0.5646 0.4705 157 | 0.5931 0.5678 0.4701 158 | 0.5970 0.5709 0.4696 159 | 0.6010 0.5740 0.4691 160 | 0.6050 0.5772 0.4685 161 | 0.6089 0.5803 0.4680 162 | 0.6129 0.5835 0.4673 163 | 0.6168 0.5866 0.4668 164 | 0.6208 0.5898 0.4662 165 | 0.6248 0.5929 0.4655 166 | 0.6288 0.5961 0.4649 167 | 0.6328 0.5993 0.4641 168 | 0.6368 0.6025 0.4632 169 | 0.6408 0.6057 0.4625 170 | 0.6449 0.6089 0.4617 171 | 0.6489 0.6121 0.4609 172 | 0.6529 0.6153 0.4600 173 | 0.6570 0.6185 0.4591 174 | 0.6610 0.6217 0.4583 175 | 0.6651 0.6250 0.4573 176 | 0.6691 0.6282 0.4562 177 | 0.6732 0.6315 0.4553 178 | 0.6773 0.6347 0.4543 179 | 0.6813 0.6380 0.4532 180 | 0.6854 0.6412 0.4521 181 | 0.6895 0.6445 0.4511 182 | 0.6936 0.6478 0.4499 183 | 0.6977 0.6511 0.4487 184 | 0.7018 0.6544 0.4475 185 | 0.7060 0.6577 0.4463 186 | 0.7101 0.6610 0.4450 187 | 0.7142 0.6643 0.4437 188 | 0.7184 0.6676 0.4424 189 | 0.7225 0.6710 0.4409 190 | 0.7267 0.6743 0.4396 191 | 0.7308 0.6776 0.4382 192 | 0.7350 0.6810 0.4368 193 | 0.7392 0.6844 0.4352 194 | 0.7434 0.6877 0.4338 195 | 0.7476 0.6911 0.4322 196 | 0.7518 0.6945 0.4307 197 | 0.7560 0.6979 0.4290 198 | 0.7602 0.7013 0.4273 199 | 0.7644 0.7047 0.4258 200 | 0.7686 0.7081 0.4241 201 | 0.7729 0.7115 0.4223 202 | 0.7771 0.7150 0.4205 203 | 0.7814 0.7184 0.4188 204 | 0.7856 0.7218 0.4168 205 | 0.7899 0.7253 0.4150 206 | 0.7942 0.7288 0.4129 207 | 0.7985 0.7322 0.4111 208 | 0.8027 0.7357 0.4090 209 | 0.8070 0.7392 0.4070 210 | 0.8114 0.7427 0.4049 211 | 0.8157 0.7462 0.4028 212 | 0.8200 0.7497 0.4007 213 | 0.8243 0.7532 0.3984 214 | 0.8287 0.7568 0.3961 215 | 0.8330 0.7603 0.3938 216 | 0.8374 0.7639 0.3915 217 | 0.8417 0.7674 0.3892 218 | 0.8461 0.7710 0.3869 219 | 0.8505 0.7745 0.3843 220 | 0.8548 0.7781 0.3818 221 | 0.8592 0.7817 0.3793 222 | 0.8636 0.7853 0.3766 223 | 0.8681 0.7889 0.3739 224 | 0.8725 0.7926 0.3712 225 | 0.8769 0.7962 0.3684 226 | 0.8813 0.7998 0.3657 227 | 0.8858 0.8035 0.3627 228 | 0.8902 0.8071 0.3599 229 | 0.8947 0.8108 0.3569 230 | 0.8992 0.8145 0.3538 231 | 0.9037 0.8182 0.3507 232 | 0.9082 0.8219 0.3474 233 | 0.9127 0.8256 0.3442 234 | 0.9172 0.8293 0.3409 235 | 0.9217 0.8330 0.3374 236 | 0.9262 0.8367 0.3340 237 | 0.9308 0.8405 0.3306 238 | 0.9353 0.8442 0.3268 239 | 0.9399 0.8480 0.3232 240 | 0.9444 0.8518 0.3195 241 | 0.9490 0.8556 0.3155 242 | 0.9536 0.8593 0.3116 243 | 0.9582 0.8632 0.3076 244 | 0.9628 0.8670 0.3034 245 | 0.9674 0.8708 0.2990 246 | 0.9721 0.8746 0.2947 247 | 0.9767 0.8785 0.2901 248 | 0.9814 0.8823 0.2856 249 | 0.9860 0.8862 0.2807 250 | 0.9907 0.8901 0.2759 251 | 0.9954 0.8940 0.2708 252 | 1.0000 0.8979 0.2655 253 | 1.0000 0.9018 0.2600 254 | 1.0000 0.9057 0.2593 255 | 1.0000 0.9094 0.2634 256 | 1.0000 0.9131 0.2680 257 | 1.0000 0.9169 0.2731 258 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Examples require python modules: 4 | 5 | + cv2 (`pip install opencv-python`) 6 | + mpl_toolkits 7 | + viscm 8 | -------------------------------------------------------------------------------- /examples/example1_optimizeMap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This example provides the ability to create figures like Figure 4 in our colormap paper. 4 | A colormap is input and the iterations to optimize it are shown at each stage. 5 | 6 | @author: Jamie R. Nunez 7 | (C) 2017 - Pacific Northwest National Laboratory 8 | """ 9 | # Imports 10 | from time import time 11 | 12 | import cv2 13 | import matplotlib.cm as cm # Used with eval() 14 | import matplotlib.patches as mpatches 15 | import matplotlib.pyplot as plt 16 | from mpl_toolkits.mplot3d import Axes3D 17 | import numpy as np 18 | 19 | import cmaputil as cmu 20 | import cmaputil.cvdutil as cvu 21 | 22 | FLABEL = 20 23 | FAX = 16 24 | 25 | #%% Get iteration data 26 | cmap = 'viridis' # The optimized version of this colormap is cividis! 27 | 28 | # Get original RGB/Jab 29 | t = time() 30 | rgb1, jab1 = cmu.get_rgb_jab(cmap) 31 | 32 | # Get CVD RGB/Jab 33 | rgb2, jab2 = cmu.get_rgb_jab(cvu.get_cvd(rgb1, severity=100)) 34 | 35 | # Make perceptual deltas (for a' vs. b') straight 36 | # Need to set a and b before straightening J 37 | jab3 = cmu.make_linear(jab2) 38 | rgb3 = cmu.convert(jab3, cmu.CSPACE2, cmu.CSPACE1) 39 | 40 | # Linearize J' 41 | jab4, jab5 = cmu.correct_J(jab3) # This will create a figure showing the J bounds 42 | if jab4 is not None: 43 | rgb4 = cmu.convert(jab4, cmu.CSPACE2, cmu.CSPACE1) 44 | rgb4 = np.clip(rgb4, 0, 1) 45 | if jab5 is not None: 46 | rgb5 = cmu.convert(jab5, cmu.CSPACE2, cmu.CSPACE1) 47 | rgb5 = np.clip(rgb5, 0, 1) 48 | 49 | #%% Figure: Iterations for optimization 50 | 51 | fig = plt.figure(figsize=(25, 12), dpi=500) 52 | 53 | # Plot original colormap properties 54 | ax = plt.subplot(5, 5, 1) 55 | plt.title('Input', fontsize=FLABEL) 56 | cmu.plot_colormap_info(fig, rgb1, sp=[5, 5, 1], show=False) 57 | cmu.plot_colormap_info(fig, rgb1, sp=[5, 5, 1], show=False, leg=False) 58 | 59 | # Plot converted (to CVD space) colormap properties 60 | ax = plt.subplot(5, 5, 2) 61 | plt.title('Step 1: CVD', fontsize=FLABEL) 62 | cmu.plot_colormap_info(fig, rgb2, sp=[5, 5, 2], show=False) 63 | 64 | # Plot for linearized a' and b' 65 | ax = plt.subplot(5, 5, 3) 66 | plt.title('Step 3:\na\' vs. b\' Linearized', fontsize=FLABEL) 67 | cmu.plot_colormap_info(fig, rgb3, sp=[5, 5, 3], show=False) 68 | 69 | # If able, plot colormap wth linear J', fitted to original J' 70 | if jab4 is not None: 71 | ax = plt.subplot(5, 5, 4) 72 | plt.title('Step 4:\nJ\' fit to original', fontsize=FLABEL) 73 | cmu.plot_colormap_info(fig, rgb4, sp=[5, 5, 4], show=False) 74 | 75 | # If able, plot colormap wth linear J', fitted to max J' range 76 | if jab5 is not None: 77 | ax = plt.subplot(5, 5, 5) 78 | plt.title('Step 4:\nJ\' fit to maximize range', fontsize=FLABEL) 79 | cmu.plot_colormap_info(fig, rgb5, sp=[5, 5, 5], show=False) 80 | 81 | plt.show() 82 | 83 | #%% Plot changes in a' vs. b' 84 | 85 | fig = plt.figure(figsize=(8, 6), dpi=500) 86 | 87 | # Line plot colors: black, red, and blue. 88 | c = [(0, 0, 0), 89 | (99 / 255., 198 / 255., 10 / 255.), 90 | (184 / 255., 156 / 255., 239 / 255.)] 91 | 92 | # Plot original a' and b' 93 | plt.plot(jab1[1, :], jab1[2, :], c='k', lw=12) 94 | plt.plot(jab1[1, :], jab1[2, :], c=c[0], lw=10) 95 | 96 | # Plot CVD a' and b' 97 | plt.plot(jab2[1, :], jab2[2, :], c='k', lw=12) 98 | plt.plot(jab2[1, :], jab2[2, :], c=c[1], lw=10) 99 | 100 | # Plot optimized a' and b' 101 | jab3 = cmu.convert(rgb3, cmu.CSPACE1, cmu.CSPACE2) 102 | plt.plot(jab3[1, :], jab3[2, :], c='k', lw=8) 103 | plt.plot(jab3[1, :], jab3[2, :], c=c[2], lw=6) 104 | 105 | # Standard Formatting 106 | plt.title('Iterative changes in a\' vs. b\'', fontsize=FLABEL) 107 | plt.xlabel('a\'', fontsize=FLABEL) 108 | plt.ylabel('b\'', fontsize=FLABEL) 109 | plt.xticks([-40, 0, 40], fontsize=FAX) 110 | plt.yticks([-40, 0, 40], fontsize=FAX) 111 | plt.axis([-43, 43, -43, 43]) 112 | 113 | # Format legend 114 | p1 = mpatches.Patch(color=c[0], label='Original') 115 | p2 = mpatches.Patch(color=c[1], label='CVD') 116 | p3 = mpatches.Patch(color=c[2], label='Perc. Unif. CVD') 117 | patches = [p1, p2, p3] 118 | plt.legend(handles=patches, loc='lower right', fontsize=FAX) 119 | 120 | plt.show() 121 | -------------------------------------------------------------------------------- /examples/example2_cdpsPlots.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Can be used to create plots like in Fig 1 and 5 of the paper 4 | 5 | @author: Jamie R. Nunez 6 | (C) 2017 - Pacific Northwest National Laboratory 7 | """ 8 | 9 | # Imports 10 | import numpy as np 11 | 12 | import cmaputil as cmu 13 | import cmaputil.cvdutil as cvu 14 | 15 | # Globals 16 | FLABEL = 20 17 | FAX = 16 18 | 19 | # Input colormap name 20 | cmap = 'viridis' 21 | 22 | # Optimize 23 | rgb1, jab1 = cmu.get_rgb_jab(cmap) # Original colormap 24 | rgb2, jab2 = cmu.get_rgb_jab(cvu.get_cvd(rgb1)) # CVD colormap 25 | jab3 = cmu.make_linear(jab2) # Uniformize hue (a' vs. b') 26 | _, jab4 = cmu.correct_J(jab3) # Linearize J' 27 | 28 | # Convert back to sRGB 29 | rgb4 = cmu.convert(jab4, cmu.CSPACE2, cmu.CSPACE1) 30 | rgb4 = np.clip(rgb4, 0, 1) 31 | 32 | # Resimulate CVD in case corrections took the map outside CVD-safe space 33 | rgb4 = cvu.get_cvd(rgb4) 34 | 35 | #%% Creat CDPS plots (shown in Fig 1 and 5 of the paper) 36 | 37 | # Import test data 38 | img_name = 'example_nanosims_image.txt' # Image used for paper 39 | img = np.loadtxt(img_name)[45:, :-45] # Make square 40 | high = 3; low = -1 # Std. Dev bounds for normalizing 41 | img = cmu.bound(cmu.normalize(img), high, low) # Normalize 42 | slice_img = np.array(img[265, 50:-50], ndmin=2) # Slice used in paper 43 | gslope = 27.4798474 # Slope for gray. Used to normalize CDPS slopes. 44 | 45 | # CDPS plot for original map 46 | cmu.cdps_plot(slice_img, cmap, rgb1, 1, gslope) 47 | 48 | # CDPS plot for CVD-simulated map 49 | cmu.cdps_plot(slice_img, cmap, rgb2, 2, gslope) 50 | 51 | # CDPS plot for optimized map 52 | cmu.cdps_plot(slice_img, cmap, rgb4, 4, gslope) 53 | -------------------------------------------------------------------------------- /examples/example3_simCVD.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Can be used to recreate Fig 2 from the paper. 4 | 5 | @author: Jamie R. Nunez 6 | (C) 2017 - Pacific Northwest National Laboratory 7 | """ 8 | 9 | #%% Imports 10 | 11 | from time import time 12 | 13 | import matplotlib.pyplot as plt 14 | from mpl_toolkits.mplot3d import Axes3D 15 | import numpy as np 16 | from scipy.spatial import ConvexHull, Delaunay 17 | from viscm.gui import sRGB_gamut_Jp_slice 18 | 19 | import cmaputil as cmu 20 | import cmaputil.cvdutil as cvu 21 | 22 | #%% Globals 23 | FLABEL = 20 24 | FAX = 16 25 | UNIFORM_SPACE = 'CAM02-UCS' 26 | 27 | #%% Functions 28 | 29 | # From https://stackoverflow.com/questions/24733185/volume-of-convex-hull-with-qhull-from-scipy 30 | def tetrahedron_volume(a, b, c, d): 31 | return np.abs(np.einsum('ij,ij->i', a-d, np.cross(b-d, c-d))) / 6 32 | 33 | # From https://stackoverflow.com/questions/24733185/volume-of-convex-hull-with-qhull-from-scipy 34 | def convex_hull_volume(pts): 35 | ch = ConvexHull(pts) 36 | dt = Delaunay(pts[ch.vertices]) 37 | tets = dt.points[dt.simplices] 38 | return np.sum(tetrahedron_volume(tets[:, 0], tets[:, 1], 39 | tets[:, 2], tets[:, 3])) 40 | 41 | # Quick help function to add J'a'b' (from a RGB value) to the given set 42 | def add(rgb, severity, jab_set, cvd_type='deuteranomaly'): 43 | 44 | if severity is None: # Full trichromatic vision 45 | jab = cmu.convert(rgb, 'sRGB1', UNIFORM_SPACE) 46 | 47 | else: # Simulate CVD 48 | jab = cmu.convert(cvu.get_cvd(rgb, severity=severity, cvd_type=cvd_type), 'sRGB1', UNIFORM_SPACE) 49 | 50 | # Add to set 51 | jab_set.add(tuple(jab)) 52 | 53 | def gen_ab_spaces(Jps, sevs): 54 | 55 | # Initialize 56 | cvdd = [] 57 | ab_values = set() 58 | cvdd_ab_values = [set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), set()] 59 | 60 | t = time() 61 | for Jp in Jps: 62 | 63 | # Get a'b' for this J' 64 | ab_space = sRGB_gamut_Jp_slice(Jp, UNIFORM_SPACE) 65 | 66 | # Iterate through each a',b' pair 67 | for i in range(ab_space.shape[0]): # b' 68 | for j in range(ab_space.shape[1]): # a' 69 | 70 | # Check if this J'a'b' converts to a RGB value 71 | if np.sum(ab_space[i, j, :]) > 0: 72 | 73 | rgb = ab_space[i, j, :3] 74 | add(rgb, None, ab_values) 75 | 76 | # Find the RGB for each severity 77 | for sev in sevs: 78 | add(rgb, sev, cvdd_ab_values[sev / 10]) 79 | 80 | # Report time taken for this iteration 81 | print '%.2f' % (time() - t) 82 | t = time() 83 | 84 | # Calculate percent areas 85 | ab_values_temp = np.vstack({tuple(row) for row in np.asarray(list(ab_values))}) 86 | normal_area = convex_hull_volume(ab_values_temp) 87 | 88 | for sev in sevs: 89 | cvdd_ab_values_temp = np.vstack({tuple(row) for row in np.asarray(list(cvdd_ab_values[sev / 10]))}) 90 | cvdd.append(convex_hull_volume(cvdd_ab_values_temp) / normal_area * 100) 91 | 92 | return cvdd, ab_values, cvdd_ab_values 93 | 94 | def plot_ab_surfaces(full_color_vision_ab, cvd_ab, severity=100): 95 | 96 | ab_xy = np.asarray(list(full_color_vision_ab)) 97 | cvd_xy = np.asarray(list(cvd_ab[severity / 10])) 98 | 99 | labels = ['J\'', 'a\'', 'b\''] 100 | pairs = [[1, 0], [2, 0], [2, 1]] 101 | 102 | for p in pairs: 103 | 104 | plt.figure(figsize=(6,6)) 105 | 106 | plt.scatter(ab_xy[:, p[0]], ab_xy[:, p[1]], c='k', lw=0) 107 | plt.scatter(cvd_xy[:, p[0]], cvd_xy[:, p[1]], c='gray', lw=0) 108 | 109 | # Format 110 | if p[0] > 0: # a' or b' bounds 111 | xmin = -50; xmax = 50; 112 | else: # J' bounds 113 | xmin = 0; xmax = 100; 114 | 115 | if p[1] > 0: 116 | ymin = -50; ymax = 50; 117 | 118 | else: 119 | ymin = 0; ymax = 100; 120 | plt.xticks([]) 121 | plt.yticks([]) 122 | plt.axis([xmin, xmax, ymin, ymax]) 123 | plt.xlabel(labels[p[0]], fontsize=16) 124 | plt.ylabel(labels[p[1]], fontsize=16) 125 | plt.show() 126 | 127 | #%% Full trichromatic color vision vs.cvd color vision 128 | 129 | # Get a'b' spaces covered by normal vision and CVD 130 | # Iterates through all possible values. Takes a while! 131 | Jps = np.linspace(0, 100, 11, dtype=int) 132 | sevs = np.linspace(0, 100, 11, dtype=int) 133 | cvdd, full_color_vision_ab, cvd_ab = gen_ab_spaces(Jps, sevs) 134 | 135 | # Plot 2D surface comparisons 136 | plot_ab_surfaces(full_color_vision_ab, cvd_ab) 137 | 138 | # Plot CVD % of full color vision 139 | fig = plt.figure(figsize=(4.5, 6)) 140 | d, = plt.plot(sevs, cvdd, c='k', lw=3) 141 | plt.xlabel('Severity', fontsize=FLABEL) 142 | plt.ylabel('Percent of Normal Color Vision', fontsize=FLABEL) 143 | plt.xticks([0, 25, 50, 75, 100], fontsize=FAX) 144 | plt.yticks([0, 25, 50, 75, 100], fontsize=FAX) 145 | plt.axis([0, 100, 0, 100]) 146 | plt.show() 147 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | numpy 3 | colorspacious -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | from os.path import dirname, join 3 | from setuptools import setup, find_packages 4 | 5 | 6 | def get_version(relpath): 7 | """Read version info from a file without importing it""" 8 | for line in io.open(join(dirname(__file__), relpath), encoding="cp437"): 9 | if "__version__" in line: 10 | if '"' in line: 11 | # __version__ = "0.9" 12 | return line.split('"')[1] 13 | elif "'" in line: 14 | return line.split("'")[1] 15 | 16 | 17 | with open('README.md') as f: 18 | readme = f.read() 19 | 20 | with open('LICENSE') as f: 21 | license = f.read() 22 | 23 | with open('requirements.txt') as f: 24 | required = f.read().splitlines() 25 | 26 | pkgs = find_packages(exclude=('examples')) 27 | 28 | setup( 29 | name='cmaputil', 30 | version=get_version("cmaputil/__init__.py"), 31 | description='Colormap analysis module.', 32 | long_description=readme, 33 | author='Jamie R. Nunez', 34 | author_email='jamie.nunez@pnnl.gov', 35 | url='https://github.com/pnnl/cmaputil', 36 | license=license, 37 | packages=pkgs, 38 | install_requires=required 39 | ) 40 | --------------------------------------------------------------------------------