1- import functools
2- import json
3- import math
4- import random
5- from dash import dcc , Input , Output , State , html
6- import dash_cytoscape as cyto
7- import py2neo
8- from py2neo import NodeMatcher , Graph , RelationshipMatcher
9-
10- import networkx as nx
11-
12- finding = ['defect' , 'corrosion' , 'marine_growth' , 'paint_peel' , 'anode' , 'over_board_valve' , 'propeller' , 'sea_chest_grating' , 'bilge_keel' ]
13-
14- empty_elements = {
15- 'nodes' : [
16- {'data' :{'label' : 'Nothing to show' , 'id' : 3 }, 'classes' : 'finding' },
17- ],
18- 'edges' : [
19- ]
20- }
21-
22- """
23- elmts have the structure {'nodes': [], 'edges': []}
24- nodes: List of nodes:
25- {
26- 'data': {arbitrary data, no meaning for graph unless used in layout, eg. size},
27- 'classes': "classes, comma seperated i guess",
28- 'position': {'x': ..., 'y': ...}
29- }
30-
31- edges: List of edges:
32- {
33- 'data': {
34- 'source' : <index of node in nodes List>
35- 'target' : <index of node in nodes List>
36- + optional arbitrary data used in layout
37- }
38- }
39- """
40-
41- def redact_json_data (json_data , positions ):
42- for i , n in enumerate (json_data ['elements' ]['nodes' ]):
43- n ['data' ]['id' ] = n ['data' ]['value' ]
44- if 'classes' in n ['data' ]: n ['classes' ] = n ['data' ]['classes' ]
45- elif n ['data' ]['id' ].startswith ('s_' ):
46- n ['classes' ] = 'ship'
47- n ['data' ]['label' ] = n ['data' ]['name' ]
48-
49- elif n ['data' ]['id' ].startswith ('p_s_' ):
50- n ['classes' ] = 'ship_part'
51- n ['data' ]['label' ] = n ['data' ]['name' ]
52- elif n ['data' ]['id' ].startswith ('in_' ):
53- n ['classes' ] = 'inspection'
54- n ['data' ]['label' ] = f"Inspection on { n ['data' ]['date' ]} "
55- elif n ['data' ]['id' ].startswith ('c' ):
56- n ['classes' ] = 'cluster'
57- n ['data' ]['label' ] = f"Cluster { n ['data' ]['id' ].split ('.' )[- 1 ]} "
58- elif n ['data' ]['id' ].startswith ('m_' ):
59- n ['classes' ] = 'mosaic'
60- n ['data' ]['image_path' ] = 'assets/imgs/mosaics/' + n ['data' ]['seg_image_file' ]
61- elif n ['data' ]['id' ].startswith ('im_' ):
62- n ['classes' ] = 'frame'
63- n ['data' ]['image_path' ] = 'assets/imgs/frames/' + n ['data' ]['thumbnail' ]
64- n ['data' ]['label' ] = f"{ n ['data' ]['frame_index' ]} "
65-
66- if 'style' in n ['data' ]: n ['style' ] = n ['data' ]['style' ]
67- try :
68- #pos = n['data']['pca'].split(',')
69- #pos = [float(p) for p in pos]
70- pos = positions [n ['data' ]['id' ]]
71- n ['position' ] = {'x' : 1000 * pos [0 ], 'y' : 1000 * pos [1 ]}
72- except :
73- n ['position' ] = {'x' : 0 , 'y' : 0 }
74-
75- for i , e in enumerate (json_data ['elements' ]['edges' ]):
76- if 'classes' in e ['data' ]: e ['classes' ] = e ['data' ]['classes' ]
77- return json_data
78-
79-
80- legend_colors_segmenter = {
81- 'anode' : (0 , 255 , 255 ),
82- 'bilge_keel' : (255 , 165 , 0 ),
83- 'corrosion' : (255 , 255 , 0 ),
84- 'defect' : (255 , 192 , 203 ),
85- 'marine_growth' : (0 , 128 , 0 ),
86- 'over_board_valves' : (64 , 224 , 208 ),
87- 'paint_peel' : (255 , 0 , 0 ),
88- 'propeller' : (128 , 0 , 128 ),
89- 'sea_chest_grating' : (255 , 255 , 255 ),
90- 'ship_hull' : (0 , 0 , 255 ),
91- }
1+ graph_stylesheet = [
2+ {
3+ 'selector' : '.msg' ,
4+ 'style' : {
5+ "border-width" : 0 ,
6+ "border-color" : "black" ,
7+ "border-opacity" : 1 ,
8+ "opacity" : 1 ,
9+ "width" : 0 ,
10+ "height" : 0 ,
9211
93- def layout ():
94- return html .Div (className = "two_columns" , children = [
95- html .Div (className = "graphContainer" , children = [
96- html .H1 ("Graph View" ),
97- cyto .Cytoscape (
98- id = 'gr' ,
99- layout = {
100- 'name' : 'preset' ,
101- 'animate' : True ,
102- #'idealEdgeLength': 400,
103- #'nodeOverlap': 20,
104- #'refresh': 20,
105- 'fit' : True ,
106- #'padding': 30,
107- #'randomize': False,
108- 'componentSpacing' : 1 ,
109- 'nodeRepulsion' : 8000 ,
110- #'edgeElasticity': 10,
111- #'nestingFactor': 5,
112- #'gravity': 80,
113- #'numIter': 1000,
114- #'initialTemp': 200,
115- #'coolingFactor': 0.95,
116- #'minTemp': 1.0
117- },
118- style = {'width' : '100%' , 'height' : '1000px' },
119- elements = {},
120- stylesheet = [
12+ "label" : "data(label)" ,
13+ "color" : "#000000" ,
14+ "text-opacity" : 1 ,
15+ "font-size" : 12 ,
16+ }
17+ },
12118 {
12219 'selector' : '.finding' ,
12320 'style' : {
@@ -282,7 +179,124 @@ def layout():
282179 }
283180 },
284181 ]
285- )
182+ import functools
183+ import json
184+ import math
185+ import random
186+ from dash import dcc , Input , Output , State , html
187+ import dash_cytoscape as cyto
188+ import py2neo
189+ from py2neo import NodeMatcher , Graph , RelationshipMatcher
190+
191+ import networkx as nx
192+
193+ finding = ['defect' , 'corrosion' , 'marine_growth' , 'paint_peel' , 'anode' , 'over_board_valve' , 'propeller' , 'sea_chest_grating' , 'bilge_keel' ]
194+
195+ empty_elements = {
196+ 'nodes' : [
197+ {'data' :{'label' : 'Nothing to show' , 'id' : 3 }, 'classes' : 'msg' },
198+ ],
199+ 'edges' : [
200+ ]
201+ }
202+ loading_elements = {
203+ 'nodes' : [
204+ {'data' :{'label' : 'Loading...' , 'id' : 3 }, 'classes' : 'msg' },
205+ ],
206+ 'edges' : [
207+ ]
208+ }
209+
210+ """
211+ elmts have the structure {'nodes': [], 'edges': []}
212+ nodes: List of nodes:
213+ {
214+ 'data': {arbitrary data, no meaning for graph unless used in layout, eg. size},
215+ 'classes': "classes, comma seperated i guess",
216+ 'position': {'x': ..., 'y': ...}
217+ }
218+
219+ edges: List of edges:
220+ {
221+ 'data': {
222+ 'source' : <index of node in nodes List>
223+ 'target' : <index of node in nodes List>
224+ + optional arbitrary data used in layout
225+ }
226+ }
227+ """
228+
229+ def redact_json_data (json_data , positions ):
230+ for i , n in enumerate (json_data ['elements' ]['nodes' ]):
231+ n ['data' ]['id' ] = n ['data' ]['value' ]
232+ if 'classes' in n ['data' ]: n ['classes' ] = n ['data' ]['classes' ]
233+ elif n ['data' ]['id' ].startswith ('s_' ):
234+ n ['classes' ] = 'ship'
235+ n ['data' ]['label' ] = n ['data' ]['name' ]
236+
237+ elif n ['data' ]['id' ].startswith ('p_s_' ):
238+ n ['classes' ] = 'ship_part'
239+ n ['data' ]['label' ] = n ['data' ]['name' ]
240+ elif n ['data' ]['id' ].startswith ('in_' ):
241+ n ['classes' ] = 'inspection'
242+ n ['data' ]['label' ] = f"Inspection on { n ['data' ]['date' ]} "
243+ elif n ['data' ]['id' ].startswith ('c' ):
244+ n ['classes' ] = 'cluster'
245+ n ['data' ]['label' ] = f"Cluster { n ['data' ]['id' ].split ('.' )[- 1 ]} "
246+ elif n ['data' ]['id' ].startswith ('m_' ):
247+ n ['classes' ] = 'mosaic'
248+ n ['data' ]['image_path' ] = 'assets/imgs/mosaics/' + n ['data' ]['seg_image_file' ]
249+ elif n ['data' ]['id' ].startswith ('im_' ):
250+ n ['classes' ] = 'frame'
251+ n ['data' ]['image_path' ] = 'assets/imgs/frames/' + n ['data' ]['thumbnail' ]
252+ n ['data' ]['label' ] = f"{ n ['data' ]['frame_index' ]} "
253+
254+ if 'style' in n ['data' ]: n ['style' ] = n ['data' ]['style' ]
255+ try :
256+ #pos = n['data']['pca'].split(',')
257+ #pos = [float(p) for p in pos]
258+ pos = positions [n ['data' ]['id' ]]
259+ n ['position' ] = {'x' : 1 * pos [0 ], 'y' : 1 * pos [1 ]}
260+ except :
261+ n ['position' ] = {'x' : 0 , 'y' : 0 }
262+
263+ for i , e in enumerate (json_data ['elements' ]['edges' ]):
264+ if 'classes' in e ['data' ]: e ['classes' ] = e ['data' ]['classes' ]
265+ return json_data
266+
267+
268+ legend_colors_segmenter = {
269+ 'anode' : (0 , 255 , 255 ),
270+ 'bilge_keel' : (255 , 165 , 0 ),
271+ 'corrosion' : (255 , 255 , 0 ),
272+ 'defect' : (255 , 192 , 203 ),
273+ 'marine_growth' : (0 , 128 , 0 ),
274+ 'over_board_valves' : (64 , 224 , 208 ),
275+ 'paint_peel' : (255 , 0 , 0 ),
276+ 'propeller' : (128 , 0 , 128 ),
277+ 'sea_chest_grating' : (255 , 255 , 255 ),
278+ 'ship_hull' : (0 , 0 , 255 ),
279+ }
280+
281+ def layout ():
282+ return html .Div (className = "two_columns" , children = [
283+ html .Div (className = "graphContainer" , children = [
284+ html .H1 ("Graph View" ),
285+ dcc .Loading (
286+ id = "loading_gr" ,
287+ type = "default" ,
288+ children = [
289+ cyto .Cytoscape (
290+ id = 'gr' ,
291+ layout = {
292+ 'name' : 'preset' ,
293+ 'animate' : False ,
294+ 'fit' : True ,
295+ },
296+ style = {'width' : '100%' , 'height' : '1000px' },
297+ elements = {},
298+ stylesheet = graph_stylesheet )
299+ ])
286300 ]),
287301 html .Div (className = "nodeInfo" , children = [
288302 html .H1 ("Node Info" ),
@@ -318,19 +332,25 @@ def update_graph(data, filter_options):
318332 relcount = 0
319333
320334 replace_with = set ()
335+
336+ SIMILAR_EDGE_WEIGHT = 0.6
337+ CLUSTER_EDGE_WEIGHT = 0.6
338+ ININSPE_EDGE_WEIGHT = 0.1
339+ PARTOFS_EDGE_WEIGHT = 0.1
340+ SHOWSPR_EDGE_WEIGHT = 0.1
321341
322342 for n1 , n2 in data ['in_inspection' ]:
323343 if not n1 in node_keys or not n2 in node_keys : continue
324344 relcount += 1
325- graph .add_edge (n1 , n2 , classes = "inspection" , weight = 0 )
345+ graph .add_edge (n1 , n2 , classes = "inspection" , weight = ININSPE_EDGE_WEIGHT )
326346 for n1 , n2 in data ['similar_to' ]:
327347 if not n1 in node_keys or not n2 in node_keys : continue
328348 relcount += 1
329- graph .add_edge (n1 , n2 , classes = "dashed" , weight = 0.08 )
349+ graph .add_edge (n1 , n2 , classes = "dashed" , weight = SIMILAR_EDGE_WEIGHT )
330350 for n1 , n2 in data ['visually_similar_to' ]:
331351 if not n1 in node_keys or not n2 in node_keys : continue
332352 relcount += 1
333- graph .add_edge (n1 , n2 , classes = "dotted" , weight = 0.08 )
353+ graph .add_edge (n1 , n2 , classes = "dotted" , weight = SIMILAR_EDGE_WEIGHT )
334354 for n1 , n2 in data ['in_mosaic' ]:
335355 if not n1 in node_keys or not n2 in node_keys : continue
336356 relcount += 1
@@ -340,16 +360,16 @@ def update_graph(data, filter_options):
340360 for n1 , n2 in data ['in_cluster' ]:
341361 if not n1 in node_keys or not n2 in node_keys : continue
342362 relcount += 1
343- graph .add_edge (n1 , n2 , weight = 0.1 )
363+ graph .add_edge (n1 , n2 , weight = CLUSTER_EDGE_WEIGHT )
344364
345365 for n1 , n2 in data ['part_of_ship' ]:
346366 if not n1 in node_keys or not n2 in node_keys : continue
347367 relcount += 1
348- graph .add_edge (n1 , n2 , weight = 0.01 )
368+ graph .add_edge (n1 , n2 , weight = PARTOFS_EDGE_WEIGHT )
349369 for n1 , n2 in data ['shows_part' ]:
350370 if not n1 in node_keys or not n2 in node_keys : continue
351371 relcount += 1
352- graph .add_edge (n1 , n2 , weight = 0.02 , classes = "part" )
372+ graph .add_edge (n1 , n2 , weight = SHOWSPR_EDGE_WEIGHT , classes = "part" )
353373
354374 for image , mosaic in replace_with :
355375 for n1 , n2 , data in graph .edges (image , data = True ):
@@ -359,20 +379,18 @@ def update_graph(data, filter_options):
359379 graph .remove_node (image )
360380
361381
362-
363-
364-
365- print ("pre layout jobs..." )
366-
367382 positions_by_pca = {
368383 key : (float (tup ['tsne' ].split (',' )[0 ]),float (tup ['tsne' ].split (',' )[1 ]))
369384
370385 for key , tup in node_tuples if 'tsne' in tup }
371386
372387 fixed_positions = [k for k in positions_by_pca .keys ()]
373388 print (f"loaded result, got { len (node_tuples )} nodes and { relcount } relationships, layouting..." )
389+
374390 #pos = nx.spring_layout(graph, fixed = fixed_positions, pos = positions_by_pca, k=1/math.sqrt(graph.order() + 1), iterations=100)
375- pos = nx .spring_layout (graph , k = 4 / math .sqrt (graph .order () + 1 ), iterations = 200 )
391+
392+ pos = nx .spring_layout (graph , scale = 1000 , k = 10 / math .sqrt (graph .order () + 1 ), iterations = 300 , seed = 84365 )
393+
376394 elmts = redact_json_data (nx .cytoscape_data (graph ), pos )['elements' ]
377395 print (f"done" )
378396
0 commit comments