view tools/myTools/bin/sfa/vis/utils.py @ 1:7e5c71b2e71f draft default tip

Uploaded
author laurenmarazzi
date Wed, 22 Dec 2021 16:00:34 +0000
parents
children
line wrap: on
line source


import numpy as np
import networkx as nx


__all__ = [
    'compute_graphics',
]


def _rgb_to_hex(tup):
    """Convert RGBA to #AARRGGBB
    """
    tup = tuple(tup)
    if len(tup) == 3:
        return '#%02x%02x%02x' % tup
    elif len(tup) == 4:
        return '#%02x%02x%02x%02x' % (tup[3], tup[0], tup[1], tup[2])
    else:
        raise ValueError("Array or tuple for RGB or RGBA should be given.")


def compute_graphics(
        F,
        act,
        A,
        n2i,
        lw_min=1.0,
        lw_max=10.0,
        pct_link=90,
        pct_act=50,
        dg=None):
    """Compute graphics of signal flow.

    This method performs a calculation for generating colors
    of nodes and links for visualizing purpose.

    Parameters
    ----------
    F : numpy.ndarray
        A matrix of signal flows.
        It is usually calculated as W2*x1 - W1*x1,
        where W is weight matrix and
        x is a vector of activities at steady-state.

    act : numpy.ndarray
        Change in the activities. It is usually calculated
        as x2 - x1, where x is
        the a vector of activities at steady-state.

    A : numpy.ndarray
        Adjacency matrix of the network.

    n2i : dict
        Name to index dictionary.

    lw_min : float, optional
        Minimum link width, which is also used for unchanged flow.

    lw_max : float, optional
        Maximum link width.

    pct_link : int, optional
        Percentile of link width, which is used to set
        the maximum value for setting link widths.
        Default value is 90.

    pct_act : int, optional
        Percentile of activity, which is used to set
        the maximum value for coloring nodes.
        Default value is 50.

    dg : NetworkX.DiGraph, optional
        Existing NetworkX object to contain graphics information
        for visualizing nodes and links.

    Returns
    -------
    dg : NetworkX.DiGraph
        NetworkX object containing graphics information
        for visualizing nodes and links.

    """

    if not dg:
        dg = nx.DiGraph()
        dg.add_nodes_from(n2i)

    _compute_graphics_nodes(dg, n2i, act, pct_act)
    _compute_graphics_links(dg, n2i, A, F, pct_link, lw_min, lw_max)

    return dg


def _compute_graphics_nodes(dg, n2i, act, pct_act):
    color_white = np.array([255, 255, 255])
    color_up = np.array([255, 0, 0])
    color_dn = np.array([0, 0, 255])

    abs_act = np.abs(act)
    thr = np.percentile(abs_act, pct_act)
    thr = 1 if thr == 0 else thr

    arr_t = np.zeros_like(act)
    for i, elem in enumerate(act):
        t = np.clip(np.abs(elem) / thr, a_min=0, a_max=1)
        arr_t[i] = t

    for iden, idx in n2i.items():
        fold = act[idx]

        if fold > 0:
            color = color_white + arr_t[idx] * (color_up - color_white)
        elif fold <= 0:
            color = color_white + arr_t[idx] * (color_dn - color_white)

        color = _rgb_to_hex(np.int32(color))

        data = dg.nodes[iden]
        data['FILL_COLOR'] = color
        data['BORDER_WIDTH'] = 2
        data['BORDER_COLOR'] = _rgb_to_hex((40, 40, 40))
    # end of for


def _compute_graphics_links(dg, n2i, A, F, pct_link, lw_min, lw_max):
    i2n = {val: key for key, val in n2i.items()}

    log_flows = np.log10(np.abs(F[F.nonzero()]))
    flow_max = log_flows.max()
    flow_min = log_flows.min()
    flow_thr = np.percentile(log_flows, pct_link)

    ir, ic = A.nonzero()  # F.nonzero()
    for i, j in zip(ir, ic):
        tgt = i2n[i]
        src = i2n[j]
        f = F[i, j]

        #link = net.nxdg[src][tgt]['VIS']
        dg.add_edge(src, tgt)
        data = dg.edges[src, tgt]


        #header_old = link.header
        #args_header = header_old.width, header_old.height, header_old.offset
        if f > 0:
            sign_link = +1 # PosHeader(*args_header)
            color_link = _rgb_to_hex((255, 10, 10, 70))
        elif f < 0:
            sign_link = -1  # NegHeader(*args_header)
            color_link = _rgb_to_hex((10, 10, 255, 70))
        else:  # When flow is zero, show the sign of the original link.
            if A[i, j] > 0:
                sign_link = +1  # PosHeader(*args_header)
            elif A[i, j] < 0:
                sign_link = -1  # NegHeader(*args_header)
            else:
                raise RuntimeError("Abnormal state has been reached in "
                                   "_compute_graphics_links.")

            color_link = _rgb_to_hex((100, 100, 100, 100))


        # If header exists, it should be removed,
        # because the sign of signal flow can be different
        # from the original sign of header.
        if 'HEADER' in data:
            data.pop('HEADER')

        data['SIGN'] = sign_link
        data['FILL_COLOR'] = color_link

        if f == 0:
            data['WIDTH'] = lw_min
        elif (flow_max - flow_min) == 0:
            data['WIDTH'] = 0.5 * (lw_max + lw_min)
        else:
            log_f = np.log10(np.abs(f))
            log_f = np.clip(log_f, a_min=flow_min, a_max=flow_thr)
            lw = (log_f - flow_min) / (flow_max - flow_min) * (
            lw_max - lw_min) + lw_min
            data['WIDTH'] = lw