-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFOON_graph_analyser.py
2285 lines (1877 loc) · 98.1 KB
/
FOON_graph_analyser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'''
FOON: Graph Analyzer (FOON_graph_analyzer):
-------------------------------------------
-- Written and maintained by:
* David Paulius ([email protected] / [email protected])
* Md Sadman Sakib ([email protected])
-- Special thanks to undergraduates Kelvin Dong Sheng Pei and Sanjeeth Bhat.
NOTE: If using this program and/or annotations provided by our lab, please kindly cite our papers
so that others may find our work:
* Paulius et al. 2016 - https://ieeexplore.ieee.org/abstract/document/7759413/
* Paulius et al. 2018 - https://ieeexplore.ieee.org/abstract/document/8460200/
'''
from __future__ import print_function
from builtins import input
import getopt, os, sys, time, ast
import tqdm
last_updated = '15th April, 2022'
# NOTE: you MUST have the accompanying 'FOON_classes.py'!
# -- you should probably find this file in the same repository as you found this script.
try:
import FOON_classes as FOON
except ImportError:
print(" -- ERROR: Missing 'FOON_classes.py' file! Make sure you have downloaded the accompanying class file!")
print("\t-- Download here: https://github.com/davidpaulius/foon_api")
exit()
#end
# NOTE: Sadman recently introduced config files; here, we check config file for any flags (if one is present):
from configparser import ConfigParser
config = None
try:
config_file = 'config.ini'
config = ConfigParser()
config.read_file(open(config_file))
except FileNotFoundError:
pass
else:
print(" -- Loaded configuration file 'config.ini' !")
#end
###############################################################################################################################
# NOTE: The following variables are typically referenced as global variables in inner functions or references:
FOON_node_count = 0 # -- total number of nodes (w.r.t level 3 of hierarchy):
# NOTE: list of all functional units for each hierarchy level:
# -- hierarchy levels, FOON-EXP and FOON-GEN discussed in greater detail in Paulius et. al 2018
FOON_lvl1 = []; FOON_lvl2 = []; FOON_lvl3 = []
# NOTE: list of all nodes used for the functional unit lists above for each hierarchy level:
nodes_lvl1 = []; nodes_lvl2 = []; nodes_lvl3 = []
# NOTE: The following are dictionary structures used to map:
# -- output objects to functional units:
FOON_outputsToUnits_lvl1 = {}; FOON_outputsToUnits_lvl2 = {}; FOON_outputsToUnits_lvl3 = {}
# -- object to functional units (for both inputs or outputs):
FOON_objectsToUnits_lvl1 = {}; FOON_objectsToUnits_lvl2 = {}; FOON_objectsToUnits_lvl3 = {}
# -- functional units to other units (determined by existence of overlapping input and output objects) :
FOON_functionalUnitMap_lvl1 = {}; FOON_functionalUnitMap_lvl2 = {}; FOON_functionalUnitMap_lvl3 = {}
# -- motion nodes from the lists above to the functional units they are part of:
motionsToFunctionalUnits_lvl1 = {}; motionsToFunctionalUnits_lvl2 = {}; motionsToFunctionalUnits_lvl3 = {}
# NOTE: list collecting all of these lists and dictionaries:
FOON_functionalUnits = [FOON_lvl1, FOON_lvl2, FOON_lvl3] # -- list of lists of functional units at all levels
FOON_nodes = [nodes_lvl1, nodes_lvl2, nodes_lvl3] # -- list of lists of nodes at all levels
# -- list of all dictionaries mapping output objects to their respective functional units:
FOON_outputsToUnits = [FOON_outputsToUnits_lvl1, FOON_outputsToUnits_lvl2, FOON_outputsToUnits_lvl3]
# -- list of all dictionaries mapping ALL objects to their respective functional units:
FOON_objectsToUnits = [FOON_objectsToUnits_lvl1, FOON_objectsToUnits_lvl2, FOON_objectsToUnits_lvl3]
# -- list of all dictionaries mapping functional units to all other functional units with overlapping objects (inputs or outputs):
FOON_functionalUnitMap = [FOON_functionalUnitMap_lvl1, FOON_functionalUnitMap_lvl2, FOON_functionalUnitMap_lvl3]
# -- list of all dictionaries mapping motion nodes to their respective functional units:
motionsToFunctionalUnits = [motionsToFunctionalUnits_lvl1, motionsToFunctionalUnits_lvl2, motionsToFunctionalUnits_lvl3]
# NOTE: dictionary objects used for one-mode projection (graph of ONLY object nodes):
FOON_oneModeProjection_lvl1 = {}; FOON_oneModeProjection_lvl2 = {}; FOON_oneModeProjection_lvl3 = {}
# -- list of objects that are used in one-mode projection of object nodes:
objects_oneModeProjection_lvl1 = []; objects_oneModeProjection_lvl2 = []; objects_oneModeProjection_lvl3 = []
FOON_oneModeProjection = [FOON_oneModeProjection_lvl1, FOON_oneModeProjection_lvl2, FOON_oneModeProjection_lvl3]
objects_oneModeProjection = [objects_oneModeProjection_lvl1, objects_oneModeProjection_lvl2, objects_oneModeProjection_lvl3]
# NOTE: dictionary mapping labels to IDs for objects, motions, and states:
FOON_objectLabels = {}; FOON_motionLabels = {}; FOON_stateLabels = {}
FOON_labels = {'objects' : FOON_objectLabels, 'motions' : FOON_motionLabels, 'states' : FOON_stateLabels}
# NOTE: storing the sense id for objects (based on WordNet) or to denote Concept-Net sense -- these were verified semi-automatically using parser!
FOON_objectSenses = {}
# NOTE: the following are dictionaries used for mapping categories to object labels (for generalization of FOON):
FOON_objectClasses = {}
# NOTE: these lists are used for the generalization of FOON:
# -- Two "generalized" versions of FOON:
# 1. FOON-EXP - expanded version of FOON that uses WordNet/Concept-Net similarities to create new units;
# -- this would use the regular FOON lists from above since we perform expansion and read the new file created
# 2. FOON-GEN - compressed version of FOON that uses object categories
# -- please refer to Paulius et al. 2018 for more explanation on these approaches.
FOON_GEN = []; nodes_GEN = []; FOON_object_map_GEN = {}
flag_EXP_complete = False
flag_GEN_complete = False
# NOTE: this dictionary is used to store the similarity value between pairs of objects:
object_similarity_index = {}
verbose = False # -- change this if you would like more output to the console
# NOTE: name of the file name used for this FOON analyzer script:
file_name = None # -- change this if you want to set default universal FOON file to FIXED file
# NOTE: flags used for _buildInternalMaps function:
flag_buildFunctionalUnitMap = False
flag_buildObjectToUnitMap = False
flag_mapsReady = False
FOON_video_source = []
# NOTE: global variable to store the location of the Concept-Net word embedding model (numberbatch);
# you can download or find out more about this model here: https://github.com/commonsense/Concept-Net-numberbatch
# -- override this with an actual path if you want to skip the prompts.
path_to_ConceptNet = None
# NOTE: version 2 means that we have performed functional unit chain compression into "combinational" functional units / hubs.
# -- see function *_findFunctionalUnitClusters* for more details
FOON_lvl1_ver2 = []; FOON_lvl2_ver2 = []; FOON_lvl3_ver2 = []
FOON_unitClusters = [FOON_lvl1_ver2, FOON_lvl2_ver2, FOON_lvl3_ver2]
###############################################################################################################################
# NOTE: loading of internal dictionaries that are used for the searching process:
def _buildInternalMaps():
# NOTE: this function builds dictionary structures that will be used for the searching algorithms:
print('\n -- [FOON-fga] : Building internal dictionaries...')
global flag_buildFunctionalUnitMap, flag_buildObjectToUnitMap
# -- make sure everything is cleared and then initialized:
_resetMaps()
# -- build mapping between output objects to the units that make them:
_buildOutputsToUnitMap()
# -- build mapping between functional units whose outputs overlap with another's inputs:
if flag_buildFunctionalUnitMap:
_buildFunctionalUnitMap()
# -- building mapping between all objects and the functional units they appear in:
if flag_buildObjectToUnitMap:
_buildObjectToUnitMap()
#enddef
def _buildOutputsToUnitMap():
# NOTE: create a mapping between all objects and related units based on outputs:
print(' -- [FOON-fga] : Building output-to-FU dictionaries...')
_buildOutputsToUnitMap_lvl1()
print(' -- Level 1: Output object map complete!')
_buildOutputsToUnitMap_lvl2()
print(' -- Level 2: Output object map complete!')
_buildOutputsToUnitMap_lvl3()
print(' -- Level 3: Output object map complete!\n')
#enddef
def _buildOutputsToUnitMap_lvl1():
global FOON_outputsToUnits_lvl1, nodes_lvl1, FOON_lvl1
for _input in nodes_lvl1:
if isinstance(_input, FOON.Object):
procedures = []
for _U in FOON_lvl1:
for _output in _U.getOutputList():
if _input.equals_functions[0](_output):
procedures.append(_U); break
#endif
#endfor
#endfor
FOON_outputsToUnits_lvl1[_input] = procedures
#endif
#endfor
#enddef
def _buildOutputsToUnitMap_lvl2():
global FOON_outputsToUnits_lvl2, nodes_lvl2, FOON_lvl2
for _input in nodes_lvl2:
if isinstance(_input, FOON.Object):
procedures = []
for _U in FOON_lvl2:
for _output in _U.getOutputList():
if _input.equals_functions[1](_output):
procedures.append(_U); break
#endif
#endfor
#endfor
FOON_outputsToUnits_lvl2[_input] = procedures
#endif
#endfor
return
#enddef
def _buildOutputsToUnitMap_lvl3():
global FOON_outputsToUnits_lvl3, nodes_lvl3, FOON_lvl3
for _input in nodes_lvl3:
if isinstance(_input, FOON.Object):
procedures = []
for _U in FOON_lvl3:
for _output in _U.getOutputList():
if _input.equals_functions[2](_output):
procedures.append(_U); break
#endif
#endfor
#endfor
FOON_outputsToUnits_lvl3[_input] = procedures
#endif
#endfor
return
#enddef
def _buildObjectToUnitMap():
# NOTE: create a mapping between all objects and related units:
print(' -- [FOON-fga] : Building object-to-FU dictionaries...')
_buildObjectToUnitMap_lvl1()
print(' -- Level 1: Object map complete!')
_buildObjectToUnitMap_lvl2()
print(' -- Level 2: Object map complete!')
_buildObjectToUnitMap_lvl3()
print(' -- Level 3: Object map complete!\n')
#enddef
def _buildObjectToUnitMap_lvl1():
global FOON_objectsToUnits_lvl1, nodes_lvl1, FOON_lvl1
for N in nodes_lvl1:
if isinstance(N, FOON.Object):
procedures = []
for _U in FOON_outputsToUnits_lvl1[N]:
procedures.append(_U)
for _U in FOON_lvl1:
for _input in _U.getInputList():
if _input.equals_functions[0](N):
procedures.append(_U); break
#endif
#endfor
#endfor
FOON_objectsToUnits_lvl1[N] = list(set(procedures))
#endif
#endfor
return
#enddef
def _buildObjectToUnitMap_lvl2():
global FOON_objectsToUnits_lvl2, nodes_lvl2, FOON_lvl2
for N in nodes_lvl2:
if isinstance(N, FOON.Object):
procedures = []
for _U in FOON_outputsToUnits_lvl2[N]:
procedures.append(_U)
for _U in FOON_lvl2:
for _input in _U.getInputList():
if _input.equals_functions[1](N):
procedures.append(_U); break
#endif
#endfor
#endfor
FOON_objectsToUnits_lvl2[N] = list(set(procedures))
#endif
#endfor
return
#enddef
def _buildObjectToUnitMap_lvl3():
global FOON_objectsToUnits_lvl3, FOON_outputsToUnits_lvl3 ,nodes_lvl3, FOON_lvl3
for N in nodes_lvl3:
if isinstance(N, FOON.Object):
procedures = []
for _U in FOON_outputsToUnits_lvl3[N]:
procedures.append(_U)
#endfor
for _U in FOON_lvl3:
for _input in _U.getInputList():
if _input.equals_functions[2](N):
procedures.append(_U); break
#endif
#endfor
#endfor
FOON_objectsToUnits_lvl3[N] = list(set(procedures))
#endif
#endfor
return
#enddef
def _buildFunctionalUnitMap():
# NOTE: create a mapping between functional units to show
# which ones are connected to one another.
print(' -- [FOON-fga] : Building FU-to-FU dictionaries...')
_buildUnitToUnitMap_lvl1()
print(' -- Level 1: Functional unit map complete!')
_buildUnitToUnitMap_lvl2()
print(' -- Level 2: Functional unit map complete!')
_buildUnitToUnitMap_lvl3()
print(' -- Level 3: Functional unit map complete!\n')
#enddef
def _buildUnitToUnitMap_lvl1():
global FOON_functionalUnitMap_lvl1
for _FU in FOON_lvl1:
prerequisite_units = []
for _input in _FU.getInputList():
# -- we already collected the units that create every single input object in FOON:
candidates = FOON_outputsToUnits_lvl1.get(_input, [])
for C in candidates:
if FOON_lvl1.index(C) not in prerequisite_units:
prerequisite_units.append(FOON_lvl1.index(C))
#endfor
FOON_functionalUnitMap_lvl1[FOON_lvl1.index(_FU)] = prerequisite_units
#endfor
return
#enddef
def _buildUnitToUnitMap_lvl2():
global FOON_functionalUnitMap_lvl2
for _FU in FOON_lvl2:
prerequisite_units = []
for _input in _FU.getInputList():
# -- we already collected the units that create every single input object in FOON:
candidates = FOON_outputsToUnits_lvl2.get(_input, [])
for C in candidates:
if FOON_lvl2.index(C) not in prerequisite_units:
prerequisite_units.append(FOON_lvl2.index(C))
#endfor
FOON_functionalUnitMap_lvl2[FOON_lvl2.index(_FU)] = prerequisite_units
#endfor
return
#enddef
def _buildUnitToUnitMap_lvl3():
global FOON_functionalUnitMap_lvl3
for _FU in FOON_lvl3:
prerequisite_units = []
for _input in _FU.getInputList():
# -- we already collected the units that create every single input object in FOON:
candidates = FOON_outputsToUnits_lvl3.get(_input, [])
for C in candidates:
if FOON_lvl3.index(C) not in prerequisite_units:
prerequisite_units.append( FOON_lvl3.index(C) )
#endfor
FOON_functionalUnitMap_lvl3[FOON_lvl3.index(_FU)] = prerequisite_units
#endfor
return
#enddef
def _printObjectToUnitMap(hierarchy_level):
if hierarchy_level == 1:
objectMap = FOON_outputsToUnits_lvl1
elif hierarchy_level == 2:
objectMap = FOON_outputsToUnits_lvl2
elif hierarchy_level == 3:
objectMap = FOON_outputsToUnits_lvl3
else:
return
for _key in objectMap:
_key.print_functions[hierarchy_level-1]()
for _FU in objectMap[_key]:
print("{")
_FU.print_functions[hierarchy_level-1]()
print("}\n")
#endfor
#endfor
return
#enddef
def _printFunctionalUnitMap():
for _key in FOON_functionalUnitMap_lvl3:
print("SOURCE:")
_key.print_functions[2]()
print("\nTARGET(S):")
for _FU in FOON_functionalUnitMap_lvl3[_key]:
print("{")
_FU.print_functions[2]()
print("}\n")
#endfor
#endfor
#enddef
def _readIndexFiles():
print('\n -- [FOON-fga] : Reading index files...')
# NOTE: first, try to open combined .JSON file (contains all labels for objects, states, and motions):
try:
import json
FOON_index = json.load( open('FOON_index.json', 'r') )
except FileNotFoundError:
print(" -- WARNING: Combined index file 'FOON_index.json' not found in current directory!")
print("\t-- Using legacy text files instead!")
else:
for O in FOON_index['objects']:
FOON_objectLabels[O] = int(FOON_index['objects'][O]['id'])
if 'sense' in FOON_index['objects'][O]:
FOON_objectSenses[O] = int(FOON_index['objects'][O]['sense']) if str(FOON_index['objects'][O]['sense']).isdigit() else FOON_index['objects'][O]['sense']
else:
FOON_objectSenses[O] = 1
print(' -- Loaded ' + str(len(FOON_objectLabels)) + ' object labels!')
for S in FOON_index['states']:
FOON_stateLabels[S] = int(FOON_index['states'][S]['id'])
print(' -- Loaded ' + str(len(FOON_stateLabels)) + ' state labels!')
for M in FOON_index['motions']:
FOON_motionLabels[M] = int(FOON_index['motions'][M]['id'])
print(' -- Loaded ' + str(len(FOON_motionLabels)) + ' motion labels!')
return
#end
# NOTE: if we get here, that means that we do not have the .JSON file, so just use regular text files:
try:
_file = open('FOON-object_index.txt', 'r')
except FileNotFoundError:
print(" -- WARNING: File 'FOON-object_index.txt' not found in current directory!")
else:
items = _file.read().splitlines()
for L in items:
if L.startswith("//"):
continue
_parts = L.split("\t")
FOON_objectLabels[_parts[1]] = int(_parts[0])
if len(_parts) > 2:
FOON_objectSenses[_parts[1]] = int(_parts[2]) if str(_parts[2]).isdigit() else _parts[2]
else:
FOON_objectSenses[_parts[1]] = 1
#endfor
_file.close()
#end
try:
_file = open('FOON-motion_index.txt', 'r')
except FileNotFoundError:
print(" -- WARNING: File 'FOON-motion_index.txt' not found in current directory!")
else:
items = _file.read().splitlines()
for L in items:
if L.startswith("//"):
continue
_parts = L.split("\t")
FOON_motionLabels[_parts[1]] = int(_parts[0])
#endfor
_file.close()
#end
try:
_file = open('FOON-state_index.txt', 'r')
except FileNotFoundError:
print(" -- WARNING: File 'FOON-state_index.txt' not found in current directory!")
else:
items = _file.read().splitlines()
for L in items:
if L.startswith("//"):
continue
_parts = L.split("\t")
FOON_stateLabels[_parts[1]] = int(_parts[0])
#endfor
_file.close()
#end
return
#enddef
###############################################################################################################################
# NOTE: network centrality algorithms for analysis:
def _buildOneModeProjections(hierarchy_level=None):
# NOTE: purpose of the function is to create one-mode projection of FOON at all levels:
if not hierarchy_level:
# -- just build them all...
_buildOneModeProjection_lvl1()
_buildOneModeProjection_lvl2()
_buildOneModeProjection_lvl3()
elif hierarchy_level == 1:
_buildOneModeProjection_lvl1()
elif hierarchy_level == 2:
_buildOneModeProjection_lvl2()
elif hierarchy_level == 3:
_buildOneModeProjection_lvl3()
else:
return
#enddef
def _buildOneModeProjection_lvl1():
global objects_oneModeProjection_lvl1, FOON_oneModeProjection_lvl1
for _node in objects_oneModeProjection_lvl1:
source = objects_oneModeProjection_lvl1.index(_node)
dest = set()
for _motion in _node.getNeighbourList():
for _output in _motion.getNeighbourList():
dest.add(objects_oneModeProjection_lvl1.index(_output))
#endfor
#endfor
FOON_oneModeProjection_lvl1[source] = dest
#endfor
#enddef
def _buildOneModeProjection_lvl2():
global objects_oneModeProjection_lvl2, FOON_oneModeProjection_lvl2
for _node in objects_oneModeProjection_lvl2:
source = objects_oneModeProjection_lvl2.index(_node)
dest = set()
for _motion in _node.getNeighbourList():
for _output in _motion.getNeighbourList():
dest.add(objects_oneModeProjection_lvl2.index(_output))
#endfor
#endfor
FOON_oneModeProjection_lvl2[source] = dest
#endfor
#enddef
def _buildOneModeProjection_lvl3():
global objects_oneModeProjection_lvl3, FOON_oneModeProjection_lvl3
for _node in objects_oneModeProjection_lvl3:
source = objects_oneModeProjection_lvl3.index(_node)
dest = set()
for _motion in _node.getNeighbourList():
for _output in _motion.getNeighbourList():
dest.add(objects_oneModeProjection_lvl3.index(_output))
#endfor
#endfor
FOON_oneModeProjection_lvl3[source] = dest
#endfor
#enddef
def _calculateCentrality(hierarchy_level):
# NOTE: Refer to "Networks: An Introduction" by Mark Newman (more info: https://dl.acm.org/doi/book/10.5555/1809753)
# for an excellent overview of this algorithm and many other neat graph theory tricks and concepts.
global file_name, verbose
try:
# -- if you don't have NumPy.. then you're gonna have a bad time.
import numpy as np
except ImportError:
print(" -- ERROR: NumPy not found! Please install NumPy to use this function!")
return
# -- first, we need to get the one-mode projection so that we can interpret the results after:
objectList = None; searchMap = None
if hierarchy_level == 1:
objectList = objects_oneModeProjection_lvl1
searchMap = FOON_oneModeProjection_lvl1
elif hierarchy_level == 2:
objectList = objects_oneModeProjection_lvl2
searchMap = FOON_oneModeProjection_lvl2
else:
objectList = objects_oneModeProjection_lvl3
searchMap = FOON_oneModeProjection_lvl3
# -- get adjacency matrix for one-mode projection of FOON:
oneModeMatrix = _populateAdjacencyMatrix(hierarchy_level)
# -- determining the dimensions of the adjacency matrix:
num_elements = oneModeMatrix.shape[1]
if verbose:
# -- saving the adjacency matrix to a file for verification:
np.savetxt("adjacency_matrix.csv", oneModeMatrix, delimiter=",")
# -- calculate eigenvalues for each object node in FOON:
eigenvalues, eigenvectors = np.linalg.eig(oneModeMatrix)
max_index = np.argmax(eigenvalues)
max_eigenvalue = eigenvalues[max_index]
max_eigen = 0
_file = open(os.path.splitext(file_name)[0] + "_eigenvector_lvl" + str(hierarchy_level) +".txt", 'w')
for E in range(num_elements):
_file.write(objectList[E].getObjectLabel() + '_' + str(objectList[E].getStatesList()) + "\t" + str(np.real(eigenvectors[max_index][E])) + '\n')
if eigenvectors[max_index][E] > eigenvectors[max_index][max_eigen]:
max_eigen = E
_file.close()
print('\n -- [NET-CENT] : Object with largest eigenvector centrality value (value=' + str(np.real(eigenvectors[max_index][max_eigen])) + ') is :')
objectList[max_eigen].print_functions[hierarchy_level-1]()
# -- necessary values for Katz centrality computation:
alpha = 1 / (max_eigenvalue + 0.25) # NOTE: recommended that it is less than 1/K^1, so 0.25 was added to arbitrarily meet this requirement.
B = np.ones((num_elements,1))
I = np.eye(num_elements)
A = np.subtract(I, np.multiply(oneModeMatrix, alpha))
# -- calculate Katz centrality, which simplifies to Ax = B (as per 7.10 in Newman):
X = np.linalg.solve(A, B)
max_katz = 0
_file = open(os.path.splitext(file_name)[0] + "_katz_lvl" + str(hierarchy_level) +".txt", 'w')
for E in range(num_elements):
_file.write(objectList[E].getObjectLabel() + '_' + str(objectList[E].getStatesList()) + "\t" + str(np.real(X.item(E))) + '\n')
if X[E] > X[max_katz]:
max_katz = E
_file.close()
# NOTE: Katz centrality : pick the node with the largest computed value in X:
print('\n -- [NET-CENT] : Object with largest Katz centrality value (katz=' + str(np.real(X[max_katz])) + ') is :')
objectList[max_katz].print_functions[hierarchy_level-1]()
# -- calculate degree centrality:
_file = open(os.path.splitext(file_name)[0] + "_degree_lvl" + str(hierarchy_level) +".txt", 'w')
max_value = 0
for E in range(len(objectList)):
_file.write(objectList[E].getObjectLabel() + '_' + str(objectList[E].getStatesList()) + "\t" + str(len(searchMap[E])) + '\n')
if len(searchMap[E]) > max_value:
max_value = len(searchMap[E])
_file.close()
max_deg = []
for x in range(len(objectList)):
if len(searchMap[x]) == max_value:
max_deg.append(x)
print('\n -- [NET-CENT] : Object(s) with largest degree centrality value (n_neighbours = ' + str(max_value) + ') are :')
for x in max_deg:
objectList[x].print_functions[hierarchy_level-1]()
print("------------------")
#enddef
def _populateAdjacencyMatrix(hierarchy_level):
# NOTE: Refer to "Networks: An Introduction" by Mark Newman (more info: https://dl.acm.org/doi/book/10.5555/1809753)
# for an excellent overview of this algorithm and many other neat graph theory tricks and concepts.
global verbose; searchMap = None
try:
import numpy as np
except ImportError:
print(" -- ERROR: NumPy not found! Please install NumPy to use this function!")
return
if hierarchy_level == 1:
searchMap = FOON_oneModeProjection_lvl1
elif hierarchy_level == 2:
searchMap = FOON_oneModeProjection_lvl2
elif hierarchy_level == 3:
searchMap = FOON_oneModeProjection_lvl3
else:
return
if not FOON_oneModeProjection_lvl3:
_buildOneModeProjections(hierarchy_level=hierarchy_level)
# -- create an adjacency matrix of size N x N, where N is number of nodes (i.e. both object and motion) :
oneModeMatrix = np.eye(( len(searchMap) ))
for src, tgt in searchMap.items():
for x in tgt:
oneModeMatrix[src][x] = 1
if verbose:
print(' -- [NET-CENT] Adjacency matrix for one-mode projection is as follows:')
print(oneModeMatrix)
return oneModeMatrix
#enddef
###############################################################################################################################
def _printSummary_FOON():
print(" -> Level 3: # of UNITS - " + str(len(FOON_lvl3)))
print(" -> Level 2: # of UNITS - " + str(len(FOON_lvl2)))
print(" -> Level 1: # of UNITS - " + str(len(FOON_lvl1)))
#enddef
def _printSummary_nodes():
print(" -> TOTAL NUMBER OF NODES : " + str(len(nodes_lvl3)) )
print(" -> Level 3: # of OBJECT - " + str(len(FOON_outputsToUnits_lvl3)) + "; # of MOTION - " + str(len(nodes_lvl3) - len(FOON_outputsToUnits_lvl3)))
print(" -> Level 2: # of OBJECT - " + str(len(FOON_outputsToUnits_lvl2)) + "; # of MOTION - " + str(len(nodes_lvl2) - len(FOON_outputsToUnits_lvl2)))
print(" -> Level 1: # of OBJECT - " + str(len(FOON_outputsToUnits_lvl1)) + "; # of MOTION - " + str(len(nodes_lvl1) - len(FOON_outputsToUnits_lvl1)))
#enddef
def _printSummary_edges():
total = 0
for N in nodes_lvl3:
total += len(N.getNeighbourList())
#endfor
print(" -> Level 3: # of EDGES - " + str(total))
total = 0
for N in nodes_lvl2:
total += len(N.getNeighbourList())
#endfor
print(" -> Level 2: # of EDGES - " + str(total))
total = 0
for N in nodes_lvl1:
total += len(N.getNeighbourList())
#endfor
print(" -> Level 1: # of EDGES - " + str(total))
#endfor
def _printAnyNode(X, hierarchy_level=3):
if hierarchy_level == 1:
node = nodes_lvl1[X]
elif hierarchy_level == 2:
node = nodes_lvl2[X]
else:
node = nodes_lvl3[X]
if isinstance(node, FOON.Motion):
node.printMotion()
return
node.print_functions[hierarchy_level-1]()
#enddef
def _printAnyFunctionalUnit(X, hierarchy_level=3):
if hierarchy_level == 1:
node = FOON_lvl1[X]
elif hierarchy_level == 2:
node = FOON_lvl2[X]
else:
node = FOON_lvl3[X]
node.print_functions[hierarchy_level-1]()
#enddef
def _objectFrequencyReport():
try:
_file = open('FOON-object_index.txt', 'r')
except FileNotFoundError:
print(" -- WARNING: File 'FOON-object_index.txt' not found in current directory!")
return
items = _file.read().splitlines()
frequency = [0] * len(items)
for FU in FOON_lvl3:
for _O in FU.getInputList():
# -- get the motion node for each functional unit and just tally them up:
frequency[_O.getObjectType()] += 1
#endfor
global file_name
_file = open(os.path.splitext(file_name)[0] + '_FOON_object_frequency_report.txt', 'w')
for x in range(len(items)):
line = items[x].split("\t")
# -- write everything to the file..
_file.write("O" + str(line[0]) + " : " + str(line[1]) + "\t" + str(frequency[x]) + "\n")
#endfor
print(" -- Object frequency file has been saved as '" + (os.path.splitext(file_name)[0] + '_FOON_object_frequency_report.txt') + "'.")
_file.close()
_file = open(os.path.splitext(file_name)[0] + '_FOON_existent_FOON_objectLabels.txt', 'w')
for x in range(len(items)):
line = items[x].split("\t")
# -- write everything to the file only if there is at least one instance!
if frequency[x] > 0:
_file.write("O" + str(line[0]) + " : " + str(line[1]) + "\t" + str(frequency[x]) + "\n")
#endif
#endfor
_file.close()
_file = open(os.path.splitext(file_name)[0] + '_FOON_existent_object-states.txt', 'w')
for x in range(len(nodes_lvl3)):
# -- write everything to the file only if there is at least one instance!
if isinstance(nodes_lvl3[x], FOON.Object):
_file.write(nodes_lvl3[x].getObjectText() + "\n" + "//" + "\n")
#endif
#endfor
_file.close()
#enddef
def _motionFrequencyReport():
try:
_file = open('FOON-motion_index.txt', 'r')
except FileNotFoundError:
print(" -- WARNING: File 'FOON-motion_index.txt' not found in current directory!")
return
items = _file.read().splitlines()
frequency = [0] * len(items)
for FU in FOON_lvl3:
# -- get the motion node for each functional unit and just tally them up:
frequency[FU.getMotion().getMotionType()] += 1
#endfor
global file_name
_file = open(os.path.splitext(file_name)[0] + '_FOON_motion_frequency_report.txt', 'w')
for x in range(len(items)):
line = items[x].split("\t")
# -- write everything to the file..
_file.write("M" + str(line[0]) + " : " + str(line[1]) + "\t" + str(frequency[x]) + "\n")
#endfor
print(" -- Motion frequency file has been saved as '" + (os.path.splitext(file_name)[0] + '_FOON_motion_frequency_report.txt') + "'.")
_file.close()
#enddef
def _stateFrequencyReport():
try:
_file = open('FOON-state_index.txt', 'r')
except FileNotFoundError:
print(" -- WARNING: File 'FOON-state_index.txt' not found in current directory!")
return
items = _file.read().splitlines()
frequency = [0] * len(items)
for N in nodes_lvl3:
if isinstance(N,FOON.Object):
for x in range(len(N.getStatesList())):
frequency[int(N.getStateType(x))] += 1
#endfor
global file_name
_file = open(os.path.splitext(file_name)[0] + '_FOON_state_frequency_report.txt', 'w')
for x in range(len(items)):
line = items[x].split("\t")
# -- write everything to the file..
_file.write("S" + str(line[0]) + " : " + str(line[1]) + "\t" + str(frequency[x]) + "\n")
#endfor
print(" -- State frequency file has been saved as '" + (os.path.splitext(file_name)[0] + '_FOON_state_frequency_report.txt') + "'.")
_file.close()
#enddef
###############################################################################################################################
# NOTE: functions used in loading FOON graph from parsing of files:
def _checkIfFUExists(U, H):
if H == 1:
if not FOON_lvl1:
return False
for _F in FOON_lvl1:
if _F.equals_lvl1(U):
return True
return False
if H == 2:
if not FOON_lvl2:
return False
for _F in FOON_lvl2:
if _F.equals_lvl2(U):
return True
return False
if H == 3:
if not FOON_lvl3:
return False
for _F in FOON_lvl3:
if _F.equals_lvl3(U):
return True
return False
else:
pass
#enddef
def _checkIfNodeExists(O, H):
objectExisting = -1
if H == 1:
for N in nodes_lvl1:
if isinstance(N, FOON.Object) and N.equals_functions[H-1](O):
objectExisting = nodes_lvl1.index(N)
elif H == 2:
for N in nodes_lvl2:
if isinstance(N, FOON.Object) and N.equals_functions[H-1](O):
objectExisting = nodes_lvl2.index(N)
elif H == 3:
for N in nodes_lvl3:
if isinstance(N, FOON.Object) and N.equals_functions[H-1](O):
objectExisting = nodes_lvl3.index(N)
else:
pass
return objectExisting
#enddef
def _isFOONLoaded():
# -- check if a FOON has been loaded based on the number of functional units at level 3:
return len(FOON_functionalUnits[-1]) > 0
def _constructFOON(graph_file=None):
# NOTE: entry point function to load a FOON subgraph file, which may either be a .TXT, .PKL or .JSON file:
# -- we need to set the global variable "file_name" to store the path to the file being loaded:
global file_name
if not file_name and not graph_file:
print(' -- ERROR: No file was provided!')
return
elif graph_file:
file_name = graph_file
print("\n -- [FOON-fga] : Opening FOON file named '" + str(file_name) + "'...")
if '.txt' in file_name.lower():
_loadFOON_txt(file_name)
FOON.print_old_style = True
elif '.json' in file_name.lower():
# -- give a .JSON file, which is typically larger than the average text file, but it structures things neatly:
_loadFOON_json(file_name)
FOON.print_old_style = False
elif '.pkl' in file_name.lower():
# WARNING: ONLY USE .PKL FILE THAT HAS BEEN PROCESSED ALREADY!!
# -- users can load a .PKL file containing the structures and references for an already processed universal FOON:
_loadFOON_pkl(file_name)
else:
print(' -- WARNING: Wrong file type or format!')
print(" -- Skipping: '" + str(file_name) + "' ...")
#enddef
def _loadFOON_txt(file=None):
# NOTE: this is the .TXT file variant of the FOON subgraph loading function:
# NOTE: 'FOON_node_count' indicates the number of nodes (i.e. both object AND motion nodes) exist in a universal FOON.
# -- this number is based on the hierarchy level 3.
global FOON_node_count
global FOON_video_source, verbose
global FOON_lvl1, FOON_lvl2, FOON_lvl3, nodes_lvl1, nodes_lvl2, nodes_lvl3
stateParts, objectParts, motionParts = [], [], [] # -- objects used to contain the split strings
# -- isInput - flag used to switch between adding to input or output nodes list for each functional unit
isInput = True
# -- newObject - this stores an object that is in the process of being read; this is important since we can have multiple AND variable states.
newObject = None
# -- objects which will hold the functional unit being read:
newFU_lvl1, newFU_lvl2, newFU_lvl3 = FOON.FunctionalUnit(), FOON.FunctionalUnit(), FOON.FunctionalUnit()
_file = open(file, 'r'); items = _file.read().splitlines()
line_count = 0
for line in tqdm.tqdm(items, desc=' -- Reading file line '):
line_count += 1
# -- checking flag for verbose (print-outs):
if verbose:
print('line ' + str(line_count) + ' - ' + line)
try:
if line.startswith("# Source:"):
line = line.split('\t')
FOON_video_source.append(line[1].strip('\n'))
elif line.startswith("//"):
if newObject:
# -- the last output object is not added right away since we need to see if we do not know how many states we are expecting:
_addObjectToFOON(newObject, isInput, objectParts[2], newFU_lvl3, newFU_lvl2, newFU_lvl1)
if newFU_lvl3.isEmpty():
# -- this means that we did not add anything to this functional unit object, so just continue:
continue
# -- we are adding a new FU, so start from scratch..
if _checkIfFUExists(newFU_lvl3, 3) == False:
# NOTE: no matter what, we add new motion nodes; we will have multiple instances everywhere.
nodes_lvl3.append(newFU_lvl3.getMotion())
FOON_lvl3.append(newFU_lvl3)
motionsToFunctionalUnits_lvl3[nodes_lvl3.index(newFU_lvl3.getMotion())] = newFU_lvl3
# -- we only keep track of the total number of nodes in the LVL3 FOON.
FOON_node_count += 1
if _checkIfFUExists(newFU_lvl2, 2) == False:
nodes_lvl2.append(newFU_lvl2.getMotion())
FOON_lvl2.append(newFU_lvl2)
motionsToFunctionalUnits_lvl2[nodes_lvl2.index(newFU_lvl2.getMotion())] = newFU_lvl2
if _checkIfFUExists(newFU_lvl1, 1) == False:
nodes_lvl1.append(newFU_lvl1.getMotion())
FOON_lvl1.append(newFU_lvl1)
motionsToFunctionalUnits_lvl1[nodes_lvl1.index(newFU_lvl1.getMotion())] = newFU_lvl1
# -- create an entirely new FU object to proceed with reading new units.
newFU_lvl1, newFU_lvl2, newFU_lvl3 = FOON.FunctionalUnit(), FOON.FunctionalUnit(), FOON.FunctionalUnit()
# -- this is the end of a FU so we will now be adding input nodes; set flag to TRUE.
isInput = True; newObject = None
elif line.startswith("O"):
# -- we have an Object already in consideration which we were appending states to:
if newObject:
_addObjectToFOON(newObject, isInput, objectParts[2], newFU_lvl3, newFU_lvl2, newFU_lvl1)
# -- this is an Object node, so we probably should read the next line one time
# -- get the Object identifier by splitting first instance of O
objectParts = line.split("O"); objectParts = objectParts[1].split("\t")
newObject = FOON.Object(objectID=int(objectParts[0]), objectLabel=objectParts[1])
# -- checking if an object is marked as intended goal of a subgraph file:
if '!' in line:
newObject.setAsGoal()
elif line.startswith("S"):