Source code for aquaduct.visual.helpers

# -*- coding: utf-8 -*-

# Aqua-Duct, a tool facilitating analysis of the flow of solvent molecules in molecular dynamic simulations
# Copyright (C) 2016-2017  Tomasz Magdziarz, Alicja Płuciennik, Michał Stolarczyk <info@aquaduct.pl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import numpy as np

# from matplotlib.colors import colorConverter

import scipy.spatial.distance as distance

from aquaduct.traj.paths import GenericPathTypeCodes as gptc
from aquaduct.traj.paths import PathTypesCodes as ptc
from aquaduct.visual.cmaps import default as default_cmap
from aquaduct.utils.helpers import zip_zip, is_number, lind

_cl2rgba = {'b': (0.0, 0.0, 1.0, 1.0),
            'c': (0.0, 0.75, 0.75, 1.0),
            'g': (0.0, 0.5, 0.0, 1.0),
            'k': (0.0, 0.0, 0.0, 1.0),
            'm': (0.75, 0, 0.75, 1.0),
            'r': (1.0, 0.0, 0.0, 1.0),
            'y': (0.75, 0.75, 0, 1.0),
            'w': (1.0, 1.0, 1.0, 1.0),
            's': (0.5, 0.5, 0.5, 1.0)}


[docs]def euclidean(A,B): return distance.cdist(A, B, 'euclidean')
[docs]def cityblock(A,B): return distance.cdist(A, B, 'cityblock')
# cc = lambda c, alpha=1.0: colorConverter.to_rgb(c)
[docs]def cc_safe(c): # color converter if c in 'rgbcmyk': c = _cl2rgba[c] c = tuple(c) assert len(c) in [3, 4], 'Color should be given either as one letter (rgbcmyk) or as rgb or rgba vector.' for e in c: assert is_number(e), 'Color vector has to be specified as numbers.' assert 0 <= e <= 1, 'Color vector elements have to be in range of 0 to 1.' return c[:3]
[docs]def cc(c): # color converter faster if c in 'rgbcmykws': c = _cl2rgba[c] return c[:3]
_dcc_is = ptc.path_in_code + gptc.scope_name _dcc_cc = ptc.path_object_code + gptc.object_name _dcc_cs = ptc.path_object_code + gptc.scope_name _dcc_os = ptc.path_out_code + gptc.scope_name _dcc_ws = ptc.path_walk_code + gptc.scope_name _dcc_i = ptc.path_in_code _dcc_c = ptc.path_object_code _dcc_o = ptc.path_out_code _dcc_w = ptc.path_walk_code _default_color_codes = {_dcc_is: 'r', _dcc_cc: 'g', _dcc_cs: 'y', _dcc_os: 'b', _dcc_ws: 's', _dcc_i: 'r', _dcc_c: 'g', _dcc_o: 'b', _dcc_w: 's'} default_color_codes = _default_color_codes
[docs]def color_codes(code, custom_codes=None): if custom_codes is None: return default_color_codes[code] else: return custom_codes[code]
[docs]def get_cmap(size): return [e[0][0] for e in zip_zip(default_cmap, N=size)]
[docs]class ColorMapDistMap(object): grey = (0.5, 0.5, 0.5, 1)
[docs] def __init__(self): self.cmap = default_cmap self.cmap = self.__do_cadex()
[docs] def distance(self,E1,E2): # E1 and E2 are RGBs matrices D = [] for e1 in E1: DD = [] for e2 in E2: DD.append(self.color_distance(e1,e2)) D.append(DD) return np.array(D)
[docs] @staticmethod def color_distance(e1,e2): # e1 and e2 are colors defs of rgb components in this order # Taken from http://www.compuphase.com/cmetric.htm # scale them to 0-255 range def to0255(E): return [int(e*255) for e in E] e1 = to0255(e1) e2 = to0255(e2) rmean = (e1[0]+e2[0])/2 r = e1[0]-e2[0] g = e1[1]-e2[1] b = e1[2]-e2[2] return np.sqrt((((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8))
def __do_cadex(self): m = len(self.cmap) # number of objects k = len(self.cmap) # indices mi = [] # model ti = range(m) # test # FIRST OBJECT # get distance to mean object Xm = np.array(self.cmap).mean(axis=0) # mean object Xm.shape = (1,Xm.shape[0]) # fix shape Dm = self.distance(Xm,np.array(self.cmap)) D = self.distance(np.array(self.cmap),np.array(self.cmap)) # min value will be the first object #mi.append(ti.pop(Dm.argmin())) mi.append(ti.pop(-1)) if k > 1: # SECOND OBJECT Xm = np.array(self.cmap)[mi[-1]] Xm.shape = (1,Xm.shape[0]) # fix shape Dm = self.distance(Xm,np.array(self.cmap)[ti,:]) # max value will be the first object mi.append(ti.pop(Dm.argmax())) if k > 2: while (len(mi)<k): Dm = D[:,ti][mi,:] mi.append(ti.pop(Dm.min(axis=0).argmax())) return lind(self.cmap,mi+ti)
[docs] def __call__(self, node): if 0 < node: # cycle... return self.cmap[(node-1) % len(self.cmap)][:3] #return self.cmap[int(np.round(self.cm_size * f_like(node)))][:3] # return grey otherwise return self.grey[:3]
[docs]def f_like(n): if n == 1: return 0.0 if n == 2: return 0.5 n -= 1 order = np.floor(np.log(n) / np.log(2)) parts = 2 ** order current = n - parts return 0.5 / parts + 1. / parts * current