1
|
1 import numpy as np
|
|
2 import networkx as nx
|
|
3
|
|
4 from qtpy.QtGui import QFont
|
|
5 from qtpy.QtGui import QColor
|
|
6 from qtpy.QtCore import QPointF
|
|
7 from qtpy.QtCore import Qt
|
|
8
|
|
9 import sfv
|
|
10 from sfv.graphics import LabelClassFactory
|
|
11 from sfv.graphics import HeaderClassFactory
|
|
12
|
|
13 import sfa
|
|
14
|
|
15 NegHeader = HeaderClassFactory.create('HAMMER')
|
|
16 PosHeader = HeaderClassFactory.create('ARROW')
|
|
17 TextLabel = LabelClassFactory.create('TEXT_LABEL')
|
|
18
|
|
19 def create_from_graphics(net, abbr=None, inputs=None, outputs=None):
|
|
20 """Create sfv.base.Data object from SIF file.
|
|
21
|
|
22 Parameters
|
|
23 ----------
|
|
24 net : sfv.graphics.Network
|
|
25 Network graphics object of SFV.
|
|
26 abbr : str
|
|
27 Abbreviation to denote this data object for the network.
|
|
28 inputs : dict, optional
|
|
29 Input information with default values
|
|
30 outputs : sequence, optional
|
|
31 Output information.
|
|
32
|
|
33 Returns
|
|
34 -------
|
|
35 obj : sfv.base.Data
|
|
36 Data object with the information of network topology.
|
|
37
|
|
38 """
|
|
39
|
|
40 if not abbr:
|
|
41 abbr = net.name
|
|
42
|
|
43 class __Data(sfa.base.Data):
|
|
44 def __init__(self):
|
|
45 self._abbr = abbr
|
|
46 self._name = self._abbr
|
|
47
|
|
48 self._dg = sfv.to_networkx(net)
|
|
49 nodes = sorted(self._dg.nodes)
|
|
50 self._n2i = {name: i for i, name in enumerate(nodes)}
|
|
51 self._i2n = {idx: name for name, idx in self._n2i.items()}
|
|
52
|
|
53 self._A = nx.to_numpy_array(self._dg, nodes).T
|
|
54 ir, ic = self._A.nonzero()
|
|
55 for i in range(ir.size):
|
|
56 r, c = ir[i], ic[i]
|
|
57
|
|
58 src = self._i2n[c]
|
|
59 tgt = self._i2n[r]
|
|
60 sign = self._dg.edges[src, tgt]['SIGN']
|
|
61 self._A[r, c] *= sign
|
|
62
|
|
63 self._inputs = inputs
|
|
64
|
|
65 if outputs:
|
|
66 self._outputs = outputs
|
|
67
|
|
68 # The following members are not defined due to the lack of data.
|
|
69 self._df_conds = None
|
|
70 self._df_exp = None
|
|
71 self._df_ptb = None
|
|
72 self._has_link_perturb = False
|
|
73 self._names_ptb = None
|
|
74 self._iadj_to_idf = None
|
|
75 # end of def __init__
|
|
76 # end of def class
|
|
77
|
|
78 class_name = ''.join([c for c in abbr.title() if c.isalnum()])
|
|
79 __Data.__name__ = class_name + "Data"
|
|
80 return __Data()
|
|
81
|
|
82
|
|
83 def visualize_signal_flow(net, F, act,
|
|
84 A,
|
|
85 n2i,
|
|
86 color_up=None, color_dn=None,
|
|
87 lw_min=1.0,
|
|
88 lw_max=10.0,
|
|
89 pct_link=90,
|
|
90 show_label=True,
|
|
91 show_act=True,
|
|
92 pct_act=50,
|
|
93 fmt_act='%.5f',
|
|
94 fix_node_size=False,
|
|
95 fix_act_label=False,
|
|
96 font=None):
|
|
97 """Visualize signal flow using SFV.
|
|
98
|
|
99 SFV (Seamless Flow Visualization) is a light-weight,
|
|
100 programming-oriented python package to visualize
|
|
101 graphs and networks.
|
|
102
|
|
103 This function is used in the SFV function,
|
|
104 'execute(nav, net)', which is called in SFV program.
|
|
105
|
|
106 Parameters
|
|
107 ----------
|
|
108 net : sfv.graphics.Network
|
|
109 Network object that is given by sfv.
|
|
110
|
|
111 F : numpy.ndarray
|
|
112 A matrix of signal flows.
|
|
113 It is usually calculated as W2*x1 - W1*x1,
|
|
114 where W is weight matrix and
|
|
115 x is a vector of activities at steady-state.
|
|
116
|
|
117 act : numpy.ndarray
|
|
118 Change in the activities. It is usually calculated
|
|
119 as x2 - x1, where x is
|
|
120 the a vector of activities at steady-state.
|
|
121
|
|
122 A : numpy.ndarray
|
|
123 Adjacency matrix of the network.
|
|
124
|
|
125 n2i : dict
|
|
126 Name to index dictionary.
|
|
127
|
|
128 color_up : numpy.ndarray or QtGui.QColor, optional
|
|
129 Default is blue (i.e., QColor(0, 0, 255)),
|
|
130 if it is None.
|
|
131
|
|
132 color_dn : numpy.ndarray or QtGui.QColor, optional
|
|
133 Default is red (i.e., QColor(255, 0, 0)),
|
|
134 if it is None.
|
|
135
|
|
136 lw_min : float, optional
|
|
137 Minimum link width, which is also used for unchanged flow.
|
|
138
|
|
139 lw_max : float, optional
|
|
140 Maximum link width.
|
|
141
|
|
142 pct_link : int, optional
|
|
143 Percentile of link width, which is used to set
|
|
144 the maximum value for setting link widths.
|
|
145 Default value is 90.
|
|
146
|
|
147 show_label : bool, optional
|
|
148 Show node label or not.
|
|
149
|
|
150 show_act : bool, optional
|
|
151 Show activity label or not.
|
|
152
|
|
153 pct_act : int, optional
|
|
154 Percentile of activity, which is used to set
|
|
155 the maximum value for coloring nodes.
|
|
156 Default value is 50.
|
|
157
|
|
158 fmt_act : str, optional
|
|
159 Format string for activity label.
|
|
160
|
|
161 fix_node_size : bool, optional
|
|
162 Change the size of node or not.
|
|
163 Default is False.
|
|
164
|
|
165 fix_act_label : bool, optional
|
|
166 Change the graphics of activity label or not.
|
|
167 The activity value is changed irrespective of
|
|
168 this parameter. Default is False.
|
|
169
|
|
170 font : QFont, optional
|
|
171 Font for the name and activity labels.
|
|
172
|
|
173 Returns
|
|
174 -------
|
|
175 None
|
|
176 """
|
|
177
|
|
178 i2n = {val:key for key, val in n2i.items()}
|
|
179 color_white = np.array([255, 255, 255])
|
|
180
|
|
181 if not color_up:
|
|
182 color_up = np.array([255, 0, 0])
|
|
183 elif isinstance(color_up, QColor):
|
|
184 color_up = np.array([color_up.red(),
|
|
185 color_up.green(),
|
|
186 color_up.blue()])
|
|
187 else:
|
|
188 raise ValueError("color_up should be 3-dimensional np.ndarray "
|
|
189 "or QtGui.QColor")
|
|
190
|
|
191 if not color_dn:
|
|
192 color_dn = np.array([0, 0, 255])
|
|
193 elif isinstance(color_dn, QColor):
|
|
194 color_dn = np.array([color_dn.red(),
|
|
195 color_dn.green(),
|
|
196 color_dn.blue()])
|
|
197 else:
|
|
198 raise ValueError("color_dn should be 3-dimensional np.ndarray "
|
|
199 "or QtGui.QColor")
|
|
200
|
|
201 # Set the default font
|
|
202 if not font:
|
|
203 font = QFont('Arial', 10)
|
|
204
|
|
205 abs_act = np.abs(act)
|
|
206 thr = np.percentile(abs_act, pct_act)
|
|
207 thr = 1 if thr == 0 else thr
|
|
208
|
|
209 arr_t = np.zeros_like(act)
|
|
210
|
|
211 for i, elem in enumerate(act):
|
|
212 t = np.clip(np.abs(elem)/thr, a_min=0, a_max=1)
|
|
213 arr_t[i] = t
|
|
214
|
|
215 for iden, node in net.nodes.items():
|
|
216 idx = n2i[iden]
|
|
217
|
|
218 if not fix_node_size:
|
|
219 radius = 20
|
|
220 node.width = node.height = radius
|
|
221
|
|
222 fold = act[idx]
|
|
223
|
|
224 if fold > 0:
|
|
225 color = color_white + arr_t[idx] * (color_up - color_white)
|
|
226 elif fold <= 0:
|
|
227 color = color_white + arr_t[idx] * (color_dn - color_white)
|
|
228
|
|
229 color = np.int32(color)
|
|
230 node['FILL_COLOR'] = QColor(*color)
|
|
231 node['BORDER_WIDTH'] = 2
|
|
232 node['BORDER_COLOR'] = QColor(40, 40, 40)
|
|
233
|
|
234 if show_label:
|
|
235 _update_single_label_name(net, node, node.name,
|
|
236 fix_node_size, font)
|
|
237
|
|
238 if show_act:
|
|
239 _update_single_label_activity(net, node, fold,
|
|
240 fix_act_label,
|
|
241 fmt_act, font)
|
|
242 else:
|
|
243 iden_label = '%s_act' % iden.upper()
|
|
244 if iden_label in net.labels:
|
|
245 net.remove_label(net.labels[iden_label])
|
|
246 # end of for : update nodes and labels
|
|
247
|
|
248 _update_links(net, A, F, i2n, pct_link, lw_min, lw_max)
|
|
249
|
|
250
|
|
251 def _update_links(net, A, F, i2n, pct_link, lw_min, lw_max):
|
|
252 log_flows = np.log10(np.abs(F[F.nonzero()]))
|
|
253 flow_max = log_flows.max()
|
|
254 flow_min = log_flows.min()
|
|
255 flow_thr = np.percentile(log_flows, pct_link)
|
|
256
|
|
257 ir, ic = A.nonzero() #F.nonzero()
|
|
258 for i, j in zip(ir, ic):
|
|
259 tgt = i2n[i]
|
|
260 src = i2n[j]
|
|
261 f = F[i, j]
|
|
262
|
|
263 link = net.nxdg[src][tgt]['VIS']
|
|
264
|
|
265 header_old = link.header
|
|
266 args_header = header_old.width, header_old.height, header_old.offset
|
|
267 if f > 0:
|
|
268 header = PosHeader(*args_header)
|
|
269 color_link = QColor(255, 10, 10, 70)
|
|
270 elif f < 0:
|
|
271 header = NegHeader(*args_header)
|
|
272 color_link = QColor(10, 10, 255, 70)
|
|
273 else: # When flow is zero, show the sign of the original link.
|
|
274 if A[i, j]>0:
|
|
275 header = PosHeader(*args_header)
|
|
276 elif A[i, j]<0:
|
|
277 header = NegHeader(*args_header)
|
|
278 else:
|
|
279 raise RuntimeError("The logic is abnormal.")
|
|
280
|
|
281 color_link = QColor(100, 100, 100, 100)
|
|
282
|
|
283 link.header = header
|
|
284 link['FILL_COLOR'] = color_link
|
|
285
|
|
286 if f == 0:
|
|
287 link.width = lw_min
|
|
288 elif (flow_max - flow_min) == 0:
|
|
289 link.width = 0.5*(lw_max + lw_min)
|
|
290 else:
|
|
291 log_f = np.log10(np.abs(f))
|
|
292 log_f = np.clip(log_f, a_min=flow_min, a_max=flow_thr)
|
|
293 lw = (log_f-flow_min)/(flow_max-flow_min)*(lw_max-lw_min) + lw_min
|
|
294 link.width = lw
|
|
295
|
|
296
|
|
297 def _update_single_label_name(net, node, name,
|
|
298 fix_node_size, font):
|
|
299 label_name = net.labels[name]
|
|
300
|
|
301 lightness = QColor(node['FILL_COLOR']).lightness()
|
|
302 label_name['TEXT_COLOR'] = Qt.black
|
|
303
|
|
304 label_name['FONT'] = font
|
|
305
|
|
306 if lightness < 200:
|
|
307 label_name['TEXT_COLOR'] = Qt.white
|
|
308 label_name['FONT_BOLD'] = True
|
|
309 else:
|
|
310 label_name['TEXT_COLOR'] = Qt.black
|
|
311 label_name['FONT_BOLD'] = False
|
|
312
|
|
313 rect = label_name.boundingRect()
|
|
314 label_name.setPos(-rect.width() / 2, -rect.height() / 2) # center
|
|
315 if not fix_node_size:
|
|
316 node.width = 1.1 * rect.width()
|
|
317 node.height = 1.1 * rect.height()
|
|
318
|
|
319
|
|
320 def _update_single_label_activity(net, node, x, fix_act_label, fmt, font):
|
|
321 iden = '%s_act' % node.iden.upper()
|
|
322 str_x = fmt % (x)
|
|
323 if iden not in net.labels:
|
|
324 label_act = TextLabel(node, text=str_x)
|
|
325 label_act.iden = iden
|
|
326 else:
|
|
327 label_act = net.labels[iden]
|
|
328 label_act.text = str_x % (x)
|
|
329
|
|
330 if not fix_act_label:
|
|
331 label_act['FONT'] = font
|
|
332 label_act['TEXT_COLOR'] = QColor(20, 20, 20)
|
|
333 rect = label_act.boundingRect()
|
|
334 pos_x = node.width/2 + 0.5
|
|
335 label_act.setPos(pos_x, -rect.height() / 2)
|
|
336
|
|
337 if iden not in net.labels:
|
|
338 net.add_label(label_act)
|