diff --git a/SpecProject/Assets/amdc2016_mass.txt b/SpecProject/Assets/amdc2016_mass.txt new file mode 100644 index 0000000..d7fe2d5 --- /dev/null +++ b/SpecProject/Assets/amdc2016_mass.txt @@ -0,0 +1,2500 @@ + N Z A EL ATOMIC MASS + (micro-u) + 1 0 1 n 1 008664.91582 + 0 1 1 H 1 007825.03224 + 1 1 2 H 2 014101.77811 + 2 1 3 H 3 016049.28199 + 1 2 3 He 3 016029.32265 + 3 1 4 H 4 026431.868 + 2 2 4 He 4 002603.25413 + 1 3 4 Li 4 027185.562 + 4 1 5 H 5 035311.493 + 3 2 5 He 5 012057.224 + 2 3 5 Li 5 012537.800 + 5 1 6 H 6 044955.437 + 4 2 6 He 6 018885.891 + 3 3 6 Li 6 015122.88742 + 2 4 6 Be 6 019726.409 + 5 2 7 He 7 027990.654 + 4 3 7 Li 7 016003.43666 + 3 4 7 Be 7 016928.717 + 2 5 7 B 7 029712.000 + 6 2 8 He 8 033934.390 + 5 3 8 Li 8 022486.246 + 4 4 8 Be 8 005305.102 + 3 5 8 B 8 024607.316 + 2 6 8 C 8 037643.042 + 7 2 9 He 9 043946.419 + 6 3 9 Li 9 026790.191 + 5 4 9 Be 9 012183.066 + 4 5 9 B 9 013329.649 + 3 6 9 C 9 031037.207 + 8 2 10 He 10 052815.308 + 7 3 10 Li 10 035483.453 + 6 4 10 Be 10 013534.695 + 5 5 10 B 10 012936.862 + 4 6 10 C 10 016853.218 + 3 7 10 N 10 041653.543 + 8 3 11 Li 11 043723.581 + 7 4 11 Be 11 021661.081 + 6 5 11 B 11 009305.166 + 5 6 11 C 11 011432.597 + 4 7 11 N 11 026090.945 + 9 3 12 Li 12 052613.941 + 8 4 12 Be 12 026922.083 + 7 5 12 B 12 014352.638 + 6 6 12 C 12 000000.0 + 5 7 12 N 12 018613.182 + 4 8 12 O 12 034261.747 + 10 3 13 Li 13 061171.503 + 9 4 13 Be 13 036134.507 + 8 5 13 B 13 017779.981 + 7 6 13 C 13 003354.83521 + 6 7 13 N 13 005738.609 + 5 8 13 O 13 024815.437 + 10 4 14 Be 14 042892.920 + 9 5 14 B 14 025404.012 + 8 6 14 C 14 003241.98843 + 7 7 14 N 14 003074.00446 + 6 8 14 O 14 008596.706 + 5 9 14 F 14 034315.199 + 11 4 15 Be 15 053490.215 + 10 5 15 B 15 031087.953 + 9 6 15 C 15 010599.256 + 8 7 15 N 15 000108.89894 + 7 8 15 O 15 003065.618 + 6 9 15 F 15 017785.139 + 5 10 15 Ne 15 043172.980 + 12 4 16 Be 16 061672.036 + 11 5 16 B 16 039841.920 + 10 6 16 C 16 014701.256 + 9 7 16 N 16 006101.925 + 8 8 16 O 15 994914.61960 + 7 9 16 F 16 011465.723 + 6 10 16 Ne 16 025750.864 + 12 5 17 B 17 046931.399 + 11 6 17 C 17 022578.672 + 10 7 17 N 17 008448.877 + 9 8 17 O 16 999131.75664 + 8 9 17 F 17 002095.238 + 7 10 17 Ne 17 017713.959 + 6 11 17 Na 17 037760.000 + 13 5 18 B 18 055601.682 + 12 6 18 C 18 026751.932 + 11 7 18 N 18 014077.565 + 10 8 18 O 17 999159.61284 + 9 9 18 F 18 000937.325 + 8 10 18 Ne 18 005708.693 + 7 11 18 Na 18 026879.386 + 14 5 19 B 19 064166.000 + 13 6 19 C 19 034797.596 + 12 7 19 N 19 017022.419 + 11 8 19 O 19 003577.970 + 10 9 19 F 18 998403.16288 + 9 10 19 Ne 19 001880.903 + 8 11 19 Na 19 013880.272 + 7 12 19 Mg 19 034169.182 + 14 6 20 C 20 040261.732 + 13 7 20 N 20 023367.295 + 12 8 20 O 20 004075.358 + 11 9 20 F 19 999981.252 + 10 10 20 Ne 19 992440.17619 + 9 11 20 Na 20 007354.426 + 8 12 20 Mg 20 018763.075 + 14 7 21 N 21 027087.573 + 13 8 21 O 21 008654.950 + 12 9 21 F 20 999948.894 + 11 10 21 Ne 20 993846.685 + 10 11 21 Na 20 997654.702 + 9 12 21 Mg 21 011705.764 + 16 6 22 C 22 057553.990 + 15 7 22 N 22 034100.918 + 14 8 22 O 22 009965.746 + 13 9 22 F 22 002998.809 + 12 10 22 Ne 21 991385.109 + 11 11 22 Na 21 994437.418 + 10 12 22 Mg 21 999570.654 + 16 7 23 N 23 039421.000 + 15 8 23 O 23 015696.686 + 14 9 23 F 23 003526.874 + 13 10 23 Ne 22 994466.900 + 12 11 23 Na 22 989769.28199 + 11 12 23 Mg 22 994123.941 + 10 13 23 Al 23 007244.351 + 16 8 24 O 24 019861.000 + 15 9 24 F 24 008099.370 + 14 10 24 Ne 23 993610.645 + 13 11 24 Na 23 990963.011 + 12 12 24 Mg 23 985041.697 + 11 13 24 Al 23 999947.541 + 10 14 24 Si 24 011535.441 + 17 8 25 O 25 029338.919 + 16 9 25 F 25 012167.727 + 15 10 25 Ne 24 997814.799 + 14 11 25 Na 24 989953.973 + 13 12 25 Mg 24 985836.964 + 12 13 25 Al 24 990428.306 + 11 14 25 Si 25 004108.801 + 18 8 26 O 26 037210.155 + 17 9 26 F 26 020020.392 + 16 10 26 Ne 26 000516.496 + 15 11 26 Na 25 992634.649 + 14 12 26 Mg 25 982592.971 + 13 13 26 Al 25 986891.863 + 12 14 26 Si 25 992333.804 + 18 9 27 F 27 027322.000 + 17 10 27 Ne 27 007569.462 + 16 11 27 Na 26 994076.408 + 15 12 27 Mg 26 984340.628 + 14 13 27 Al 26 981538.408 + 13 14 27 Si 26 986704.688 + 12 15 27 P 26 999224.409 + 19 9 28 F 28 036223.095 + 18 10 28 Ne 28 012130.767 + 17 11 28 Na 27 998939.000 + 16 12 28 Mg 27 983876.606 + 15 13 28 Al 27 981910.087 + 14 14 28 Si 27 976926.53499 + 13 15 28 P 27 992326.585 + 12 16 28 S 28 004372.766 + 20 9 29 F 29 043103.000 + 19 10 29 Ne 29 019753.000 + 18 11 29 Na 29 002877.092 + 17 12 29 Mg 28 988617.393 + 16 13 29 Al 28 980453.164 + 15 14 29 Si 28 976494.66525 + 14 15 29 P 28 981800.368 + 13 16 29 S 28 996611.448 + 12 17 29 Cl 29 014130.178 + 20 10 30 Ne 30 024992.235 + 19 11 30 Na 30 009097.932 + 18 12 30 Mg 29 990462.826 + 17 13 30 Al 29 982968.388 + 16 14 30 Si 29 973770.136 + 15 15 30 P 29 978313.489 + 14 16 30 S 29 984906.769 + 12 18 30 Ar 30 022470.511 + 21 10 31 Ne 31 033474.816 + 20 11 31 Na 31 013146.656 + 19 12 31 Mg 30 996648.232 + 18 13 31 Al 30 983949.756 + 17 14 31 Si 30 975363.194 + 16 15 31 P 30 973761.99863 + 15 16 31 S 30 979557.007 + 14 17 31 Cl 30 992448.098 + 21 11 32 Na 32 020011.026 + 20 12 32 Mg 31 999110.139 + 19 13 32 Al 31 988084.339 + 18 14 32 Si 31 974151.539 + 17 15 32 P 31 973907.643 + 16 16 32 S 31 972071.17443 + 15 17 32 Cl 31 985684.637 + 14 18 32 Ar 31 997637.826 + 22 11 33 Na 33 025529.000 + 21 12 33 Mg 33 005327.245 + 20 13 33 Al 32 990877.687 + 19 14 33 Si 32 977976.964 + 18 15 33 P 32 971725.694 + 17 16 33 S 32 971458.90985 + 16 17 33 Cl 32 977451.989 + 15 18 33 Ar 32 989925.547 + 23 11 34 Na 34 034010.000 + 22 12 34 Mg 34 008935.481 + 21 13 34 Al 33 996779.057 + 20 14 34 Si 33 978575.437 + 19 15 34 P 33 973645.887 + 18 16 34 S 33 967867.012 + 17 17 34 Cl 33 973762.491 + 16 18 34 Ar 33 980270.093 + 23 12 35 Mg 35 016790.000 + 22 13 35 Al 34 999759.817 + 21 14 35 Si 34 984550.134 + 20 15 35 P 34 973314.053 + 19 16 35 S 34 969032.322 + 18 17 35 Cl 34 968852.694 + 17 18 35 Ar 34 975257.721 + 16 19 35 K 34 988005.407 + 24 12 36 Mg 36 021879.000 + 23 13 36 Al 36 006388.000 + 22 14 36 Si 35 986649.271 + 21 15 36 P 35 978259.619 + 20 16 36 S 35 967080.699 + 19 17 36 Cl 35 968306.822 + 18 18 36 Ar 35 967545.105 + 17 19 36 K 35 981302.010 + 16 20 36 Ca 35 993074.406 + 25 12 37 Mg 37 030286.265 + 24 13 37 Al 37 010531.000 + 23 14 37 Si 36 992945.191 + 22 15 37 P 36 979606.956 + 21 16 37 S 36 971125.507 + 20 17 37 Cl 36 965902.584 + 19 18 37 Ar 36 966776.314 + 18 19 37 K 36 973375.889 + 17 20 37 Ca 36 985897.852 + 25 13 38 Al 38 017402.000 + 24 14 38 Si 37 995523.000 + 23 15 38 P 37 984303.105 + 22 16 38 S 37 971163.310 + 21 17 38 Cl 37 968010.418 + 20 18 38 Ar 37 962732.104 + 19 19 38 K 37 969081.116 + 18 20 38 Ca 37 976319.226 + 25 14 39 Si 39 002491.000 + 24 15 39 P 38 986285.865 + 23 16 39 S 38 975133.852 + 22 17 39 Cl 38 968008.162 + 21 18 39 Ar 38 964313.039 + 20 19 39 K 38 963706.48661 + 19 20 39 Ca 38 970710.813 + 18 21 39 Sc 38 984784.970 + 26 14 40 Si 40 005829.000 + 25 15 40 P 39 991288.865 + 24 16 40 S 39 975482.562 + 23 17 40 Cl 39 970415.469 + 22 18 40 Ar 39 962383.12378 + 21 19 40 K 39 963998.166 + 20 20 40 Ca 39 962590.865 + 19 21 40 Sc 39 977967.292 + 18 22 40 Ti 39 990498.721 + 27 14 41 Si 41 013011.000 + 26 15 41 P 40 994654.000 + 25 16 41 S 40 979593.451 + 24 17 41 Cl 40 970684.525 + 23 18 41 Ar 40 964500.571 + 22 19 41 K 40 961825.25796 + 21 20 41 Ca 40 962277.921 + 20 21 41 Sc 40 969251.104 + 19 22 41 Ti 40 983148.000 + 27 15 42 P 42 001084.000 + 26 16 42 S 41 981065.100 + 25 17 42 Cl 41 973342.000 + 24 18 42 Ar 41 963045.736 + 23 19 42 K 41 962402.306 + 22 20 42 Ca 41 958617.828 + 21 21 42 Sc 41 965516.522 + 20 22 42 Ti 41 973049.022 + 28 15 43 P 43 005024.000 + 27 16 43 S 42 986907.635 + 26 17 43 Cl 42 974063.700 + 25 18 43 Ar 42 965636.055 + 24 19 43 K 42 960734.703 + 23 20 43 Ca 42 958766.430 + 22 21 43 Sc 42 961150.472 + 21 22 43 Ti 42 968522.521 + 20 23 43 V 42 980766.000 + 28 16 44 S 43 990118.848 + 27 17 44 Cl 43 978116.312 + 26 18 44 Ar 43 964923.816 + 25 19 44 K 43 961586.986 + 24 20 44 Ca 43 955481.543 + 23 21 44 Sc 43 959402.867 + 22 22 44 Ti 43 959689.951 + 21 23 44 V 43 974110.000 + 29 16 45 S 44 995717.000 + 28 17 45 Cl 44 980394.353 + 27 18 45 Ar 44 968039.733 + 26 19 45 K 44 960691.493 + 25 20 45 Ca 44 956186.326 + 24 21 45 Sc 44 955907.503 + 23 22 45 Ti 44 958121.211 + 22 23 45 V 44 965768.951 + 21 24 45 Cr 44 979050.000 + 29 17 46 Cl 45 985121.323 + 28 18 46 Ar 45 968037.446 + 27 19 46 K 45 961981.586 + 26 20 46 Ca 45 953687.988 + 25 21 46 Sc 45 955167.485 + 24 22 46 Ti 45 952626.856 + 23 23 46 V 45 960197.971 + 22 24 46 Cr 45 968360.970 + 29 18 47 Ar 46 972768.114 + 28 19 47 K 46 961661.614 + 27 20 47 Ca 46 954541.394 + 26 21 47 Sc 46 952402.704 + 25 22 47 Ti 46 951757.752 + 24 23 47 V 46 954904.038 + 23 24 47 Cr 46 962895.544 + 22 25 47 Mn 46 975774.000 + 30 18 48 Ar 47 976080.000 + 29 19 48 K 47 965341.186 + 28 20 48 Ca 47 952522.904 + 27 21 48 Sc 47 952223.157 + 26 22 48 Ti 47 947940.932 + 25 23 48 V 47 952251.229 + 24 24 48 Cr 47 954028.667 + 23 25 48 Mn 47 968549.085 + 30 19 49 K 48 968210.755 + 29 20 49 Ca 48 955662.875 + 28 21 49 Sc 48 950014.423 + 27 22 49 Ti 48 947864.627 + 26 23 49 V 48 948510.746 + 25 24 49 Cr 48 951332.955 + 24 25 49 Mn 48 959612.585 + 23 26 49 Fe 48 973429.000 + 31 19 50 K 49 972380.017 + 30 20 50 Ca 49 957499.217 + 29 21 50 Sc 49 952176.415 + 28 22 50 Ti 49 944785.839 + 27 23 50 V 49 947155.845 + 26 24 50 Cr 49 946041.443 + 25 25 50 Mn 49 954237.391 + 24 26 50 Fe 49 962988.000 + 32 19 51 K 50 975827.867 + 31 20 51 Ca 50 960995.665 + 30 21 51 Sc 50 953592.095 + 29 22 51 Ti 50 946609.600 + 28 23 51 V 50 943956.867 + 27 24 51 Cr 50 944764.652 + 26 25 51 Mn 50 948208.065 + 25 26 51 Fe 50 956840.779 + 24 27 51 Co 50 970647.000 + 33 19 52 K 51 981602.000 + 32 20 52 Ca 51 963213.648 + 31 21 52 Sc 51 956582.351 + 30 22 52 Ti 51 946891.960 + 29 23 52 V 51 944772.839 + 28 24 52 Cr 51 940504.992 + 27 25 52 Mn 51 945563.488 + 26 26 52 Fe 51 948115.217 + 25 27 52 Co 51 963112.000 + 34 19 53 K 52 986800.000 + 33 20 53 Ca 52 968451.000 + 32 21 53 Sc 52 958231.821 + 31 22 53 Ti 52 949724.785 + 30 23 53 V 52 944335.593 + 29 24 53 Cr 52 940646.961 + 28 25 53 Mn 52 941287.742 + 27 26 53 Fe 52 945305.574 + 26 27 53 Co 52 954203.217 + 25 28 53 Ni 52 968190.000 + 34 20 54 Ca 53 972989.000 + 33 21 54 Sc 53 963616.620 + 32 22 54 Ti 53 951022.786 + 31 23 54 V 53 946437.472 + 30 24 54 Cr 53 938878.012 + 29 25 54 Mn 53 940356.429 + 28 26 54 Fe 53 939608.306 + 27 27 54 Co 53 948459.192 + 26 28 54 Ni 53 957833.000 + 34 21 55 Sc 54 967622.601 + 33 22 55 Ti 54 955267.465 + 32 23 55 V 54 947241.114 + 31 24 55 Cr 54 940837.289 + 30 25 55 Mn 54 938043.172 + 29 26 55 Fe 54 938291.283 + 28 27 55 Co 54 941996.531 + 27 28 55 Ni 54 951329.961 + 26 29 55 Cu 54 966038.000 + 35 21 56 Sc 55 973320.000 + 34 22 56 Ti 55 957788.190 + 33 23 56 V 55 950450.694 + 32 24 56 Cr 55 940649.107 + 31 25 56 Mn 55 938902.947 + 30 26 56 Fe 55 934935.617 + 29 27 56 Co 55 939838.150 + 28 28 56 Ni 55 942127.872 + 27 29 56 Cu 55 958515.000 + 36 21 57 Sc 56 977460.000 + 35 22 57 Ti 56 963590.068 + 34 23 57 V 56 952320.197 + 33 24 57 Cr 56 943612.409 + 32 25 57 Mn 56 938285.968 + 31 26 57 Fe 56 935392.134 + 30 27 57 Co 56 936289.913 + 29 28 57 Ni 56 939791.525 + 28 29 57 Cu 56 949211.819 + 35 23 58 V 57 956626.932 + 34 24 58 Cr 57 944184.502 + 33 25 58 Mn 57 940066.646 + 32 26 58 Fe 57 933273.738 + 31 27 58 Co 57 935751.429 + 30 28 58 Ni 57 935341.780 + 29 29 58 Cu 57 944532.413 + 28 30 58 Zn 57 954590.428 + 36 23 59 V 58 959385.659 + 35 24 59 Cr 58 948377.810 + 34 25 59 Mn 58 940391.113 + 33 26 59 Fe 58 934873.649 + 32 27 59 Co 58 933193.656 + 31 28 59 Ni 58 934345.571 + 30 29 59 Cu 58 939496.844 + 29 30 59 Zn 58 949312.017 + 37 23 60 V 59 964313.290 + 36 24 60 Cr 59 949898.146 + 35 25 60 Mn 59 943136.576 + 34 26 60 Fe 59 934070.411 + 33 27 60 Co 59 933815.667 + 32 28 60 Ni 59 930785.256 + 31 29 60 Cu 59 937363.916 + 30 30 60 Zn 59 941841.450 + 38 23 61 V 60 967250.000 + 37 24 61 Cr 60 954400.963 + 36 25 61 Mn 60 944452.544 + 35 26 61 Fe 60 936746.244 + 34 27 61 Co 60 932476.145 + 33 28 61 Ni 60 931054.945 + 32 29 61 Cu 60 933457.371 + 31 30 61 Zn 60 939506.960 + 30 31 61 Ga 60 949398.859 + 38 24 62 Cr 61 956097.451 + 37 25 62 Mn 61 947907.386 + 36 26 62 Fe 61 936791.812 + 35 27 62 Co 61 934058.317 + 34 28 62 Ni 61 928344.871 + 33 29 62 Cu 61 932594.921 + 32 30 62 Zn 61 934333.477 + 31 31 62 Ga 61 944189.757 + 39 24 63 Cr 62 961344.384 + 38 25 63 Mn 62 949664.675 + 37 26 63 Fe 62 940272.700 + 36 27 63 Co 62 933599.744 + 35 28 63 Ni 62 929669.139 + 34 29 63 Cu 62 929597.236 + 33 30 63 Zn 62 933211.167 + 32 31 63 Ga 62 939294.195 + 31 32 63 Ge 62 949628.000 + 40 24 64 Cr 63 964058.000 + 39 25 64 Mn 63 953849.370 + 38 26 64 Fe 63 940987.763 + 37 27 64 Co 63 935810.291 + 36 28 64 Ni 63 927966.341 + 35 29 64 Cu 63 929763.857 + 34 30 64 Zn 63 929141.772 + 33 31 64 Ga 63 936840.365 + 32 32 64 Ge 63 941689.913 + 40 25 65 Mn 64 956019.750 + 39 26 65 Fe 64 945015.324 + 38 27 65 Co 64 936462.073 + 37 28 65 Ni 64 930084.697 + 36 29 65 Cu 64 927789.487 + 35 30 65 Zn 64 929240.532 + 34 31 65 Ga 64 932734.395 + 33 32 65 Ge 64 939368.137 + 32 33 65 As 64 949611.000 + 41 25 66 Mn 65 960546.834 + 40 26 66 Fe 65 946249.960 + 39 27 66 Co 65 939442.945 + 38 28 66 Ni 65 929139.334 + 37 29 66 Cu 65 928868.814 + 36 30 66 Zn 65 926033.704 + 35 31 66 Ga 65 931589.832 + 34 32 66 Ge 65 933862.126 + 33 33 66 As 65 944148.779 + 41 26 67 Fe 66 951035.482 + 40 27 67 Co 66 940609.628 + 39 28 67 Ni 66 931569.414 + 38 29 67 Cu 66 927729.526 + 37 30 67 Zn 66 927127.482 + 36 31 67 Ga 66 928202.384 + 35 32 67 Ge 66 932733.620 + 34 33 67 As 66 939251.111 + 33 34 67 Se 66 949994.000 + 42 26 68 Fe 67 953314.875 + 41 27 68 Co 67 944250.135 + 40 28 68 Ni 67 931868.789 + 39 29 68 Cu 67 929610.889 + 38 30 68 Zn 67 924844.291 + 37 31 68 Ga 67 927980.221 + 36 32 68 Ge 67 928095.308 + 35 33 68 As 67 936774.130 + 34 34 68 Se 67 941825.239 + 42 27 69 Co 68 946023.102 + 41 28 69 Ni 68 935610.268 + 40 29 69 Cu 68 929429.268 + 39 30 69 Zn 68 926550.418 + 38 31 69 Ga 68 925573.531 + 37 32 69 Ge 68 927964.471 + 36 33 69 As 68 932246.294 + 35 34 69 Se 68 939414.847 + 34 35 69 Br 68 950338.413 + 42 28 70 Ni 69 936431.303 + 41 29 70 Cu 69 932392.079 + 40 30 70 Zn 69 925319.181 + 39 31 70 Ga 69 926021.917 + 38 32 70 Ge 69 924248.706 + 37 33 70 As 69 930926.151 + 36 34 70 Se 69 933515.523 + 35 35 70 Br 69 944792.323 + 44 27 71 Co 70 952366.923 + 43 28 71 Ni 70 940518.964 + 42 29 71 Cu 70 932676.832 + 41 30 71 Zn 70 927719.580 + 40 31 71 Ga 70 924702.536 + 39 32 71 Ge 70 924952.284 + 38 33 71 As 70 927113.758 + 37 34 71 Se 70 932209.432 + 36 35 71 Br 70 939342.156 + 35 36 71 Kr 70 950265.696 + 44 28 72 Ni 71 941785.926 + 43 29 72 Cu 71 935820.307 + 42 30 72 Zn 71 926842.807 + 41 31 72 Ga 71 926367.434 + 40 32 72 Ge 71 922075.826 + 39 33 72 As 71 926752.295 + 38 34 72 Se 71 927140.507 + 37 35 72 Br 71 936594.607 + 36 36 72 Kr 71 942092.407 + 45 28 73 Ni 72 946206.683 + 44 29 73 Cu 72 936674.378 + 43 30 73 Zn 72 929582.582 + 42 31 73 Ga 72 925174.682 + 41 32 73 Ge 72 923458.956 + 40 33 73 As 72 923829.089 + 39 34 73 Se 72 926754.883 + 38 35 73 Br 72 931671.621 + 37 36 73 Kr 72 939289.195 + 45 29 74 Cu 73 939874.862 + 44 30 74 Zn 73 929407.262 + 43 31 74 Ga 73 926945.726 + 42 32 74 Ge 73 921177.762 + 41 33 74 As 73 923928.598 + 40 34 74 Se 73 922475.935 + 39 35 74 Br 73 929910.281 + 38 36 74 Kr 73 933084.017 + 37 37 74 Rb 73 944265.868 + 46 29 75 Cu 74 941522.606 + 45 30 75 Zn 74 932840.246 + 44 31 75 Ga 74 926500.246 + 43 32 75 Ge 74 922858.371 + 42 33 75 As 74 921594.562 + 41 34 75 Se 74 922522.871 + 40 35 75 Br 74 925810.570 + 39 36 75 Kr 74 930945.746 + 38 37 75 Rb 74 938573.201 + 37 38 75 Sr 74 949952.770 + 47 29 76 Cu 75 945275.025 + 46 30 76 Zn 75 933114.957 + 45 31 76 Ga 75 928827.625 + 44 32 76 Ge 75 921402.726 + 43 33 76 As 75 922392.010 + 42 34 76 Se 75 919213.704 + 41 35 76 Br 75 924541.577 + 40 36 76 Kr 75 925910.726 + 39 37 76 Rb 75 935073.032 + 38 38 76 Sr 75 941762.761 + 47 30 77 Zn 76 936887.199 + 46 31 77 Ga 76 929154.300 + 45 32 77 Ge 76 923549.844 + 44 33 77 As 76 920647.564 + 43 34 77 Se 76 919914.150 + 42 35 77 Br 76 921379.194 + 41 36 77 Kr 76 924670.000 + 40 37 77 Rb 76 930401.600 + 39 38 77 Sr 76 937945.455 + 49 29 78 Cu 77 952230.000 + 48 30 78 Zn 77 938289.205 + 47 31 78 Ga 77 931608.845 + 46 32 78 Ge 77 922852.912 + 45 33 78 As 77 921827.795 + 44 34 78 Se 77 917309.243 + 43 35 78 Br 77 921145.859 + 42 36 78 Kr 77 920366.341 + 41 37 78 Rb 77 928141.868 + 40 38 78 Sr 77 932179.980 + 49 30 79 Zn 78 942638.068 + 48 31 79 Ga 78 932852.301 + 47 32 79 Ge 78 925360.129 + 46 33 79 As 78 920948.445 + 45 34 79 Se 78 918499.251 + 44 35 79 Br 78 918337.601 + 43 36 79 Kr 78 920082.945 + 42 37 79 Rb 78 923989.864 + 41 38 79 Sr 78 929707.664 + 40 39 79 Y 78 937930.000 + 50 30 80 Zn 79 944552.930 + 49 31 80 Ga 79 936420.774 + 48 32 80 Ge 79 925350.774 + 47 33 80 As 79 922474.548 + 46 34 80 Se 79 916521.785 + 45 35 80 Br 79 918529.810 + 44 36 80 Kr 79 916378.048 + 43 37 80 Rb 79 922516.444 + 42 38 80 Sr 79 924517.540 + 41 39 80 Y 79 934354.755 + 51 30 81 Zn 80 950402.619 + 50 31 81 Ga 80 938133.842 + 49 32 81 Ge 80 928832.942 + 48 33 81 As 80 922132.290 + 47 34 81 Se 80 917993.044 + 46 35 81 Br 80 916288.206 + 45 36 81 Kr 80 916589.714 + 44 37 81 Rb 80 918993.927 + 43 38 81 Sr 80 923211.394 + 42 39 81 Y 80 929454.283 + 41 40 81 Zr 80 938314.000 + 52 30 82 Zn 81 954574.099 + 51 31 82 Ga 81 943176.533 + 50 32 82 Ge 81 929774.033 + 49 33 82 As 81 924738.733 + 48 34 82 Se 81 916699.537 + 47 35 82 Br 81 916801.760 + 46 36 82 Kr 81 913481.15520 + 45 37 82 Rb 81 918209.024 + 44 38 82 Sr 81 918399.847 + 43 39 82 Y 81 926930.188 + 42 40 82 Zr 81 931689.000 + 52 31 83 Ga 82 947120.301 + 51 32 83 Ge 82 934539.101 + 50 33 83 As 82 925206.901 + 49 34 83 Se 82 919118.609 + 48 35 83 Br 82 915175.289 + 47 36 83 Kr 82 914126.518 + 46 37 83 Rb 82 915114.182 + 45 38 83 Sr 82 917554.374 + 44 39 83 Y 82 922484.025 + 43 40 83 Zr 82 929240.925 + 42 41 83 Nb 82 938211.000 + 52 32 84 Ge 83 937575.091 + 51 33 84 As 83 929303.291 + 50 34 84 Se 83 918466.762 + 49 35 84 Br 83 916496.419 + 48 36 84 Kr 83 911497.72863 + 47 37 84 Rb 83 914375.225 + 46 38 84 Sr 83 913419.120 + 45 39 84 Y 83 920671.061 + 44 40 84 Zr 83 923325.662 + 43 41 84 Nb 83 934279.000 + 53 32 85 Ge 84 942969.659 + 52 33 85 As 84 932163.659 + 51 34 85 Se 84 922260.759 + 50 35 85 Br 84 915645.759 + 49 36 85 Kr 84 912527.262 + 48 37 85 Rb 84 911789.73760 + 47 38 85 Sr 84 912932.043 + 46 39 85 Y 84 916433.039 + 45 40 85 Zr 84 921443.198 + 44 41 85 Nb 84 928845.837 + 43 42 85 Mo 84 938260.737 + 54 32 86 Ge 85 946967.000 + 53 33 86 As 85 936701.533 + 52 34 86 Se 85 924311.733 + 51 35 86 Br 85 918805.433 + 50 36 86 Kr 85 910610.62627 + 49 37 86 Rb 85 911167.443 + 48 38 86 Sr 85 909260.72631 + 47 39 86 Y 85 914886.098 + 46 40 86 Zr 85 916296.815 + 45 41 86 Nb 85 925781.535 + 44 42 86 Mo 85 931174.817 + 54 33 87 As 86 940291.718 + 53 34 87 Se 86 928688.618 + 52 35 87 Br 86 920674.018 + 51 36 87 Kr 86 913354.759 + 50 37 87 Rb 86 909180.531 + 49 38 87 Sr 86 908877.49615 + 48 39 87 Y 86 910876.102 + 47 40 87 Zr 86 914817.339 + 46 41 87 Nb 86 920692.472 + 45 42 87 Mo 86 928196.201 + 44 43 87 Tc 86 938067.187 + 54 34 88 Se 87 931417.491 + 53 35 88 Br 87 924083.291 + 52 36 88 Kr 87 914447.881 + 51 37 88 Rb 87 911315.591 + 50 38 88 Sr 87 905612.25561 + 49 39 88 Y 87 909501.276 + 48 40 88 Zr 87 910220.709 + 47 41 88 Nb 87 918224.287 + 46 42 88 Mo 87 921967.781 + 45 43 88 Tc 87 933782.381 + 55 34 89 Se 88 936669.059 + 54 35 89 Br 88 926704.559 + 53 36 89 Kr 88 917835.450 + 52 37 89 Rb 88 912278.137 + 51 38 89 Sr 88 907450.808 + 50 39 89 Y 88 905841.205 + 49 40 89 Zr 88 908882.332 + 48 41 89 Nb 88 913445.272 + 47 42 89 Mo 88 919468.150 + 46 43 89 Tc 88 927648.650 + 56 34 90 Se 89 940096.000 + 55 35 90 Br 89 931292.850 + 54 36 90 Kr 89 919527.930 + 53 37 90 Rb 89 914798.803 + 52 38 90 Sr 89 907730.885 + 51 39 90 Y 89 907144.800 + 50 40 90 Zr 89 904698.758 + 49 41 90 Nb 89 911259.204 + 48 42 90 Mo 89 913931.272 + 47 43 90 Tc 89 924073.921 + 46 44 90 Ru 89 930344.379 + 57 34 91 Se 90 945700.000 + 56 35 91 Br 90 934398.618 + 55 36 91 Kr 90 923806.310 + 54 37 91 Rb 90 916537.265 + 53 38 91 Sr 90 910195.958 + 52 39 91 Y 90 907298.066 + 51 40 91 Zr 90 905640.223 + 50 41 91 Nb 90 906990.274 + 49 42 91 Mo 90 911745.195 + 48 43 91 Tc 90 918424.975 + 47 44 91 Ru 90 926741.532 + 57 35 92 Br 91 939631.597 + 56 36 92 Kr 91 926173.094 + 55 37 92 Rb 91 919728.481 + 54 38 92 Sr 91 911038.224 + 53 39 92 Y 91 908945.745 + 52 40 92 Zr 91 905035.322 + 51 41 92 Nb 91 907188.568 + 50 42 92 Mo 91 906807.155 + 49 43 92 Tc 91 915269.779 + 48 44 92 Ru 91 920234.375 + 47 45 92 Rh 91 932367.694 + 58 35 93 Br 92 943220.000 + 57 36 93 Kr 92 931147.174 + 56 37 93 Rb 92 922039.325 + 55 38 93 Sr 92 914024.311 + 54 39 93 Y 92 909578.422 + 53 40 93 Zr 92 906470.646 + 52 41 93 Nb 92 906373.161 + 51 42 93 Mo 92 906808.773 + 50 43 93 Tc 92 910245.149 + 49 44 93 Ru 92 917104.444 + 48 45 93 Rh 92 925912.781 + 58 36 94 Kr 93 934140.454 + 57 37 94 Rb 93 926394.818 + 56 38 94 Sr 93 915355.643 + 55 39 94 Y 93 911592.063 + 54 40 94 Zr 93 906312.524 + 53 41 94 Nb 93 907278.992 + 52 42 94 Mo 93 905083.592 + 51 43 94 Tc 93 909652.325 + 50 44 94 Ru 93 911342.863 + 49 45 94 Rh 93 921730.453 + 48 46 94 Pd 93 929036.292 + 59 36 95 Kr 94 939710.923 + 58 37 95 Rb 94 929262.568 + 57 38 95 Sr 94 919355.840 + 56 39 95 Y 94 912818.711 + 55 40 95 Zr 94 908040.267 + 54 41 95 Nb 94 906831.115 + 53 42 95 Mo 94 905837.442 + 52 43 95 Tc 94 907652.287 + 51 44 95 Ru 94 910404.420 + 50 45 95 Rh 94 915897.895 + 49 46 95 Pd 94 924888.512 + 60 36 96 Kr 95 943016.618 + 59 37 96 Rb 95 934133.393 + 58 38 96 Sr 95 921712.692 + 57 39 96 Y 95 915902.953 + 56 40 96 Zr 95 908277.621 + 55 41 96 Nb 95 908101.591 + 54 42 96 Mo 95 904674.774 + 53 43 96 Tc 95 907866.681 + 52 44 96 Ru 95 907588.914 + 51 45 96 Rh 95 914451.710 + 50 46 96 Pd 95 918213.744 + 49 47 96 Ag 95 930743.906 + 61 36 97 Kr 96 949088.784 + 60 37 97 Rb 96 937177.118 + 59 38 97 Sr 96 926374.776 + 58 39 97 Y 96 918280.286 + 57 40 97 Zr 96 910957.386 + 56 41 97 Nb 96 908098.414 + 55 42 97 Mo 96 906016.903 + 54 43 97 Tc 96 906360.723 + 53 44 97 Ru 96 907545.779 + 52 45 97 Rh 96 911327.876 + 51 46 97 Pd 96 916471.987 + 50 47 97 Ag 96 923965.326 + 61 37 98 Rb 97 941632.317 + 60 38 98 Sr 97 928691.860 + 59 39 98 Y 97 922388.360 + 58 40 98 Zr 97 912735.124 + 57 41 98 Nb 97 910332.650 + 56 42 98 Mo 97 905403.608 + 55 43 98 Tc 97 907211.205 + 54 44 98 Ru 97 905286.713 + 53 45 98 Rh 97 910707.740 + 52 46 98 Pd 97 912698.337 + 51 47 98 Ag 97 921559.972 + 50 48 98 Cd 97 927389.317 + 62 37 99 Rb 98 945119.192 + 61 38 99 Sr 98 932880.511 + 60 39 99 Y 98 924154.288 + 59 40 99 Zr 98 916670.835 + 58 41 99 Nb 98 911609.371 + 57 42 99 Mo 98 907707.298 + 56 43 99 Tc 98 906249.678 + 55 44 99 Ru 98 905930.278 + 54 45 99 Rh 98 908124.690 + 53 46 99 Pd 98 911773.290 + 52 47 99 Ag 98 917645.768 + 51 48 99 Cd 98 924925.847 + 63 37 100 Rb 99 950351.731 + 62 38 100 Sr 99 935779.615 + 61 39 100 Y 99 927721.063 + 60 40 100 Zr 99 918005.444 + 59 41 100 Nb 99 914333.963 + 58 42 100 Mo 99 907467.976 + 57 43 100 Tc 99 907652.711 + 56 44 100 Ru 99 904210.452 + 55 45 100 Rh 99 908114.141 + 54 46 100 Pd 99 908520.315 + 53 47 100 Ag 99 916115.445 + 52 48 100 Cd 99 920348.820 + 51 49 100 In 99 930957.180 + 50 50 100 Sn 99 938504.196 + 63 38 101 Sr 100 940606.266 + 62 39 101 Y 100 930154.138 + 61 40 101 Zr 100 921453.110 + 60 41 101 Nb 100 915306.496 + 59 42 101 Mo 100 910337.641 + 58 43 101 Tc 100 907305.260 + 57 44 101 Ru 100 905573.075 + 56 45 101 Rh 100 906158.905 + 55 46 101 Pd 100 908284.828 + 54 47 101 Ag 100 912683.953 + 53 48 101 Cd 100 918586.211 + 51 50 101 Sn 100 935259.244 + 64 38 102 Sr 101 944004.680 + 63 39 102 Y 101 934327.889 + 62 40 102 Zr 101 923147.431 + 61 41 102 Nb 101 918083.697 + 60 42 102 Mo 101 910288.138 + 59 43 102 Tc 101 909207.275 + 58 44 102 Ru 101 904340.300 + 57 45 102 Rh 101 906834.270 + 56 46 102 Pd 101 905632.058 + 55 47 102 Ag 101 911704.540 + 54 48 102 Cd 101 914481.799 + 53 49 102 In 101 924105.916 + 52 50 102 Sn 101 930289.530 + 64 39 103 Y 102 937243.208 + 63 40 103 Zr 102 927197.240 + 62 41 103 Nb 102 919453.403 + 61 42 103 Mo 102 913085.140 + 60 43 103 Tc 102 909174.008 + 59 44 103 Ru 102 906314.833 + 58 45 103 Rh 102 905494.068 + 57 46 103 Pd 102 906110.840 + 56 47 103 Ag 102 908960.560 + 55 48 103 Cd 102 913416.923 + 54 49 103 In 102 919878.613 + 53 50 103 Sn 102 928101.962 + 64 40 104 Zr 103 929442.315 + 63 41 104 Nb 103 922899.115 + 62 42 104 Mo 103 913740.756 + 61 43 104 Tc 103 911428.905 + 60 44 104 Ru 103 905425.360 + 59 45 104 Rh 103 906645.295 + 58 46 104 Pd 103 904030.401 + 57 47 104 Ag 103 908623.725 + 56 48 104 Cd 103 909856.230 + 55 49 104 In 103 918214.540 + 54 50 104 Sn 103 923105.197 + 53 51 104 Sb 103 936474.502 + 66 39 105 Y 104 944959.000 + 65 40 105 Zr 104 934014.890 + 64 41 105 Nb 104 924942.564 + 63 42 105 Mo 104 916975.159 + 62 43 105 Tc 104 911657.952 + 61 44 105 Ru 104 907745.525 + 60 45 105 Rh 104 905687.806 + 59 46 105 Pd 104 905079.487 + 58 47 105 Ag 104 906525.607 + 57 48 105 Cd 104 909463.895 + 56 49 105 In 104 914502.324 + 55 50 105 Sn 104 921268.423 + 54 51 105 Sb 104 931276.549 + 53 52 105 Te 104 943304.508 + 66 40 106 Zr 105 937144.000 + 65 41 106 Nb 105 928927.768 + 64 42 106 Mo 105 918266.218 + 63 43 106 Tc 105 914356.697 + 62 44 106 Ru 105 907328.203 + 61 45 106 Rh 105 907285.901 + 60 46 106 Pd 105 903480.293 + 59 47 106 Ag 105 906663.507 + 58 48 106 Cd 105 906459.797 + 57 49 106 In 105 913463.603 + 56 50 106 Sn 105 916957.396 + 55 51 106 Sb 105 928637.982 + 54 52 106 Te 105 937498.526 + 67 40 107 Zr 106 941621.000 + 66 41 107 Nb 106 931589.672 + 65 42 107 Mo 106 922112.692 + 64 43 107 Tc 106 915458.485 + 63 44 107 Ru 106 909969.885 + 62 45 107 Rh 106 906747.974 + 61 46 107 Pd 106 905128.064 + 60 47 107 Ag 106 905091.531 + 59 48 107 Cd 106 906612.108 + 58 49 107 In 106 910290.071 + 57 50 107 Sn 106 915713.651 + 56 51 107 Sb 106 924150.624 + 55 52 107 Te 106 935008.356 + 67 41 108 Nb 107 936074.988 + 66 42 108 Mo 107 924040.367 + 65 43 108 Tc 107 918493.541 + 64 44 108 Ru 107 910185.841 + 63 45 108 Rh 107 908714.688 + 62 46 108 Pd 107 903891.805 + 61 47 108 Ag 107 905950.266 + 60 48 108 Cd 107 904183.587 + 59 49 108 In 107 909693.655 + 58 50 108 Sn 107 911894.292 + 57 51 108 Sb 107 922226.734 + 56 52 108 Te 107 929380.471 + 55 53 108 I 107 943478.321 + 68 41 109 Nb 108 939141.000 + 67 42 109 Mo 108 928431.106 + 66 43 109 Tc 108 920254.156 + 65 44 109 Ru 108 913323.756 + 64 45 109 Rh 108 908749.326 + 63 46 109 Pd 108 905950.574 + 62 47 109 Ag 108 904755.773 + 61 48 109 Cd 108 904986.698 + 60 49 109 In 108 907149.685 + 59 50 109 Sn 108 911292.843 + 58 51 109 Sb 108 918141.204 + 57 52 109 Te 108 927304.534 + 56 53 109 I 108 938086.025 + 55 54 109 Xe 108 950434.948 + 69 41 110 Nb 109 943843.000 + 68 42 110 Mo 109 930710.680 + 67 43 110 Tc 109 923741.312 + 66 44 110 Ru 109 914038.548 + 65 45 110 Rh 109 911079.742 + 64 46 110 Pd 109 905172.868 + 63 47 110 Ag 109 906110.719 + 62 48 110 Cd 109 903007.460 + 61 49 110 In 109 907170.665 + 60 50 110 Sn 109 907844.835 + 59 51 110 Sb 109 916854.286 + 58 52 110 Te 109 922458.104 + 57 53 110 I 109 935089.033 + 56 54 110 Xe 109 944258.765 + 69 42 111 Mo 110 935652.016 + 68 43 111 Tc 110 925899.016 + 67 44 111 Ru 110 917567.616 + 66 45 111 Rh 110 911642.531 + 65 46 111 Pd 110 907690.347 + 64 47 111 Ag 110 905296.816 + 63 48 111 Cd 110 904183.766 + 62 49 111 In 110 905107.233 + 61 50 111 Sn 110 907741.126 + 60 51 111 Sb 110 913218.189 + 59 52 111 Te 110 921000.589 + 58 53 111 I 110 930269.239 + 57 54 111 Xe 110 941603.989 + 69 43 112 Tc 111 929941.644 + 68 44 112 Ru 111 918806.972 + 67 45 112 Rh 111 914404.705 + 66 46 112 Pd 111 907329.986 + 65 47 112 Ag 111 907048.550 + 64 48 112 Cd 111 902763.883 + 63 49 112 In 111 905538.704 + 62 50 112 Sn 111 904824.877 + 61 51 112 Sb 111 912399.903 + 60 52 112 Te 111 916727.850 + 59 53 112 I 111 928004.550 + 58 54 112 Xe 111 935559.071 + 57 55 112 Cs 111 950305.341 + 70 43 113 Tc 112 932569.033 + 69 44 113 Ru 112 922846.396 + 68 45 113 Rh 112 915439.567 + 67 46 113 Pd 112 910261.267 + 66 47 113 Ag 112 906572.858 + 65 48 113 Cd 112 904408.097 + 64 49 113 In 112 904060.448 + 63 50 113 Sn 112 905175.845 + 62 51 113 Sb 112 909374.652 + 61 52 113 Te 112 915891.000 + 60 53 113 I 112 923650.064 + 59 54 113 Xe 112 933221.666 + 58 55 113 Cs 112 944428.488 + 71 43 114 Tc 113 937090.000 + 70 44 114 Ru 113 924613.780 + 69 45 114 Rh 113 918721.296 + 68 46 114 Pd 113 910368.780 + 67 47 114 Ag 113 908823.031 + 66 48 114 Cd 113 903364.990 + 65 49 114 In 113 904916.402 + 64 50 114 Sn 113 902780.132 + 63 51 114 Sb 113 909289.191 + 62 52 114 Te 113 912089.000 + 60 54 114 Xe 113 927980.331 + 59 55 114 Cs 113 941296.175 + 58 56 114 Ba 113 950718.495 + 72 43 115 Tc 114 939538.000 + 71 44 115 Ru 114 928942.393 + 70 45 115 Rh 114 920310.993 + 69 46 115 Pd 114 913658.718 + 68 47 115 Ag 114 908767.363 + 67 48 115 Cd 114 905437.417 + 66 49 115 In 114 903878.773 + 65 50 115 Sn 114 903344.697 + 64 51 115 Sb 114 906598.000 + 63 52 115 Te 114 911902.000 + 62 53 115 I 114 918048.000 + 61 54 115 Xe 114 926293.945 + 72 44 116 Ru 115 931219.193 + 71 45 116 Rh 115 924061.645 + 70 46 116 Pd 115 914297.210 + 69 47 116 Ag 115 911386.812 + 68 48 116 Cd 115 904763.230 + 67 49 116 In 115 905259.992 + 66 50 116 Sn 115 901742.824 + 65 51 116 Sb 115 906792.583 + 64 52 116 Te 115 908460.000 + 63 53 116 I 115 916808.658 + 62 54 116 Xe 115 921581.112 + 73 44 117 Ru 116 936135.000 + 72 45 117 Rh 116 926035.623 + 71 46 117 Pd 116 917954.944 + 70 47 117 Ag 116 911773.974 + 69 48 117 Cd 116 907226.038 + 68 49 117 In 116 904515.712 + 67 50 117 Sn 116 902954.017 + 66 51 117 Sb 116 904841.535 + 65 52 117 Te 116 908646.313 + 64 53 117 I 116 913648.314 + 63 54 117 Xe 116 920358.760 + 62 55 117 Cs 116 928616.726 + 61 56 117 Ba 116 938316.561 + 73 45 118 Rh 117 930340.443 + 72 46 118 Pd 117 919066.847 + 71 47 118 Ag 117 914595.487 + 70 48 118 Cd 117 906921.955 + 69 49 118 In 117 906356.659 + 68 50 118 Sn 117 901606.609 + 67 51 118 Sb 117 905532.174 + 66 52 118 Te 117 905853.839 + 65 53 118 I 117 913074.000 + 64 54 118 Xe 117 916178.680 + 63 55 118 Cs 117 926559.519 + 74 45 119 Rh 118 932556.952 + 73 46 119 Pd 118 923340.459 + 72 47 119 Ag 118 915570.293 + 71 48 119 Cd 118 909846.903 + 70 49 119 In 118 905850.944 + 69 50 119 Sn 118 903311.216 + 68 51 119 Sb 118 903945.512 + 67 52 119 Te 118 906407.148 + 66 53 119 I 118 910074.000 + 65 54 119 Xe 118 915410.713 + 64 55 119 Cs 118 922377.330 + 63 56 119 Ba 118 930659.686 + 74 46 120 Pd 119 924551.258 + 73 47 120 Ag 119 918784.767 + 72 48 120 Cd 119 909868.067 + 71 49 120 In 119 907966.805 + 70 50 120 Sn 119 902201.873 + 69 51 120 Sb 119 905079.624 + 68 52 120 Te 119 904059.514 + 67 53 120 I 119 910087.465 + 66 54 120 Xe 119 911784.270 + 65 55 120 Cs 119 920677.279 + 64 56 120 Ba 119 926045.000 + 76 45 121 Rh 120 939613.000 + 75 46 121 Pd 120 928950.343 + 74 47 121 Ag 120 920125.282 + 73 48 121 Cd 120 912963.663 + 72 49 121 In 120 907851.286 + 71 50 121 Sn 120 904242.792 + 70 51 121 Sb 120 903810.093 + 69 52 121 Te 120 904942.488 + 68 53 121 I 120 907405.255 + 67 54 121 Xe 120 911453.014 + 66 55 121 Cs 120 917227.238 + 65 56 121 Ba 120 924052.289 + 76 46 122 Pd 121 930631.694 + 75 47 122 Ag 121 923664.448 + 74 48 122 Cd 121 913459.052 + 73 49 122 In 121 910280.966 + 72 50 122 Sn 121 903444.001 + 71 51 122 Sb 121 905168.074 + 70 52 122 Te 121 903043.434 + 69 53 122 I 121 907588.820 + 68 54 122 Xe 121 908367.658 + 67 55 122 Cs 121 916108.145 + 66 56 122 Ba 121 919904.000 + 77 46 123 Pd 122 935126.000 + 76 47 123 Ag 122 925337.062 + 75 48 123 Cd 122 916892.453 + 74 49 123 In 122 910433.826 + 73 50 123 Sn 122 905725.446 + 72 51 123 Sb 122 904214.016 + 71 52 123 Te 122 904269.747 + 70 53 123 I 122 905588.520 + 69 54 123 Xe 122 908481.750 + 68 55 123 Cs 122 912996.062 + 67 56 123 Ba 122 918781.062 + 77 47 124 Ag 123 928931.229 + 76 48 124 Cd 123 917657.363 + 75 49 124 In 123 913182.263 + 74 50 124 Sn 123 905276.692 + 73 51 124 Sb 123 905935.789 + 72 52 124 Te 123 902817.064 + 71 53 124 I 123 906209.021 + 70 54 124 Xe 123 905891.588 + 69 55 124 Cs 123 912257.798 + 68 56 124 Ba 123 915093.629 + 67 57 124 La 123 924574.275 + 78 47 125 Ag 124 930735.000 + 77 48 125 Cd 124 921257.577 + 76 49 125 In 124 913604.591 + 75 50 125 Sn 124 907786.442 + 74 51 125 Sb 124 905252.987 + 73 52 125 Te 124 904429.900 + 72 53 125 I 124 904629.333 + 71 54 125 Xe 124 906394.050 + 70 55 125 Cs 124 909727.867 + 69 56 125 Ba 124 914471.843 + 68 57 125 La 124 920815.932 + 78 48 126 Cd 125 922429.127 + 77 49 126 In 125 916507.344 + 76 50 126 Sn 125 907658.836 + 75 51 126 Sb 125 907253.036 + 74 52 126 Te 125 903310.866 + 73 53 126 I 125 905623.313 + 72 54 126 Xe 125 904296.794 + 71 55 126 Cs 125 909445.655 + 70 56 126 Ba 125 911250.204 + 69 57 126 La 125 919512.667 + 68 58 126 Ce 125 923971.000 + 79 48 127 Cd 126 926196.624 + 78 49 127 In 126 917448.546 + 77 50 127 Sn 126 910390.401 + 76 51 127 Sb 126 906924.277 + 75 52 127 Te 126 905225.714 + 74 53 127 I 126 904471.838 + 73 54 127 Xe 126 905182.899 + 72 55 127 Cs 126 907417.381 + 71 56 127 Ba 126 911091.275 + 70 57 127 La 126 916375.084 + 69 58 127 Ce 126 922727.000 + 80 48 128 Cd 127 927812.857 + 79 49 128 In 127 920401.053 + 78 50 128 Sn 127 910507.197 + 77 51 128 Sb 127 909145.645 + 76 52 128 Te 127 904461.311 + 75 53 128 I 127 905808.600 + 74 54 128 Xe 127 903530.996 + 73 55 128 Cs 127 907748.648 + 72 56 128 Ba 127 908342.408 + 71 57 128 La 127 915592.123 + 70 58 128 Ce 127 918911.000 + 69 59 128 Pr 127 928791.000 + 81 48 129 Cd 128 932304.399 + 80 49 129 In 128 921805.486 + 79 50 129 Sn 128 913482.102 + 78 51 129 Sb 128 909146.696 + 77 52 129 Te 128 906596.492 + 76 53 129 I 128 904983.687 + 75 54 129 Xe 128 904780.85892 + 74 55 129 Cs 128 906065.690 + 73 56 129 Ba 128 908680.896 + 72 57 129 La 128 912694.475 + 71 58 129 Ce 128 918102.000 + 70 59 129 Pr 128 925095.000 + 82 48 130 Cd 129 934387.566 + 81 49 130 In 129 924977.288 + 80 50 130 Sn 129 913974.533 + 79 51 130 Sb 129 911662.688 + 78 52 130 Te 129 906222.747 + 77 53 130 I 129 906670.211 + 76 54 130 Xe 129 903509.349 + 75 55 130 Cs 129 906709.283 + 74 56 130 Ba 129 906320.874 + 73 57 130 La 129 912369.413 + 72 58 130 Ce 129 914736.000 + 71 59 130 Pr 129 923590.000 + 70 60 130 Nd 129 928506.000 + 83 48 131 Cd 130 940720.000 + 82 49 131 In 130 926972.122 + 81 50 131 Sn 130 917053.066 + 80 51 131 Sb 130 911989.341 + 79 52 131 Te 130 908522.211 + 78 53 131 I 130 906126.384 + 77 54 131 Xe 130 905084.136 + 76 55 131 Cs 130 905464.999 + 75 56 131 Ba 130 906941.181 + 74 57 131 La 130 910070.000 + 73 58 131 Ce 130 914429.465 + 72 59 131 Pr 130 920234.960 + 71 60 131 Nd 130 927248.020 + 83 49 132 In 131 932998.449 + 82 50 132 Sn 131 917823.902 + 81 51 132 Sb 131 914508.015 + 80 52 132 Te 131 908546.716 + 79 53 132 I 131 907993.514 + 78 54 132 Xe 131 904155.08697 + 77 55 132 Cs 131 906437.743 + 76 56 132 Ba 131 905061.098 + 75 57 132 La 131 910118.959 + 74 58 132 Ce 131 911463.846 + 73 59 132 Pr 131 919240.000 + 72 60 132 Nd 131 923321.237 + 83 50 133 Sn 132 923913.756 + 82 51 133 Sb 132 915272.130 + 81 52 133 Te 132 910963.332 + 80 53 133 I 132 907827.361 + 79 54 133 Xe 132 905910.750 + 78 55 133 Cs 132 905451.961 + 77 56 133 Ba 132 906007.325 + 76 57 133 La 132 908218.000 + 75 58 133 Ce 132 911520.402 + 74 59 133 Pr 132 916330.561 + 73 60 133 Nd 132 922348.000 + 72 61 133 Pm 132 929782.000 + 84 50 134 Sn 133 928680.433 + 83 51 134 Sb 133 920535.675 + 82 52 134 Te 133 911396.379 + 81 53 134 I 133 909775.663 + 80 54 134 Xe 133 905393.033 + 79 55 134 Cs 133 906718.503 + 78 56 134 Ba 133 904508.399 + 77 57 134 La 133 908514.011 + 76 58 134 Ce 133 908928.142 + 75 59 134 Pr 133 915696.729 + 74 60 134 Nd 133 918790.210 + 73 61 134 Pm 133 928353.000 + 85 50 135 Sn 134 934908.605 + 84 51 135 Sb 134 925184.357 + 83 52 135 Te 134 916554.718 + 82 53 135 I 134 910059.382 + 81 54 135 Xe 134 907231.661 + 80 55 135 Cs 134 905977.234 + 79 56 135 Ba 134 905688.606 + 78 57 135 La 134 906984.568 + 77 58 135 Ce 134 909160.799 + 76 59 135 Pr 134 913111.774 + 75 60 135 Nd 134 918181.320 + 74 61 135 Pm 134 924796.000 + 73 62 135 Sm 134 932520.000 + 85 51 136 Sb 135 930749.011 + 84 52 136 Te 135 920101.182 + 83 53 136 I 135 914604.695 + 82 54 136 Xe 135 907214.476 + 81 55 136 Cs 135 907311.590 + 80 56 136 Ba 135 904575.959 + 79 57 136 La 135 907634.962 + 78 58 136 Ce 135 907129.438 + 77 59 136 Pr 135 912677.532 + 76 60 136 Nd 135 914976.064 + 75 61 136 Pm 135 923595.949 + 74 62 136 Sm 135 928275.555 + 86 51 137 Sb 136 935522.522 + 85 52 137 Te 136 925599.357 + 84 53 137 I 136 918028.180 + 83 54 137 Xe 136 911557.773 + 82 55 137 Cs 136 907089.464 + 81 56 137 Ba 136 905827.375 + 80 57 137 La 136 906450.618 + 79 58 137 Ce 136 907762.596 + 78 59 137 Pr 136 910679.304 + 77 60 137 Nd 136 914562.448 + 76 61 137 Pm 136 920479.522 + 75 62 137 Sm 136 926970.517 + 74 63 137 Eu 136 935430.722 + 87 51 138 Sb 137 941792.000 + 86 52 138 Te 137 929472.454 + 85 53 138 I 137 922726.394 + 84 54 138 Xe 137 914146.271 + 83 55 138 Cs 137 911017.207 + 82 56 138 Ba 137 905247.229 + 81 57 138 La 137 907117.834 + 80 58 138 Ce 137 905988.743 + 79 59 138 Pr 137 910752.059 + 78 60 138 Nd 137 911949.717 + 77 61 138 Pm 137 919548.077 + 76 62 138 Sm 137 923243.990 + 75 63 138 Eu 137 933709.000 + 87 52 139 Te 138 935367.193 + 86 53 139 I 138 926493.403 + 85 54 139 Xe 138 918792.203 + 84 55 139 Cs 138 913363.992 + 83 56 139 Ba 138 908841.334 + 82 57 139 La 138 906358.804 + 81 58 139 Ce 138 906657.625 + 80 59 139 Pr 138 908943.270 + 79 60 139 Nd 138 911954.407 + 78 61 139 Pm 138 916799.806 + 77 62 139 Sm 138 922296.634 + 76 63 139 Eu 138 929792.310 + 88 52 140 Te 139 939262.917 + 87 53 140 I 139 931715.917 + 86 54 140 Xe 139 921645.817 + 85 55 140 Cs 139 917283.305 + 84 56 140 Ba 139 910606.666 + 83 57 140 La 139 909483.184 + 82 58 140 Ce 139 905446.424 + 81 59 140 Pr 139 909083.592 + 80 60 140 Nd 139 909544.332 + 79 61 140 Pm 139 916034.122 + 78 62 140 Sm 139 918994.717 + 77 63 140 Eu 139 928087.637 + 76 64 140 Gd 139 933674.000 + 75 65 140 Tb 139 945805.049 + 88 53 141 I 140 935666.084 + 87 54 141 Xe 140 926787.184 + 86 55 141 Cs 140 920045.086 + 85 56 141 Ba 140 914403.500 + 84 57 141 La 140 910969.222 + 83 58 141 Ce 140 908283.987 + 82 59 141 Pr 140 907658.403 + 81 60 141 Nd 140 909615.488 + 80 61 141 Pm 140 913555.084 + 79 62 141 Sm 140 918481.591 + 78 63 141 Eu 140 924931.745 + 77 64 141 Gd 140 932126.000 + 76 65 141 Tb 140 941448.000 + 89 53 142 I 141 941202.000 + 88 54 142 Xe 141 929973.098 + 87 55 142 Cs 141 924299.512 + 86 56 142 Ba 141 916432.888 + 85 57 142 La 141 914090.454 + 84 58 142 Ce 141 909249.884 + 83 59 142 Pr 141 910050.440 + 82 60 142 Nd 141 907728.895 + 81 61 142 Pm 141 912890.428 + 80 62 142 Sm 141 915204.532 + 79 63 142 Eu 141 923441.836 + 78 64 142 Gd 141 928116.000 + 77 65 142 Tb 141 939280.859 + 89 54 143 Xe 142 935369.553 + 88 55 143 Cs 142 927347.348 + 87 56 143 Ba 142 920625.150 + 86 57 143 La 142 916079.422 + 85 58 143 Ce 142 912391.630 + 84 59 143 Pr 142 910822.564 + 83 60 143 Nd 142 909819.887 + 82 61 143 Pm 142 910938.073 + 81 62 143 Sm 142 914634.821 + 80 63 143 Eu 142 920298.681 + 79 64 143 Gd 142 926750.682 + 78 65 143 Tb 142 935137.335 + 77 66 143 Dy 142 943994.335 + 90 54 144 Xe 143 938945.079 + 89 55 144 Cs 143 932075.404 + 88 56 144 Ba 143 922954.821 + 87 57 144 La 143 919645.589 + 86 58 144 Ce 143 913652.830 + 85 59 144 Pr 143 913310.750 + 84 60 144 Nd 143 910092.865 + 83 61 144 Pm 143 912596.224 + 82 62 144 Sm 143 912006.373 + 81 63 144 Eu 143 918819.517 + 80 64 144 Gd 143 922963.000 + 79 65 144 Tb 143 933045.000 + 78 66 144 Dy 143 939269.514 + 77 67 144 Ho 143 952109.714 + 91 54 145 Xe 144 944719.634 + 90 55 145 Cs 144 935528.930 + 89 56 145 Ba 144 927518.400 + 88 57 145 La 144 921808.066 + 87 58 145 Ce 144 917265.144 + 86 59 145 Pr 144 914518.033 + 85 60 145 Nd 144 912579.199 + 84 61 145 Pm 144 912755.773 + 83 62 145 Sm 144 913417.244 + 82 63 145 Eu 144 916272.668 + 81 64 145 Gd 144 921710.370 + 80 65 145 Tb 144 928729.105 + 79 66 145 Dy 144 937473.994 + 78 67 145 Ho 144 947267.394 + 92 54 146 Xe 145 948518.248 + 91 55 146 Cs 145 940621.870 + 90 56 146 Ba 145 930276.431 + 89 57 146 La 145 925871.468 + 88 58 146 Ce 145 918802.065 + 87 59 146 Pr 145 917679.549 + 86 60 146 Nd 145 913122.503 + 85 61 146 Pm 145 914702.286 + 84 62 146 Sm 145 913046.881 + 83 63 146 Eu 145 917210.909 + 82 64 146 Gd 145 918318.548 + 81 65 146 Tb 145 927252.768 + 80 66 146 Dy 145 932844.529 + 79 67 146 Ho 145 944993.506 + 78 68 146 Er 145 952418.359 + 92 55 147 Cs 146 944261.515 + 91 56 147 Ba 146 935303.900 + 90 57 147 La 146 928417.800 + 89 58 147 Ce 146 922689.903 + 88 59 147 Pr 146 919007.458 + 87 60 147 Nd 146 916106.010 + 86 61 147 Pm 146 915144.638 + 85 62 147 Sm 146 914904.064 + 84 63 147 Eu 146 916752.276 + 83 64 147 Gd 146 919100.987 + 82 65 147 Tb 146 924054.620 + 81 66 147 Dy 146 931082.715 + 80 67 147 Ho 146 940142.295 + 79 68 147 Er 146 949964.458 + 78 69 147 Tm 146 961379.890 + 93 55 148 Cs 147 949639.029 + 92 56 148 Ba 147 938170.578 + 91 57 148 La 147 932679.400 + 90 58 148 Ce 147 924424.196 + 89 59 148 Pr 147 922130.015 + 88 60 148 Nd 147 916899.093 + 87 61 148 Pm 147 917481.255 + 86 62 148 Sm 147 914829.012 + 85 63 148 Eu 147 918089.294 + 84 64 148 Gd 147 918121.503 + 83 65 148 Tb 147 924275.323 + 82 66 148 Dy 147 927149.772 + 81 67 148 Ho 147 937743.928 + 80 68 148 Er 147 944735.029 + 79 69 148 Tm 147 958384.029 + 93 56 149 Ba 148 942973.000 + 92 57 149 La 148 935351.260 + 91 58 149 Ce 148 928426.900 + 90 59 149 Pr 148 923736.100 + 89 60 149 Nd 148 920154.648 + 88 61 149 Pm 148 918341.658 + 87 62 149 Sm 148 917191.375 + 86 63 149 Eu 148 917937.086 + 85 64 149 Gd 148 919347.831 + 84 65 149 Tb 148 923253.753 + 83 66 149 Dy 148 927325.448 + 82 67 149 Ho 148 933819.672 + 81 68 149 Er 148 942306.000 + 93 57 150 La 149 939742.000 + 92 58 150 Ce 149 930384.035 + 91 59 150 Pr 149 926676.415 + 90 60 150 Nd 149 920901.525 + 89 61 150 Pm 149 920990.217 + 88 62 150 Sm 149 917282.195 + 87 63 150 Eu 149 919707.229 + 86 64 150 Gd 149 918664.066 + 85 65 150 Tb 149 923664.864 + 84 66 150 Dy 149 925593.080 + 83 67 150 Ho 149 933498.358 + 82 68 150 Er 149 937915.528 + 94 57 151 La 150 942769.000 + 93 58 151 Ce 150 934272.200 + 92 59 151 Pr 150 928309.114 + 91 60 151 Nd 150 923839.565 + 90 61 151 Pm 150 921216.817 + 89 62 151 Sm 150 919939.066 + 88 63 151 Eu 150 919856.860 + 87 64 151 Gd 150 920355.109 + 86 65 151 Tb 150 923109.001 + 85 66 151 Dy 150 926191.253 + 84 67 151 Ho 150 931698.177 + 83 68 151 Er 150 937448.567 + 82 69 151 Tm 150 945493.201 + 81 70 151 Yb 150 955402.458 + 93 59 152 Pr 151 931552.900 + 92 60 152 Nd 151 924691.509 + 91 61 152 Pm 151 923505.481 + 90 62 152 Sm 151 919739.040 + 89 63 152 Eu 151 921751.235 + 88 64 152 Gd 151 919798.822 + 87 65 152 Tb 151 924082.263 + 86 66 152 Dy 151 924725.363 + 85 67 152 Ho 151 931717.465 + 84 68 152 Er 151 935050.169 + 83 69 152 Tm 151 944476.000 + 82 70 152 Yb 151 950326.700 + 94 59 153 Pr 152 933903.532 + 93 60 153 Nd 152 927717.949 + 92 61 153 Pm 152 924156.436 + 91 62 153 Sm 152 922103.969 + 90 63 153 Eu 152 921237.043 + 89 64 153 Gd 152 921757.359 + 88 65 153 Tb 152 923441.978 + 87 66 153 Dy 152 925771.992 + 86 67 153 Ho 152 930206.632 + 85 68 153 Er 152 935084.279 + 84 69 153 Tm 152 942057.244 + 82 71 153 Lu 152 958805.054 + 95 59 154 Pr 153 937621.738 + 94 60 154 Nd 153 929333.977 + 93 61 154 Pm 153 926449.364 + 92 62 154 Sm 153 922216.164 + 91 63 154 Eu 153 922985.955 + 90 64 154 Gd 153 920873.398 + 89 65 154 Tb 153 924684.106 + 88 66 154 Dy 153 924429.028 + 87 67 154 Ho 153 930606.841 + 86 68 154 Er 153 932790.743 + 85 69 154 Tm 153 941570.067 + 84 70 154 Yb 153 946395.701 + 96 59 155 Pr 154 940509.259 + 95 60 155 Nd 154 933135.668 + 94 61 155 Pm 154 928137.024 + 93 62 155 Sm 154 924647.051 + 92 63 155 Eu 154 922900.102 + 91 64 155 Gd 154 922629.796 + 90 65 155 Tb 154 923509.921 + 89 66 155 Dy 154 925758.459 + 88 67 155 Ho 154 929103.634 + 87 68 155 Er 154 933215.684 + 86 69 155 Tm 154 939209.578 + 85 70 155 Yb 154 945783.217 + 84 71 155 Lu 154 954326.011 + 96 60 156 Nd 155 935078.868 + 95 61 156 Pm 155 931117.490 + 94 62 156 Sm 155 925538.511 + 93 63 156 Eu 155 924763.285 + 92 64 156 Gd 155 922130.562 + 91 65 156 Tb 155 924754.430 + 90 66 156 Dy 155 924284.038 + 89 67 156 Ho 155 929705.436 + 88 68 156 Er 155 931065.890 + 87 69 156 Tm 155 938985.597 + 86 70 156 Yb 155 942816.893 + 85 71 156 Lu 155 953086.606 + 84 72 156 Hf 155 959401.889 + 97 60 157 Nd 156 939386.037 + 96 61 157 Pm 156 933121.370 + 95 62 157 Sm 156 928418.673 + 94 63 157 Eu 156 925432.791 + 93 64 157 Gd 156 923967.870 + 92 65 157 Tb 156 924032.328 + 91 66 157 Dy 156 925469.667 + 90 67 157 Ho 156 928251.999 + 89 68 157 Er 156 931922.655 + 88 69 157 Tm 156 936973.000 + 87 70 157 Yb 156 942649.230 + 86 71 157 Lu 156 950144.045 + 84 73 157 Ta 156 968230.251 + 97 61 158 Pm 157 936565.121 + 96 62 158 Sm 157 929950.979 + 95 63 158 Eu 157 927798.581 + 94 64 158 Gd 157 924111.646 + 93 65 158 Tb 157 925420.166 + 92 66 158 Dy 157 924414.597 + 91 67 158 Ho 157 928944.692 + 90 68 158 Er 157 929893.474 + 89 69 158 Tm 157 936979.525 + 88 70 158 Yb 157 939870.534 + 87 71 158 Lu 157 949315.626 + 86 72 158 Hf 157 954801.222 + 98 61 159 Pm 158 939286.479 + 97 62 159 Sm 158 933217.202 + 96 63 159 Eu 158 929099.612 + 95 64 159 Gd 158 926396.267 + 94 65 159 Tb 158 925353.933 + 93 66 159 Dy 158 925746.023 + 92 67 159 Ho 158 927718.768 + 91 68 159 Er 158 930690.875 + 90 69 159 Tm 158 934975.000 + 89 70 159 Yb 158 940054.787 + 88 71 159 Lu 158 946635.615 + 87 72 159 Hf 158 953995.838 + 86 73 159 Ta 158 963028.052 + 98 62 160 Sm 159 935335.286 + 97 63 160 Eu 159 931850.916 + 96 64 160 Gd 159 927061.537 + 95 65 160 Tb 159 927174.778 + 94 66 160 Dy 159 925203.244 + 93 67 160 Ho 159 928735.204 + 92 68 160 Er 159 929077.130 + 91 69 160 Tm 159 935263.106 + 90 70 160 Yb 159 937559.763 + 89 71 160 Lu 159 946033.000 + 88 72 160 Hf 159 950682.513 + 87 73 160 Ta 159 961541.679 + 86 74 160 W 159 968516.753 + 99 62 161 Sm 160 939160.143 + 98 63 161 Eu 160 933664.066 + 97 64 161 Gd 160 929676.602 + 96 65 161 Tb 160 927577.001 + 95 66 161 Dy 160 926939.088 + 94 67 161 Ho 160 927860.759 + 93 68 161 Er 160 930003.191 + 92 69 161 Tm 160 933549.000 + 91 70 161 Yb 160 937906.846 + 90 71 161 Lu 160 943572.000 + 89 72 161 Hf 160 950279.151 + 88 73 161 Ta 160 958369.031 + 86 75 161 Re 160 977627.121 + 99 63 162 Eu 161 936979.303 + 98 64 162 Gd 161 930992.146 + 97 65 162 Tb 161 929493.955 + 96 66 162 Dy 161 926804.168 + 95 67 162 Ho 161 929101.485 + 94 68 162 Er 161 928786.960 + 93 69 162 Tm 161 934000.872 + 92 70 162 Yb 161 935773.771 + 91 71 162 Lu 161 943282.776 + 90 72 162 Hf 161 947214.896 + 89 73 162 Ta 161 957294.202 + 88 74 162 W 161 963500.347 + 100 63 163 Eu 162 939360.977 + 99 64 163 Gd 162 934176.832 + 98 65 163 Tb 162 930653.261 + 97 66 163 Dy 162 928736.879 + 96 67 163 Ho 162 928739.921 + 95 68 163 Er 162 930039.567 + 94 69 163 Tm 162 932657.941 + 93 70 163 Yb 162 936339.800 + 92 71 163 Lu 162 941179.000 + 91 72 163 Hf 162 947113.258 + 90 73 163 Ta 162 954337.195 + 89 74 163 W 162 962524.511 + 88 75 163 Re 162 972085.441 + 99 65 164 Tb 163 933356.559 + 98 66 164 Dy 163 929180.472 + 97 67 164 Ho 163 930239.483 + 96 68 164 Er 163 929207.392 + 95 69 164 Tm 163 933543.281 + 94 70 164 Yb 163 934495.103 + 93 71 164 Lu 163 941339.000 + 92 72 164 Hf 163 944370.544 + 91 73 164 Ta 163 953534.000 + 90 74 164 W 163 958952.222 + 89 75 164 Re 163 970507.124 + 88 76 164 Os 163 978075.966 + 99 66 165 Dy 164 931709.054 + 98 67 165 Ho 164 930328.047 + 97 68 165 Er 164 930733.198 + 96 69 165 Tm 164 932442.269 + 95 70 165 Yb 164 935270.241 + 94 71 165 Lu 164 939406.758 + 93 72 165 Hf 164 944567.000 + 92 73 165 Ta 164 950780.303 + 91 74 165 W 164 958280.974 + 90 75 165 Re 164 967085.375 + 101 65 166 Tb 165 937858.119 + 100 66 166 Dy 165 932812.461 + 99 67 166 Ho 165 932290.139 + 98 68 166 Er 165 930299.023 + 97 69 166 Tm 165 933560.092 + 96 70 166 Yb 165 933874.249 + 95 71 166 Lu 165 939859.000 + 94 72 166 Hf 165 942180.000 + 93 73 166 Ta 165 950512.000 + 92 74 166 W 165 955031.346 + 91 75 166 Re 165 965760.940 + 90 76 166 Os 165 972698.141 + 101 66 167 Dy 166 935661.823 + 100 67 167 Ho 166 933138.994 + 99 68 167 Er 166 932054.119 + 98 69 167 Tm 166 932856.635 + 97 70 167 Yb 166 934953.337 + 96 71 167 Lu 166 938270.000 + 95 72 167 Hf 166 942600.000 + 94 73 167 Ta 166 948093.000 + 93 74 167 W 166 954805.873 + 91 76 167 Os 166 971548.938 + 90 77 167 Ir 166 981671.981 + 102 66 168 Dy 167 937133.716 + 101 67 168 Ho 167 935521.676 + 100 68 168 Er 167 932376.192 + 99 69 168 Tm 167 934177.868 + 98 70 168 Yb 167 933889.106 + 97 71 168 Lu 167 938735.139 + 96 72 168 Hf 167 940568.000 + 95 73 168 Ta 167 948047.000 + 94 74 168 W 167 951805.262 + 93 75 168 Re 167 961572.608 + 92 76 168 Os 167 967798.812 + 91 77 168 Ir 167 979960.981 + 90 78 168 Pt 167 988183.004 + 103 66 169 Dy 168 940313.971 + 102 67 169 Ho 168 936878.630 + 101 68 169 Er 168 934596.353 + 100 69 169 Tm 168 934218.350 + 99 70 169 Yb 168 935182.016 + 98 71 169 Lu 168 937643.653 + 97 72 169 Hf 168 941259.000 + 96 73 169 Ta 168 946011.000 + 95 74 169 W 168 951778.677 + 94 75 169 Re 168 958765.991 + 93 76 169 Os 168 967017.833 + 92 77 169 Ir 168 976281.287 + 103 67 170 Ho 169 939625.289 + 102 68 170 Er 169 935470.673 + 101 69 170 Tm 169 935806.507 + 100 70 170 Yb 169 934767.245 + 99 71 170 Lu 169 938479.234 + 98 72 170 Hf 169 939609.000 + 97 73 170 Ta 169 946175.000 + 96 74 170 W 169 949231.200 + 95 75 170 Re 169 958224.966 + 94 76 170 Os 169 963578.673 + 92 78 170 Pt 169 982502.095 + 104 67 171 Ho 170 941471.490 + 103 68 171 Er 170 938036.148 + 102 69 171 Tm 170 936435.126 + 101 70 171 Yb 170 936331.517 + 100 71 171 Lu 170 937918.660 + 99 72 171 Hf 170 940492.000 + 98 73 171 Ta 170 944476.000 + 97 74 171 W 170 949451.000 + 96 75 171 Re 170 955716.000 + 95 76 171 Os 170 963175.348 + 94 77 171 Ir 170 971645.522 + 93 78 171 Pt 170 981245.502 + 92 79 171 Au 170 991881.542 + 104 68 172 Er 171 939362.344 + 103 69 172 Tm 171 938406.067 + 102 70 172 Yb 171 936386.658 + 101 71 172 Lu 171 939091.417 + 100 72 172 Hf 171 939449.716 + 99 73 172 Ta 171 944895.000 + 98 74 172 W 171 947292.000 + 97 75 172 Re 171 955408.079 + 96 76 172 Os 171 960017.088 + 95 77 172 Ir 171 970607.036 + 94 78 172 Pt 171 977340.788 + 93 79 172 Au 171 989996.708 + 92 80 172 Hg 171 998863.391 + 104 69 173 Tm 172 939606.632 + 103 70 173 Yb 172 938216.215 + 102 71 173 Lu 172 938935.822 + 101 72 173 Hf 172 940513.000 + 100 73 173 Ta 172 943750.000 + 99 74 173 W 172 947689.000 + 98 75 173 Re 172 953243.000 + 97 76 173 Os 172 959808.375 + 96 77 173 Ir 172 967505.496 + 95 78 173 Pt 172 976443.315 + 94 79 173 Au 172 986223.808 + 105 69 174 Tm 173 942174.064 + 104 70 174 Yb 173 938867.548 + 103 71 174 Lu 173 940342.938 + 102 72 174 Hf 173 940048.480 + 101 73 174 Ta 173 944454.000 + 100 74 174 W 173 946079.000 + 99 75 174 Re 173 953115.000 + 98 76 174 Os 173 957063.152 + 97 77 174 Ir 173 966866.676 + 96 78 174 Pt 173 972819.832 + 94 80 174 Hg 173 992870.583 + 106 69 175 Tm 174 943842.313 + 105 70 175 Yb 174 941281.910 + 104 71 175 Lu 174 940777.308 + 103 72 175 Hf 174 941511.527 + 102 73 175 Ta 174 943737.000 + 101 74 175 W 174 946717.000 + 100 75 175 Re 174 951381.000 + 99 76 175 Os 174 956945.105 + 98 77 175 Ir 174 964149.521 + 97 78 175 Pt 174 972395.457 + 96 79 175 Au 174 981316.085 + 95 80 175 Hg 174 991441.086 + 107 69 176 Tm 175 946997.711 + 106 70 176 Yb 175 942574.708 + 105 71 176 Lu 175 942691.809 + 104 72 176 Hf 175 941409.905 + 103 73 176 Ta 175 944857.000 + 102 74 176 W 175 945634.000 + 101 75 176 Re 175 951623.000 + 100 76 176 Os 175 954806.000 + 99 77 176 Ir 175 963630.119 + 98 78 176 Pt 175 968938.214 + 97 79 176 Au 175 980116.927 + 96 80 176 Hg 175 987348.335 + 95 81 176 Tl 176 000624.367 + 107 70 177 Yb 176 945263.848 + 106 71 177 Lu 176 943763.668 + 105 72 177 Hf 176 943230.320 + 104 73 177 Ta 176 944482.073 + 103 74 177 W 176 946643.000 + 102 75 177 Re 176 950328.000 + 101 76 177 Os 176 954957.882 + 100 77 177 Ir 176 961301.500 + 99 78 177 Pt 176 968469.529 + 98 79 177 Au 176 976870.379 + 97 80 177 Hg 176 986277.376 + 96 81 177 Tl 176 996413.797 + 108 70 178 Yb 177 946649.710 + 107 71 178 Lu 177 945960.162 + 106 72 178 Hf 177 943708.456 + 104 74 178 W 177 945885.925 + 103 75 178 Re 177 950989.000 + 102 76 178 Os 177 953253.300 + 101 77 178 Ir 177 961082.000 + 100 78 178 Pt 177 965649.248 + 99 79 178 Au 177 976055.945 + 98 80 178 Hg 177 982484.158 + 96 82 178 Pb 178 003837.163 + 108 71 179 Lu 178 947333.082 + 107 72 179 Hf 178 945825.838 + 106 73 179 Ta 178 945939.187 + 105 74 179 W 178 947079.501 + 104 75 179 Re 178 949989.715 + 103 76 179 Os 178 953816.669 + 102 77 179 Ir 178 959117.596 + 101 78 179 Pt 178 965358.719 + 100 79 179 Au 178 973173.668 + 99 80 179 Hg 178 981826.899 + 98 81 179 Tl 178 991123.405 + 97 82 179 Pb 179 002201.452 + 109 71 180 Lu 179 949890.876 + 108 72 180 Hf 179 946559.669 + 107 73 180 Ta 179 947468.392 + 106 74 180 W 179 946713.435 + 105 75 180 Re 179 950791.568 + 104 76 180 Os 179 952379.930 + 103 77 180 Ir 179 959229.446 + 102 78 180 Pt 179 963031.563 + 101 79 180 Au 179 972489.883 + 100 80 180 Hg 179 978260.249 + 99 81 180 Tl 179 989923.019 + 98 82 180 Pb 179 997915.842 + 110 71 181 Lu 180 951908.000 + 109 72 181 Hf 180 949110.965 + 108 73 181 Ta 180 947999.331 + 107 74 181 W 180 948218.863 + 106 75 181 Re 180 950061.523 + 105 76 181 Os 180 953247.188 + 104 77 181 Ir 180 957634.694 + 103 78 181 Pt 180 963089.927 + 102 79 181 Au 180 970079.103 + 101 80 181 Hg 180 977819.357 + 100 81 181 Tl 180 986259.992 + 99 82 181 Pb 180 996653.386 + 110 72 182 Hf 181 950563.816 + 109 73 182 Ta 181 950155.413 + 108 74 182 W 181 948205.721 + 107 75 182 Re 181 951211.645 + 106 76 182 Os 181 952110.153 + 105 77 182 Ir 181 958076.296 + 104 78 182 Pt 181 961171.571 + 103 79 182 Au 181 969617.874 + 102 80 182 Hg 181 974689.132 + 101 81 182 Tl 181 985691.880 + 100 82 182 Pb 181 992672.940 + 112 71 183 Lu 182 957363.000 + 111 72 183 Hf 182 953534.004 + 110 73 183 Ta 182 951376.180 + 109 74 183 W 182 950224.500 + 108 75 183 Re 182 950821.390 + 107 76 183 Os 182 953124.719 + 106 77 183 Ir 182 956839.968 + 105 78 183 Pt 182 961596.653 + 104 79 183 Au 182 967588.108 + 103 80 183 Hg 182 974444.629 + 102 81 183 Tl 182 982192.846 + 101 82 183 Pb 182 991867.668 + 112 72 184 Hf 183 955448.587 + 111 73 184 Ta 183 954010.038 + 110 74 184 W 183 950933.260 + 109 75 184 Re 183 952528.267 + 108 76 184 Os 183 952492.949 + 107 77 184 Ir 183 957476.000 + 106 78 184 Pt 183 959920.039 + 105 79 184 Au 183 967451.524 + 104 80 184 Hg 183 971713.221 + 103 81 184 Tl 183 981875.093 + 102 82 184 Pb 183 988135.702 + 101 83 184 Bi 184 001141.250 + 113 72 185 Hf 184 958862.000 + 112 73 185 Ta 184 955561.396 + 111 74 185 W 184 953421.286 + 110 75 185 Re 184 952958.337 + 109 76 185 Os 184 954045.995 + 108 77 185 Ir 184 956698.000 + 107 78 185 Pt 184 960613.659 + 106 79 185 Au 184 965798.874 + 105 80 185 Hg 184 971890.676 + 104 81 185 Tl 184 978789.191 + 103 82 185 Pb 184 987609.989 + 114 72 186 Hf 185 960897.000 + 113 73 186 Ta 185 958553.111 + 112 74 186 W 185 954365.215 + 111 75 186 Re 185 954989.419 + 110 76 186 Os 185 953837.660 + 109 77 186 Ir 185 957946.754 + 108 78 186 Pt 185 959350.846 + 107 79 186 Au 185 965952.703 + 106 80 186 Hg 185 969362.017 + 105 81 186 Tl 185 978650.841 + 104 82 186 Pb 185 984238.196 + 103 83 186 Bi 185 996622.402 + 102 84 186 Po 186 004402.577 + 114 73 187 Ta 186 960391.000 + 113 74 187 W 186 957161.323 + 112 75 187 Re 186 955752.288 + 111 76 187 Os 186 955749.640 + 110 77 187 Ir 186 957542.000 + 109 78 187 Pt 186 960616.976 + 108 79 187 Au 186 964543.155 + 107 80 187 Hg 186 969814.158 + 106 81 187 Tl 186 975904.743 + 105 82 187 Pb 186 983910.836 + 104 83 187 Bi 186 993147.276 + 103 84 187 Po 187 003036.624 + 115 73 188 Ta 187 963916.000 + 114 74 188 W 187 958488.395 + 113 75 188 Re 187 958113.728 + 112 76 188 Os 187 955837.361 + 111 77 188 Ir 187 958835.046 + 110 78 188 Pt 187 959397.560 + 109 79 188 Au 187 965247.969 + 108 80 188 Hg 187 967576.910 + 107 81 188 Tl 187 976020.886 + 106 82 188 Pb 187 980874.592 + 105 83 188 Bi 187 992276.184 + 104 84 188 Po 187 999415.655 + 115 74 189 W 188 961763.000 + 114 75 189 Re 188 959227.817 + 113 76 189 Os 188 958146.005 + 112 77 189 Ir 188 958722.669 + 111 78 189 Pt 188 960848.542 + 110 79 189 Au 188 963948.286 + 109 80 189 Hg 188 968194.748 + 108 81 189 Tl 188 973573.527 + 107 82 189 Pb 188 980843.639 + 106 83 189 Bi 188 989195.141 + 105 84 189 Po 188 998473.415 + 116 74 190 W 189 963089.066 + 115 75 190 Re 189 961743.360 + 114 76 190 Os 189 958445.496 + 113 77 190 Ir 189 960543.445 + 112 78 190 Pt 189 959949.876 + 111 79 190 Au 189 964751.750 + 110 80 190 Hg 189 966322.169 + 109 81 190 Tl 189 973835.551 + 108 82 190 Pb 189 978081.828 + 107 83 190 Bi 189 988620.883 + 106 84 190 Po 189 995100.519 + 117 74 191 W 190 966531.000 + 116 75 191 Re 190 963123.437 + 115 76 191 Os 190 960928.159 + 114 77 191 Ir 190 960591.527 + 113 78 191 Pt 190 961676.363 + 112 79 191 Au 190 963716.455 + 111 80 191 Hg 190 967158.247 + 110 81 191 Tl 190 971784.096 + 109 82 191 Pb 190 978281.000 + 108 83 191 Bi 190 985786.975 + 107 84 191 Po 190 994558.488 + 106 85 191 At 191 004148.086 + 117 75 192 Re 191 966088.000 + 116 76 192 Os 191 961478.881 + 115 77 192 Ir 191 962602.485 + 114 78 192 Pt 191 961042.736 + 113 79 192 Au 191 964817.684 + 112 80 192 Hg 191 965634.182 + 111 81 192 Tl 191 972225.000 + 110 82 192 Pb 191 975785.115 + 109 83 192 Bi 191 985470.078 + 108 84 192 Po 191 991335.788 + 107 85 192 At 192 003141.034 + 118 75 193 Re 192 967545.000 + 117 76 193 Os 192 964149.753 + 116 77 193 Ir 192 962923.824 + 115 78 193 Pt 192 962984.616 + 114 79 193 Au 192 964138.447 + 113 80 193 Hg 192 966653.377 + 112 81 193 Tl 192 970501.997 + 111 82 193 Pb 192 976173.234 + 110 83 193 Bi 192 982947.223 + 109 84 193 Po 192 991062.403 + 108 85 193 At 192 999927.728 + 107 86 193 Rn 193 009707.964 + 118 76 194 Os 193 965179.477 + 117 77 194 Ir 193 965075.773 + 116 78 194 Pt 193 962683.527 + 115 79 194 Au 193 965419.062 + 114 80 194 Hg 193 965449.111 + 113 81 194 Tl 193 971081.411 + 112 82 194 Pb 193 974011.706 + 111 83 194 Bi 193 982792.362 + 110 84 194 Po 193 988186.015 + 109 85 194 At 193 999226.872 + 108 86 194 Rn 194 006144.424 + 119 76 195 Os 194 968318.000 + 118 77 195 Ir 194 965976.967 + 117 78 195 Pt 194 964794.353 + 116 79 195 Au 194 965037.851 + 115 80 195 Hg 194 966705.751 + 114 81 195 Tl 194 969774.096 + 113 82 195 Pb 194 974548.743 + 112 83 195 Bi 194 980648.762 + 111 84 195 Po 194 988130.617 + 110 85 195 At 194 996274.485 + 109 86 195 Rn 195 005421.699 + 120 76 196 Os 195 969643.277 + 119 77 196 Ir 195 968399.696 + 118 78 196 Pt 195 964954.675 + 117 79 196 Au 195 966571.221 + 116 80 196 Hg 195 965833.444 + 115 81 196 Tl 195 970481.192 + 114 82 196 Pb 195 972787.466 + 113 83 196 Bi 195 980666.509 + 112 84 196 Po 195 985536.094 + 111 85 196 At 195 995797.421 + 110 86 196 Rn 196 002115.945 + 120 77 197 Ir 196 969657.233 + 119 78 197 Pt 196 967343.053 + 118 79 197 Au 196 966570.114 + 117 80 197 Hg 196 967213.713 + 116 81 197 Tl 196 969573.986 + 115 82 197 Pb 196 973434.717 + 114 83 197 Bi 196 978864.929 + 113 84 197 Po 196 985659.607 + 112 85 197 At 196 993177.357 + 111 86 197 Rn 197 001621.430 + 110 87 197 Fr 197 011008.090 + 120 78 198 Pt 197 967896.734 + 119 79 198 Au 197 968243.724 + 118 80 198 Hg 197 966769.179 + 117 81 198 Tl 197 970446.673 + 116 82 198 Pb 197 972015.397 + 115 83 198 Bi 197 979206.000 + 114 84 198 Po 197 983388.672 + 113 85 198 At 197 992791.673 + 112 86 198 Rn 197 998679.156 + 111 87 198 Fr 198 010278.138 + 122 77 199 Ir 198 973807.115 + 121 78 199 Pt 198 970597.038 + 120 79 199 Au 198 968766.582 + 119 80 199 Hg 198 968280.989 + 118 81 199 Tl 198 969877.000 + 117 82 199 Pb 198 972912.542 + 116 83 199 Bi 198 977672.893 + 115 84 199 Po 198 983673.021 + 114 85 199 At 198 990527.719 + 113 86 199 Rn 198 998390.273 + 112 87 199 Fr 199 007269.389 + 122 78 200 Pt 199 971444.625 + 121 79 200 Au 199 970756.556 + 120 80 200 Hg 199 968326.934 + 119 81 200 Tl 199 970963.602 + 118 82 200 Pb 199 971818.332 + 117 83 200 Bi 199 978131.093 + 116 84 200 Po 199 981812.270 + 115 85 200 At 199 990351.100 + 114 86 200 Rn 199 995700.707 + 113 87 200 Fr 200 006583.507 + 123 78 201 Pt 200 974513.293 + 122 79 201 Au 200 971657.665 + 121 80 201 Hg 200 970303.038 + 120 81 201 Tl 200 970820.168 + 119 82 201 Pb 200 972870.425 + 118 83 201 Bi 200 977008.512 + 117 84 201 Po 200 982263.777 + 116 85 201 At 200 988417.061 + 115 86 201 Rn 200 995628.179 + 114 87 201 Fr 201 003852.496 + 113 88 201 Ra 201 012814.683 + 124 78 202 Pt 201 975639.000 + 123 79 202 Au 201 973856.000 + 122 80 202 Hg 201 970643.585 + 121 81 202 Tl 201 972109.089 + 120 82 202 Pb 201 972151.604 + 119 83 202 Bi 201 977733.100 + 118 84 202 Po 201 980738.881 + 117 85 202 At 201 988630.380 + 116 86 202 Rn 201 993263.902 + 115 87 202 Fr 202 003323.946 + 114 88 202 Ra 202 009742.264 + 124 79 203 Au 202 975154.498 + 123 80 203 Hg 202 972872.326 + 122 81 203 Tl 202 972344.022 + 121 82 203 Pb 202 973390.535 + 120 83 203 Bi 202 976892.145 + 119 84 203 Po 202 981415.995 + 118 85 203 At 202 986942.957 + 117 86 203 Rn 202 993393.732 + 116 87 203 Fr 203 000940.872 + 115 88 203 Ra 203 009298.745 + 124 80 204 Hg 203 973494.037 + 123 81 204 Tl 203 973863.337 + 122 82 204 Pb 203 973043.420 + 121 83 204 Bi 203 977835.717 + 120 84 204 Po 203 980309.863 + 119 85 204 At 203 987251.197 + 118 86 204 Rn 203 991443.644 + 117 87 204 Fr 204 000651.974 + 116 88 204 Ra 204 006502.228 + 125 80 205 Hg 204 976073.125 + 124 81 205 Tl 204 974427.237 + 123 82 205 Pb 204 974481.597 + 122 83 205 Bi 204 977386.323 + 121 84 205 Po 204 981190.004 + 120 85 205 At 204 986074.041 + 119 86 205 Rn 204 991723.204 + 118 87 205 Fr 204 998593.858 + 117 88 205 Ra 205 006268.415 + 116 89 205 Ac 205 015144.158 + 126 80 206 Hg 205 977513.756 + 125 81 206 Tl 205 976110.026 + 124 82 206 Pb 205 974465.124 + 123 83 206 Bi 205 978498.757 + 122 84 206 Po 205 980473.654 + 121 85 206 At 205 986656.148 + 120 86 206 Rn 205 990195.358 + 119 87 206 Fr 205 998666.211 + 118 88 206 Ra 206 003827.763 + 117 89 206 Ac 206 014470.787 + 127 80 207 Hg 206 982300.000 + 126 81 207 Tl 206 977418.586 + 125 82 207 Pb 206 975896.735 + 124 83 207 Bi 206 978470.471 + 123 84 207 Po 206 981593.252 + 122 85 207 At 206 985799.783 + 121 86 207 Rn 206 990730.200 + 120 87 207 Fr 206 996946.474 + 119 88 207 Ra 207 003805.161 + 118 89 207 Ac 207 011965.973 + 128 80 208 Hg 207 985759.000 + 127 81 208 Tl 207 982017.992 + 126 82 208 Pb 207 976651.918 + 125 83 208 Bi 207 979741.981 + 124 84 208 Po 207 981245.616 + 123 85 208 At 207 986613.042 + 122 86 208 Rn 207 989634.295 + 121 87 208 Fr 207 997138.018 + 120 88 208 Ra 208 001854.929 + 119 89 208 Ac 208 011544.073 + 118 90 208 Th 208 017910.722 + 128 81 209 Tl 208 985351.750 + 127 82 209 Pb 208 981089.898 + 126 83 209 Bi 208 980398.519 + 125 84 209 Po 208 982430.276 + 124 85 209 At 208 986169.944 + 123 86 209 Rn 208 990401.388 + 122 87 209 Fr 208 995953.197 + 121 88 209 Ra 209 001994.879 + 120 89 209 Ac 209 009494.220 + 129 81 210 Tl 209 990072.970 + 128 82 210 Pb 209 984188.301 + 127 83 210 Bi 209 984120.156 + 126 84 210 Po 209 982873.601 + 125 85 210 At 209 987147.338 + 124 86 210 Rn 209 989688.854 + 123 87 210 Fr 209 996421.657 + 122 88 210 Ra 210 000475.356 + 121 89 210 Ac 210 009436.130 + 120 90 210 Th 210 015093.437 + 130 81 211 Tl 210 993475.000 + 129 82 211 Pb 210 988735.356 + 128 83 211 Bi 210 987268.698 + 127 84 211 Po 210 986653.085 + 126 85 211 At 210 987496.147 + 125 86 211 Rn 210 990600.686 + 124 87 211 Fr 210 995555.259 + 123 88 211 Ra 211 000893.213 + 122 89 211 Ac 211 007731.894 + 121 90 211 Th 211 014933.183 + 130 82 212 Pb 211 991895.975 + 129 83 212 Bi 211 991285.016 + 128 84 212 Po 211 988867.896 + 127 85 212 At 211 990737.223 + 126 86 212 Rn 211 990703.528 + 125 87 212 Fr 211 996225.453 + 124 88 212 Ra 211 999786.399 + 123 89 212 Ac 212 007812.501 + 122 90 212 Th 212 013001.487 + 121 91 212 Pa 212 023181.425 + 132 81 213 Tl 213 001915.000 + 131 82 213 Pb 212 996560.867 + 130 83 213 Bi 212 994383.608 + 129 84 213 Po 212 992857.083 + 128 85 213 At 212 992936.514 + 127 86 213 Rn 212 993885.064 + 126 87 213 Fr 212 996185.861 + 125 88 213 Ra 213 000370.970 + 124 89 213 Ac 213 006607.333 + 123 90 213 Th 213 013011.447 + 122 91 213 Pa 213 021108.697 + 132 82 214 Pb 213 999803.788 + 131 83 214 Bi 213 998710.938 + 130 84 214 Po 213 995201.208 + 129 85 214 At 213 996371.601 + 128 86 214 Rn 213 995362.566 + 127 87 214 Fr 213 998970.785 + 126 88 214 Ra 214 000099.554 + 125 89 214 Ac 214 006917.762 + 124 90 214 Th 214 011481.431 + 123 91 214 Pa 214 020918.561 + 133 82 215 Pb 215 004661.590 + 132 83 215 Bi 215 001749.149 + 131 84 215 Po 214 999418.454 + 130 85 215 At 214 998651.890 + 129 86 215 Rn 214 998745.498 + 128 87 215 Fr 215 000341.456 + 127 88 215 Ra 215 002720.080 + 126 89 215 Ac 215 006474.132 + 125 90 215 Th 215 011724.805 + 124 91 215 Pa 215 019177.728 + 123 92 215 U 215 026756.035 + 133 83 216 Bi 216 006305.989 + 132 84 216 Po 216 001913.506 + 131 85 216 At 216 002422.631 + 130 86 216 Rn 216 000271.464 + 129 87 216 Fr 216 003189.445 + 128 88 216 Ra 216 003533.117 + 127 89 216 Ac 216 008743.367 + 126 90 216 Th 216 011055.714 + 125 91 216 Pa 216 019108.242 + 124 92 216 U 216 024762.747 + 134 83 217 Bi 217 009372.000 + 133 84 217 Po 217 006316.216 + 132 85 217 At 217 004717.835 + 131 86 217 Rn 217 003927.562 + 130 87 217 Fr 217 004631.902 + 129 88 217 Ra 217 006322.806 + 128 89 217 Ac 217 009343.777 + 127 90 217 Th 217 013103.444 + 126 91 217 Pa 217 018323.692 + 135 83 218 Bi 218 014188.000 + 134 84 218 Po 218 008971.502 + 133 85 218 At 218 008693.735 + 132 86 218 Rn 218 005601.052 + 131 87 218 Fr 218 007578.274 + 130 88 218 Ra 218 007140.325 + 129 89 218 Ac 218 011641.093 + 128 90 218 Th 218 013276.242 + 127 91 218 Pa 218 020057.853 + 126 92 218 U 218 023504.829 + 135 84 219 Po 219 013614.000 + 134 85 219 At 219 011160.647 + 133 86 219 Rn 219 009478.753 + 132 87 219 Fr 219 009251.553 + 131 88 219 Ra 219 010085.176 + 130 89 219 Ac 219 012420.348 + 129 90 219 Th 219 015535.677 + 128 91 219 Pa 219 019903.650 + 127 92 219 U 219 024999.161 + 126 93 219 Np 219 031623.021 + 136 84 220 Po 220 016386.000 + 135 85 220 At 220 015433.000 + 134 86 220 Rn 220 011392.534 + 133 87 220 Fr 220 012326.778 + 132 88 220 Ra 220 011025.562 + 131 89 220 Ac 220 014754.450 + 130 90 220 Th 220 015747.926 + 137 84 221 Po 221 021228.000 + 136 85 221 At 221 018017.000 + 135 86 221 Rn 221 015535.709 + 134 87 221 Fr 221 014253.757 + 133 88 221 Ra 221 013917.224 + 132 89 221 Ac 221 015591.199 + 131 90 221 Th 221 018186.236 + 130 91 221 Pa 221 021874.846 + 129 92 221 U 221 026323.299 + 138 84 222 Po 222 024140.000 + 137 85 222 At 222 022494.000 + 136 86 222 Rn 222 017576.286 + 135 87 222 Fr 222 017582.620 + 134 88 222 Ra 222 015373.355 + 133 89 222 Ac 222 017843.887 + 132 90 222 Th 222 018468.300 + 130 92 222 U 222 026057.953 + 138 85 223 At 223 025151.000 + 137 86 223 Rn 223 021889.285 + 136 87 223 Fr 223 019734.313 + 135 88 223 Ra 223 018500.719 + 134 89 223 Ac 223 019136.872 + 133 90 223 Th 223 020811.546 + 132 91 223 Pa 223 023962.232 + 131 92 223 U 223 027737.168 + 139 85 224 At 224 029749.000 + 138 86 224 Rn 224 024095.804 + 137 87 224 Fr 224 023348.100 + 136 88 224 Ra 224 020210.453 + 135 89 224 Ac 224 021722.239 + 134 90 224 Th 224 021464.157 + 133 91 224 Pa 224 025617.210 + 132 92 224 U 224 027613.974 + 139 86 225 Rn 225 028485.574 + 138 87 225 Fr 225 025572.478 + 137 88 225 Ra 225 023610.574 + 136 89 225 Ac 225 023228.647 + 135 90 225 Th 225 023950.907 + 134 91 225 Pa 225 026130.844 + 133 92 225 U 225 029393.555 + 132 93 225 Np 225 033910.797 + 140 86 226 Rn 226 030861.382 + 139 87 226 Fr 226 029544.515 + 138 88 226 Ra 226 025408.455 + 137 89 226 Ac 226 026097.069 + 136 90 226 Th 226 024903.686 + 135 91 226 Pa 226 027947.872 + 134 92 226 U 226 029338.749 + 141 86 227 Rn 227 035304.396 + 140 87 227 Fr 227 031865.417 + 139 88 227 Ra 227 029176.474 + 138 89 227 Ac 227 027750.666 + 137 90 227 Th 227 027702.618 + 136 91 227 Pa 227 028804.477 + 135 92 227 U 227 031181.587 + 134 93 227 Np 227 034956.832 + 142 86 228 Rn 228 037835.418 + 141 87 228 Fr 228 035839.437 + 140 88 228 Ra 228 031068.657 + 139 89 228 Ac 228 031019.767 + 138 90 228 Th 228 028739.835 + 137 91 228 Pa 228 031050.748 + 136 92 228 U 228 031371.351 + 135 93 228 Np 228 036066.462 + 134 94 228 Pu 228 038741.387 + 143 86 229 Rn 229 042257.276 + 142 87 229 Fr 229 038291.455 + 141 88 229 Ra 229 034956.707 + 140 89 229 Ac 229 032947.000 + 139 90 229 Th 229 031761.431 + 138 91 229 Pa 229 032095.652 + 137 92 229 U 229 033505.909 + 136 93 229 Np 229 036263.974 + 135 94 229 Pu 229 040145.819 + 134 95 229 Am 229 045249.909 + 143 87 230 Fr 230 042390.791 + 142 88 230 Ra 230 037054.780 + 141 89 230 Ac 230 036327.000 + 140 90 230 Th 230 033132.358 + 139 91 230 Pa 230 034539.789 + 138 92 230 U 230 033940.102 + 137 93 230 Np 230 037827.716 + 136 94 230 Pu 230 039650.703 + 144 87 231 Fr 231 045175.357 + 143 88 231 Ra 231 041027.086 + 142 89 231 Ac 231 038393.000 + 141 90 231 Th 231 036302.853 + 140 91 231 Pa 231 035882.575 + 139 92 231 U 231 036292.252 + 138 93 231 Np 231 038244.490 + 137 94 231 Pu 231 041126.410 + 145 87 232 Fr 232 049461.224 + 144 88 232 Ra 232 043475.270 + 143 89 232 Ac 232 042034.000 + 142 90 232 Th 232 038053.689 + 141 91 232 Pa 232 038590.300 + 140 92 232 U 232 037154.860 + 138 94 232 Pu 232 041184.526 + 146 87 233 Fr 233 052517.838 + 145 88 233 Ra 233 047594.573 + 144 89 233 Ac 233 044346.000 + 143 90 233 Th 233 041580.208 + 142 91 233 Pa 233 040246.605 + 141 92 233 U 233 039634.367 + 140 93 233 Np 233 040739.489 + 139 94 233 Pu 233 042997.345 + 137 96 233 Cm 233 050772.206 + 146 88 234 Ra 234 050382.104 + 145 89 234 Ac 234 048139.000 + 144 90 234 Th 234 043599.860 + 143 91 234 Pa 234 043305.615 + 142 92 234 U 234 040950.370 + 141 93 234 Np 234 042893.320 + 140 94 234 Pu 234 043317.478 + 138 96 234 Cm 234 050160.959 + 146 89 235 Ac 235 050840.000 + 145 90 235 Th 235 047255.000 + 144 91 235 Pa 235 045399.000 + 143 92 235 U 235 043928.190 + 142 93 235 Np 235 044061.591 + 141 94 235 Pu 235 045284.682 + 140 95 235 Am 235 047907.371 + 147 89 236 Ac 236 054988.000 + 146 90 236 Th 236 049657.000 + 145 91 236 Pa 236 048668.000 + 144 92 236 U 236 045566.201 + 143 93 236 Np 236 046568.392 + 142 94 236 Pu 236 046056.756 + 140 96 236 Cm 236 051374.506 + 147 90 237 Th 237 053629.000 + 146 91 237 Pa 237 051023.000 + 145 92 237 U 237 048728.380 + 144 93 237 Np 237 048171.710 + 143 94 237 Pu 237 048407.957 + 141 96 237 Cm 237 052868.923 + 139 98 237 Cf 237 062199.993 + 147 91 238 Pa 238 054637.000 + 146 92 238 U 238 050786.996 + 145 93 238 Np 238 050944.671 + 144 94 238 Pu 238 049558.250 + 143 95 238 Am 238 051982.607 + 142 96 238 Cm 238 053081.595 + 147 92 239 U 239 054292.048 + 146 93 239 Np 239 052937.599 + 145 94 239 Pu 239 052161.669 + 144 95 239 Am 239 053022.803 + 143 96 239 Cm 239 054908.593 + 148 92 240 U 240 056592.425 + 147 93 240 Np 240 056163.830 + 146 94 240 Pu 240 053811.812 + 145 95 240 Am 240 055298.444 + 144 96 240 Cm 240 055528.329 + 142 98 240 Cf 240 062255.842 + 148 93 241 Np 241 058250.697 + 147 94 241 Pu 241 056849.722 + 146 95 241 Am 241 056827.413 + 145 96 241 Cm 241 057651.288 + 149 93 242 Np 242 061639.615 + 148 94 242 Pu 242 058741.045 + 147 95 242 Am 242 059547.428 + 146 96 242 Cm 242 058834.263 + 144 98 242 Cf 242 063754.533 + 149 94 243 Pu 243 062002.119 + 148 95 243 Am 243 061379.940 + 147 96 243 Cm 243 061387.403 + 146 97 243 Bk 243 063005.980 + 150 94 244 Pu 244 064204.415 + 149 95 244 Am 244 064282.964 + 148 96 244 Cm 244 062750.694 + 147 97 244 Bk 244 065179.039 + 146 98 244 Cf 244 065999.543 + 151 94 245 Pu 245 067824.568 + 150 95 245 Am 245 066452.890 + 149 96 245 Cm 245 065491.113 + 148 97 245 Bk 245 066359.885 + 147 98 245 Cf 245 068046.825 + 152 94 246 Pu 246 070204.209 + 150 96 246 Cm 246 067222.082 + 149 97 246 Bk 246 068671.367 + 148 98 246 Cf 246 068803.762 + 146 100 246 Fm 246 075350.815 + 151 96 247 Cm 247 070352.726 + 150 97 247 Bk 247 070305.940 + 149 98 247 Cf 247 070965.462 + 148 99 247 Es 247 073621.932 + 152 96 248 Cm 248 072349.101 + 150 98 248 Cf 248 072182.978 + 148 100 248 Fm 248 077185.528 + 153 96 249 Cm 249 075954.006 + 152 97 249 Bk 249 074983.182 + 151 98 249 Cf 249 074850.491 + 149 100 249 Fm 249 078926.098 + 154 96 250 Cm 250 078357.556 + 153 97 250 Bk 250 078315.027 + 152 98 250 Cf 250 076404.561 + 150 100 250 Fm 250 079519.828 + 155 96 251 Cm 251 082285.036 + 154 97 251 Bk 251 080760.603 + 153 98 251 Cf 251 079587.219 + 152 99 251 Es 251 079992.224 + 151 100 251 Fm 251 081539.889 + 150 101 251 Md 251 084774.291 + 154 98 252 Cf 252 081626.523 + 153 99 252 Es 252 082979.189 + 152 100 252 Fm 252 082464.972 + 150 102 252 No 252 088966.141 + 155 98 253 Cf 253 085133.738 + 154 99 253 Es 253 084821.305 + 153 100 253 Fm 253 085181.160 + 151 102 253 No 253 090562.831 + 156 98 254 Cf 254 087323.590 + 155 99 254 Es 254 088020.527 + 154 100 254 Fm 254 086852.726 + 152 102 254 No 254 090954.259 + 156 99 255 Es 255 090273.553 + 155 100 255 Fm 255 089962.633 + 154 101 255 Md 255 091082.787 + 153 102 255 No 255 093191.404 + 152 103 255 Lr 255 096562.404 + 156 100 256 Fm 256 091773.878 + 154 102 256 No 256 094280.866 + 153 103 256 Lr 256 098494.029 + 152 104 256 Rf 256 101151.535 + 157 100 257 Fm 257 095105.317 + 156 101 257 Md 257 095537.977 + 155 102 257 No 257 096884.419 + 153 104 257 Rf 257 102916.848 + 157 101 258 Md 258 098429.825 + 154 104 258 Rf 258 103426.362 + 157 102 259 No 259 100997.503 + 154 105 259 Db 259 109491.865 + 154 106 260 Sg 260 114383.508 + 157 104 261 Rf 261 108769.990 + 155 106 261 Sg 261 115948.188 + 156 106 262 Sg 262 116335.446 + 156 108 264 Hs 264 128356.405 + 157 108 265 Hs 265 129791.799 + 158 108 266 Hs 266 130045.252 + 159 110 269 Ds 269 144751.021 + 160 110 270 Ds 270 144583.090 \ No newline at end of file diff --git a/SpecProject/Assets/fonts/LICENSE_FontAwesome.txt b/SpecProject/Assets/fonts/LICENSE_FontAwesome.txt new file mode 100644 index 0000000..d46aa01 --- /dev/null +++ b/SpecProject/Assets/fonts/LICENSE_FontAwesome.txt @@ -0,0 +1,34 @@ +Font Awesome Free License +------------------------- + +Font Awesome Free is free, open source, and GPL friendly. You can use it for +commercial projects, open source projects, or really almost whatever you want. +Full Font Awesome Free license: https://fontawesome.com/license/free. + +# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) +In the Font Awesome Free download, the CC BY 4.0 license applies to all icons +packaged as SVG and JS file types. + +# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) +In the Font Awesome Free download, the SIL OFL license applies to all icons +packaged as web and desktop font files. + +# Code: MIT License (https://opensource.org/licenses/MIT) +In the Font Awesome Free download, the MIT license applies to all non-font and +non-icon files. + +# Attribution +Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font +Awesome Free files already contain embedded comments with sufficient +attribution, so you shouldn't need to do anything additional when using these +files normally. + +We've kept attribution comments terse, so we ask that you do not actively work +to remove them from files, especially code. They're a great way for folks to +learn about Font Awesome. + +# Brand Icons +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** \ No newline at end of file diff --git a/SpecProject/Assets/fonts/LICENSE_Roboto.txt b/SpecProject/Assets/fonts/LICENSE_Roboto.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/SpecProject/Assets/fonts/LICENSE_Roboto.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/SpecProject/Assets/fonts/Roboto-Regular.ttf b/SpecProject/Assets/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..3d6861b Binary files /dev/null and b/SpecProject/Assets/fonts/Roboto-Regular.ttf differ diff --git a/SpecProject/Assets/fonts/fa-regular-400.ttf b/SpecProject/Assets/fonts/fa-regular-400.ttf new file mode 100644 index 0000000..6be32ef Binary files /dev/null and b/SpecProject/Assets/fonts/fa-regular-400.ttf differ diff --git a/SpecProject/Assets/fonts/fa-solid-900.ttf b/SpecProject/Assets/fonts/fa-solid-900.ttf new file mode 100644 index 0000000..4dd1199 Binary files /dev/null and b/SpecProject/Assets/fonts/fa-solid-900.ttf differ diff --git a/SpecProject/src/MassMap.cpp b/SpecProject/src/MassMap.cpp new file mode 100644 index 0000000..af18581 --- /dev/null +++ b/SpecProject/src/MassMap.cpp @@ -0,0 +1,69 @@ +/* + MassMap.h + A represnetation of the AMDC mass table. We provide capability to retrieve the mass of an isotope + as well as the isotopic symbol. This sort of code is pretty ubiquitous in flexible nuclear physics + analysis. + + GWM -- Feb 2022 +*/ +#include "MassMap.h" + +/* + Read in AMDC mass file, preformated to remove excess info. Here assumes that by default + the file is in a local directory Resources. Navigator build process handles this. +*/ +MassMap::MassMap() +{ + SPEC_PROFILE_FUNCTION(); + std::ifstream massfile("Assets/amdc2016_mass.txt"); + if (massfile.is_open()) + { + std::string junk, A, element; + int Z; + double atomicMassBig, atomicMassSmall, isotopicMass; + getline(massfile, junk); + getline(massfile, junk); + while (massfile >> junk) + { + massfile >> Z >> A >> element >> atomicMassBig >> atomicMassSmall; + isotopicMass = (atomicMassBig + atomicMassSmall * 1e-6 - Z * electron_mass) * u_to_mev; + std::string key = "(" + std::to_string(Z) + "," + A + ")"; + massTable[key] = isotopicMass; + elementTable[Z] = element; + } + } + else + { + SPEC_ERROR("Unable to load mass file! Make sure that the Resources folder exists!"); + } +} + +MassMap::~MassMap() {} + +//Returns nuclear mass in MeV +double MassMap::FindMass(int Z, int A) +{ + SPEC_PROFILE_FUNCTION(); + std::string key = "(" + std::to_string(Z) + "," + std::to_string(A) + ")"; + auto data = massTable.find(key); + if (data == massTable.end()) + { + SPEC_ERROR("Invalid nucleus at MassMap::FindMass()! Z: {0} A: {1}", Z, A); + return 0.0; + } + return data->second; +} + +//returns element symbol +std::string MassMap::FindSymbol(int Z, int A) +{ + SPEC_PROFILE_FUNCTION(); + auto data = elementTable.find(Z); + if (data == elementTable.end()) + { + SPEC_ERROR("Invalid nucleus at MassMap::FindSymbol()! Z: {0} A: {1}", Z, A); + return ""; + } + std::string fullsymbol = std::to_string(A) + data->second; + return fullsymbol; +} \ No newline at end of file diff --git a/SpecProject/src/MassMap.h b/SpecProject/src/MassMap.h new file mode 100644 index 0000000..398a378 --- /dev/null +++ b/SpecProject/src/MassMap.h @@ -0,0 +1,31 @@ +/* + MassMap.h + A represnetation of the AMDC mass table. We provide capability to retrieve the mass of an isotope + as well as the isotopic symbol. This sort of code is pretty ubiquitous in flexible nuclear physics + analysis. + + GWM -- Feb 2022 +*/ +#ifndef MASS_MAP_H +#define MASS_MAP_H + +#include "Specter.h" + +class MassMap +{ +public: + MassMap(); + ~MassMap(); + double FindMass(int Z, int A); + std::string FindSymbol(int Z, int A); + +private: + std::unordered_map massTable; + std::unordered_map elementTable; + + //constants + static constexpr double u_to_mev = 931.4940954; + static constexpr double electron_mass = 0.000548579909; +}; + +#endif diff --git a/SpecProject/src/SPSAnalysisStage.cpp b/SpecProject/src/SPSAnalysisStage.cpp new file mode 100644 index 0000000..dff1576 --- /dev/null +++ b/SpecProject/src/SPSAnalysisStage.cpp @@ -0,0 +1,135 @@ +/* + SPSAnalysisStage.cpp + Example of a user AnalysisStage. This one is based around the SE-SPS detector system in FoxLab at FSU. + + GWM -- Feb 2022 +*/ +#include "SPSAnalysisStage.h" + +namespace Specter { + + //Construct each NavParameter with their unique name. Then bind them to the SpectrumManager. + SPSAnalysisStage::SPSAnalysisStage() : + AnalysisStage("SPSAnalysis"), delayFLTime("delayFLTime"), delayFRTime("delayFRTime"), delayBLTime("delayBLTime"), delayBRTime("delayBRTime"), x1("x1"), x2("x2"), xavg("xavg"), + scintLeft("scintLeft"), anodeBack("anodeBack"), anodeFront("anodeFront"), cathode("cathode"), xavg_sabreCoinc("xavg_sabreCoinc"), x1_weight("x1_weight"), x2_weight("x2_weight") + { + SPEC_PROFILE_FUNCTION(); + SpectrumManager& manager = SpectrumManager::GetInstance(); + manager.BindParameter(delayFLTime); + manager.BindParameter(delayFRTime); + manager.BindParameter(delayBLTime); + manager.BindParameter(delayBRTime); + //Bind parameters with some default histograms. Saves us the effort of making them in the UI. + manager.BindParameter(x1, 600, -300.0, 300.0); + manager.BindParameter(x2, 600, -300.0, 300.0); + manager.BindParameter(xavg, 600, -300.0, 300.0); + manager.BindParameter(scintLeft, 4096, 0.0, 4096.0); + manager.BindParameter(anodeBack, 4096, 0.0, 4096.0); + manager.BindParameter(anodeFront, 4096, 0.0, 4096.0); + manager.BindParameter(cathode, 4096, 0.0, 4096); + manager.BindParameter(xavg_sabreCoinc, 600, -300.0, 300.0); + + std::vector sabre_list; + for (int i = 0; i < 127; i++) + { + sabre_list.push_back("sabre_" + std::to_string(i)); + sabre.emplace_back(sabre_list[i]); + manager.BindParameter(sabre[i]); + } + + //If you want to make a histogram default available, you can add one like this. + manager.AddHistogramSummary(HistogramArgs("sabre_summary", "", 512, 0.0, 16384), sabre_list); + //Note that if you save a config, the config rep of this histogram will supercede this version. + + manager.BindVariable(x1_weight); + manager.BindVariable(x2_weight); + } + + SPSAnalysisStage::~SPSAnalysisStage() {} + + //Do some physics! + void SPSAnalysisStage::AnalyzePhysicsEvent(const SpecEvent& event) + { + + SPEC_PROFILE_FUNCTION(); + //Most analysis stages will start kinda like this. Take the raw event data and + //put it into NavParameters using the hit id. Switches are perfect for this. Can also + //create mapping classes to use text-file-based id association (commonly called channel maps). + bool sabreFlag = false; + for(auto& hit : event) + { + if (hit.id < 127) + { + sabreFlag = true; + if (hit.longEnergy > sabre[hit.id].GetValue()) + sabre[hit.id].SetValue(hit.longEnergy); + } + switch (hit.id) + { + case 129: + { + if (hit.longEnergy > scintLeft.GetValue()) + scintLeft.SetValue(hit.longEnergy); + break; + } + case 135: + { + if (hit.longEnergy > cathode.GetValue()) + cathode.SetValue(hit.longEnergy); + break; + } + case 136: + { + if (!delayFLTime.IsValid()) + delayFLTime.SetValue(hit.timestamp / 1.0e3); + break; + } + case 137: + { + if (!delayFRTime.IsValid()) + delayFRTime.SetValue(hit.timestamp / 1.0e3); + break; + } + case 138: + { + if (!delayBLTime.IsValid()) + delayBLTime.SetValue(hit.timestamp / 1.0e3); + break; + } + case 139: + { + if (!delayBRTime.IsValid()) + delayBRTime.SetValue(hit.timestamp / 1.0e3); + break; + } + case 141: + { + if (hit.longEnergy > anodeFront.GetValue()) + anodeFront.SetValue(hit.longEnergy); + break; + } + case 143: + { + if (hit.longEnergy > anodeBack.GetValue()) + anodeBack.SetValue(hit.longEnergy); + break; + } + } + } + + //If you want to use parameters to calculate another parameter, you + //need to check that the parameter is valid (set in this event)! + if(delayFLTime.IsValid() && delayFRTime.IsValid()) + x1.SetValue((delayFLTime.GetValue() - delayFRTime.GetValue())*0.5*0.4762); + + if(delayBLTime.IsValid() && delayBRTime.IsValid()) + x2.SetValue((delayBLTime.GetValue() - delayBRTime.GetValue())*0.5*0.5051); + + if (x1.IsValid() && x2.IsValid()) + { + xavg.SetValue(x1_weight.GetValue() * x1.GetValue() + x2_weight.GetValue() * x2.GetValue()); + if (sabreFlag) + xavg_sabreCoinc.SetValue(xavg.GetValue()); + } + } +} \ No newline at end of file diff --git a/SpecProject/src/SPSAnalysisStage.h b/SpecProject/src/SPSAnalysisStage.h new file mode 100644 index 0000000..0113b06 --- /dev/null +++ b/SpecProject/src/SPSAnalysisStage.h @@ -0,0 +1,41 @@ +/* + SPSAnalysisStage.h + Example of a user AnalysisStage. This one is based around the SE-SPS detector system in FoxLab at FSU. + + GWM -- Feb 2022 +*/ +#include "Specter.h" + +namespace Specter { + + class SPSAnalysisStage : public AnalysisStage + { + public: + SPSAnalysisStage(); + virtual ~SPSAnalysisStage(); + + virtual void AnalyzePhysicsEvent(const SpecEvent& event) override; + + private: + //Create a bunch of parameters + Parameter delayFLTime; + Parameter delayFRTime; + Parameter delayBLTime; + Parameter delayBRTime; + Parameter x1; + Parameter x2; + Parameter xavg; + Parameter scintLeft; + Parameter anodeBack; + Parameter anodeFront; + Parameter cathode; + Parameter xavg_sabreCoinc; + + std::vector sabre; + + //Create a few variables + Variable x1_weight; + Variable x2_weight; + }; + +} \ No newline at end of file diff --git a/SpecProject/src/SPSInputLayer.cpp b/SpecProject/src/SPSInputLayer.cpp new file mode 100644 index 0000000..335e6c9 --- /dev/null +++ b/SpecProject/src/SPSInputLayer.cpp @@ -0,0 +1,140 @@ +/* + SPSInputLayer.cpp + An example of what a user created layer might look like. This is how one would extend the base editor to have more + functionality, specific to their experiment/setup. In this case, we provide inputs for reaction information so that + the kinematic shift of the SE-SPS focal plane can be calculated, and weights for tracing particle trajectories are + produced for use in analysis (as NavVariables). + + A reminder that these layers should not be that intense. The more work that is shoved into the UI, the less responsive + and more sluggish overall the UI will become. The vast bulk of the analysis work should be left to the PhysicsLayer which has its own + thread to work upon. + + GWM -- Feb 2022 +*/ +#include "SPSInputLayer.h" +#include "imgui.h" + +namespace Specter { + + SPSInputLayer::SPSInputLayer() : + Layer("SPSInputLayer"), x1_weight("x1_weight"), x2_weight("x2_weight"), m_bfield(0.0), m_theta(0.0), m_beamKE(0.0), + m_rxnEqn("") + { + for (int i = 0; i < 2; i++) + { + m_targNums[i] = 0; + m_projNums[i] = 0; + m_ejectNums[i] = 0; + m_residNums[i] = 0; + } + + SpectrumManager& manager = SpectrumManager::GetInstance(); + manager.BindVariable(x1_weight); + manager.BindVariable(x2_weight); + } + + SPSInputLayer::~SPSInputLayer() {} + + void SPSInputLayer::OnAttach() {} + + void SPSInputLayer::OnDetach() {} + + void SPSInputLayer::OnUpdate(Timestep& step) {} + + void SPSInputLayer::OnEvent(Event& event) {} + + void SPSInputLayer::OnImGuiRender() + { + SPEC_PROFILE_FUNCTION(); + if (ImGui::Begin("SPS Input")) + { + //Create widgets for all of our inputs + ImGui::InputDouble("Bfield(kG)", &m_bfield, 0.01, 0.1); + ImGui::InputDouble("Theta(deg)", &m_theta, 0.1, 1.0); + ImGui::InputDouble("BeamKE(MeV)", &m_beamKE, 0.1, 1.0); + ImGui::InputInt2("Target Z,A", m_targNums); + ImGui::InputInt2("Projectile Z,A", m_projNums); + ImGui::InputInt2("Ejectile Z,A", m_ejectNums); + if (ImGui::Button("Set")) + { + //We dont want to calculate the weights every frame, so + //we lock that calculation behind a button. + UpdateWeights(); + } + //Display some info about the internal state + ImGui::Text("-------Current Settings-------"); + ImGui::Text("Reaction Equation: "); + ImGui::SameLine(); + ImGui::Text(m_rxnEqn.c_str()); + ImGui::Text("X1 Weight: %f", x1_weight.GetValue()); + ImGui::Text("X2 Weight: %f", x2_weight.GetValue()); + } + ImGui::End(); + } + + void SPSInputLayer::UpdateWeights() + { + SPEC_PROFILE_FUNCTION(); + m_rxnEqn = ""; //reset + + //Calculate residual nucleus from reaction + for (int i = 0; i < 2; i++) + m_residNums[i] = m_targNums[i] + m_projNums[i] - m_ejectNums[i]; + if (m_residNums[0] < 0 || m_residNums[1] <= 0) + { + SPEC_ERROR("Invalid residual nucleus at SPSInputLayer::UpdateMasses()! ZR: {0} AR: {1}", m_residNums[0], m_residNums[1]); + return; + } + + if (m_bfield == 0.0 || m_beamKE == 0.0) + { + SPEC_ERROR("Invaild kinematic settings at SPSInputLayer::UpdateWeights()! BeamKE: {0} Bfield: {1}", m_beamKE, m_bfield); + return; + } + + //Obtain masses from the AMDC table + double targMass = m_masses.FindMass(m_targNums[0], m_targNums[1]); + double projMass = m_masses.FindMass(m_projNums[0], m_projNums[1]); + double ejectMass = m_masses.FindMass(m_ejectNums[0], m_ejectNums[1]); + double residMass = m_masses.FindMass(m_residNums[0], m_residNums[1]); + if (targMass == 0.0 || projMass == 0.0 || ejectMass == 0.0 || residMass == 0.0) + return; + + std::string temp; + temp = m_masses.FindSymbol(m_targNums[0], m_targNums[1]); + m_rxnEqn += temp + "("; + temp = m_masses.FindSymbol(m_projNums[0], m_projNums[1]); + m_rxnEqn += temp + ","; + temp = m_masses.FindSymbol(m_ejectNums[0], m_ejectNums[1]); + m_rxnEqn += temp + ")"; + temp = m_masses.FindSymbol(m_residNums[0], m_residNums[1]); + m_rxnEqn += temp; + + double theta_rad = m_theta * c_deg2rad; //convert to radians + double bfield_t = m_bfield * 0.1; //convert to tesla + double Q = targMass + projMass - ejectMass - residMass; + //kinematics a la Iliadis p.590 + double term1 = std::sqrt(projMass * ejectMass * m_beamKE) / (ejectMass + residMass) * std::cos(theta_rad); + double term2 = (m_beamKE * (residMass - projMass) + residMass * Q) / (ejectMass + residMass); + + double ejectKE = term1 + std::sqrt(term1 * term1 + term2); + ejectKE *= ejectKE; + + //momentum + double ejectP = std::sqrt(ejectKE * (ejectKE + 2.0 * ejectMass)); + + //calculate rho from B a la B*rho = (proj. momentum)/(proj. charge) + double rho = (ejectP * c_mev2j) / (m_ejectNums[0] * c_e * c_C * bfield_t) * 100.0; //in cm + + double K; + K = sqrt(projMass * ejectMass * m_beamKE / ejectKE); + K *= std::sin(theta_rad); + + double denom = ejectMass + residMass - std::sqrt(projMass * ejectMass * m_beamKE / ejectKE) * std::cos(theta_rad); + + K /= denom; + double zshift = -1 * rho * c_spsDisp * c_spsMag * K; //delta-Z in cm + x1_weight.SetValue((0.5 - zshift / c_wireDist)); + x2_weight.SetValue((1.0 - x1_weight.GetValue())); + } +} diff --git a/SpecProject/src/SPSInputLayer.h b/SpecProject/src/SPSInputLayer.h new file mode 100644 index 0000000..16ae851 --- /dev/null +++ b/SpecProject/src/SPSInputLayer.h @@ -0,0 +1,68 @@ +/* + SPSInputLayer.h + An example of what a user created layer might look like. This is how one would extend the base editor to have more + functionality, specific to their experiment/setup. In this case, we provide inputs for reaction information so that + the kinematic shift of the SE-SPS focal plane can be calculated, and weights for tracing particle trajectories are + produced for use in analysis (as Variables). + + A reminder that these layers should not be that intense. The more work that is shoved into the UI, the less responsive + and more sluggish overall the UI will become. The vast bulk of the analysis work should be left to the PhysicsLayer which has its own + thread to work upon. + + GWM -- Feb 2022 +*/ +#ifndef SPS_INPUT_LAYER_H +#define SPS_INPUT_LAYER_H + +#include "Specter.h" +#include "MassMap.h" + +namespace Specter { + + class SPSInputLayer : public Layer + { + public: + SPSInputLayer(); + ~SPSInputLayer(); + + virtual void OnAttach() override; + virtual void OnDetach() override; + virtual void OnUpdate(Timestep& step) override; + virtual void OnEvent(Event& event) override; //If you want to respond to events + virtual void OnImGuiRender() override; //"Main" function + + private: + void UpdateWeights(); + + //Variables for use in analysis + Variable x1_weight; + Variable x2_weight; + + //UI facing inputs + double m_bfield; //kG + double m_theta; //deg + double m_beamKE; //MeV + //Z, A inputs for reaction nuclei + int m_targNums[2]; + int m_projNums[2]; + int m_ejectNums[2]; + int m_residNums[2]; + + //Text for UI + std::string m_rxnEqn; + + //Map for mass table + MassMap m_masses; + + static constexpr double c_mev2j = 1.60218E-13; //J per MeV + static constexpr double c_e = 1.602E-19; //unit charge Coulombs + static constexpr double c_C = 2.9979E8; //speed of light m/s + static constexpr double c_spsDisp = 1.96; //dispersion (x/rho) + static constexpr double c_spsMag = 0.39; //magnification in x + static constexpr double c_wireDist = 4.28625; //FPD anode wire separation in cm + static constexpr double c_deg2rad = 3.14159265358979323846 / 180.0; //pi/180 + }; + +} + +#endif \ No newline at end of file diff --git a/SpecProject/src/main.cpp b/SpecProject/src/main.cpp new file mode 100644 index 0000000..e32607b --- /dev/null +++ b/SpecProject/src/main.cpp @@ -0,0 +1,44 @@ +/* + main.cpp + Entry point for the example NavProject. Also contains example of a simple user Navigator::Application. + + GWM -- Feb 2022 +*/ +#include "Specter.h" +#include "SPSAnalysisStage.h" +#include "SPSInputLayer.h" + +//User application class. Pushes user analysis stages. +class SPSApp : public Specter::Application +{ +public: + SPSApp() : + Specter::Application() + { + PushLayer(new Specter::SPSInputLayer()); + //PushLayer(new Navigator::TestServerLayer()); + PushAnalysisStage(new Specter::SPSAnalysisStage()); + } +}; + +//Define the creation function to make our user application +Specter::Application* Specter::CreateApplication() { return new SPSApp(); } + +//Make sure to initialize log BEFORE creating application. +int main(int argc, const char** argv) +{ + Specter::Logger::Init(); + SPEC_TRACE("Logger Initialized!"); + + SPEC_PROFILE_BEGIN_SESSION("Startup", "navprofile_startup.json"); + auto app = Specter::CreateApplication(); + SPEC_PROFILE_END_SESSION(); + + SPEC_PROFILE_BEGIN_SESSION("Runtime", "navprofile_runtime.json"); + app->Run(); + SPEC_PROFILE_END_SESSION(); + + SPEC_PROFILE_BEGIN_SESSION("Shutdown", "navprofile_shutdown.json"); + delete app; + SPEC_PROFILE_END_SESSION(); +} \ No newline at end of file diff --git a/Specter/src/Platform/OpenGL/OpenGLContext.cpp b/Specter/src/Platform/OpenGL/OpenGLContext.cpp new file mode 100644 index 0000000..8263229 --- /dev/null +++ b/Specter/src/Platform/OpenGL/OpenGLContext.cpp @@ -0,0 +1,41 @@ +/* + OpenGLContext.h + Implementation of OpenGL rendering context. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details. + + GWM -- Feb 2022 +*/ +#include "OpenGLContext.h" + +#include "GLFW/glfw3.h" +#include "glad/glad.h" + +namespace Specter { + + OpenGLContext::OpenGLContext(GLFWwindow* window) : + m_windowHandle(window) + { + } + + OpenGLContext::~OpenGLContext() {} + + void OpenGLContext::Init() + { + SPEC_PROFILE_FUNCTION(); + glfwMakeContextCurrent(m_windowHandle); + + int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); + + //Report graphics status + SPEC_TRACE("Loaded OpenGL with glad Init status {0}", status); + + SPEC_INFO("Loaded OpenGL renderer"); + SPEC_INFO("Vendor: {0}", glGetString(GL_VENDOR)); + SPEC_INFO("Renderer: {0}", glGetString(GL_RENDERER)); + SPEC_INFO("Version: {0}", glGetString(GL_VERSION)); + } + + void OpenGLContext::SwapBuffers() + { + glfwSwapBuffers(m_windowHandle); + } +} \ No newline at end of file diff --git a/Specter/src/Platform/OpenGL/OpenGLContext.h b/Specter/src/Platform/OpenGL/OpenGLContext.h new file mode 100644 index 0000000..b2f1fd2 --- /dev/null +++ b/Specter/src/Platform/OpenGL/OpenGLContext.h @@ -0,0 +1,31 @@ +/* + OpenGLContext.h + Implementation of OpenGL rendering context. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details. + + GWM -- Feb 2022 +*/ +#ifndef OPEGL_CONTEXT_H +#define OPEGL_CONTEXT_H + +#include "Specter/Core/SpecCore.h" +#include "Specter/Renderer/GraphicsContext.h" + +struct GLFWwindow; + +namespace Specter { + + class OpenGLContext : public GraphicsContext + { + public: + OpenGLContext(GLFWwindow* window); + ~OpenGLContext(); + + virtual void Init() override; + virtual void SwapBuffers() override; + private: + GLFWwindow* m_windowHandle; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Platform/OpenGL/OpenGLRendererAPI.cpp b/Specter/src/Platform/OpenGL/OpenGLRendererAPI.cpp new file mode 100644 index 0000000..5211b4a --- /dev/null +++ b/Specter/src/Platform/OpenGL/OpenGLRendererAPI.cpp @@ -0,0 +1,29 @@ +/* + OpenGLRendererAPI.h + Implementation of OpenGL API in Specter context. Note here only a few things exist. We don't need to implement much ourselves, + ImGui handles a lot of the heavy lifting for us. Based entirely upon @TheCherno's work in his game engine series. + + GWM -- Feb 2022 +*/ +#include "OpenGLRendererAPI.h" + +#include "glad/glad.h" +#include "GLFW/glfw3.h" + +namespace Specter { + + void OpenGLRendererAPI::Clear() + { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + void OpenGLRendererAPI::SetClearColor(const glm::vec4& color_array) + { + glClearColor(color_array[0], color_array[1], color_array[2], color_array[3]); + } + + float OpenGLRendererAPI::GetFrameTime() + { + return (float)glfwGetTime(); + } +} \ No newline at end of file diff --git a/Specter/src/Platform/OpenGL/OpenGLRendererAPI.h b/Specter/src/Platform/OpenGL/OpenGLRendererAPI.h new file mode 100644 index 0000000..b5f93a7 --- /dev/null +++ b/Specter/src/Platform/OpenGL/OpenGLRendererAPI.h @@ -0,0 +1,26 @@ +/* + OpenGLRendererAPI.h + Implementation of OpenGL API in Specter context. Note here only a few things exist. We don't need to implement much ourselves, + ImGui handles a lot of the heavy lifting for us. Based entirely upon @TheCherno's work in his game engine series. + + GWM -- Feb 2022 +*/ +#ifndef OPENGL_RENDERER_API_H +#define OPENGL_RENDERER_API_H + +#include "Specter/Core/SpecCore.h" +#include "Specter/Renderer/RendererAPI.h" + +namespace Specter { + + class OpenGLRendererAPI : public RendererAPI + { + public: + virtual void Clear() override; + virtual void SetClearColor(const glm::vec4& color_array) override; + virtual float GetFrameTime() override; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Platform/OpenGL/OpenGLWindow.cpp b/Specter/src/Platform/OpenGL/OpenGLWindow.cpp new file mode 100644 index 0000000..b838f87 --- /dev/null +++ b/Specter/src/Platform/OpenGL/OpenGLWindow.cpp @@ -0,0 +1,180 @@ +/* + OpenGLWindow.h + Implementation of a window with OpenGL context. Not really OpenGL specific, other than in creation of GraphicsContext. + Bulk of creation can be used in any api/context (glfw compatible with Cocoa, X11, or Windows). Based entirely upon the + work of @TheCherno in his game engine series. + + GWM -- Feb 2022 +*/ +#include "OpenGLWindow.h" +#include "OpenGLContext.h" +#include "Specter/Core/SpecCore.h" +#include "Specter/Events/AppEvent.h" +#include "Specter/Events/KeyEvent.h" +#include "Specter/Events/MouseEvent.h" + +namespace Specter { + + static bool s_glfwInitialized = false; + static void GLFWErrorCallback(int error, const char* description) + { + SPEC_ERROR("GLFW Error Code {0}: {1}", error, description); + } + + Window* Window::Create(const WindowProperties& props) { return new OpenGLWindow(props); } + + OpenGLWindow::OpenGLWindow(const WindowProperties& props) + { + Init(props); + } + + OpenGLWindow::~OpenGLWindow() + { + Shutdown(); + } + + void OpenGLWindow::Init(const WindowProperties& props) + { + SPEC_PROFILE_FUNCTION(); + m_data.width = props.width; + m_data.height = props.height; + m_data.name = props.name; + + SPEC_INFO("Creating OpenGLWindow with height {0} width {1} and name {2}", m_data.height, m_data.width, m_data.name); + + if(!s_glfwInitialized) + { + int passed = glfwInit(); + SPEC_INFO("Initializing GLFW ... Returned value {0}", passed); + glfwSetErrorCallback(GLFWErrorCallback); + } + + //Apple specific. OpenGL is technically deprecated, so a little extra work to force the correct version +#ifdef __APPLE__ + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,true); +#endif + + m_window = glfwCreateWindow((int)m_data.width, (int)m_data.height, m_data.name.c_str(), nullptr, nullptr); + + m_context = new OpenGLContext(m_window); //This is the only seriously OpenGL specific code + m_context->Init(); + + glfwSetWindowUserPointer(m_window, &m_data); + SetVSync(true); + + //Set all of the callback functions for the window. + glfwSetWindowSizeCallback(m_window, [](GLFWwindow* window, int width, int height) + { + Data& data = *(Data*) glfwGetWindowUserPointer(window); + data.width = width; + data.height = height; + WindowResizeEvent event(width, height); + data.event_callback_func(event); + }); + + glfwSetWindowCloseCallback(m_window, [](GLFWwindow* window) + { + Data& data = *(Data*) glfwGetWindowUserPointer(window); + WindowCloseEvent event; + data.event_callback_func(event); + }); + + glfwSetKeyCallback(m_window, [](GLFWwindow* window, int key, int scancode, int action, int mods) + { + Data& data = *(Data*) glfwGetWindowUserPointer(window); + switch(action) + { + case GLFW_PRESS: + { + KeyPressedEvent event(key, 0); + data.event_callback_func(event); + break; + } + case GLFW_RELEASE: + { + KeyReleasedEvent event(key); + data.event_callback_func(event); + break; + } + case GLFW_REPEAT: + { + //GLFW doesnt have a hold count, so here we just pass 1 to indicate a hold is happening. + KeyPressedEvent event(key, 1); + data.event_callback_func(event); + break; + } + } + }); + + glfwSetCharCallback(m_window, [](GLFWwindow* window, unsigned int character) + { + Data& data = *(Data*) glfwGetWindowUserPointer(window); + KeyTypedEvent event(character); + data.event_callback_func(event); + }); + + glfwSetMouseButtonCallback(m_window, [](GLFWwindow* window, int button, int action, int mods) + { + Data& data = *(Data*) glfwGetWindowUserPointer(window); + switch(action) + { + case GLFW_PRESS: + { + MouseButtonPressedEvent event(button); + data.event_callback_func(event); + } + case GLFW_RELEASE: + { + MouseButtonReleasedEvent event(button); + data.event_callback_func(event); + } + } + }); + + glfwSetScrollCallback(m_window, [](GLFWwindow* window, double xoffset, double yoffset) + { + Data& data = *(Data*) glfwGetWindowUserPointer(window); + MouseScrolledEvent event((float)xoffset, (float)yoffset); + data.event_callback_func(event); + }); + + glfwSetCursorPosCallback(m_window, [](GLFWwindow* window, double xpos, double ypos) + { + Data& data = *(Data*) glfwGetWindowUserPointer(window); + MouseMovedEvent event((float)xpos, (float)ypos); + data.event_callback_func(event); + }); + } + + void OpenGLWindow::Shutdown() + { + glfwDestroyWindow(m_window); + } + + void OpenGLWindow::OnUpdate() + { + glfwPollEvents(); + m_context->SwapBuffers(); + } + + void OpenGLWindow::SetVSync(bool enabled) + { + if(enabled) + { + glfwSwapInterval(1); + } + else + { + glfwSwapInterval(0); + } + m_data.vsyncFlag = enabled; + } + + bool OpenGLWindow::IsVSync() const + { + return m_data.vsyncFlag; + } +} diff --git a/Specter/src/Platform/OpenGL/OpenGLWindow.h b/Specter/src/Platform/OpenGL/OpenGLWindow.h new file mode 100644 index 0000000..831d664 --- /dev/null +++ b/Specter/src/Platform/OpenGL/OpenGLWindow.h @@ -0,0 +1,56 @@ +/* + OpenGLWindow.h + Implementation of a window with OpenGL context. Not really OpenGL specific, other than in creation of GraphicsContext. + Bulk of creation can be used in any api/context (glfw compatible with Cocoa, X11, or Windows). Based entirely upon the + work of @TheCherno in his game engine series. + + GWM -- Feb 2022 +*/ +#ifndef OPENGL_WINDOW_H +#define OPENGL_WINDOW_H + +#include "Specter/Core/SpecCore.h" +#include "Specter/Core/Window.h" +#include "Specter/Renderer/GraphicsContext.h" +#include "GLFW/glfw3.h" + +namespace Specter { + + class OpenGLWindow : public Window + { + + public: + OpenGLWindow(const WindowProperties& props); + virtual ~OpenGLWindow(); + + void OnUpdate() override; + + inline void SetEventCallback(const EventCallbackFunc& function) override { m_data.event_callback_func = function; } + inline unsigned int GetWidth() const override { return m_data.width; } + inline unsigned int GetHeight() const override { return m_data.height; } + inline std::string GetName() const override { return m_data.name; } + void SetVSync(bool enabled) override; + bool IsVSync() const override; + + inline virtual void* GetNativeWindow() const override { return m_window; } + private: + virtual void Init(const WindowProperties& props); + virtual void Shutdown(); + + GraphicsContext* m_context; + + GLFWwindow* m_window; + + struct Data + { + int height, width; + std::string name; + bool vsyncFlag; + EventCallbackFunc event_callback_func; + }; + + Data m_data; + }; +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter.h b/Specter/src/Specter.h new file mode 100644 index 0000000..075e584 --- /dev/null +++ b/Specter/src/Specter.h @@ -0,0 +1,38 @@ +/* + Specter.h + This header file contains all of the essential includes for a project made using the Specter library. + Should be included into your main project files. + + GWM -- Feb 2022 +*/ +#ifndef SPECTER_H +#define SPECTER_H + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "Specter/Core/Logger.h" +#include "Specter/Core/Application.h" +#include "Specter/Physics/PhysicsLayer.h" +#include "Specter/Physics/AnalysisStage.h" +#include "Specter/Core/Parameter.h" +#include "Specter/Core/SpectrumManager.h" +#include "Specter/Core/Layer.h" +#include "Specter/Events/Event.h" +#include "Specter/Utils/TestServerLayer.h" +#include "Specter/Utils/Instrumentor.h" + +#endif diff --git a/Specter/src/Specter/Core/Application.cpp b/Specter/src/Specter/Core/Application.cpp new file mode 100644 index 0000000..a549cf8 --- /dev/null +++ b/Specter/src/Specter/Core/Application.cpp @@ -0,0 +1,105 @@ +/* + Application.cpp + This is the main application class, master and controller of program flow. Contains a layer stack, where each layer represets an + aspect of the application. Based on the Application class written by @TheCherno during his tutorial series on making a game engine. + Check out his work to learn more! + + Note that Application is a singleton. Should only ever be one application ever created in the project. + + GWM -- Feb 2022 +*/ +#include "Application.h" +#include "Specter/Renderer/RenderCommand.h" +#include "Specter/Editor/EditorLayer.h" +#include "Timestep.h" + +namespace Specter { + + + Application* Application::s_instance = nullptr; + + Application::Application() : + m_runFlag(true) + { + SPEC_PROFILE_FUNCTION(); + + s_instance = this; + + m_window = std::unique_ptr(Window::Create()); + m_window->SetEventCallback(BIND_EVENT_FUNCTION(Application::OnEvent)); //Allow window to pass events back + + m_physicsLayer = new PhysicsLayer(); + PushLayer(m_physicsLayer); + EditorLayer* editor = new EditorLayer(); //memory handled by layer stack + editor->SetEventCallbackFunc(BIND_EVENT_FUNCTION(Application::OnEvent)); //Allow editor to pass events back + PushLayer(editor); + m_imgui_layer = new ImGuiLayer(); + PushOverlay(m_imgui_layer); + } + + Application::~Application() + { + } + + void Application::OnEvent(Event& event) + { + SPEC_PROFILE_FUNCTION(); + EventDispatcher dispatch(event); + dispatch.Dispatch(BIND_EVENT_FUNCTION(Application::OnWindowCloseEvent)); + for(auto iter = m_stack.end(); iter != m_stack.begin(); ) + { + (*(--iter))->OnEvent(event); + if(event.handledFlag) + break; + } + } + + bool Application::OnWindowCloseEvent(WindowCloseEvent& event) + { + m_runFlag = false; + SPEC_WARN("Closing the window!"); + return true; + } + + void Application::PushLayer(Layer* layer) + { + SPEC_PROFILE_FUNCTION(); + m_stack.PushLayer(layer); + layer->OnAttach(); + } + + void Application::PushOverlay(Layer* layer) + { + SPEC_PROFILE_FUNCTION(); + m_stack.PushOverlay(layer); + layer->OnAttach(); + } + + void Application::Run() + { + SPEC_PROFILE_FUNCTION(); + + float lastFrameTime = 0; + float time; + Timestep step; + + while(m_runFlag) + { + RenderCommand::SetClearColor(m_bckgnd_color); + RenderCommand::Clear(); + + time = RenderCommand::GetFrameTime(); + step.SetTime(time - lastFrameTime); + lastFrameTime = time; + + for(auto layer : m_stack) + layer->OnUpdate(step); + + m_imgui_layer->Begin(); + for(auto layer : m_stack) + layer->OnImGuiRender(); + m_imgui_layer->End(); + m_window->OnUpdate(); + } + } +} diff --git a/Specter/src/Specter/Core/Application.h b/Specter/src/Specter/Core/Application.h new file mode 100644 index 0000000..f512b29 --- /dev/null +++ b/Specter/src/Specter/Core/Application.h @@ -0,0 +1,68 @@ +/* + Application.h + This is the main application class, master and controller of program flow. Contains a layer stack, where each layer represets an + aspect of the application. Based on the Application class written by @TheCherno during his tutorial series on making a game engine. + Check out his work to learn more! + + Note that Application is a singleton. Should only ever be one application ever created in the project. + + GWM -- Feb 2022 +*/ +#ifndef APPLICATION_H +#define APPLICATION_H + +#include "Specter/Core/SpecCore.h" +#include "Specter/Events/Event.h" +#include "Specter/Events/AppEvent.h" +#include "Specter/Events/PhysicsEvent.h" +#include "Specter/Core/LayerStack.h" +#include "Specter/Core/Layer.h" +#include "Specter/Core/Window.h" +#include "Specter/ImGui/ImGuiLayer.h" +#include "Specter/Physics/PhysicsLayer.h" +#include "glm/vec4.hpp" + +namespace Specter { + + class Application + { + public: + Application(); + virtual ~Application(); + + void Run(); + inline void Close() { m_runFlag = false; } + + void OnEvent(Event& event); + void PushLayer(Layer* layer); + inline void PushAnalysisStage(AnalysisStage* stage) { m_physicsLayer->PushStage(stage); } + void PushOverlay(Layer* layer); + + inline static Application& Get() { return *s_instance; } + + inline Window& GetWindow() { return *m_window; } + + private: + bool OnWindowCloseEvent(WindowCloseEvent& event); + + LayerStack m_stack; + std::unique_ptr m_window; + ImGuiLayer* m_imgui_layer; + PhysicsLayer* m_physicsLayer; + bool m_runFlag; + + //Dark grey background + glm::vec4 m_bckgnd_color = {0.1f, 0.1f, 0.1f, 1.0f}; + + static Application* s_instance; + }; + + + /* + This function is left to be defined by the user. In principle we don't need to do this, as the Specter library doesn't handle creation of the application, + but I like it and might be useful for changing to a system with a pre-defined entry point. + */ + Application* CreateApplication(); +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Core/Cut.cpp b/Specter/src/Specter/Core/Cut.cpp new file mode 100644 index 0000000..1e7af2a --- /dev/null +++ b/Specter/src/Specter/Core/Cut.cpp @@ -0,0 +1,133 @@ +/* + Cut.cpp + Cut related classes. A cut here is defined as a filter upon a histogram based on some range for a parameter or set of parameters. + + CutArgs is the underlying data that defines a cut (excluding the actual points). + + Cut is the base class for all cut objects. Should not be used in practice. All cut objects have functions which can query what kind of cut it is. If one has the cut object, + Is1D() or Is2D() can be called. If one has the CutArgs, a 1D cut will have y_par set to "None" while a 2D cut will have a valid parameter name. + + Cut1D is a one-dimensional (single parameter cut) while Cut2D is a two-dimensional (two parameter cut). There are a few differences between 1D and 2D cuts. + A Cut1D only contains two values, a min and a max. The parameter is checked that it falls within these bounds. + A Cut2D contains a set of (x,y) points that form a closed polygon. The polygon may be convex. The parameter is checked that it falls within the polygon using similar methods + to flood-fill algorithms. + + GWM -- Feb 2022 +*/ +#include "Cut.h" +#include "implot.h" + +namespace Specter { + + std::string ConvertCutTypeToString(CutType type) + { + SPEC_PROFILE_FUNCTION(); + switch(type) + { + case CutType::Cut1D: return "Cut1D"; + case CutType::Cut2D: return "Cut2D"; + case CutType::CutSummaryAll: return "CutSummaryAll"; + case CutType::CutSummaryAny: return "CutSummaryAny"; + case CutType::None: return "None"; + } + return "None"; + } + + /*1D Cuts -- Can be made on and applied to either 1D or 2D histograms*/ + Cut1D::Cut1D(const CutArgs& params, double min, double max) : + Cut(params), m_minVal(min), m_maxVal(max) + { + m_params.type = CutType::Cut1D; + } + + Cut1D::~Cut1D() {} + + void Cut1D::IsInside(double x, double y) + { + SPEC_PROFILE_FUNCTION(); + m_isValid = x >= m_minVal && x <= m_maxVal; + } + + //Only within an ImPlot/ImGui context!!! + void Cut1D::Draw() const + { + SPEC_PROFILE_FUNCTION(); + double points[2] = { m_minVal, m_maxVal }; + ImPlot::PlotVLines(m_params.name.c_str(), points, 2); + } + + /*2D Cuts -- Can only be made on 2D histogram, but applied to either 1D or 2D histograms*/ + Cut2D::Cut2D(const CutArgs& params, const std::vector& xpoints, const std::vector& ypoints) : + Cut(params), m_xpoints(xpoints), m_ypoints(ypoints) + { + m_params.type = CutType::Cut2D; + } + + Cut2D::~Cut2D() {} + + /* + Even-odd point in polygon algorithm (see Wikipedia) + Walk around the sides of the polygon and check intersection with each of the sides. + Cast a ray from the point to infinity in any direction and check the number of intersections. + If odd number of intersections, point is inside. Even, point is outside. + Edge cases of point is a vertex or on a side considered. + */ + void Cut2D::IsInside(double x, double y) + { + SPEC_PROFILE_FUNCTION(); + m_isValid = false; + double slope; + for(size_t i=0; i<(m_xpoints.size()-1); i++) + { + if (x == m_xpoints[i + 1] && y == m_ypoints[i + 1]) + { + m_isValid = true; + return; + } + else if((m_ypoints[i+1] > y) != (m_ypoints[i] > y)) + { + slope = (x - m_xpoints[i+1])*(m_ypoints[i] - m_ypoints[i+1]) - (m_xpoints[i] - m_xpoints[i+1])*(y - m_ypoints[i+1]); + if (slope == 0.0) + { + m_isValid = true; + return; + } + else if ((slope < 0.0) != (m_ypoints[i] < m_ypoints[i + 1])) + { + m_isValid = !m_isValid; + } + } + } + } + + //Only in ImPlot/ImGui context!!!! + void Cut2D::Draw() const + { + SPEC_PROFILE_FUNCTION(); + ImPlot::PlotLine(m_params.name.c_str(), m_xpoints.data(), m_ypoints.data(), (int)m_xpoints.size()); + } + + /*CutSummaryAll -- Can only be made on a HistogramSummary but can be applied to any*/ + CutSummary::CutSummary(const CutArgs& params, const std::vector& subhistos, double min, double max) : + Cut(params), m_subhistos(subhistos), m_minVal(min), m_maxVal(max) + { + } + + CutSummary::~CutSummary() + { + } + + void CutSummary::IsInside(double x, double y) + { + SPEC_PROFILE_FUNCTION(); + m_isValid = x >= m_minVal && x <= m_maxVal; + } + + //Only within an ImPlot/ImGui context!!! + void CutSummary::Draw() const + { + SPEC_PROFILE_FUNCTION(); + double points[2] = { m_minVal, m_maxVal }; + ImPlot::PlotVLines(m_params.name.c_str(), points, 2); + } +} diff --git a/Specter/src/Specter/Core/Cut.h b/Specter/src/Specter/Core/Cut.h new file mode 100644 index 0000000..eb4dd8a --- /dev/null +++ b/Specter/src/Specter/Core/Cut.h @@ -0,0 +1,126 @@ +/* + Cut.h + Cut related classes. A cut here is defined as a filter upon a histogram based on some range for a parameter or set of parameters. + + CutArgs is the underlying data that defines a cut (excluding the actual points). + + Cut is the base class for all cut objects. Should not be used in practice. All cut objects have functions which can query what kind of cut it is. If one has the cut object, + Is1D() or Is2D() can be called. If one has the CutArgs, a 1D cut will have y_par set to "None" while a 2D cut will have a valid parameter name. + + Cut1D is a one-dimensional (single parameter cut) while Cut2D is a two-dimensional (two parameter cut). There are a few differences between 1D and 2D cuts. + A Cut1D only contains two values, a min and a max. The parameter is checked that it falls within these bounds. + A Cut2D contains a set of (x,y) points that form a closed polygon. The polygon may be convex. The parameter is checked that it falls within the polygon using similar methods + to flood-fill algorithms. + + GWM -- Feb 2022 +*/ +#ifndef CUT_H +#define CUT_H + +#include "SpecCore.h" +#include "imgui.h" + +namespace Specter { + + enum class CutType + { + Cut1D, + Cut2D, + CutSummaryAny, + CutSummaryAll, + None + }; + + std::string ConvertCutTypeToString(CutType type); + + struct CutArgs + { + CutArgs() {} + CutArgs(const std::string& n, const std::string& x, const std::string& y = "None") : + name(n), x_par(x), y_par(y) + { + } + + CutType type = CutType::None; + std::string name = "None"; + std::string x_par = "None"; + std::string y_par = "None"; + }; + + class Cut + { + public: + + Cut(const CutArgs& params) : + m_params(params), m_isValid(false) + { + } + + virtual ~Cut() {} + + virtual void IsInside(double x, double y=0.0) = 0; + virtual void Draw() const = 0; + virtual std::vector GetXValues() const = 0; + virtual std::vector GetYValues() const = 0; + + inline const bool IsValid() const { return m_isValid; } + inline void ResetValidity() { m_isValid = false; } + inline CutType GetType() const { return m_params.type; } + inline const std::string& GetName() const { return m_params.name; } + inline const std::string& GetXParameter() const { return m_params.x_par; } + inline const std::string& GetYParameter() const { return m_params.y_par; } + inline const CutArgs& GetCutArgs() const { return m_params; } + protected: + CutArgs m_params; + bool m_isValid; + }; + + class Cut1D : public Cut + { + public: + Cut1D(const CutArgs& params, double min, double max); + virtual ~Cut1D(); + virtual void IsInside(double x, double y=0.0) override; + virtual void Draw() const override; + virtual std::vector GetXValues() const override { return std::vector({ m_minVal, m_maxVal }); } + virtual std::vector GetYValues() const override { return std::vector(); } + + private: + double m_minVal, m_maxVal; + }; + + class Cut2D : public Cut + { + public: + Cut2D(const CutArgs& params, const std::vector& xpoints, const std::vector& ypoints); + virtual ~Cut2D(); + virtual void IsInside(double x, double y) override; + virtual void Draw() const override; + virtual std::vector GetXValues() const override { return m_xpoints; } + virtual std::vector GetYValues() const override { return m_ypoints; } + + private: + std::vector m_xpoints; + std::vector m_ypoints; + }; + + class CutSummary : public Cut + { + public: + CutSummary(const CutArgs& params, const std::vector& subhistos, double min, double max); + virtual ~CutSummary(); + virtual void IsInside(double x, double y) override; + virtual void Draw() const override; + virtual std::vector GetXValues() const override { return std::vector({ m_minVal, m_maxVal }); } + virtual std::vector GetYValues() const override { return std::vector(); } + + inline const std::vector& GetSubHistograms() const { return m_subhistos; } + + private: + double m_minVal, m_maxVal; + std::vector m_subhistos; + }; + +} + +#endif diff --git a/Specter/src/Specter/Core/Graph.cpp b/Specter/src/Specter/Core/Graph.cpp new file mode 100644 index 0000000..a294549 --- /dev/null +++ b/Specter/src/Specter/Core/Graph.cpp @@ -0,0 +1,42 @@ +#include "Graph.h" +#include "implot.h" + +namespace Specter { + + ScalerGraph::ScalerGraph(const GraphArgs& args) : + m_args(args), m_lastScalerVal(0) + { + m_xPoints.reserve(m_args.maxPoints); + m_xPoints.reserve(m_args.maxPoints); + } + + ScalerGraph::~ScalerGraph() {} + + void ScalerGraph::UpdatePoints(double timeStep, uint64_t scalerVal) + { + double rate = (scalerVal - m_lastScalerVal) / timeStep; + if (m_xPoints.size() < m_args.maxPoints) + { + m_xPoints.push_back(timeStep); + m_yPoints.push_back(rate); + } + else + { + for (size_t i = 0; i < (m_args.maxPoints - 1); i++) + { + m_xPoints[i] = m_xPoints[i + 1]; + m_yPoints[i] = m_yPoints[i + 1]; + } + m_xPoints[m_args.maxPoints - 1] = timeStep; + m_yPoints[m_args.maxPoints - 1] = rate; + } + + m_lastScalerVal = scalerVal; + } + + void ScalerGraph::Draw() + { + ImPlot::SetupAxes("time (s)", "rate (Hz)"); + ImPlot::PlotLine(m_args.name.c_str(), m_xPoints.data(), m_yPoints.data(), m_xPoints.size()); + } +} \ No newline at end of file diff --git a/Specter/src/Specter/Core/Graph.h b/Specter/src/Specter/Core/Graph.h new file mode 100644 index 0000000..723f548 --- /dev/null +++ b/Specter/src/Specter/Core/Graph.h @@ -0,0 +1,39 @@ +#ifndef GRAPH_H +#define GRAPH_H + +namespace Specter { + + struct GraphArgs + { + std::string name; + std::string scalerName; + size_t maxPoints; + }; + + class ScalerGraph + { + public: + ScalerGraph(const GraphArgs& args); + ~ScalerGraph(); + + void UpdatePoints(double timeStep, uint64_t scalerVal); + void Draw(); + inline void Clear() { m_xPoints.clear(); m_yPoints.clear(); } + + inline const std::string& GetName() const { return m_args.name; } + inline const std::string& GetScaler() const { return m_args.scalerName; } + inline const GraphArgs& GetArgs() const { return m_args; } + + private: + + GraphArgs m_args; + + uint64_t m_lastScalerVal; + + std::vector m_yPoints; + std::vector m_xPoints; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Core/Histogram.cpp b/Specter/src/Specter/Core/Histogram.cpp new file mode 100644 index 0000000..6b39f8d --- /dev/null +++ b/Specter/src/Specter/Core/Histogram.cpp @@ -0,0 +1,421 @@ +/* + Histogram.cpp + Histogram related classes. We use a custom histogram class here because the ImPlot histograms are not a data structure, but rather just a function. This means that at every draw call for an ImPlot + histogram the entire data set will need to be properly binned and memory will need to be allocated to make histogram arrays. For our use case this is obviously bad. For one thing, data runs can have + thousands to millions of events. In the ImPlot paradigm we would need to loop over all of this data and bin it, not to mention explicitly store all of this data in memory for every histogram. I point this + out not to say that ImPlot histograms are bad intrinsically, because they definitely have a use for smaller data sets, but rather to explain why for this program I have re-invented the wheel somewhat. + + HistogramArgs are the underlying data which define a histogram. This is grouped in a struct to easily pass these around for use in contexts like the Editor. + Every histogram has a set of histogram parameters. + + Histogram is the base class of all histograms. Should not be used in practice. Every histogram contains functions to query what type of underlying histogram it is. If one has + the Histogram object, Is1D() or Is2D() can be called. If one only has the HistogramArgs, the values of x_par and y_par can be inspected. In particular, a 1D histogram will have + y_par set to "None", while a 2D histogram should have a valid parameter name for y_par. + + Histogram1D is a one dimensional (single parameter) histogram. Histogram2D is a two dimensional (two parameter) histogram. The only real difference between these in practice, other than + the obvious two vs. one parameter thing, is that a Histogram2D contains methods to set the z-axis range (color scale) which ImPlot does not provide intrinsic access to from the plot itself. + When the range is set to (0,0), the color scale is set to the default (0, maxValue). Otherwise the color is scaled as appropriate. If you query a Histogram1D for its color scale you will recieve + a nullptr. + + StatResults is a struct containing statistical information about a region of a histogram. + + GWM -- Feb 2022 +*/ +#include "Histogram.h" +#include "implot.h" + +/* + For Specter histograms, binnning is done over ranges as + [min, max), so that each bin is exclusive. +*/ + +namespace Specter { + + std::string ConvertSpectrumTypeToString(SpectrumType type) + { + SPEC_PROFILE_FUNCTION(); + switch (type) + { + case SpectrumType::Histo1D: return "Histogram1D"; + case SpectrumType::Histo2D: return "Histogram2D"; + case SpectrumType::Summary: return "Summary"; + case SpectrumType::None: return "None"; + } + return "None"; + } + + /* + 1D Histogram class + */ + Histogram1D::Histogram1D(const HistogramArgs& params) : + Histogram(params) + { + InitBins(); + } + + Histogram1D::~Histogram1D() {} + + void Histogram1D::InitBins() + { + SPEC_PROFILE_FUNCTION(); + m_params.type = SpectrumType::Histo1D; + if(m_params.nbins_x == 0 || (m_params.min_x >= m_params.max_x)) + { + SPEC_WARN("Attempting to create an illegal Histogram1D {0} with {1} bins and a range from {2} to {3}. Historgram not initialized.", m_params.name, m_params.nbins_x, m_params.min_x, m_params.max_x); + m_initFlag = false; + return; + } + + m_binWidth = (m_params.max_x - m_params.min_x)/m_params.nbins_x; + + m_binCenters.resize(m_params.nbins_x); + m_binCounts.resize(m_params.nbins_x); + + for(int i=0; i= m_params.max_x) + return; + int bin = int((x - m_params.min_x)/(m_binWidth)); + m_binCounts[bin] += 1.0; + } + + //Can only be used within an ImGui / ImPlot context!! + void Histogram1D::Draw() + { + SPEC_PROFILE_FUNCTION(); + ImPlot::SetupAxes(m_params.x_par.c_str(), "Counts",0, ImPlotAxisFlags_LockMin | ImPlotAxisFlags_AutoFit); + ImPlot::PlotBars(m_params.name.c_str(), &m_binCenters.data()[0], &m_binCounts.data()[0], m_params.nbins_x, m_binWidth); + } + + void Histogram1D::ClearData() + { + for(int i=0; i= m_params.max_x) + bin_max = m_params.nbins_x - 1; + else + bin_max = int((x_max - m_params.min_x) / (m_binWidth)); + + for (int i = bin_min; i <= bin_max; i++) + { + results.integral += m_binCounts[i]; + results.cent_x += m_binCounts[i] * (m_params.min_x + m_binWidth * i); + } + if (results.integral == 0) + return results; + + results.cent_x /= results.integral; + for (int i = bin_min; i <= bin_max; i++) + results.sigma_x += m_binCounts[i] * ((m_params.min_x + m_binWidth * i) - results.cent_x) * ((m_params.min_x + m_binWidth * i) - results.cent_x); + results.sigma_x = std::sqrt(results.sigma_x / (results.integral - 1)); + return results; + } + + /* + 2D Histogram class + Note for 2D: Rendering is done from top left to bottom right. So ybins run from top to bottom (ymin is last row, ymax is first row) + */ + Histogram2D::Histogram2D(const HistogramArgs& params) : + Histogram(params) + { + m_colorScaleRange[0] = 0.0f; + m_colorScaleRange[1] = 0.0f; + InitBins(); + } + + Histogram2D::~Histogram2D() {} + + void Histogram2D::InitBins() + { + SPEC_PROFILE_FUNCTION(); + m_params.type = SpectrumType::Histo2D; + if(m_params.nbins_x <= 0 || m_params.nbins_y <= 0 || m_params.min_x >= m_params.max_x || m_params.min_y >= m_params.max_y) + { + SPEC_WARN("Attempting to create illegal Histogram2D {0} with {1} x bins, {2} y bins, an x range of {3} to {4}, and a y range of {5} to {6}. Not initialized.", m_params.name, m_params.nbins_x, m_params.nbins_y, + m_params.min_x, m_params.max_x, m_params.min_y, m_params.max_y); + m_initFlag = false; + return; + } + + m_binWidthX = (m_params.max_x - m_params.min_x)/m_params.nbins_x; + m_binWidthY = (m_params.max_y - m_params.min_y)/m_params.nbins_y; + + m_nBinsTotal = m_params.nbins_x*m_params.nbins_y; + + m_binCounts.resize(m_nBinsTotal); + for(int i=0; i= m_params.max_x || y <= m_params.min_y || y > m_params.max_y) + return; + int bin_x = int((x - m_params.min_x)/m_binWidthX); + int bin_y = int((m_params.max_y - y)/m_binWidthY); + int bin = bin_y*m_params.nbins_x + bin_x; + + m_binCounts[bin] += 1.0; + + m_maxBinContent = m_binCounts[bin] > m_maxBinContent ? (m_binCounts[bin]) : m_maxBinContent; + } + + //Can only be used within an ImGui / ImPlot context!! + /* + Brief note on colormaps: There are several kinds of colormaps, each with specific use cases. But broadly, the two main categories are discrete and continuous. + Discrete maps are good for categorical data, and we use these as the standard for plots we draw. This is how you make cuts standout from your histograms. + But for the colors of the bins, we want a continuous scale, i.e. smooth variation on a single hue, as the color differences represent the value differences + relative to other bins. Hence the pushing of the Matplotlib virdis colormap. For more on colormaps, I suggest looking at matplotlib documentation. + */ + void Histogram2D::Draw() + { + SPEC_PROFILE_FUNCTION(); + ImPlot::SetupAxes(m_params.x_par.c_str(), m_params.y_par.c_str()); + ImPlot::PushColormap(ImPlotColormap_Viridis); + ImPlot::PlotHeatmap(m_params.name.c_str(), &m_binCounts.data()[0], m_params.nbins_y, m_params.nbins_x, m_colorScaleRange[0], m_colorScaleRange[1], NULL, + ImPlotPoint(m_params.min_x, m_params.min_y), ImPlotPoint(m_params.max_x, m_params.max_y)); + ImPlot::PopColormap(); + } + + void Histogram2D::ClearData() + { + for(int i=0; i= m_params.max_x) + xbin_max = m_params.nbins_x - 1; + else + xbin_max = int((x_max - m_params.min_x) / (m_binWidthX)); + + if (y_min <= m_params.min_y) + ybin_max = m_params.nbins_y - 1; + else + ybin_max = int((m_params.max_y - y_min) / m_binWidthY); + + if (y_max >= m_params.max_y) + ybin_min = 0; + else + ybin_min = int((m_params.max_y - y_max) / m_binWidthY); + + for (int y = ybin_min; y <= ybin_max; y++) + { + for (int x = xbin_min; x <= xbin_max; x++) + { + curbin = y * m_params.nbins_x + x; + results.integral += m_binCounts[curbin]; + results.cent_x += m_binCounts[curbin] * (m_params.min_x + m_binWidthX * x); + results.cent_y += m_binCounts[curbin] * (m_params.max_y - m_binWidthY * y); + } + } + + if (results.integral == 0) + return results; + + results.cent_x /= results.integral; + results.cent_y /= results.integral; + for (int y = ybin_min; y <= ybin_max; y++) + { + for (int x = xbin_min; x <= xbin_max; x++) + { + curbin = y * m_params.nbins_x + x; + results.sigma_x += m_binCounts[curbin] * ((m_params.min_x + m_binWidthX * x) - results.cent_x) * ((m_params.min_x + m_binWidthX * x) - results.cent_x); + results.sigma_y += m_binCounts[curbin] * ((m_params.max_y - m_binWidthY * y) - results.cent_y) * ((m_params.max_y - m_binWidthY * y) - results.cent_y); + } + } + + results.sigma_x = std::sqrt(results.sigma_x / (results.integral - 1)); + results.sigma_y = std::sqrt(results.sigma_y / (results.integral - 1)); + + return results; + } + + /* + HistogramSummary class methods + + -- 03/18/22 Adding in the basics, unsure of how to exactly approach design + + -- Fill data to independent histogram? Steal data from other histograms? + + -- Cuts? + + -- Literally everything hahaha + */ + + HistogramSummary::HistogramSummary(const HistogramArgs& params, const std::vector& subhistos) : + Histogram(params), m_subhistos(subhistos), m_labels(nullptr) + { + m_colorScaleRange[0] = 0.0f; + m_colorScaleRange[1] = 0.0f; + InitBins(); + } + + HistogramSummary::~HistogramSummary() + { + if (m_labels) + delete[] m_labels; + } + + void HistogramSummary::InitBins() + { + SPEC_PROFILE_FUNCTION(); + m_params.type = SpectrumType::Summary; + if (m_params.nbins_x <= 0 || m_params.min_x >= m_params.max_x) + { + SPEC_WARN("Attempting to create illegal HistogramSummary {0} with {1} x bins and an x range of {2} to {3}. Not initialized.", m_params.name, m_params.nbins_x, m_params.min_x, m_params.max_x); + m_initFlag = false; + return; + } + + m_labels = new const char* [m_subhistos.size() + 1]; + for (size_t i = 0; i < m_subhistos.size(); i++) + m_labels[i] = m_subhistos[i].c_str(); + m_labels[m_subhistos.size()] = ""; + m_params.nbins_y = m_subhistos.size(); + m_params.min_y = 0.0; + m_params.max_y = m_subhistos.size(); + m_binWidthX = (m_params.max_x - m_params.min_x) / m_params.nbins_x; + + m_nBinsTotal = m_params.nbins_x * m_params.nbins_y; + + m_binCounts.resize(m_nBinsTotal); + for (int i = 0; i < m_nBinsTotal; i++) + m_binCounts[i] = 0; + + m_initFlag = true; + } + + void HistogramSummary::FillData(double x, double y) + { + SPEC_PROFILE_FUNCTION(); + if (x < m_params.min_x || x >= m_params.max_x || y <= m_params.min_y || y > m_params.max_y) + return; + int bin_x = int((x - m_params.min_x) / m_binWidthX); + int bin_y = int((m_params.max_y - y) / m_binWidthY); + int bin = bin_y * m_params.nbins_x + bin_x; + + m_binCounts[bin] += 1.0; + } + + void HistogramSummary::Draw() + { + SPEC_PROFILE_FUNCTION(); + ImPlot::SetupAxisTicks(ImAxis_Y1, m_params.min_y, m_params.max_y, m_params.nbins_y, m_labels, false); + ImPlot::PushColormap(ImPlotColormap_Viridis); + ImPlot::PlotHeatmap(m_params.name.c_str(), &m_binCounts.data()[0], m_params.nbins_y, m_params.nbins_x, m_colorScaleRange[0], m_colorScaleRange[1], NULL, + ImPlotPoint(m_params.min_x, m_params.min_y), ImPlotPoint(m_params.max_x, m_params.max_y)); + ImPlot::PopColormap(); + } + + void HistogramSummary::ClearData() + { + for (int i = 0; i < m_nBinsTotal; i++) + m_binCounts[i] = 0; + } + + StatResults HistogramSummary::AnalyzeRegion(double x_min, double x_max, double y_min, double y_max) + { + SPEC_PROFILE_FUNCTION(); + int xbin_min, xbin_max, ybin_min, ybin_max; + int curbin; + + StatResults results; + + //We clamp to the boundaries of the histogram + if (x_min <= m_params.min_x) + xbin_min = 0; + else + xbin_min = int((x_min - m_params.min_x) / (m_binWidthX)); + + if (x_max >= m_params.max_x) + xbin_max = m_params.nbins_x - 1; + else + xbin_max = int((x_max - m_params.min_x) / (m_binWidthX)); + + if (y_min <= m_params.min_y) + ybin_max = m_params.nbins_y - 1; + else + ybin_max = int((m_params.max_y - y_min) / m_binWidthY); + + if (y_max >= m_params.max_y) + ybin_min = 0; + else + ybin_min = int((m_params.max_y - y_max) / m_binWidthY); + + for (int y = ybin_min; y <= ybin_max; y++) + { + for (int x = xbin_min; x <= xbin_max; x++) + { + curbin = y * m_params.nbins_x + x; + results.integral += m_binCounts[curbin]; + results.cent_x += m_binCounts[curbin] * (m_params.min_x + m_binWidthX * x); + results.cent_y += m_binCounts[curbin] * (m_params.max_y - m_binWidthY * y); + } + } + + if (results.integral == 0) + return results; + + results.cent_x /= results.integral; + results.cent_y /= results.integral; + for (int y = ybin_min; y <= ybin_max; y++) + { + for (int x = xbin_min; x <= xbin_max; x++) + { + curbin = y * m_params.nbins_x + x; + results.sigma_x += m_binCounts[curbin] * ((m_params.min_x + m_binWidthX * x) - results.cent_x) * ((m_params.min_x + m_binWidthX * x) - results.cent_x); + results.sigma_y += m_binCounts[curbin] * ((m_params.max_y - m_binWidthY * y) - results.cent_y) * ((m_params.max_y - m_binWidthY * y) - results.cent_y); + } + } + + results.sigma_x = std::sqrt(results.sigma_x / (results.integral - 1)); + results.sigma_y = std::sqrt(results.sigma_y / (results.integral - 1)); + + return results; + } +} diff --git a/Specter/src/Specter/Core/Histogram.h b/Specter/src/Specter/Core/Histogram.h new file mode 100644 index 0000000..950b667 --- /dev/null +++ b/Specter/src/Specter/Core/Histogram.h @@ -0,0 +1,184 @@ +/* + Histogram.h + Histogram related classes. We use a custom histogram class here because the ImPlot histograms are not a data structure, but rather just a function. This means that at every draw call for an ImPlot + histogram the entire data set will need to be properly binned and memory will need to be allocated to make histogram arrays. For our use case this is obviously bad. For one thing, data runs can have + thousands to millions of events. In the ImPlot paradigm we would need to loop over all of this data and bin it, not to mention explicitly store all of this data in memory for every histogram. I point this + out not to say that ImPlot histograms are bad intrinsically, because they definitely have a use for smaller data sets, but rather to explain why for this program I have re-invented the wheel somewhat. + + HistogramArgs are the underlying data which define a histogram. This is grouped in a struct to easily pass these around for use in contexts like the Editor. + Every histogram has a set of histogram parameters. + + Histogram is the base class of all histograms. Should not be used in practice. Every histogram contains functions to query what type of underlying histogram it is. If one has + the Histogram object, Is1D() or Is2D() can be called. If one only has the HistogramArgs, the values of x_par and y_par can be inspected. In particular, a 1D histogram will have + y_par set to "None", while a 2D histogram should have a valid parameter name for y_par. + + Histogram1D is a one dimensional (single parameter) histogram. Histogram2D is a two dimensional (two parameter) histogram. The only real difference between these in practice, other than + the obvious two vs. one parameter thing, is that a Histogram2D contains methods to set the z-axis range (color scale) which ImPlot does not provide intrinsic access to from the plot itself. + When the range is set to (0,0), the color scale is set to the default (0, maxValue). Otherwise the color is scaled as appropriate. If you query a Histogram1D for its color scale you will recieve + a nullptr. + + StatResults is a struct containing statistical information about a region of a histogram. + + GWM -- Feb 2022 +*/ +#ifndef HISTOGRAM_H +#define HISTOGRAM_H + +#include "SpecCore.h" + +namespace Specter { + + enum class SpectrumType + { + Histo1D, + Histo2D, + Summary, + None + }; + + std::string ConvertSpectrumTypeToString(SpectrumType type); + + + struct StatResults + { + double integral = 0.0; + double cent_x = 0.0; + double cent_y = 0.0; + double sigma_x = 0.0; + double sigma_y = 0.0; + }; + + struct HistogramArgs + { + HistogramArgs() {} + + HistogramArgs(const std::string& n, const std::string& x, int bins, double min, double max) : + name(n), x_par(x), nbins_x(bins), min_x(min), max_x(max) + { + } + + HistogramArgs(const std::string& n, const std::string& x, const std::string& y, int binsx, double minx, double maxx, int binsy, double miny, double maxy) : + name(n), x_par(x), y_par(y), nbins_x(binsx), min_x(minx), max_x(maxx), nbins_y(binsy), min_y(miny), max_y(maxy) + { + } + + SpectrumType type = SpectrumType::None; + std::string name = "None"; + std::string x_par = "None"; + std::string y_par = "None"; + std::vector cutsDrawnUpon; + std::vector cutsAppliedTo; + int nbins_x = 0; + double min_x = 0; + double max_x = 0; + int nbins_y = 0; + double min_y = 0; + double max_y = 0; + }; + + class Histogram + { + public: + Histogram() : + m_initFlag(false) + { + } + Histogram(const HistogramArgs& params) : + m_params(params), m_initFlag(false) + { + } + + virtual ~Histogram() {}; + virtual void FillData(double x, double y=0.0) { SPEC_WARN("Trying to fill a default histogram!"); } + virtual void Draw() {} + virtual void ClearData() {} + virtual StatResults AnalyzeRegion(double x_min, double x_max, double y_min = 0.0, double y_max = 0.0) { return StatResults(); } + inline virtual float* GetColorScaleRange() { return nullptr; } + inline virtual std::vector GetBinData() { return std::vector(); } + inline HistogramArgs& GetParameters() { return m_params; } + inline SpectrumType GetType() { return m_params.type; } + inline const std::string& GetXParam() const { return m_params.x_par; }; + inline const std::string& GetYParam() const { return m_params.y_par; }; + inline const std::string& GetName() const { return m_params.name; } + inline void AddCutToBeDrawn(const std::string& name) { m_params.cutsDrawnUpon.push_back(name); } + inline void AddCutToBeApplied(const std::string& name) { m_params.cutsAppliedTo.push_back(name); } + + protected: + HistogramArgs m_params; + bool m_initFlag; + }; + + class Histogram1D : public Histogram + { + public: + Histogram1D(const HistogramArgs& params); + virtual ~Histogram1D(); + virtual void FillData(double x, double y=0.0) override; + virtual void Draw() override; + virtual void ClearData() override; + virtual StatResults AnalyzeRegion(double x_min, double x_max, double y_min = 0.0, double y_max = 0.0) override; + inline virtual std::vector GetBinData() override { return m_binCounts; } + + private: + void InitBins(); + + std::vector m_binCenters; + std::vector m_binCounts; + double m_binWidth; + + }; + + class Histogram2D : public Histogram + { + public: + Histogram2D(const HistogramArgs& params); + virtual ~Histogram2D(); + virtual void FillData(double x, double y) override; + virtual void Draw() override; + virtual void ClearData() override; + virtual StatResults AnalyzeRegion(double x_min, double x_max, double y_min = 0.0, double y_max = 0.0) override; + inline virtual std::vector GetBinData() override { return m_binCounts; } + + inline virtual float* GetColorScaleRange() override { return m_colorScaleRange; } + + private: + void InitBins(); + + std::vector m_binCounts; + int m_nBinsTotal; + double m_binWidthY; + double m_binWidthX; + double m_maxBinContent; + float m_colorScaleRange[2]; + }; + + class HistogramSummary : public Histogram + { + public: + HistogramSummary(const HistogramArgs& params, const std::vector& subhistos); + ~HistogramSummary(); + + inline const std::vector& GetSubHistograms() const { return m_subhistos; } + + virtual void FillData(double x, double y) override; + virtual void ClearData() override; + virtual void Draw() override; + inline virtual float* GetColorScaleRange() override { return m_colorScaleRange; } + virtual StatResults AnalyzeRegion(double x_min, double x_max, double y_min = 0.0, double y_max = 0.0) override; + inline virtual std::vector GetBinData() override { return m_binCounts; } + + private: + void InitBins(); + + std::vector m_subhistos; + const char** m_labels; + std::vector m_binCounts; + int m_nBinsTotal; + double m_binWidthX; + const double m_binWidthY = 1.0; + float m_colorScaleRange[2]; + }; + +} + +#endif diff --git a/Specter/src/Specter/Core/Layer.cpp b/Specter/src/Specter/Core/Layer.cpp new file mode 100644 index 0000000..6b3df18 --- /dev/null +++ b/Specter/src/Specter/Core/Layer.cpp @@ -0,0 +1,17 @@ +/* + Layer.cpp + Layer is an abstract class representing an aspect of the application. Based entirely upon @TheCherno's tutorials in his game engine series. + + GWM -- Feb 2022 +*/ +#include "Layer.h" + +namespace Specter { + + Layer::Layer(const std::string& name) : + m_name(name) + { + } + + Layer::~Layer() {} +} \ No newline at end of file diff --git a/Specter/src/Specter/Core/Layer.h b/Specter/src/Specter/Core/Layer.h new file mode 100644 index 0000000..89ef0a1 --- /dev/null +++ b/Specter/src/Specter/Core/Layer.h @@ -0,0 +1,36 @@ +/* + Layer.h + Layer is an abstract class representing an aspect of the application. Based entirely upon @TheCherno's tutorials in his game engine series. + + GWM -- Feb 2022 +*/ +#ifndef LAYER_H +#define LAYER_H + +#include "SpecCore.h" +#include "Specter/Events/Event.h" +#include "Timestep.h" + +namespace Specter { + + class Layer + { + public: + Layer(const std::string& name="Layer"); + virtual ~Layer(); + + virtual void OnAttach() {} + virtual void OnDetach() {} + virtual void OnImGuiRender() {} + virtual void OnUpdate(Timestep& step) {} + virtual void OnEvent(Event& event) {} + + inline const std::string& GetName() { return m_name; } + + private: + std::string m_name; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Core/LayerStack.cpp b/Specter/src/Specter/Core/LayerStack.cpp new file mode 100644 index 0000000..69be01a --- /dev/null +++ b/Specter/src/Specter/Core/LayerStack.cpp @@ -0,0 +1,49 @@ +/* + LayerStack.cpp + LayerStack is a container for Layers. Should only be owned by the Application. Layers are + managed by the LayerStack (memory-wise). There are two types of layers, overlays and regular layers. + Overlays are processed first in the event stack. This is entirely based upon @TheCherno's work shown in his + game engine tutorial series. + + GWM -- Feb 2022 +*/ +#include "LayerStack.h" + +namespace Specter { + + LayerStack::LayerStack() {} + + LayerStack::~LayerStack() + { + for(Layer* layer : m_stack) + delete layer; + } + + void LayerStack::PushLayer(Layer* layer) + { + m_stack.emplace(m_stack.begin() + m_insertIndex, layer); + m_insertIndex++; + } + + void LayerStack::PushOverlay(Layer* layer) + { + m_stack.emplace_back(layer); + } + + void LayerStack::PopLayer(Layer* layer) + { + auto iter = std::find(m_stack.begin(), m_stack.end(), layer); + if(iter != m_stack.end()) + { + m_stack.erase(iter); + m_insertIndex--; + } + } + + void LayerStack::PopOverlay(Layer* layer) + { + auto iter = std::find(m_stack.begin(), m_stack.end(), layer); + if(iter != m_stack.end()) + m_stack.erase(iter); + } +} \ No newline at end of file diff --git a/Specter/src/Specter/Core/LayerStack.h b/Specter/src/Specter/Core/LayerStack.h new file mode 100644 index 0000000..2793d81 --- /dev/null +++ b/Specter/src/Specter/Core/LayerStack.h @@ -0,0 +1,39 @@ +/* + LayerStack.h + LayerStack is a container for Layers. Should only be owned by the Application. Layers are + managed by the LayerStack (memory-wise). There are two types of layers, overlays and regular layers. + Overlays are processed first in the event stack. This is entirely based upon @TheCherno's work shown in his + game engine tutorial series. + + GWM -- Feb 2022 +*/ +#ifndef LAYER_STACK_H +#define LAYER_STACK_H + +#include "SpecCore.h" +#include "Layer.h" + +namespace Specter { + + class LayerStack + { + public: + LayerStack(); + ~LayerStack(); + + void PushLayer(Layer* layer); + void PopLayer(Layer* layer); + void PushOverlay(Layer* layer); + void PopOverlay(Layer* layer); + + //helpers for iterator for loops + std::vector::iterator begin() { return m_stack.begin(); } + std::vector::iterator end() { return m_stack.end(); } + private: + std::vector m_stack; //These layers are owned by the LayerStack! + unsigned int m_insertIndex=0; //used to keep track of where to put layers vs. overlays. + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Core/Logger.cpp b/Specter/src/Specter/Core/Logger.cpp new file mode 100644 index 0000000..843c00e --- /dev/null +++ b/Specter/src/Specter/Core/Logger.cpp @@ -0,0 +1,26 @@ +/* + Logger.cpp + Logging class which is a thin wrapper on the spdlog library. Must be initialized in the project at start (see NavProject main.cpp for example). + Again, strongly based upon @TheCherno's work, see his Hazel repository for more details. + + Note Logger is a singleton. Should only ever be intialized once. Macros for calling the log provided to make clean looking code at the other side. + Consider making some logging calls only defined on Debug. + + GWM -- Feb 2022 +*/ +#include "Logger.h" +#include "spdlog/sinks/stdout_color_sinks.h" + +namespace Specter { + + std::shared_ptr Logger::s_logger; + + void Logger::Init() + { + spdlog::set_pattern("%^[%T] %n: %v%$"); + + s_logger = spdlog::stdout_color_mt("NAV"); + s_logger->set_level(spdlog::level::trace); + } + +} \ No newline at end of file diff --git a/Specter/src/Specter/Core/Logger.h b/Specter/src/Specter/Core/Logger.h new file mode 100644 index 0000000..52fbfd0 --- /dev/null +++ b/Specter/src/Specter/Core/Logger.h @@ -0,0 +1,42 @@ +/* + Logger.h + Logging class which is a thin wrapper on the spdlog library. Must be initialized in the project at start (see NavProject main.cpp for example). + Again, strongly based upon @TheCherno's work, see his Hazel repository for more details. + + Note Logger is a singleton. Should only ever be intialized once. Macros for calling the log provided to make clean looking code at the other side. + Consider making some logging calls only defined on Debug. + + GWM -- Feb 2022 +*/ +#ifndef LOGGER_H +#define LOGGER_H + +#include "SpecCore.h" +#include "spdlog/spdlog.h" +#include "spdlog/fmt/ostr.h" + +namespace Specter { + + class Logger + { + + public: + static void Init(); + + inline static std::shared_ptr GetLogger() { return s_logger; } + + private: + static std::shared_ptr s_logger; + + }; + +} + +//Macros for clean code. Different logging levels. +#define SPEC_CRITICAL(...) ::Specter::Logger::GetLogger()->critical(__VA_ARGS__) +#define SPEC_WARN(...) ::Specter::Logger::GetLogger()->warn(__VA_ARGS__) +#define SPEC_ERROR(...) ::Specter::Logger::GetLogger()->error(__VA_ARGS__) +#define SPEC_TRACE(...) ::Specter::Logger::GetLogger()->trace(__VA_ARGS__) +#define SPEC_INFO(...) ::Specter::Logger::GetLogger()->info(__VA_ARGS__) + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Core/Parameter.cpp b/Specter/src/Specter/Core/Parameter.cpp new file mode 100644 index 0000000..c07f8bd --- /dev/null +++ b/Specter/src/Specter/Core/Parameter.cpp @@ -0,0 +1,96 @@ +/* + Parameter.cpp + Contains two data related structures, ParameterData and Parameter. Parameter here refers to a value which + can be associated with the axis of a histogram. ParameterData is a two component struct which represents the state of a single parameter, which is stored + in the global SpectrumManager (see SpectrumManager documentation). Parameter is an access point class for use within analysis. It provides scoped access to + the managed data. There are several important caveats to using the parameters this way, which is mostly related to synchronization: + + - Parameter contains no guarantee of thread safety. THIS IS BY DESIGN. Parameters should only be created within the context of an AnalysisStage (see AnalysisStage documentation). + As long as this is true, there is no risk of accessing parameter data outside of the physics thread, and that thread explicitly handles calculation of parameters + and updating of histograms. DO NOT make a Parameter outside of the AnalysisStage context. + - Parameters must be bound to the active SpectrumManager. This is done using the BindParameter function of the manager; Parameters are keyed based on their name string. + If two Parameters are made that are both named "x1", they are in fact the same parameter. In this way, values can be passed from one AnalysisStage to another. If you make a stage + called InitialStage with a Parameter named "x1" where x1 is set to 1.0, and then have a later stage called LaterStage with another Parameter named x1, it implicitly has the value 1.0 + due to it being set in the previous stage. + - Each Parameter has a valid flag. This is a boolean which idicates whether or not the parameter is in a valid state (the data event contianed a value for this parameter). Before using a parameter + in a calculation one should check if the parameter is valid using the IsValid() function. When a parameter is set using the SetValue() function, the valid flag is set to true. After the event is completely + processed (all analysis stages have been run and histograms have been updated) the manager should be called to run InvalidateParameters() to set all parameters as invalid (valid flag false). + + Those are the key points I can think of now. As more people use the code, I expect this section to grow and change. + + Credit to nscldaq and in particular NSCLSpecTcl which provided the inspiration for this parameter model. + + GWM -- Feb 2022 +*/ +#include "Parameter.h" + +namespace Specter { + + Parameter::Parameter() : + m_name(""), m_pdata(nullptr) + { + } + + Parameter::Parameter(const std::string& name) : + m_name(name), m_pdata(nullptr) + { + } + + Parameter::~Parameter() {} + + //So that you can make arrays, std::vectors of Parameters (useful for big detector arrays) + //SpectrumManager::BindParameter() still needs to be used after this! + void Parameter::SetName(const std::string& name) + { + if (m_name != "") + { + SPEC_ERROR("Attempting to change the name of an already bound Parameter! Set name: {0} New name: {1}", m_name, name); + return; + } + m_name = name; + } + + Variable::Variable() : + m_name(""), m_pdata(nullptr) + { + } + + Variable::Variable(const std::string& name) : + m_name(name), m_pdata(nullptr) + { + } + + Variable::~Variable() {} + + void Variable::SetName(const std::string& name) + { + if (m_name != "") + { + SPEC_ERROR("Attempting to change the name of an already bound Variable! Set name: {0} New name: {1}", m_name, name); + return; + } + m_name = name; + } + + Scaler::Scaler() : + m_pdata(nullptr), m_name("") + { + } + + Scaler::Scaler(const std::string& name) : + m_pdata(nullptr), m_name(name) + { + } + + Scaler::~Scaler() {} + + void Scaler::SetName(const std::string& name) + { + if (m_name != "") + { + SPEC_ERROR("Attempting to change the name of an already bound Scaler! Set name: {0} New name: {1}", m_name, name); + return; + } + m_name = name; + } +} diff --git a/Specter/src/Specter/Core/Parameter.h b/Specter/src/Specter/Core/Parameter.h new file mode 100644 index 0000000..888b6fe --- /dev/null +++ b/Specter/src/Specter/Core/Parameter.h @@ -0,0 +1,106 @@ +/* + Parameter.h + Contains two data related structures, ParameterData and Parameter. Parameter here refers to a value which + can be associated with the axis of a histogram. ParameterData is a two component struct which represents the state of a single parameter, which is stored + in the global SpectrumManager (see SpectrumManager documentation). Parameter is an access point class for use within analysis. It provides scoped access to + the managed data. There are several important caveats to using the parameters this way, which is mostly related to synchronization: + + - Parameter contains no guarantee of thread safety. THIS IS BY DESIGN. Parameters should only be created within the context of an AnalysisStage (see AnalysisStage documentation). + As long as this is true, there is no risk of accessing parameter data outside of the physics thread, and that thread explicitly handles calculation of parameters + and updating of histograms. DO NOT make a Parameter outside of the AnalysisStage context. + - Parameters must be bound to the active SpectrumManager. This is done using the BindParameter function of the manager; Parameters are keyed based on their name string. + If two Parameters are made that are both named "x1", they are in fact the same parameter. In this way, values can be passed from one AnalysisStage to another. If you make a stage + called InitialStage with a Parameter named "x1" where x1 is set to 1.0, and then have a later stage called LaterStage with another Parameter named x1, it implicitly has the value 1.0 + due to it being set in the previous stage. + - Each Parameter has a valid flag. This is a boolean which idicates whether or not the parameter is in a valid state (the data event contianed a value for this parameter). Before using a parameter + in a calculation one should check if the parameter is valid using the IsValid() function. When a parameter is set using the SetValue() function, the valid flag is set to true. After the event is completely + processed (all analysis stages have been run and histograms have been updated) the manager should be called to run InvalidateParameters() to set all parameters as invalid (valid flag false). + + Those are the key points I can think of now. As more people use the code, I expect this section to grow and change rapidly. + + Credit to nscldaq and in particular NSCLSpecTcl which provided the inspiration for this parameter model. + + GWM -- Feb 2022 +*/ +#ifndef PARAMETER_H +#define PARAMETER_H + +#include "SpecCore.h" + +namespace Specter { + + //Underlying data + struct ParameterData + { + double value=0.0; + bool validFlag=false; + }; + + //Interface to parameter data + class Parameter + { + + public: + Parameter(); + Parameter(const std::string& name); + ~Parameter(); + + inline bool IsValid() const { return m_pdata->validFlag; } + inline void Invalidate() { m_pdata->validFlag = false; } + inline void SetValue(double value) { m_pdata->validFlag = true; m_pdata->value = value; } + inline double GetValue() const { return m_pdata->value; } + inline const std::string& GetName() const { return m_name; } + void SetName(const std::string& name); + + friend class SpectrumManager; + private: + std::string m_name; + std::shared_ptr m_pdata; + + }; + + //Similar to parameters, sometimes you want to have a numeric input (in calculation terms, a constant) + //which you can use with your analysis. To be able to expose these numeric values to the UI, we need to implement them + //in the manager. To help with this, Variables are atomics. So unlike Parameters they are implicity thread safe on read and write. + //However, this does not mean they can be modified in the analysis! To the AnalysisStage they should be treated as constant, while the UI + //should view them as modifiable. These are real god damn dangerous, but I think the power they offer outweighs the risk, for now. + class Variable + { + public: + Variable(); + Variable(const std::string& name); + ~Variable(); + + inline void SetValue(double value) { *(m_pdata) = value; } + inline double GetValue() { return *(m_pdata); } + inline const std::string& GetName() { return m_name; } + void SetName(const std::string& name); + + friend class SpectrumManager; + private: + std::shared_ptr> m_pdata; + std::string m_name; + }; + + class Scaler + { + public: + Scaler(); + Scaler(const std::string& name); + ~Scaler(); + + inline void Increment() { ++(*m_pdata); } + + inline const std::string& GetName() { return m_name; } + inline uint64_t GetCounts() { return *m_pdata; } + void SetName(const std::string& name); + + friend class SpectrumManager; + private: + std::shared_ptr> m_pdata; + std::string m_name; + }; + +} + +#endif diff --git a/Specter/src/Specter/Core/SpecCore.h b/Specter/src/Specter/Core/SpecCore.h new file mode 100644 index 0000000..3eef7d5 --- /dev/null +++ b/Specter/src/Specter/Core/SpecCore.h @@ -0,0 +1,26 @@ +#ifndef SPECCORE_H +#define SPECCORE_H + +#ifdef SPEC_WINDOWS + #ifdef _MSC_VER + #pragma warning (disable: 4127) // condition expression is constant + #pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when NAV_API is set to__declspec(dllexport) + #pragma warning (disable: 4091) // '__declspec(dllimport)': ignored on left of class 'xxx' when no variable is declared + #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen + #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later + #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types + #endif + #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). + #pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). + #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). + #endif +#endif + + +//Bit field setter +#define BIT(x) (1< decltype(auto) { return this->x(std::forward(args)...); } + +#endif diff --git a/Specter/src/Specter/Core/SpectrumManager.cpp b/Specter/src/Specter/Core/SpectrumManager.cpp new file mode 100644 index 0000000..cf9ef25 --- /dev/null +++ b/Specter/src/Specter/Core/SpectrumManager.cpp @@ -0,0 +1,631 @@ +/* + SpectrumManager.cpp + SpectrumManager is the global resource management class. Controls everything related to spectra (histograms, cuts, parameters). Since + the manager must traverse threads, explicit synchronoization is handled through a mutex. To this end, excessive calls to the manager should be + avoided if possible, as this will increase the lock deadtime in the application, which is especially bad for online data sources. + + Note that SpectrumManager is a singleton. There should only ever be one SpectrumManager with a given application. + + GWM -- Feb 2022 +*/ +#include "SpectrumManager.h" + +#include "implot.h" + +namespace Specter { + + SpectrumManager* SpectrumManager::s_instance = new SpectrumManager(); + + SpectrumManager::SpectrumManager() + { + } + + SpectrumManager::~SpectrumManager() + { + } + + /*************Histogram Functions Begin*************/ + + void SpectrumManager::AddHistogram(const HistogramArgs& params) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + if (params.y_par == "None") //Check dimensionality + m_histoMap[params.name].reset(new Histogram1D(params)); + else + m_histoMap[params.name].reset(new Histogram2D(params)); + } + + void SpectrumManager::AddHistogramSummary(const HistogramArgs& params, const std::vector& subhistos) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + m_histoMap[params.name].reset(new HistogramSummary(params, subhistos)); + } + + void SpectrumManager::RemoveHistogram(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + m_histoMap.erase(name); + } + + void SpectrumManager::AddCutToHistogramDraw(const std::string& cutname, const std::string& histoname) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(histoname); + if (iter != m_histoMap.end()) + iter->second->AddCutToBeDrawn(cutname); + } + + void SpectrumManager::AddCutToHistogramApplied(const std::string& cutname, const std::string& histoname) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(histoname); + if (iter != m_histoMap.end()) + iter->second->AddCutToBeApplied(cutname); + } + + //Use this to fill histograms. Currently can only be filled in bulk; maybe a use case for individual fills? + void SpectrumManager::UpdateHistograms() + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + + //Set state of all cuts for the event + CheckCuts(); + + bool cutFlag; + for (auto& pair : m_histoMap) + { + cutFlag = true; + for (auto& cutname : pair.second->GetParameters().cutsAppliedTo) //check the associated cuts + { + if (!IsCutValid(cutname)) + { + cutFlag = false; + break; + } + } + if (!cutFlag) + continue; + + switch (pair.second->GetType()) + { + case SpectrumType::Histo1D: + { + auto iterX = m_paramMap.find(pair.second->GetXParam()); + if (iterX != m_paramMap.end() && iterX->second->validFlag) + pair.second->FillData(iterX->second->value); + break; + } + case SpectrumType::Histo2D: + { + auto iterX = m_paramMap.find(pair.second->GetXParam()); + auto iterY = m_paramMap.find(pair.second->GetYParam()); + if (iterX != m_paramMap.end() && iterY != m_paramMap.end() && iterX->second->validFlag && iterY->second->validFlag) + pair.second->FillData(iterX->second->value, iterY->second->value); + break; + } + case SpectrumType::Summary: + { + const std::vector& subhistos = std::static_pointer_cast(pair.second)->GetSubHistograms(); + for (size_t i = 0; i < subhistos.size(); i++) + { + auto iterX = m_paramMap.find(subhistos[i]); + if (iterX != m_paramMap.end() && iterX->second->validFlag) + pair.second->FillData(iterX->second->value, i + 0.5); //avoid floating point conversion issues + } + break; + } + case SpectrumType::None: + { + SPEC_WARN("Found a spectrum with None type!"); + break; + } + } + } + + //Reset the state of all cuts in preparation for next event + ResetCutValidities(); + } + + void SpectrumManager::ClearHistograms() + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + for (auto& pair : m_histoMap) + pair.second->ClearData(); + } + + void SpectrumManager::ClearHistogram(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(name); + if (iter != m_histoMap.end()) + iter->second->ClearData(); + } + + void SpectrumManager::DrawHistogram(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(name); + if (iter != m_histoMap.end()) + { + iter->second->Draw(); + for (auto& cutname : iter->second->GetParameters().cutsDrawnUpon) //Draw all cuts made upon the histogram + DrawCut(cutname); + } + } + + const HistogramArgs& SpectrumManager::GetHistogramParams(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(name); + if (iter != m_histoMap.end()) + return iter->second->GetParameters(); + else + return m_nullHistoResult; + } + + //For 2D spectra, we want to allow zooming along the z-axis (color) + float* SpectrumManager::GetColorScaleRange(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(name); + if (iter != m_histoMap.end()) + { + return iter->second->GetColorScaleRange(); + } + + return nullptr; + } + + std::vector SpectrumManager::GetBinData(const std::string& name) + { + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(name); + if (iter != m_histoMap.end()) + { + return iter->second->GetBinData(); + } + + return std::vector(); + } + + std::vector SpectrumManager::GetSubHistograms(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(name); + if (iter != m_histoMap.end() && iter->second->GetType() == SpectrumType::Summary) + { + auto gram = std::static_pointer_cast(iter->second); + return gram->GetSubHistograms(); + } + return std::vector(); + } + + //Pass through for stats + StatResults SpectrumManager::AnalyzeHistogramRegion(const std::string& name, const ImPlotRect& region) + { + std::scoped_lock guard(m_managerMutex); + auto iter = m_histoMap.find(name); + if (iter != m_histoMap.end()) + return iter->second->AnalyzeRegion(region.X.Min, region.X.Max, region.Y.Min, region.Y.Max); + else + return StatResults(); + } + + //This function allows us to obtain the key histogram info in a list, avoiding excessive manager calls and thread-locks + //in something like the Editor. + std::vector SpectrumManager::GetListOfHistograms() + { + std::scoped_lock guard(m_managerMutex); + std::vector list; + list.reserve(m_histoMap.size()); + for (auto& gram : m_histoMap) + { + list.push_back(gram.second->GetParameters()); + } + return list; + } + + /*************Histogram Functions End*************/ + + /*************Graph Functions Begin*************/ + + void SpectrumManager::AddGraph(const GraphArgs& args) + { + std::scoped_lock guard(m_managerMutex); + m_graphMap[args.name].reset(new ScalerGraph(args)); + } + + void SpectrumManager::RemoveGraph(const std::string& name) + { + std::scoped_lock guard(m_managerMutex); + m_graphMap.erase(name); + } + + void SpectrumManager::UpdateGraphs(const Timestep& step) + { + std::scoped_lock guard(m_managerMutex); + uint64_t scalerVal; + for (auto& graph : m_graphMap) + { + auto& scalerIter = m_scalerMap.find(graph.second->GetScaler()); + if (scalerIter != m_scalerMap.end()) + { + scalerVal = *(scalerIter->second); + graph.second->UpdatePoints(step, scalerVal); + } + } + } + + void SpectrumManager::ClearGraphs() + { + std::scoped_lock guard(m_managerMutex); + for (auto& graph : m_graphMap) + graph.second->Clear(); + } + + void SpectrumManager::ClearGraph(const std::string& name) + { + std::scoped_lock guard(m_managerMutex); + auto& iter = m_graphMap.find(name); + if (iter != m_graphMap.end()) + iter->second->Clear(); + } + + void SpectrumManager::DrawGraph(const std::string& name) + { + std::scoped_lock guard(m_managerMutex); + auto& iter = m_graphMap.find(name); + if (iter != m_graphMap.end()) + iter->second->Draw(); + } + + const GraphArgs& SpectrumManager::GetGraphArgs(const std::string& name) + { + std::scoped_lock guard(m_managerMutex); + auto& iter = m_graphMap.find(name); + if (iter != m_graphMap.end()) + return iter->second->GetArgs(); + return m_nullGraphResult; + } + + std::vector SpectrumManager::GetListOfGraphs() + { + std::scoped_lock guard(m_managerMutex); + std::vector list; + list.reserve(m_graphMap.size()); + for (auto& graph : m_graphMap) + list.push_back(graph.second->GetArgs()); + return list; + } + + /*************Graph Functions End*************/ + + /*************Parameter Functions Begin*************/ + + //Bind a Parameter instance to the manager. If the Parameter doesn't exist, make a new one, otherwise attach to extant memory + void SpectrumManager::BindParameter(Parameter& param) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_paramMap.find(param.GetName()); + if (iter == m_paramMap.end()) + { + m_paramMap[param.GetName()].reset(new ParameterData()); + } + + param.m_pdata = m_paramMap[param.GetName()]; + } + + //Bind a Parameter instance to the manager. If the Parameter doesn't exist, make a new one, otherwise attach to extant memory + //Additionally, make a default 1D histogram for the parameter (histogram has same name as parameter) + void SpectrumManager::BindParameter(Parameter& param, int nbins, double minVal, double maxVal) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_paramMap.find(param.GetName()); + if (iter == m_paramMap.end()) + { + m_paramMap[param.GetName()].reset(new ParameterData()); + } + + param.m_pdata = m_paramMap[param.GetName()]; + + auto histoIter = m_histoMap.find(param.GetName()); + if (histoIter == m_histoMap.end()) + { + HistogramArgs histo(param.GetName(), param.GetName(), nbins, minVal, maxVal); + m_histoMap[param.GetName()].reset(new Histogram1D(histo)); + } + } + + //Once an analysis pass is done and histograms filled, reset all parameters + void SpectrumManager::InvalidateParameters() + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + for (auto& param : m_paramMap) + { + param.second->validFlag = false; + param.second->value = 0.0; + } + } + + //Similar to GetListOfHistograms, see that documentation + std::vector SpectrumManager::GetListOfParameters() + { + std::scoped_lock guard(m_managerMutex); + std::vector list; + list.reserve(m_paramMap.size()); + for (auto iter : m_paramMap) + { + list.push_back(iter.first); + } + + return list; + } + + /*************Parameter Functions End*************/ + + /*************Variable Functions Begin*************/ + + void SpectrumManager::BindVariable(Variable& var) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_varMap.find(var.GetName()); + if (iter == m_varMap.end()) + { + m_varMap[var.GetName()].reset(new std::atomic(0.0)); + } + var.m_pdata = m_varMap[var.GetName()]; + } + + std::vector SpectrumManager::GetListOfVariables() + { + std::scoped_lock guard(m_managerMutex); + std::vector list; + list.reserve(m_varMap.size()); + for (auto iter : m_varMap) + list.push_back(iter.first); + return list; + } + + /*************Variable Functions End*************/ + + void SpectrumManager::BindScaler(Scaler& scaler) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + auto iter = m_scalerMap.find(scaler.GetName()); + if (iter == m_scalerMap.end()) + { + m_scalerMap[scaler.GetName()].reset(new std::atomic(0)); + } + scaler.m_pdata = m_scalerMap[scaler.GetName()]; + } + + void SpectrumManager::ResetScalers() + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + for (auto& scaler : m_scalerMap) + { + *(scaler.second) = 0; + } + } + + std::vector SpectrumManager::GetListOfScalers() + { + std::scoped_lock guard(m_managerMutex); + std::vector list; + list.reserve(m_scalerMap.size()); + for (auto& iter : m_scalerMap) + list.push_back(iter.first); + return list; + } + + /*************Cut Functions Begin*************/ + + void SpectrumManager::AddCut(const CutArgs& params, double min, double max) + { + std::scoped_lock guard(m_managerMutex); + m_cutMap[params.name].reset(new Cut1D(params, min, max)); + } + + void SpectrumManager::AddCut(const CutArgs& params, const std::vector& xpoints, const std::vector& ypoints) + { + std::scoped_lock guard(m_managerMutex); + m_cutMap[params.name].reset(new Cut2D(params, xpoints, ypoints)); + } + + void SpectrumManager::AddCut(const CutArgs& params, const std::vector& subhistos, double min, double max) + { + std::scoped_lock guard(m_managerMutex); + m_cutMap[params.name].reset(new CutSummary(params, subhistos, min, max)); + } + + void SpectrumManager::RemoveCut(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_managerMutex); + m_cutMap.erase(name); + RemoveCutFromHistograms(name); //Once a cut is gone, remove all references to it. + } + + std::vector SpectrumManager::GetCutXPoints(const std::string& name) + { + std::scoped_lock guard(m_managerMutex); + std::vector null_result; + auto iter = m_cutMap.find(name); + if (iter != m_cutMap.end()) + return iter->second->GetXValues(); + return null_result; + } + + std::vector SpectrumManager::GetCutYPoints(const std::string& name) + { + std::scoped_lock guard(m_managerMutex); + std::vector null_result; + auto iter = m_cutMap.find(name); + if (iter != m_cutMap.end()) + return iter->second->GetYValues(); + return null_result; + } + + std::vector SpectrumManager::GetCutSubHistograms(const std::string& cutname) + { + std::scoped_lock guard(m_managerMutex); + auto iter = m_cutMap.find(cutname); + if (iter != m_cutMap.end() && iter->second->GetType() == CutType::CutSummaryAny || iter->second->GetType() == CutType::CutSummaryAll) + { + auto cut = std::static_pointer_cast(iter->second); + return cut->GetSubHistograms(); + } + return std::vector(); + } + + //Similar to GetListOfHistograms, see that documentation + std::vector SpectrumManager::GetListOfCuts() + { + std::scoped_lock guard(m_managerMutex); + std::vector list; + list.reserve(m_cutMap.size()); + for (auto& entry : m_cutMap) + list.push_back(entry.second->GetCutArgs()); + return list; + } + + /*************Cut Functions End*************/ + + /* + Private Functions + Can only be called from within the SpectrumManager, therefore the lock should already have been aquired by + whatever parent function calls them. No explicit synchronization. + */ + + //Can only be called by RemoveCut currently. May be a use case where this should be promoted to public to on the fly mod a gram. + void SpectrumManager::RemoveCutFromHistograms(const std::string& cutname) + { + SPEC_PROFILE_FUNCTION(); + for (auto& gram : m_histoMap) + { + auto& params = gram.second->GetParameters(); + for (size_t i = 0; i < params.cutsDrawnUpon.size(); ++i) + { + if (params.cutsDrawnUpon[i] == cutname) + { + params.cutsDrawnUpon.erase(params.cutsDrawnUpon.begin() + i); + } + } + for (size_t i = 0; i < params.cutsAppliedTo.size(); ++i) + { + if (params.cutsAppliedTo[i] == cutname) + { + params.cutsAppliedTo.erase(params.cutsAppliedTo.begin() + i); + } + } + } + } + + //Obv. only need to draw a cut if its parent histogram is drawn. + void SpectrumManager::DrawCut(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + auto iter = m_cutMap.find(name); + if (iter != m_cutMap.end()) + iter->second->Draw(); + } + + //Set the state of the cuts for the current event. Called by the + void SpectrumManager::CheckCuts() + { + SPEC_PROFILE_FUNCTION(); + for (auto& iter : m_cutMap) + { + const std::string& xpar = iter.second->GetXParameter(); + const std::string& ypar = iter.second->GetYParameter(); + switch (iter.second->GetType()) + { + case CutType::Cut1D: + { + auto iterX = m_paramMap.find(xpar); + if (iterX != m_paramMap.end() && iterX->second->validFlag) + iter.second->IsInside(iterX->second->value); + break; + } + case CutType::Cut2D: + { + auto iterX = m_paramMap.find(xpar); + auto iterY = m_paramMap.find(ypar); + if (iterX != m_paramMap.end() && iterX->second->validFlag && iterY != m_paramMap.end() && iterY->second->validFlag) + iter.second->IsInside(iterX->second->value, iterY->second->value); + break; + } + case CutType::CutSummaryAll: + { + std::vector paramlist = std::static_pointer_cast(iter.second)->GetSubHistograms(); + for (auto& param : paramlist) + { + auto iterX = m_paramMap.find(param); + if (iterX != m_paramMap.end() && iterX->second->validFlag) + { + iter.second->IsInside(iterX->second->value); + if (!iter.second->IsValid()) + break; + } + } + break; + } + case CutType::CutSummaryAny: + { + std::vector paramlist = std::static_pointer_cast(iter.second)->GetSubHistograms(); + for (auto& param : paramlist) + { + auto iterX = m_paramMap.find(param); + if (iterX != m_paramMap.end() && iterX->second->validFlag) + { + iter.second->IsInside(iterX->second->value); + if (iter.second->IsValid()) + break; + } + } + break; + } + case CutType::None: + { + SPEC_WARN("Found a cut with None type!"); + break; + } + } + } + } + + bool SpectrumManager::IsCutValid(const std::string& name) + { + SPEC_PROFILE_FUNCTION(); + auto iter = m_cutMap.find(name); + if (iter != m_cutMap.end()) + return iter->second->IsValid(); + return false; + } + + void SpectrumManager::ResetCutValidities() + { + for (auto& iter : m_cutMap) + { + iter.second->ResetValidity(); + } + } +} diff --git a/Specter/src/Specter/Core/SpectrumManager.h b/Specter/src/Specter/Core/SpectrumManager.h new file mode 100644 index 0000000..2334007 --- /dev/null +++ b/Specter/src/Specter/Core/SpectrumManager.h @@ -0,0 +1,126 @@ +/* + SpectrumManager.h + SpectrumManager is the global resource management class. Controls everything related to spectra (histograms, cuts, parameters). Since + the manager must traverse threads, explicit synchronoization is handled through a mutex. To this end, excessive calls to the manager should be + avoided if possible, as this will increase the lock deadtime in the application, which is especially bad for online data sources. + + Note that SpectrumManager is a singleton. There should only ever be one SpectrumManager with a given application. + + GWM -- Feb 2022 +*/ +#ifndef SPECTRUM_MANAGER_H +#define SPECTRUM_MANAGER_H + +#include "Histogram.h" +#include "Cut.h" +#include "Parameter.h" +#include "Graph.h" +#include "Timestep.h" + +#include + +struct ImPlotRect; + +namespace Specter { + + class SpectrumManager + { + public: + SpectrumManager(); + ~SpectrumManager(); + + inline static SpectrumManager& GetInstance() { return *s_instance; } + + //To clear all managed spectra. Note that Parameters are left untouched. + inline void RemoveAllSpectra() + { + std::scoped_lock guard(m_managerMutex); + m_histoMap.clear(); + m_cutMap.clear(); + } + + /*Histogram Functions*/ + void AddHistogram(const HistogramArgs& params); + void AddHistogramSummary(const HistogramArgs& params, const std::vector& subhistos); + void RemoveHistogram(const std::string& name); + void AddCutToHistogramDraw(const std::string& cutname, const std::string& histoname); + void AddCutToHistogramApplied(const std::string& cutname, const std::string& histoname); + void UpdateHistograms(); + void ClearHistograms(); + void ClearHistogram(const std::string& name); + void DrawHistogram(const std::string& name); + const HistogramArgs& GetHistogramParams(const std::string& name); + float* GetColorScaleRange(const std::string& name); + std::vector GetBinData(const std::string& name); + std::vector GetSubHistograms(const std::string& name); + StatResults AnalyzeHistogramRegion(const std::string& name, const ImPlotRect& region); + std::vector GetListOfHistograms(); + /********************/ + + /*ScalerGraph Functions*/ + void AddGraph(const GraphArgs& args); + void RemoveGraph(const std::string& name); + void UpdateGraphs(const Timestep& step); + void ClearGraphs(); + void ClearGraph(const std::string& name); + void DrawGraph(const std::string& name); + const GraphArgs& GetGraphArgs(const std::string& name); + std::vector GetListOfGraphs(); + /********************/ + + /*Parameter Functions*/ + void BindParameter(Parameter& param); + void BindParameter(Parameter& param, int nbins, double maxVal, double minVal); + void InvalidateParameters(); + std::vector GetListOfParameters(); + /*********************/ + + /*Variable Functions*/ + void BindVariable(Variable& var); + std::vector GetListOfVariables(); + /********************/ + + /*Scaler Functions*/ + void BindScaler(Scaler& scaler); + void ResetScalers(); + std::vector GetListOfScalers(); + /******************/ + + /*Cut Functions*/ + void AddCut(const CutArgs& params, double min, double max); + void AddCut(const CutArgs& params, const std::vector& xpoints, const std::vector& ypoints); + void AddCut(const CutArgs& params, const std::vector& subhistos, double min, double max); + void RemoveCut(const std::string& name); + std::vector GetCutXPoints(const std::string& name); + std::vector GetCutYPoints(const std::string& name); + std::vector GetCutSubHistograms(const std::string& cutname); + std::vector GetListOfCuts(); + /**************/ + + private: + //Only used from within manager + void RemoveCutFromHistograms(const std::string& cutname); + void DrawCut(const std::string& name); + void CheckCuts(); + bool IsCutValid(const std::string& name); + void ResetCutValidities(); + + static SpectrumManager* s_instance; + + //Actual data + std::unordered_map> m_histoMap; + std::unordered_map> m_cutMap; + std::unordered_map> m_paramMap; + std::unordered_map>> m_varMap; + std::unordered_map>> m_scalerMap; + std::unordered_map> m_graphMap; + + HistogramArgs m_nullHistoResult; //For handling bad query + GraphArgs m_nullGraphResult; //For handling bad query + + std::mutex m_managerMutex; //synchronization + }; + +} + +#endif diff --git a/Specter/src/Specter/Core/SpectrumSerializer.cpp b/Specter/src/Specter/Core/SpectrumSerializer.cpp new file mode 100644 index 0000000..60df38f --- /dev/null +++ b/Specter/src/Specter/Core/SpectrumSerializer.cpp @@ -0,0 +1,455 @@ +/* + SpectrumSerializer.h + SpectrumSerializer class providing method to write/read spectra (histograms and cuts) to/from a .nav file. These are formated text files. + Note that by virtue of the way that cuts work, they are written first. + + A couple of notes: + - Writing/reading data is a pretty expensive concept from a thread-locking perspective. Not recommended for use during active analysis. + - Deserializing (reading) by default removes all current histograms and cuts. This avoids any issues with collisions, but may not always be desireable. + - There is no intrinsic checking of whether a parameter for a cut/histogram to be loaded exists within the current project. If you load something and the histograms + don't fill, it is most likely due to the parameter not being defined in the current project. + + GWM -- Feb 2022 +*/ +#include "SpectrumSerializer.h" +#include "SpectrumManager.h" + +#include + +namespace Specter { + + SpectrumSerializer::SpectrumSerializer(const std::string& filepath) : + m_filename(filepath) + { + } + + SpectrumSerializer::~SpectrumSerializer() {} + + void SpectrumSerializer::SerializeData(const std::vector& histoList, const std::vector& cutList) + { + SpectrumManager& manager = SpectrumManager::GetInstance(); + + std::ofstream output(m_filename); + if (!output.is_open()) + { + SPEC_ERROR("Unable to open {0} to write data.", m_filename); + return; + } + + output << "begin_cuts" << std::endl; + for (auto& cut : cutList) + { + if (cut.type == CutType::Cut1D) + { + std::vector xpoints = manager.GetCutXPoints(cut.name); + output << "\tbegin_cut1D" << std::endl; + output << "\t\tname: " << cut.name << std::endl; + output << "\t\txparam: " << cut.x_par << std::endl; + output << "\t\tminValue: " << xpoints[0] << std::endl; + output << "\t\tmaxValue: " << xpoints[1] << std::endl; + output << "\tend_cut1D" << std::endl; + } + else if (cut.type == CutType::Cut2D) + { + std::vector xpoints = manager.GetCutXPoints(cut.name); + std::vector ypoints = manager.GetCutYPoints(cut.name); + output << "\tbegin_cut2D" << std::endl; + output << "\t\tname: " << cut.name << std::endl; + output << "\t\txparam: " << cut.x_par << std::endl; + output << "\t\typaram: " << cut.y_par << std::endl; + output << "\t\tbegin_xvalues" << std::endl; + for (const auto& value : xpoints) + { + output << "\t\t\t" << value << std::endl; + } + output << "\t\tend_xvalues" << std::endl; + output << "\t\tbegin_yvalues" << std::endl; + for (const auto& value : ypoints) + { + output << "\t\t\t" << value << std::endl; + } + output << "\t\tend_yvalues" << std::endl; + output << "\tend_cut2D" << std::endl; + } + else if (cut.type == CutType::CutSummaryAll) + { + std::vector subhistos = manager.GetCutSubHistograms(cut.name); + std::vector xpoints = manager.GetCutXPoints(cut.name); + output << "\tbegin_cutSummaryAll" << std::endl; + output << "\t\tname: " << cut.name << std::endl; + output << "\t\tminValue: " << xpoints[0] << std::endl; + output << "\t\tmaxValue: " << xpoints[1] << std::endl; + output << "\t\tbegin_parameters" << std::endl; + for (auto& par : subhistos) + { + output << "\t\t\t" << par << std::endl; + } + output << "\t\tend_parameters" << std::endl; + output << "\tend_cutSummaryAll" << std::endl; + } + else if (cut.type == CutType::CutSummaryAny) + { + std::vector subhistos = manager.GetCutSubHistograms(cut.name); + std::vector xpoints = manager.GetCutXPoints(cut.name); + output << "\tbegin_cutSummaryAny" << std::endl; + output << "\t\tname: " << cut.name << std::endl; + output << "\t\tminValue: " << xpoints[0] << std::endl; + output << "\t\tmaxValue: " << xpoints[1] << std::endl; + output << "\t\tbegin_parameters" << std::endl; + for (auto& par : subhistos) + { + output << "\t\t\t" << par << std::endl; + } + output << "\t\tend_parameters" << std::endl; + output << "\tend_cutSummaryAny" << std::endl; + } + } + output << "end_cuts" << std::endl; + + output << "begin_histograms" << std::endl; + for (auto& params : histoList) + { + if (params.type == SpectrumType::Histo1D) + { + output << "\tbegin_histogram1D" << std::endl; + output << "\t\tname: " << params.name << std::endl; + output << "\t\txparam: " << params.x_par << std::endl; + output << "\t\tNxbins: " << params.nbins_x << std::endl; + output << "\t\tXMin: " << params.min_x << std::endl; + output << "\t\tXMax: " << params.max_x << std::endl; + output << "\t\tbegin_cutsdrawn" << std::endl; + for (const auto& name : params.cutsDrawnUpon) + { + output << "\t\t\t" << name << std::endl; + } + output << "\t\tend_cutsdrawn" << std::endl; + output << "\t\tbegin_cutsapplied" << std::endl; + for (const auto& name : params.cutsAppliedTo) + { + output << "\t\t\t" << name << std::endl; + } + output << "\t\tend_cutsapplied" << std::endl; + output << "\tend_histogram1D" << std::endl; + } + else if (params.type == SpectrumType::Histo2D) + { + output << "\tbegin_histogram2D" << std::endl; + output << "\t\tname: " << params.name << std::endl; + output << "\t\txparam: " << params.x_par << std::endl; + output << "\t\typaram: " << params.y_par << std::endl; + output << "\t\tNxbins: " << params.nbins_x << std::endl; + output << "\t\tXMin: " << params.min_x << std::endl; + output << "\t\tXMax: " << params.max_x << std::endl; + output << "\t\tNybins: " << params.nbins_y << std::endl; + output << "\t\tYMin: " << params.min_y << std::endl; + output << "\t\tYMax: " << params.max_y << std::endl; + output << "\t\tbegin_cutsdrawn" << std::endl; + for (const auto& name : params.cutsDrawnUpon) + { + output << "\t\t\t" << name << std::endl; + } + output << "\t\tend_cutsdrawn" << std::endl; + output << "\t\tbegin_cutsapplied" << std::endl; + for (const auto& name : params.cutsAppliedTo) + { + output << "\t\t\t" << name << std::endl; + } + output << "\t\tend_cutsapplied" << std::endl; + output << "\tend_histogram2D" << std::endl; + } + else if (params.type == SpectrumType::Summary) + { + output << "\tbegin_histogramSummary" << std::endl; + output << "\t\tname: " << params.name << std::endl; + output << "\t\tNxbins: " << params.nbins_x << std::endl; + output << "\t\tXMin: " << params.min_x << std::endl; + output << "\t\tXMax: " << params.max_x << std::endl; + output << "\t\tbegin_subhistos" << std::endl; + std::vector subhistos = manager.GetSubHistograms(params.name); + for (auto& name : subhistos) + { + output << "\t\t\t" << name << std::endl; + } + output << "\t\tend_subhistos" << std::endl; + output << "\t\tbegin_cutsdrawn" << std::endl; + for (const auto& name : params.cutsDrawnUpon) + { + output << "\t\t\t" << name << std::endl; + } + output << "\t\tend_cutsdrawn" << std::endl; + output << "\t\tbegin_cutsapplied" << std::endl; + for (const auto& name : params.cutsAppliedTo) + { + output << "\t\t\t" << name << std::endl; + } + output << "\t\tend_cutsapplied" << std::endl; + output << "\tend_histogram1D" << std::endl; + } + } + output << "end_histograms" << std::endl; + + SPEC_INFO("Successfully saved data to {0}", m_filename); + output.close(); + } + + void SpectrumSerializer::DeserializeData() + { + SpectrumManager& manager = SpectrumManager::GetInstance(); + manager.RemoveAllSpectra(); //When loading in, we remove all extant data, to avoid any potential collisions. + + std::ifstream input(m_filename); + if (!input.is_open()) + { + SPEC_ERROR("Unable to open {0} to read data!", m_filename); + return; + } + + std::string check; + double value_doub; + CutArgs cut_data, reset_cut; + std::vector cut_xdata; + std::vector cut_ydata; + std::vector subhistos; + HistogramArgs hist_data, reset_hist; + + while (input >> check) + { + if (check == "begin_cuts") + { + while (input >> check) + { + cut_data = reset_cut; + cut_xdata.clear(); + cut_ydata.clear(); + subhistos.clear(); + if (check == "begin_cut1D") + { + cut_data.type = CutType::Cut1D; + input >> check >> cut_data.name; + input >> check >> cut_data.x_par; + input >> check >> value_doub; + cut_xdata.push_back(value_doub); + input >> check >> value_doub; + cut_xdata.push_back(value_doub); + input >> check; + manager.AddCut(cut_data, cut_xdata[0], cut_xdata[1]); + } + else if (check == "begin_cut2D") + { + cut_data.type = CutType::Cut2D; + input >> check >> cut_data.name; + input >> check >> cut_data.x_par; + input >> check >> cut_data.y_par; + while (input >> check) + { + if (check == "begin_xvalues") + continue; + else if (check == "end_xvalues") + break; + else + cut_xdata.push_back(std::stod(check)); + } + while (input >> check) + { + if (check == "begin_yvalues") + continue; + else if (check == "end_yvalues") + break; + else + cut_ydata.push_back(std::stod(check)); + } + input >> check; + manager.AddCut(cut_data, cut_xdata, cut_ydata); + } + else if (check == "begin_cutSummaryAll") + { + cut_data.type = CutType::CutSummaryAll; + input >> check >> cut_data.name; + input >> check >> value_doub; + cut_xdata.push_back(value_doub); + input >> check >> value_doub; + cut_xdata.push_back(value_doub); + while (input >> check) + { + if (check == "begin_parameters") + continue; + else if (check == "end_parameters") + break; + else + subhistos.push_back(check); + } + input >> check; + manager.AddCut(cut_data, subhistos, cut_xdata[0], cut_xdata[1]); + } + else if (check == "begin_cutSummaryAny") + { + cut_data.type = CutType::CutSummaryAny; + input >> check >> cut_data.name; + input >> check >> value_doub; + cut_xdata.push_back(value_doub); + input >> check >> value_doub; + cut_xdata.push_back(value_doub); + while (input >> check) + { + if (check == "begin_parameters") + continue; + else if (check == "end_parameters") + break; + else + subhistos.push_back(check); + } + input >> check; + manager.AddCut(cut_data, subhistos, cut_xdata[0], cut_xdata[1]); + } + else if (check == "end_cuts") + break; + else + { + SPEC_ERROR("Deserialization error; unexpected check condition while parsing cut data! Current value: {0}", check); + input.close(); + return; + } + } + } + else if (check == "begin_histograms") + { + while (input >> check) + { + hist_data = reset_hist; + if (check == "begin_histogram1D") + { + hist_data.type = SpectrumType::Histo1D; + input >> check >> hist_data.name; + input >> check >> hist_data.x_par; + input >> check >> hist_data.nbins_x; + input >> check >> hist_data.min_x; + input >> check >> hist_data.max_x; + while (input >> check) + { + if (check == "begin_cutsdrawn") + continue; + else if (check == "end_cutsdrawn") + break; + else + { + hist_data.cutsDrawnUpon.push_back(check); + } + } + while (input >> check) + { + if (check == "begin_cutsapplied") + continue; + else if (check == "end_cutsapplied") + break; + else + { + hist_data.cutsAppliedTo.push_back(check); + } + } + input >> check; + manager.AddHistogram(hist_data); + } + else if (check == "begin_histogram2D") + { + hist_data.type = SpectrumType::Histo2D; + input >> check >> hist_data.name; + input >> check >> hist_data.x_par; + input >> check >> hist_data.y_par; + input >> check >> hist_data.nbins_x; + input >> check >> hist_data.min_x; + input >> check >> hist_data.max_x; + input >> check >> hist_data.nbins_y; + input >> check >> hist_data.min_y; + input >> check >> hist_data.max_y; + while (input >> check) + { + if (check == "begin_cutsdrawn") + continue; + else if (check == "end_cutsdrawn") + break; + else + { + hist_data.cutsDrawnUpon.push_back(check); + } + } + while (input >> check) + { + if (check == "begin_cutsapplied") + continue; + else if (check == "end_cutsapplied") + break; + else + { + hist_data.cutsAppliedTo.push_back(check); + } + } + input >> check; + manager.AddHistogram(hist_data); + } + else if (check == "begin_histogramSummary") + { + subhistos.clear(); + hist_data.type = SpectrumType::Summary; + input >> check >> hist_data.name; + input >> check >> hist_data.nbins_x; + input >> check >> hist_data.min_x; + input >> check >> hist_data.max_x; + while (input >> check) + { + if (check == "begin_subhistos") + continue; + else if (check == "end_subhistos") + break; + else + { + subhistos.push_back(check); + } + } + while (input >> check) + { + if (check == "begin_cutsdrawn") + continue; + else if (check == "end_cutsdrawn") + break; + else + { + hist_data.cutsDrawnUpon.push_back(check); + } + } + while (input >> check) + { + if (check == "begin_cutsapplied") + continue; + else if (check == "end_cutsapplied") + break; + else + { + hist_data.cutsAppliedTo.push_back(check); + } + } + input >> check; + manager.AddHistogramSummary(hist_data, subhistos); + } + else if (check == "end_histograms") + break; + else + { + SPEC_ERROR("Deserialization error; unexpected check condition while parsing histogram data! Current value: {0}", check); + input.close(); + return; + } + } + } + else + { + SPEC_ERROR("Deserialization error; unexpected check condition at top level! Current value: {0}", check); + input.close(); + return; + } + } + + SPEC_INFO("Successfully loaded data from {0}", m_filename); + input.close(); + } + +} \ No newline at end of file diff --git a/Specter/src/Specter/Core/SpectrumSerializer.h b/Specter/src/Specter/Core/SpectrumSerializer.h new file mode 100644 index 0000000..86edfe3 --- /dev/null +++ b/Specter/src/Specter/Core/SpectrumSerializer.h @@ -0,0 +1,39 @@ +/* + SpectrumSerializer.h + SpectrumSerializer class providing method to write/read spectra (histograms and cuts) to/from a .nav file. These are formated text files. + Note that by virtue of the way that cuts work, they are written first. + + A couple of notes: + - Writing/reading data is a pretty expensive concept from a thread-locking perspective. Not recommended for use during active analysis. + - Deserializing (reading) by default removes all current histograms and cuts. This avoids any issues with collisions, but may not always be desireable. + - There is no intrinsic checking of whether a parameter for a cut/histogram to be loaded exists within our framework. If you load something and the histograms + don't fill, it is most likely due to the parameter not being defined in the current project. + + GWM -- Feb 2022 +*/ +#ifndef SPECTRUM_SERIALIZER_H +#define SPECTRUM_SERIALIZER_H + +#include "Histogram.h" +#include "Cut.h" + +namespace Specter { + + class SpectrumSerializer + { + public: + SpectrumSerializer(const std::string& filepath); + ~SpectrumSerializer(); + + void SerializeData(const std::vector& histoList, const std::vector& cutList); + void DeserializeData(); + + inline const std::string& GetFilename() { return m_filename; } + + private: + std::string m_filename; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Core/Timestep.h b/Specter/src/Specter/Core/Timestep.h new file mode 100644 index 0000000..b103819 --- /dev/null +++ b/Specter/src/Specter/Core/Timestep.h @@ -0,0 +1,25 @@ +#ifndef TIMESTEP_H +#define TIMESTEP_H + +namespace Specter { + + class Timestep + { + public: + Timestep(float time=0.0f) : + m_time(time) + { + } + + void SetTime(float time) { m_time = time; } + + operator float() const { return m_time; } + + float GetElapsedSeconds() const { return m_time; } + float GetElapsedMilliseconds() const { return m_time * 1000.0f; } + private: + float m_time; + }; +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Core/Window.h b/Specter/src/Specter/Core/Window.h new file mode 100644 index 0000000..e88757d --- /dev/null +++ b/Specter/src/Specter/Core/Window.h @@ -0,0 +1,53 @@ +/* + Window.h + + Abstract class for a window in Specter. Based entirely upon the work of @TheCherno, see his Hazel repository. This exists primarily to allow for the option + to extend Specter to other rendering backends, most likely Metal, or potentially DX12. See code in Platform for specific implementations (currently only for OpenGL). + + GWM -- Feb 2022 +*/ +#ifndef WINDOW_H +#define WINDOW_H + +#include "SpecCore.h" +#include "Specter/Events/Event.h" + +namespace Specter { + + class WindowProperties + { + public: + unsigned int width, height; + std::string name; + + WindowProperties(const std::string& title="Specter", unsigned int w=1280, unsigned int h=720) : + width(w), height(h), name(title) + { + } + + }; + + class Window + { + public: + + using EventCallbackFunc = std::function; + + virtual ~Window() {} + + virtual void OnUpdate() = 0; + virtual unsigned int GetWidth() const = 0; + virtual unsigned int GetHeight() const = 0; + virtual std::string GetName() const = 0; + + virtual void SetEventCallback(const EventCallbackFunc& function) = 0; + virtual void SetVSync(bool enabled) = 0; + virtual bool IsVSync() const = 0; + + virtual void* GetNativeWindow() const =0; + + static Window* Create(const WindowProperties& props = WindowProperties()); + }; +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Editor/EditorLayer.cpp b/Specter/src/Specter/Editor/EditorLayer.cpp new file mode 100644 index 0000000..6c0315a --- /dev/null +++ b/Specter/src/Specter/Editor/EditorLayer.cpp @@ -0,0 +1,416 @@ +/* + EditorLayer.cpp + Application layer encapsulating the editor for Specter. Written using the Dear ImGui library. Setup based off of @TheCherno's Hazel game engine. + EditorLayer essentially controls the state for UI related actions. + + GWM -- Feb 2022 +*/ +#include "EditorLayer.h" +#include "imgui.h" +#include "misc/cpp/imgui_stdlib.h" +#include "implot.h" +#include "FileDialog.h" +#include "Specter/Core/Application.h" +#include "Specter/Core/SpectrumSerializer.h" +#include "Specter/Core/SpectrumManager.h" + +#include "IconsFontAwesome5.h" + +namespace Specter { + + bool SortByString(const std::string& p1, const std::string& p2) + { + return p1 < p2; + } + + EditorLayer::EditorLayer() : + Layer("EditorLayer"), m_removeHistogram(false), m_removeCut(false), m_exportHistogram(false) + { + } + + EditorLayer::~EditorLayer() {} + + void EditorLayer::OnAttach() + { + } + + void EditorLayer::OnDetach() + { + } + + void EditorLayer::OnUpdate(Timestep& step) + { + } + + void EditorLayer::OnEvent(Event& e) + { + } + + //These updates are used whenever a new object is added to the manager. + void EditorLayer::UpdateHistogramList() + { + m_histoList = SpectrumManager::GetInstance().GetListOfHistograms(); + std::sort(m_histoList.begin(), m_histoList.end(), SortByName); + } + + void EditorLayer::UpdateCutList() + { + m_cutList = SpectrumManager::GetInstance().GetListOfCuts(); + std::sort(m_cutList.begin(), m_cutList.end(), SortByName); + } + + void EditorLayer::UpdateParameterList() + { + m_paramList = SpectrumManager::GetInstance().GetListOfParameters(); + std::sort(m_paramList.begin(), m_paramList.end(), SortByString); + } + + //The main function + void EditorLayer::OnImGuiRender() + { + SPEC_PROFILE_FUNCTION(); + static bool startFlag = true; //first render retrieve base + if(startFlag) + { + UpdateParameterList(); + UpdateHistogramList(); + UpdateCutList(); + startFlag = false; + } + // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, + // because it would be confusing to have two docking targets within each others. + if (opt_fullscreen) + { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + } + + // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background and handle the pass-thru hole, so we ask Begin() to not render a background. + if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) + window_flags |= ImGuiWindowFlags_NoBackground; + + // Important: note that we proceed even if Begin() returns false (aka window is collapsed). + // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive, + // all active windows docked into it will lose their parent and become undocked. + // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise + // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible. + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("MyTestSpace", &dockspaceOpen, window_flags); + ImGui::PopStyleVar(); + + if (opt_fullscreen) + ImGui::PopStyleVar(2); + + // DockSpace + ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle& style = ImGui::GetStyle(); + float minWinSizeX = style.WindowMinSize.x; + style.WindowMinSize.x = 370.0f; + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); + } + + style.WindowMinSize.x = minWinSizeX; + + if(ImGui::BeginMenuBar()) + { + if(ImGui::BeginMenu("File")) + { + if(ImGui::MenuItem(ICON_FA_FOLDER_OPEN "\tOpen")) + { + m_fileDialog.OpenDialog(FileDialog::Type::OpenFile); + } + if(ImGui::MenuItem(ICON_FA_SAVE "\tSave")) + { + m_fileDialog.OpenDialog(FileDialog::Type::SaveFile); + } + if (ImGui::MenuItem(ICON_FA_TIMES_CIRCLE "\tExit")) + { + Application::Get().Close(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Data Source")) + { + if (ImGui::MenuItem(ICON_FA_LINK "\tAttach Source")) + { + m_sourceDialog.OpenSourceDialog(); + } + if (ImGui::MenuItem(ICON_FA_UNLINK "\tDetach Source")) + { + PhysicsStopEvent event; + m_callbackFunc(event); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Add")) + { + if (ImGui::MenuItem(ICON_FA_CHART_BAR "\tSpectrum")) + { + m_spectrumDialog.SetSpectrumDialog(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Remove")) + { + if (ImGui::MenuItem(ICON_FA_CHART_BAR "\tSpectrum")) + { + m_removeHistogram = true; + } + if (ImGui::MenuItem(ICON_FA_CUT "\tCut")) + { + m_removeCut = true; + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Export")) + { + if (ImGui::MenuItem(ICON_FA_SAVE "\tAs .csv")) + { + m_exportHistogram = true; + } + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + //Render all of our sub-windows, dialogs, panels, etc + auto fd_result = m_fileDialog.RenderFileDialog(".spec"); + if (!fd_result.first.empty()) + { + switch (fd_result.second) + { + case FileDialog::Type::OpenFile: + { + SpectrumSerializer serializer(fd_result.first); + serializer.DeserializeData(); + UpdateHistogramList(); + UpdateCutList(); + break; + } + case FileDialog::Type::SaveFile: + { + SPEC_INFO("Found a Save File! {0}", fd_result.first); + SpectrumSerializer serializer(fd_result.first); + serializer.SerializeData(m_histoList, m_cutList); + break; + } + } + } + + + if(m_spectrumDialog.ImGuiRenderSpectrumDialog(m_histoList, m_cutList, m_paramList)) + UpdateHistogramList(); + + m_sourceDialog.ImGuiRenderSourceDialog(); + + RemoveHistogramDialog(); + + RemoveCutDialog(); + + ExportHistogramDialog(); + + if(m_spectrumPanel.OnImGuiRender(m_histoList, m_cutList, m_paramList)) + { + UpdateCutList(); + UpdateHistogramList(); + } + + if (ImGui::Begin(ICON_FA_CHART_BAR " Spectra")) + { + for (auto& params : m_histoList) + { + if (ImGui::TreeNode(params.name.c_str())) + { + ImGui::BulletText("%s", ("X Parameter: "+params.x_par).c_str()); + ImGui::BulletText("X Bins: %d X Min: %f X Max: %f", params.nbins_x, params.min_x, params.max_x); + if (params.y_par != "None") + { + ImGui::BulletText("%s", ("Y Parameter: "+params.y_par).c_str()); + ImGui::BulletText("Y Bins: %d Y Min: %f Y Max: %f", params.nbins_y, params.min_y, params.max_y); + } + if(params.cutsDrawnUpon.size() != 0 && ImGui::TreeNode("Cuts Drawn")) + { + for(auto& cut : params.cutsDrawnUpon) + ImGui::BulletText("%s", cut.c_str()); + ImGui::TreePop(); + } + if(params.cutsAppliedTo.size() != 0 && ImGui::TreeNode("Cuts Applied")) + { + for(auto& cut : params.cutsAppliedTo) + ImGui::BulletText("%s", cut.c_str()); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + } + } + ImGui::End(); + + if(ImGui::Begin(ICON_FA_CUT " Cuts")) + { + for(auto& params : m_cutList) + { + if(ImGui::TreeNode(params.name.c_str())) + { + ImGui::BulletText("%s", ("X Parameter: "+params.x_par).c_str()); + if(params.y_par != "None") + ImGui::BulletText("%s", ("Y Parameter: "+params.y_par).c_str()); + ImGui::TreePop(); + } + } + } + ImGui::End(); + + ImGui::End(); + } + + //Simple dialogs, no need for separate class + + void EditorLayer::RemoveHistogramDialog() + { + SPEC_PROFILE_FUNCTION(); + static std::string selectedGram = ""; + if (m_removeHistogram) + { + selectedGram = ""; + m_removeHistogram = false; + ImGui::OpenPopup("Remove Histogram"); + } + if (ImGui::BeginPopupModal("Remove Histogram")) + { + if (ImGui::BeginCombo("Histogram", selectedGram.c_str())) + { + for (auto& gram : m_histoList) + { + if (ImGui::Selectable(gram.name.c_str(), gram.name == selectedGram, ImGuiSelectableFlags_DontClosePopups)) + selectedGram = gram.name; + } + ImGui::EndCombo(); + } + if (ImGui::Button("Ok")) + { + SpectrumManager::GetInstance().RemoveHistogram(selectedGram); + UpdateHistogramList(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + + void EditorLayer::RemoveCutDialog() + { + SPEC_PROFILE_FUNCTION(); + static std::string selectedCut = ""; + if (m_removeCut) + { + selectedCut = ""; + m_removeCut = false; + ImGui::OpenPopup("Remove Cut"); + } + if (ImGui::BeginPopupModal("Remove Cut")) + { + if (ImGui::BeginCombo("Cut", selectedCut.c_str())) + { + for (auto& cut : m_cutList) + { + if (ImGui::Selectable(cut.name.c_str(), cut.name == selectedCut, ImGuiSelectableFlags_DontClosePopups)) + selectedCut = cut.name; + } + ImGui::EndCombo(); + } + if (ImGui::Button("Ok")) + { + SpectrumManager::GetInstance().RemoveCut(selectedCut); + UpdateHistogramList(); + UpdateCutList(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + + void EditorLayer::ExportHistogramDialog() + { + SPEC_PROFILE_FUNCTION(); + static std::string filename = ""; + static HistogramArgs selectedGram = HistogramArgs(); + if(m_exportHistogram) + { + filename = ""; + selectedGram = HistogramArgs(); + m_exportHistogram = false; + ImGui::OpenPopup("Export Histogram"); + } + if(ImGui::BeginPopupModal("Export Histogram")) + { + if(ImGui::BeginCombo("Histogram", selectedGram.name.c_str())) + { + for (auto& gram : m_histoList) + { + if (ImGui::Selectable(gram.name.c_str(), gram.name == selectedGram.name, ImGuiSelectableFlags_DontClosePopups)) + selectedGram = gram; + } + ImGui::EndCombo(); + } + ImGui::InputText("File", &filename); + ImGui::SameLine(); + if(ImGui::Button("Open")) + { + m_fileDialog.OpenDialog(FileDialog::Type::SaveFile); + } + auto result = m_fileDialog.RenderFileDialog(".csv"); + if(!result.first.empty() && result.second == FileDialog::Type::SaveFile) + filename = result.first; + if(ImGui::Button("Ok")) + { + ExportHistogram(selectedGram, filename); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if(ImGui::Button("Cancel")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + } + + void EditorLayer::ExportHistogram(HistogramArgs selectedGram, const std::string& filename) + { + std::ofstream output(filename); + if(!output.is_open()) + { + SPEC_ERROR("Unable to create export file {0}. Check pathing.", filename); + return; + } + + std::vector data = SpectrumManager::GetInstance().GetBinData(selectedGram.name); + + output<<"Histogram Name,"<; + + EditorLayer(); + ~EditorLayer(); + + void SetEventCallbackFunc(const EventCallbackFunc& f) { m_callbackFunc = f; } + + virtual void OnAttach() override; + virtual void OnDetach() override; + virtual void OnImGuiRender() override; + virtual void OnUpdate(Timestep& step) override; + virtual void OnEvent(Event& event) override; + + + private: + void RemoveCutDialog(); + void RemoveHistogramDialog(); + void ExportHistogramDialog(); + void UpdateHistogramList(); + void UpdateCutList(); + void UpdateParameterList(); //Currently not really used, only once. Params all made at construction time of PhysicsLayer + void ExportHistogram(HistogramArgs selectedGram, const std::string& filename); + + EventCallbackFunc m_callbackFunc; + + SpectrumPanel m_spectrumPanel; + FileDialog m_fileDialog; + SpectrumDialog m_spectrumDialog; + SourceDialog m_sourceDialog; + + + std::vector m_histoList; + std::vector m_cutList; + std::vector m_paramList; + + //ImGui Settings + bool dockspaceOpen = true; + bool opt_fullscreen = true; + bool opt_fullscreen_persistant = true; + ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; + ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + + bool m_removeHistogram; + bool m_removeCut; + bool m_exportHistogram; + }; + + template + bool SortByName(const T& p1, const T& p2) + { + return p1.name < p2.name; + } + +} + +#endif diff --git a/Specter/src/Specter/Editor/FileDialog.cpp b/Specter/src/Specter/Editor/FileDialog.cpp new file mode 100644 index 0000000..a28c0ff --- /dev/null +++ b/Specter/src/Specter/Editor/FileDialog.cpp @@ -0,0 +1,272 @@ +/* + FileDialog.cpp + File dialog window in ImGui using std::filesystem. This is slightly complicated, as file dialogs change function + based on the type of action one wants to perform. In our case we have OpenFile, SaveFile, and OpenDirectory. One can also + specify the kind of file (extension). Use FontAwesome icons. + + Use style: + if(ImGui::Button()) + Set...FileDialog(true); + std::string value = ImGuiRender...(extension); + + GWM -- Feb 2022 +*/ +#include "FileDialog.h" +#include "misc/cpp/imgui_stdlib.h" +#include "IconsFontAwesome5.h" + +namespace Specter { + + //Helper function to handle file size printing + std::string ConvertFileSystemSizeToString(std::uintmax_t value) + { + SPEC_PROFILE_FUNCTION(); + int i = 0; + double mantissa = (double)value; + for (; mantissa >= 1024.0; ++i) + mantissa /= 1024.0; + mantissa = std::ceil(mantissa * 10.0) / 10.0; + return std::to_string(int(mantissa)) + "BKMGTPE"[i]; + } + + FileDialog::FileDialog() : + m_currentPath(std::filesystem::current_path()), m_type(Type::None), m_selectedItem(""), m_openDialogFlag(false) + { + table_flags = ImGuiTableFlags_BordersH | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_RowBg; + select_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_DontClosePopups; + } + + FileDialog::~FileDialog() {} + + //Each type of action has its own render function + + std::pair FileDialog::RenderFileDialog(const std::string& ext) + { + SPEC_PROFILE_FUNCTION(); + if (m_openDialogFlag) + { + m_selectedItem = ""; + m_openDialogFlag = false; + m_currentPath = std::filesystem::current_path(); + ImGui::OpenPopup("File Dialog"); + } + + std::string result = ""; + if (ImGui::BeginPopupModal("File Dialog")) + { + switch (m_type) + { + case Type::OpenFile: + { + result = ImGuiRenderOpenFile(ext); + break; + } + case Type::SaveFile: + { + result = ImGuiRenderSaveFile(ext); + break; + } + case Type::OpenDir: + { + result = ImGuiRenderOpenDir(); + break; + } + case Type::None: break; + } + ImGui::EndPopup(); + } + + return std::make_pair(result, m_type); + } + + std::string FileDialog::ImGuiRenderOpenFile(const std::string& ext) + { + SPEC_PROFILE_FUNCTION(); + std::string result = ""; + std::string text = ""; + + ImGui::Text("%s", ("Current Directory: " + m_currentPath.lexically_normal().string()).c_str()); + ImGui::SameLine(); + ImGui::Text("%s", ("Extension Filter: "+ext).c_str()); + ImGui::InputText("Selected", &m_selectedItem); + if (ImGui::Button("Ok")) + { + std::filesystem::path filepath = m_currentPath / m_selectedItem; + result = filepath.string(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + ImGui::CloseCurrentPopup(); + + if (ImGui::BeginTable("File System", 2, table_flags, ImVec2(-1, -1))) + { + ImGui::TableSetupColumn("Name"); + ImGui::TableSetupColumn("Size"); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(ICON_FA_FOLDER " ..", false, select_flags)) + { + m_selectedItem.clear(); + m_currentPath.append(".."); + } + ImGui::TableNextColumn(); + ImGui::Text("N/A"); + for (auto& entry : std::filesystem::directory_iterator(m_currentPath)) + { + if (entry.is_directory()) + { + ImGui::TableNextRow(); + text = ICON_FA_FOLDER " " + std::filesystem::relative(entry.path(), m_currentPath).string(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(text.c_str(), false, select_flags)) + { + m_selectedItem.clear(); + m_currentPath /= entry.path(); + } + ImGui::TableNextColumn(); + ImGui::Text("N/A"); + } + else if (entry.path().filename().extension() == ext) + { + ImGui::TableNextRow(); + text = ICON_FA_FILE " " + entry.path().filename().string(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(text.c_str(), false, select_flags)) + m_selectedItem = entry.path().filename().string(); + ImGui::TableNextColumn(); + ImGui::Text("%s", ConvertFileSystemSizeToString(entry.file_size()).c_str()); + } + } + ImGui::EndTable(); + } + return result; + } + + std::string FileDialog::ImGuiRenderSaveFile(const std::string& ext) + { + SPEC_PROFILE_FUNCTION(); + std::string result = ""; + std::string text = ""; + + ImGui::Text("%s", ("Current Directory: "+m_currentPath.lexically_normal().string()).c_str()); + ImGui::SameLine(); + ImGui::Text("%s", ("Extension Filter: "+ext).c_str()); + ImGui::InputText("Selected", &m_selectedItem); + if (ImGui::Button("Ok")) + { + std::filesystem::path filepath = m_currentPath / m_selectedItem; + result = filepath.string(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + ImGui::CloseCurrentPopup(); + + if (ImGui::BeginTable("File System", 2, table_flags, ImVec2(-1, -1))) + { + ImGui::TableSetupColumn("Name"); + ImGui::TableSetupColumn("Size"); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(ICON_FA_FOLDER " ..", false, select_flags)) + { + m_selectedItem.clear(); + m_currentPath.append(".."); + } + ImGui::TableNextColumn(); + ImGui::Text("N/A"); + for (auto& entry : std::filesystem::directory_iterator(m_currentPath)) + { + if (entry.is_directory()) + { + ImGui::TableNextRow(); + text = ICON_FA_FOLDER " " + std::filesystem::relative(entry.path(), m_currentPath).string(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(text.c_str(), false, select_flags)) + { + m_selectedItem.clear(); + m_currentPath /= entry.path(); + } + ImGui::TableNextColumn(); + ImGui::Text("N/A"); + } + else if (entry.path().filename().extension() == ext) + { + ImGui::TableNextRow(); + text = ICON_FA_FILE " " + entry.path().filename().string(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(text.c_str(), false, select_flags)) + m_selectedItem = entry.path().filename().string(); + ImGui::TableNextColumn(); + ImGui::Text("%s", ConvertFileSystemSizeToString(entry.file_size()).c_str()); + } + } + ImGui::EndTable(); + } + return result; + } + + std::string FileDialog::ImGuiRenderOpenDir() + { + SPEC_PROFILE_FUNCTION(); + std::string result = ""; + std::string text = ""; + + ImGui::Text("%s", ("Current Directory: "+m_currentPath.lexically_normal().string()).c_str()); + ImGui::InputText("Selected", &m_selectedItem); + if (ImGui::Button("Ok")) + { + result = m_selectedItem; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + ImGui::CloseCurrentPopup(); + + if (ImGui::BeginTable("File System", 2, table_flags, ImVec2(-1, -1))) + { + ImGui::TableSetupColumn("Name"); + ImGui::TableSetupColumn("Size"); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(ICON_FA_FOLDER " ..", false, select_flags)) + { + m_currentPath.append(".."); + m_selectedItem = m_currentPath.string(); + } + ImGui::TableNextColumn(); + ImGui::Text("N/A"); + for (auto& entry : std::filesystem::directory_iterator(m_currentPath)) + { + ImGui::TableNextRow(); + if (entry.is_directory()) + { + text = ICON_FA_FOLDER " " + std::filesystem::relative(entry.path(), m_currentPath).string(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(text.c_str(), false, select_flags)) + { + m_currentPath /= entry.path(); + m_selectedItem = m_currentPath.string(); + } + ImGui::TableNextColumn(); + ImGui::Text("N/A"); + } + else + { + text = ICON_FA_FILE " " + entry.path().filename().string(); + ImGui::TableNextColumn(); + ImGui::Text("%s", text.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", ConvertFileSystemSizeToString(entry.file_size()).c_str()); + } + } + ImGui::EndTable(); + } + return result; + } + +} diff --git a/Specter/src/Specter/Editor/FileDialog.h b/Specter/src/Specter/Editor/FileDialog.h new file mode 100644 index 0000000..38e8391 --- /dev/null +++ b/Specter/src/Specter/Editor/FileDialog.h @@ -0,0 +1,58 @@ +/* + FileDialog.h + File dialog window in ImGui using std::filesystem. This is slightly complicated, as file dialogs change function + based on the type of action one wants to perform. In our case we have OpenFile, SaveFile, and OpenDirectory. One can also + specify the kind of file (extension). Use FontAwesome icons. + + Use style: + if(ImGui::Button()) + Set...FileDialog(true); + std::string value = ImGuiRender...(extension); + + GWM -- Feb 2022 +*/ +#ifndef FILE_DIALOG_H +#define FILE_DIALOG_H + +#include +#include "imgui.h" + +namespace Specter { + + class FileDialog + { + public: + + enum class Type + { + OpenFile, + SaveFile, + OpenDir, + None + }; + + FileDialog(); + ~FileDialog(); + + inline void OpenDialog(Type type) { m_type = type; m_openDialogFlag = true; } + std::pair RenderFileDialog(const std::string& ext = ""); + + private: + std::string ImGuiRenderOpenFile(const std::string& ext); + std::string ImGuiRenderSaveFile(const std::string& ext); + std::string ImGuiRenderOpenDir(); + + std::filesystem::path m_currentPath; + Type m_type; + + std::string m_selectedItem; + + bool m_openDialogFlag; + + ImGuiTableFlags table_flags; + ImGuiSelectableFlags select_flags; + }; + +} + +#endif diff --git a/Specter/src/Specter/Editor/SourceDialog.cpp b/Specter/src/Specter/Editor/SourceDialog.cpp new file mode 100644 index 0000000..ba2be7e --- /dev/null +++ b/Specter/src/Specter/Editor/SourceDialog.cpp @@ -0,0 +1,121 @@ +/* + SourceDialog.cpp + Handles selection of data source type and location specification. Needs to be updated when new source + types are added to Specter. + + GWM -- Feb 2022 +*/ +#include "SourceDialog.h" +#include "Specter/Events/PhysicsEvent.h" +#include "Specter/Events/Event.h" +#include "Specter/Core/Application.h" +#include "Specter/Physics/Caen/CompassHit.h" + +#include "imgui.h" +#include "misc/cpp/imgui_stdlib.h" +#include "IconsFontAwesome5.h" + +namespace Specter { + + SourceDialog::SourceDialog() : + m_openFlag(false), m_chosenPort("51489"), m_chosenWindow(2000000) + { + } + + SourceDialog::~SourceDialog() + { + } + + void SourceDialog::ImGuiRenderSourceDialog() + { + SPEC_PROFILE_FUNCTION(); + static bool onlineFlag = false; + static bool offlineFlag = false; + static std::vector availTypes = { DataSource::SourceType::CompassOnline, DataSource::SourceType::CompassOffline }; + + if (m_openFlag) + { + onlineFlag = false; + offlineFlag = false; + m_openFlag = false; + m_chosenType = DataSource::SourceType::None; + m_chosenLocation = ""; + m_chosenPort = "51489"; + m_chosenWindow = 3000000; + m_bitflags = 0; + m_channels_per_board = 16; + ImGui::OpenPopup(ICON_FA_LINK " Attach Source"); + } + if (ImGui::BeginPopupModal(ICON_FA_LINK " Attach Source")) + { + if (ImGui::BeginCombo("Source Type", ConvertDataSourceTypeToString(m_chosenType).c_str())) + { + for (auto& type : availTypes) + { + if (ImGui::Selectable(ConvertDataSourceTypeToString(type).c_str(), type == m_chosenType, ImGuiSelectableFlags_DontClosePopups)) + { + m_chosenType = type; + } + } + ImGui::EndCombo(); + } + + if (m_chosenType == DataSource::SourceType::CompassOnline) + { + ImGui::InputText("Hostname", &m_chosenLocation); + ImGui::InputText("Port", &m_chosenPort); + if (ImGui::RadioButton("Energy", (m_bitflags & CompassHeaders::Energy) != 0)) + { + m_bitflags = m_bitflags ^ CompassHeaders::Energy; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Energy Short", (m_bitflags & CompassHeaders::EnergyShort) != 0)) + { + m_bitflags = m_bitflags ^ CompassHeaders::EnergyShort; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Energy Calibrated", (m_bitflags & CompassHeaders::EnergyCalibrated) != 0)) + { + m_bitflags = m_bitflags ^ CompassHeaders::EnergyCalibrated; + } + ImGui::InputInt("Channels Per Digitizer Board", &m_channels_per_board); + } + else if (m_chosenType == DataSource::SourceType::CompassOffline) + { + ImGui::InputText("Run Directory", &m_chosenLocation); + if (ImGui::Button("Choose Location")) + { + m_fileDialog.OpenDialog(FileDialog::Type::OpenDir); + } + auto temp = m_fileDialog.RenderFileDialog(); + if (!temp.first.empty() && temp.second == FileDialog::Type::OpenDir) + m_chosenLocation = temp.first; + ImGui::InputInt("Channels Per Digitizer Board", &m_channels_per_board); + } + ImGui::InputInt("Coinc. Window (ps)", &m_chosenWindow); + + + if (ImGui::Button("Ok")) + { + if (m_chosenType == DataSource::SourceType::CompassOffline) + { + PhysicsStartEvent event(m_chosenLocation, m_chosenType, m_chosenWindow, m_chosenPort, false, 0U, m_channels_per_board); + Application::Get().OnEvent(event); + } + else if (m_chosenType == DataSource::SourceType::CompassOnline) + { + PhysicsStartEvent event(m_chosenLocation, m_chosenType, m_chosenWindow, m_chosenPort, true, m_bitflags, m_channels_per_board); + Application::Get().OnEvent(event); + } + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + +} diff --git a/Specter/src/Specter/Editor/SourceDialog.h b/Specter/src/Specter/Editor/SourceDialog.h new file mode 100644 index 0000000..37d7192 --- /dev/null +++ b/Specter/src/Specter/Editor/SourceDialog.h @@ -0,0 +1,38 @@ +/* + SourceDialog.h + Handles selection of data source type and location specification. Needs to be updated when new source + types are added to Specter. + + GWM -- Feb 2022 +*/ +#ifndef SOURCE_DIALOG_H +#define SOURCE_DIALOG_H + +#include "FileDialog.h" +#include "Specter/Physics/DataSource.h" + +namespace Specter { + + class SourceDialog + { + public: + SourceDialog(); + ~SourceDialog(); + + void ImGuiRenderSourceDialog(); + + inline void OpenSourceDialog() { m_openFlag = true; } + private: + bool m_openFlag; + DataSource::SourceType m_chosenType; + std::string m_chosenLocation; + std::string m_chosenPort; + FileDialog m_fileDialog; + uint16_t m_bitflags; + int m_chosenWindow; + int m_channels_per_board; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Editor/SpectrumDialog.cpp b/Specter/src/Specter/Editor/SpectrumDialog.cpp new file mode 100644 index 0000000..17dfc5b --- /dev/null +++ b/Specter/src/Specter/Editor/SpectrumDialog.cpp @@ -0,0 +1,280 @@ +/* + SpectrumDialog.h + Handles creation of new spectra. Pretty much that simple. + + GWM -- Feb 2022 +*/ +#include "SpectrumDialog.h" +#include "Specter/Core/SpectrumManager.h" + +#include "misc/cpp/imgui_stdlib.h" + +#include "IconsFontAwesome5.h" + +namespace Specter { + + SpectrumDialog::SpectrumDialog() : + m_openFlag(false), m_openCutFlag(false) + { + selectFlags = ImGuiSelectableFlags_DontClosePopups; + tableFlags = ImGuiTableFlags_BordersH | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_RowBg; + } + + SpectrumDialog::~SpectrumDialog() + { + } + + bool SpectrumDialog::ImGuiRenderSpectrumDialog(const std::vector& histoList, const std::vector& cutList, const std::vector& paramList) + { + SPEC_PROFILE_FUNCTION(); + static std::string selectedCut = ""; + bool result = false; + if (m_openFlag) + { + m_openFlag = false; + m_newParams = m_blank; + m_subhistos.clear(); + ImGui::OpenPopup(ICON_FA_CHART_BAR " New Spectrum Dialog"); + } + + if (ImGui::BeginPopupModal(ICON_FA_CHART_BAR " New Spectrum Dialog")) + { + ImGui::InputText("Spectrum Name", &m_newParams.name); + if (ImGui::BeginCombo("Spectrum Type", ConvertSpectrumTypeToString(m_newParams.type).c_str())) + { + if (ImGui::Selectable("Histogram1D", m_newParams.type == SpectrumType::Histo1D, selectFlags)) + m_newParams.type = SpectrumType::Histo1D; + else if (ImGui::Selectable("Histogram2D", m_newParams.type == SpectrumType::Histo2D, selectFlags)) + m_newParams.type = SpectrumType::Histo2D; + else if (ImGui::Selectable("Summary", m_newParams.type == SpectrumType::Summary, selectFlags)) + m_newParams.type = SpectrumType::Summary; + ImGui::EndCombo(); + } + + switch (m_newParams.type) + { + case SpectrumType::Histo1D: RenderDialog1D(paramList); break; + case SpectrumType::Histo2D: RenderDialog2D(paramList); break; + case SpectrumType::Summary: RenderDialogSummary(paramList); break; + case SpectrumType::None: break; + } + + if (ImGui::TreeNode("Applied Cuts")) + { + for (auto& name : m_newParams.cutsAppliedTo) + { + ImGui::BulletText("%s", name.c_str()); + } + ImGui::TreePop(); + } + + if (ImGui::Button("Apply Cuts")) + { + m_openCutFlag = true; + } + RenderCutDialog(cutList); + + if (ImGui::Button("Ok")) + { + switch (m_newParams.type) + { + case SpectrumType::Histo1D: SpectrumManager::GetInstance().AddHistogram(m_newParams); break; + case SpectrumType::Histo2D: SpectrumManager::GetInstance().AddHistogram(m_newParams); break; + case SpectrumType::Summary: SpectrumManager::GetInstance().AddHistogramSummary(m_newParams, m_subhistos); break; + case SpectrumType::None: break; + } + ImGui::CloseCurrentPopup(); + result = true; + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + result = false; + } + ImGui::EndPopup(); + } + return result; + } + + void SpectrumDialog::RenderDialog1D(const std::vector& paramList) + { + if (ImGui::BeginTable("SpecParamsTable", 4)) + { + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("X Param.", m_newParams.x_par.c_str())) + { + for (auto& params : paramList) + { + if (ImGui::Selectable(params.c_str(), params == m_newParams.x_par, selectFlags)) + m_newParams.x_par = params; + } + ImGui::EndCombo(); + } + ImGui::TableNextColumn(); + ImGui::InputInt("X Bins", &m_newParams.nbins_x); + ImGui::TableNextColumn(); + ImGui::InputDouble("Min X", &m_newParams.min_x); + ImGui::TableNextColumn(); + ImGui::InputDouble("Max X", &m_newParams.max_x); + + ImGui::EndTable(); + } + } + + void SpectrumDialog::RenderDialog2D(const std::vector& paramList) + { + if (ImGui::BeginTable("SpecParamsTable", 4)) + { + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("X Param.", m_newParams.x_par.c_str())) + { + for (auto& params : paramList) + { + if (ImGui::Selectable(params.c_str(), params == m_newParams.x_par, selectFlags)) + m_newParams.x_par = params; + } + ImGui::EndCombo(); + } + ImGui::TableNextColumn(); + ImGui::InputInt("X Bins", &m_newParams.nbins_x); + ImGui::TableNextColumn(); + ImGui::InputDouble("Min X", &m_newParams.min_x); + ImGui::TableNextColumn(); + ImGui::InputDouble("Max X", &m_newParams.max_x); + + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("Y Param.", m_newParams.y_par.c_str())) + { + for (auto& params : paramList) + { + if (ImGui::Selectable(params.c_str(), params == m_newParams.y_par, selectFlags)) + m_newParams.y_par = params; + } + ImGui::EndCombo(); + } + ImGui::TableNextColumn(); + ImGui::InputInt("Y Bins", &m_newParams.nbins_y); + ImGui::TableNextColumn(); + ImGui::InputDouble("Min Y", &m_newParams.min_y); + ImGui::TableNextColumn(); + ImGui::InputDouble("Max Y", &m_newParams.max_y); + + + ImGui::EndTable(); + } + } + + void SpectrumDialog::RenderDialogSummary(const std::vector& paramList) + { + if (ImGui::BeginTable("SpecParamsTable", 3)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::InputInt("X Bins", &m_newParams.nbins_x); + ImGui::TableNextColumn(); + ImGui::InputDouble("Min X", &m_newParams.min_x); + ImGui::TableNextColumn(); + ImGui::InputDouble("Max X", &m_newParams.max_x); + ImGui::EndTable(); + } + if (ImGui::TreeNode("Selected Parameters")) + { + for (auto& name : m_subhistos) + { + ImGui::BulletText("%s", name.c_str()); + } + ImGui::TreePop(); + } + if (ImGui::Button("Add Parameter")) + { + m_subhistos.clear(); + ImGui::OpenPopup("Param List"); + } + if (ImGui::BeginPopupModal("Param List")) + { + if (ImGui::Button("Ok")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + m_subhistos.clear(); + ImGui::CloseCurrentPopup(); + } + + auto check_func = [this](const std::string& name) + { + for (auto& par : m_subhistos) + { + if (name == par) + return true; + } + return false; + }; + + if (ImGui::BeginTable("Parameters", 1, tableFlags, ImVec2(-1, -1))) + { + for (auto& param : paramList) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(param.c_str(), check_func(param), selectFlags)) + { + if (!check_func(param)) + { + m_subhistos.push_back(param); + } + else + { + auto iter = std::remove(m_subhistos.begin(), m_subhistos.end(), param); + m_subhistos.erase(iter, m_subhistos.end()); + } + } + } + + ImGui::EndTable(); + } + + ImGui::EndPopup(); + } + } + + void SpectrumDialog::RenderCutDialog(const std::vector& cutList) + { + static std::string selectedCut = ""; + if (m_openCutFlag) + { + selectedCut = ""; + m_openCutFlag = false; + ImGui::OpenPopup("Cut List"); + } + if (ImGui::BeginPopup("Cut List")) + { + for (auto& cut : cutList) + { + if (ImGui::Selectable(cut.name.c_str(), cut.name == selectedCut, selectFlags)) + selectedCut = cut.name; + } + ImGui::InputText("Selected Cut", &selectedCut); + if (ImGui::Button("Ok")) + { + m_newParams.cutsAppliedTo.push_back(selectedCut); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } +} \ No newline at end of file diff --git a/Specter/src/Specter/Editor/SpectrumDialog.h b/Specter/src/Specter/Editor/SpectrumDialog.h new file mode 100644 index 0000000..873d113 --- /dev/null +++ b/Specter/src/Specter/Editor/SpectrumDialog.h @@ -0,0 +1,45 @@ +/* + SpectrumDialog.h + Handles creation of new spectra. Pretty much that simple. + + GWM -- Feb 2022 +*/ +#ifndef SPECTRUM_DIALOG_H +#define SPECTRUM_DIALOG_H + +#include "Specter/Core/Histogram.h" +#include "Specter/Core/Cut.h" + +#include "imgui.h" + + +namespace Specter { + + class SpectrumDialog + { + public: + SpectrumDialog(); + ~SpectrumDialog(); + + bool ImGuiRenderSpectrumDialog(const std::vector& histoList, const std::vector& cutList, const std::vector& paramList); + + inline void SetSpectrumDialog() { m_openFlag = true; } + private: + void RenderDialog1D(const std::vector& paramList); + void RenderDialog2D(const std::vector& paramList); + void RenderDialogSummary(const std::vector& paramList); + void RenderCutDialog(const std::vector& cutList); + + bool m_openFlag; + bool m_openCutFlag; + HistogramArgs m_newParams; + HistogramArgs m_blank; + std::vector m_subhistos; + + ImGuiSelectableFlags selectFlags; + ImGuiTableFlags tableFlags; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Editor/SpectrumPanel.cpp b/Specter/src/Specter/Editor/SpectrumPanel.cpp new file mode 100644 index 0000000..8351a69 --- /dev/null +++ b/Specter/src/Specter/Editor/SpectrumPanel.cpp @@ -0,0 +1,393 @@ +/* + SpectrumPanel.cpp + This is the big boi. Renders a panel holding all of the drawn plots. Good news is that in general only a few things really require + any modification if new types of plots are to be rendered, basically just the zoomed in spectrum rendering. + + GWM -- Feb 2022 +*/ +#include "SpectrumPanel.h" +#include "Specter/Core/SpectrumManager.h" +#include "misc/cpp/imgui_stdlib.h" +#include "IconsFontAwesome5.h" + +namespace Specter { + + //Convert a StatResults struct from analysis to a std::string helper function + std::string GenerateStatString(const std::string& name, const StatResults& results, bool is2D = true) + { + SPEC_PROFILE_FUNCTION(); + std::stringstream stream; + stream << "Region: " << name << "\n" << "Integral: " << results.integral << "\n"; + if (results.integral == 0.0) + return stream.str(); + stream << "Centroid X: " << results.cent_x << "\nStd. Dev. X: " << results.sigma_x << "\nFWHM X: " << 2.355 * results.sigma_x << "\n"; + if(is2D) + stream << "Centroid Y: " << results.cent_y << "\nStd. Dev. Y: " << results.sigma_y << "\nFWHM Y: " << 2.355 * results.sigma_y << "\n"; + return stream.str(); + } + + SpectrumPanel::SpectrumPanel() : + m_zoomedFlag(false), m_cutModeFlag(false), m_acceptCutFlag(false), m_zoomedGram(), m_totalSlots(1), m_nRegions(0) + { + m_tableSizes[0] = 1; m_tableSizes[1] = 1; + } + + SpectrumPanel::~SpectrumPanel() {} + + //Main render function. Handles generating subplot regions as well as the zoomed in region + bool SpectrumPanel::OnImGuiRender(const std::vector& histoList, const std::vector& cutList, const std::vector& paramList) + { + SPEC_PROFILE_FUNCTION(); + static bool acceptCutFlag = false; + m_result = false; + if (ImGui::Begin("Active View")) + { + if (histoList.size() > 0) + { + if (m_zoomedFlag && m_zoomedGram.type != SpectrumType::None) + { + RenderCutButton(); + ImGui::SameLine(); + if(ImGui::Button("Clear")) + { + SpectrumManager::GetInstance().ClearHistogram(m_zoomedGram.name); + } + ImGui::SameLine(); + RenderRemoveRegionButton(); + if (m_zoomedGram.type == SpectrumType::Histo2D || m_zoomedGram.type == SpectrumType::Summary) + { + float* scale = SpectrumManager::GetInstance().GetColorScaleRange(m_zoomedGram.name); + ImGui::DragFloatRange2("Min / Max", &(scale[0]), &(scale[1]), 0.01f); + ImPlot::ColormapScale("##HistogramScale", scale[0], scale[1], ImVec2(0, -1), ImPlotColormap_Viridis); + ImGui::SameLine(); + } + if (ImPlot::BeginPlot(m_zoomedGram.name.c_str(), ImVec2(-1, -1))) + { + SpectrumManager::GetInstance().DrawHistogram(m_zoomedGram.name); + if (!m_cutModeFlag && ImPlot::IsPlotHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + m_zoomedFlag = false; + m_zoomedGram = HistogramArgs(); + } + else if (m_cutModeFlag) + { + HandleCutMode(); + } + + if (ImPlot::IsPlotSelected()) { + auto select = ImPlot::GetPlotSelection(); + if (ImGui::IsMouseClicked(ImPlot::GetInputMap().SelectCancel)) { + ImPlot::CancelPlotSelection(); + m_integralRegions.emplace_back(select, "integralRegion_"+std::to_string(m_nRegions), m_zoomedGram.name); + m_nRegions++; + } + } + for (size_t i = 0; i < m_integralRegions.size(); i++) + { + auto& region = m_integralRegions[i]; + if (m_zoomedGram.name == region.histogram_name) + { + ImPlot::DragRect(int(i), ®ion.region.X.Min, ®ion.region.Y.Min, ®ion.region.X.Max, ®ion.region.Y.Max, ImVec4(1, 0, 1, 1), ImPlotDragToolFlags_NoFit); + StatResults results = SpectrumManager::GetInstance().AnalyzeHistogramRegion(m_zoomedGram.name, region.region); + ImPlot::PlotText(GenerateStatString(region.name, results, m_zoomedGram.y_par != "None").c_str(), (region.region.X.Max + region.region.X.Min) * 0.5, + (region.region.Y.Min + region.region.Y.Max) * 0.5); + } + } + ImPlot::EndPlot(); + } + RenderAcceptCutDialog(); + } + else + { + ImGui::SliderInt2("Rows, Columns", m_tableSizes, 1, 3); + ImGui::SameLine(); + if (ImGui::Button("Clear All")) + { + SpectrumManager::GetInstance().ClearHistograms(); + } + m_totalSlots = m_tableSizes[0] * m_tableSizes[1]; + m_selectedGrams.resize(m_totalSlots); + if (ImGui::BeginTable("Select Histograms", m_tableSizes[1])) + { + std::string label; + int this_gram; + for (int i = 0; i < m_tableSizes[0]; i++) + { + ImGui::TableNextRow(); + for (int j = 0; j < m_tableSizes[1]; j++) + { + ImGui::TableNextColumn(); + this_gram = i * m_tableSizes[1] + j; + label = "Histogram" + std::to_string(this_gram); + if (ImGui::BeginCombo(label.c_str(), m_selectedGrams[this_gram].name.c_str())) + { + for (auto& params : histoList) + { + if (ImGui::Selectable(params.name.c_str(), params.name == m_selectedGrams[this_gram].name)) + m_selectedGrams[this_gram] = params; + } + ImGui::EndCombo(); + } + } + } + ImGui::EndTable(); + } + + if (ImPlot::BeginSubplots("Histograms", m_tableSizes[0], m_tableSizes[1], ImVec2(-1, -1))) + { + int i = 0; + for (auto& spec : m_selectedGrams) + { + if (ImPlot::BeginPlot(spec.name.c_str())) + { + SpectrumManager::GetInstance().DrawHistogram(spec.name); + if (ImPlot::IsPlotHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + m_zoomedFlag = true; + m_zoomedGram = spec; + } + + for (size_t i = 0; i < m_integralRegions.size(); i++) + { + auto& region = m_integralRegions[i]; + if (spec.name == region.histogram_name) + { + ImPlot::DragRect(int(i), ®ion.region.X.Min, ®ion.region.Y.Min, ®ion.region.X.Max, ®ion.region.Y.Max, ImVec4(1, 0, 1, 1)); + } + } + ImPlot::EndPlot(); + } + i++; + } + ImPlot::EndSubplots(); + } + } + } + } + ImGui::End(); + return m_result; + } + + void SpectrumPanel::HandleCutMode() + { + switch (m_zoomedGram.type) + { + case SpectrumType::Histo1D: + { + if (m_newCutX.size() == 2) + { + m_acceptCutFlag = true; + } + else if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + m_newCutX.push_back(ImPlot::GetPlotMousePos().x); + } + ImPlot::PlotVLines(m_newCutArgs.name.c_str(), m_newCutX.data(), int(m_newCutX.size())); + break; + } + case SpectrumType::Histo2D: + { + if (m_newCutX.size() >= 2 && ImPlot::IsPlotHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + m_acceptCutFlag = true; + m_newCutX.push_back(m_newCutX[0]); + m_newCutY.push_back(m_newCutY[0]); + } + else if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + auto point = ImPlot::GetPlotMousePos(); + m_newCutX.push_back(point.x); + m_newCutY.push_back(point.y); + } + ImPlot::PlotLine(m_newCutArgs.name.c_str(), m_newCutX.data(), m_newCutY.data(), int(m_newCutX.size())); + break; + } + case SpectrumType::Summary: + { + if (m_newCutX.size() == 2) + { + m_acceptCutFlag = true; + } + else if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + m_newCutX.push_back(ImPlot::GetPlotMousePos().x); + } + ImPlot::PlotVLines(m_newCutArgs.name.c_str(), m_newCutX.data(), int(m_newCutX.size())); + break; + } + case SpectrumType::None: + { + m_cutModeFlag = false; + break; + } + } + } + + void SpectrumPanel::RenderAcceptCutDialog() + { + if (m_acceptCutFlag) + { + m_acceptCutFlag = false; + m_cutModeFlag = false; + ImGui::OpenPopup("Accept Cut"); + } + if (ImGui::BeginPopupModal("Accept Cut")) + { + ImGui::Text("Save this Cut?"); + if (ImGui::Button("Yes")) + { + SpectrumManager& manager = SpectrumManager::GetInstance(); + switch (m_newCutArgs.type) + { + case CutType::Cut1D: + { + std::sort(m_newCutX.begin(), m_newCutX.end()); + manager.AddCut(m_newCutArgs, m_newCutX[0], m_newCutX[1]); + manager.AddCutToHistogramDraw(m_newCutArgs.name, m_zoomedGram.name); + break; + } + case CutType::Cut2D: + { + manager.AddCut(m_newCutArgs, m_newCutX, m_newCutY); + manager.AddCutToHistogramDraw(m_newCutArgs.name, m_zoomedGram.name); + break; + } + case CutType::CutSummaryAny: + { + std::sort(m_newCutX.begin(), m_newCutX.end()); + std::vector subhistos = manager.GetSubHistograms(m_zoomedGram.name); + manager.AddCut(m_newCutArgs, subhistos, m_newCutX[0], m_newCutX[1]); + manager.AddCutToHistogramDraw(m_newCutArgs.name, m_zoomedGram.name); + break; + } + case CutType::CutSummaryAll: + { + std::sort(m_newCutX.begin(), m_newCutX.end()); + std::vector subhistos = manager.GetSubHistograms(m_zoomedGram.name); + manager.AddCut(m_newCutArgs, subhistos, m_newCutX[0], m_newCutX[1]); + manager.AddCutToHistogramDraw(m_newCutArgs.name, m_zoomedGram.name); + break; + } + case CutType::None: + { + SPEC_ERROR("Trying to add None type cut to manager at SpectrumPanel::RenderAcceptCutDialog!"); + break; + } + } + ImGui::CloseCurrentPopup(); + m_result = true; + } + ImGui::SameLine(); + if (ImGui::Button("No")) + { + ImGui::CloseCurrentPopup(); + m_result = false; + } + ImGui::EndPopup(); + } + } + + //Renders Cut button as well as dialog for creating cuts. + void SpectrumPanel::RenderCutButton() + { + if (ImGui::Button(ICON_FA_CUT " Draw Cut")) + { + m_newCutArgs = CutArgs(); + m_newCutX.resize(0); + m_newCutY.resize(0); + ImGui::OpenPopup(ICON_FA_CUT " New Cut Dialog"); + } + if (ImGui::BeginPopupModal(ICON_FA_CUT " New Cut Dialog")) + { + m_newCutArgs.x_par = m_zoomedGram.x_par; + m_newCutArgs.y_par = m_zoomedGram.y_par; + switch (m_zoomedGram.type) + { + case SpectrumType::Histo1D: + { + m_newCutArgs.type = CutType::Cut1D; + ImGui::BulletText("%s", ("X Parameter: " + m_newCutArgs.x_par).c_str()); + break; + } + case SpectrumType::Histo2D: + { + m_newCutArgs.type = CutType::Cut2D; + ImGui::BulletText("%s", ("X Parameter: " + m_newCutArgs.x_par).c_str()); + ImGui::BulletText("%s", ("Y Parameter: " + m_newCutArgs.y_par).c_str()); + break; + } + case SpectrumType::Summary: + { + if (ImGui::RadioButton("CutSummaryAny", m_newCutArgs.type == CutType::CutSummaryAny)) + m_newCutArgs.type = CutType::CutSummaryAny; + ImGui::SameLine(); + if (ImGui::RadioButton("CutSummaryAll", m_newCutArgs.type == CutType::CutSummaryAll)) + m_newCutArgs.type = CutType::CutSummaryAll; + break; + } + case SpectrumType::None: m_newCutArgs.type = CutType::None; break; + } + ImGui::InputText("Cut Name", &m_newCutArgs.name); + if (ImGui::Button("Accept & Draw")) + { + m_cutModeFlag = true; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + + //Simple button/dialog for removing integration regions + void SpectrumPanel::RenderRemoveRegionButton() + { + static std::string selectedRegion = ""; + if (ImGui::Button("Delete Region")) + { + selectedRegion = ""; + ImGui::OpenPopup("Remove Integral Region"); + } + if (ImGui::BeginPopupModal("Remove Integral Region")) + { + if (ImGui::BeginCombo("Region", selectedRegion.c_str())) + { + for (auto& region : m_integralRegions) + { + if (region.histogram_name == m_zoomedGram.name) + { + if (ImGui::Selectable(region.name.c_str(), region.name == selectedRegion, ImGuiSelectableFlags_DontClosePopups)) + selectedRegion = region.name; + } + } + ImGui::EndCombo(); + } + if (ImGui::Button("Ok")) + { + RemoveSelectedRegion(selectedRegion); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + + void SpectrumPanel::RemoveSelectedRegion(const std::string& region) + { + for (size_t i=0; i& histoList, const std::vector& cutList, const std::vector& paramList); + inline const std::string& GetZoomedOnHistogram() { return m_zoomedGram.name; } + inline const bool IsZoomed() { return m_zoomedFlag; } + + private: + void HandleCutMode(); + void RenderAcceptCutDialog(); + void RenderCutButton(); + void RenderRemoveRegionButton(); + void RemoveSelectedRegion(const std::string& region); + std::vector m_selectedGrams; + std::vector m_integralRegions; + bool m_zoomedFlag; + bool m_cutModeFlag; + bool m_acceptCutFlag; + bool m_result; + HistogramArgs m_zoomedGram; + int m_tableSizes[2]; + int m_totalSlots; + int m_nRegions; + CutArgs m_newCutArgs; + std::vector m_newCutX; + std::vector m_newCutY; + }; + +} + +#endif diff --git a/Specter/src/Specter/Events/AppEvent.h b/Specter/src/Specter/Events/AppEvent.h new file mode 100644 index 0000000..ac03af3 --- /dev/null +++ b/Specter/src/Specter/Events/AppEvent.h @@ -0,0 +1,59 @@ +/* + AppEvent.h + Events related to the main application (window events mostly). Again, based on @TheCherno's work. + + GWM -- Feb 2022 +*/ +#ifndef APP_EVENT_H +#define APP_EVENT_H + +#include "Event.h" + +namespace Specter { + + // Window closing is pure event (no data) + class WindowCloseEvent : public Event + { + public: + WindowCloseEvent() {}; + + EVENT_CATEGORY_SETUP(EventCategoryApp) + EVENT_TYPE_SETUP(WindowClose) + }; + + class WindowResizeEvent : public Event + { + public: + WindowResizeEvent(int x, int y) : + m_xSize(x), m_ySize(y) + { + } + + inline int GetXSize() { return m_xSize; } + inline int GetYSize() { return m_ySize; } + std::string ToString() const override + { + std::stringstream ss; + ss << GetName() << " to size: (" << m_xSize << ", " << m_ySize << ")"; + return ss.str(); + } + + EVENT_CATEGORY_SETUP(EventCategoryApp) + EVENT_TYPE_SETUP(WindowResize) + + private: + int m_xSize, m_ySize; + }; + + class AppUpdateEvent : public Event + { + public: + AppUpdateEvent() = default; + + EVENT_CATEGORY_SETUP(EventCategoryApp) + EVENT_TYPE_SETUP(AppUpdate) + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Events/Event.h b/Specter/src/Specter/Events/Event.h new file mode 100644 index 0000000..d2ca7ef --- /dev/null +++ b/Specter/src/Specter/Events/Event.h @@ -0,0 +1,89 @@ +/* + Event.h + Event system for Specter. Based entirely upon the work of @TheCherno in his game engine series, with additons specific to + Specter. Abstract Event class and an EventDispatcher. EventDispatcher links a function to the event for handling. See Application::OnEvent for + an example of use. + + GWM -- Feb 2022 +*/ +#ifndef EVENT_H +#define EVENT_H + +#include "Specter/Core/SpecCore.h" + +namespace Specter { + + enum class EventType + { + None=0, + WindowClose, WindowResize, WindowFocus, WindowLostFocus, WindowMoved, + KeyPressed, KeyReleased, KeyTyped, + MouseButtonPressed, MouseButtonReleased, MouseScrolled, MouseMoved, + AppUpdate, + PhysicsStart, PhysicsStop, PhysicsParam + }; + + enum EventCategory + { + EventCategoryNone=0, + EventCategoryApp=BIT(0), + EventCategoryInput=BIT(1), + EventCategoryKey=BIT(2), + EventCategoryMouse=BIT(3), + EventCategoryWindow=BIT(4), + EventCategoryPhysics=BIT(5), + }; + + //Some function generation automation to reduce code written for all events + #define EVENT_CATEGORY_SETUP(cat) virtual int GetCategoryFlags() const override { return cat; } + + #define EVENT_TYPE_SETUP(type) static EventType GetStaticType() { return EventType::type; } \ + virtual EventType GetEventType() const override { return GetStaticType(); } \ + virtual const char* GetName() const override { return #type; } + + class Event + { + friend class EventDispatcher; + public: + virtual EventType GetEventType() const = 0; + virtual const char* GetName() const = 0; + virtual int GetCategoryFlags() const = 0; + virtual std::string ToString() const { return GetName(); } + inline bool IsCategory(EventCategory cat) const { return GetCategoryFlags() & cat; } + bool handledFlag = false; + }; + + class EventDispatcher + { + public: + EventDispatcher(Event& e) : + m_event(e) + { + } + + template + bool Dispatch(const F& function) + { + if(m_event.GetEventType() == T::GetStaticType()) + { + m_event.handledFlag = function(static_cast(m_event)); + return true; + } + else + { + return false; + } + } + + private: + Event& m_event; + }; + + //For easy printing + inline std::ostream& operator<<(std::ostream& os, const Event& e) + { + return os << e.ToString(); + } +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Events/KeyEvent.h b/Specter/src/Specter/Events/KeyEvent.h new file mode 100644 index 0000000..2212498 --- /dev/null +++ b/Specter/src/Specter/Events/KeyEvent.h @@ -0,0 +1,95 @@ +/* + KeyEvent.h + Events related to key presses. Again, based on @TheCherno's work. + + GWM -- Feb 2022 +*/ +#ifndef KEY_EVENT_H +#define KEY_EVENT_H + +#include "Event.h" + +namespace Specter { + + //Since so many key related events, have a base + class KeyEvent : public Event + { + public: + inline int GetKeycode() const { return m_keycode; } + EVENT_CATEGORY_SETUP(EventCategoryKey | EventCategoryInput) + protected: + KeyEvent(int code) : + m_keycode(code) + { + } + + int m_keycode; + + }; + + class KeyPressedEvent : public KeyEvent + { + public: + KeyPressedEvent(int code, int count) : + KeyEvent(code), m_repeatCount(count) + { + } + + EVENT_TYPE_SETUP(KeyPressed) + + inline int GetRepeatCount() const { return m_repeatCount; } + + std::string ToString() const override + { + std::stringstream stream; + stream << GetName() << " with code "< +#include +#include "IconsFontAwesome5.h" + +namespace Specter { + + ImGuiLayer::ImGuiLayer() : + Layer("ImGuiLayer") + { + } + + ImGuiLayer::~ImGuiLayer() + { + } + + void ImGuiLayer::OnAttach() + { + SPEC_PROFILE_FUNCTION(); + IMGUI_CHECKVERSION(); + + SPEC_INFO("Creating ImGui Context..."); + + ImGui::CreateContext(); + ImPlot::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + //Viewports are real wonky on Linux and sometimes on MacOS + //Can currently cause assertion failure on checking number of monitors in ImGui sanity checks. + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; + + ImGui::StyleColorsDark(); //Hacker mode + ImPlot::StyleColorsDark(); + + ImGuiStyle& style = ImGui::GetStyle(); + + if(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + style.WindowRounding = 0.0f; + style.Colors[ImGuiCol_WindowBg].w = 1.0f; + } + + //Setup our fonts. We have Roboto for text and FontAwesome for our icons. + //Note the .ttf files are found in NavProject, or in the bin dir. This is because + //the actual program (NavProject) is launched from either the bin/ ... /NaProject or the NavProject directory + //io.Fonts->AddFontDefault(); + ImFontConfig latin_config; + latin_config.RasterizerMultiply = 1.3f; + ImFontConfig config; + config.MergeMode = true; + //config.GlyphMinAdvanceX = 13.0f; // Use if you want to make the icon monospaced + config.PixelSnapH = true; + static const ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; + io.Fonts->AddFontFromFileTTF("Assets/fonts/Roboto-Regular.ttf", 16.0f, &latin_config, io.Fonts->GetGlyphRangesDefault()); + io.Fonts->AddFontFromFileTTF("Assets/fonts/fa-solid-900.ttf", 16.0f, &config, icon_ranges); + + //ImPlot styling + ImPlot::GetStyle().FillAlpha = 0.75; + + Application& app = Application::Get(); + GLFWwindow* window = static_cast(app.GetWindow().GetNativeWindow()); + + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init("#version 410"); //GLSL version + + SPEC_INFO("ImGui Finished initializing."); + } + + void ImGuiLayer::OnDetach() + { + SPEC_PROFILE_FUNCTION(); + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); + } + + void ImGuiLayer::Begin() + { + SPEC_PROFILE_FUNCTION(); + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + + ImGui::NewFrame(); + } + + void ImGuiLayer::End() + { + SPEC_PROFILE_FUNCTION(); + Application& app = Application::Get(); + ImGuiIO& io = ImGui::GetIO(); + + io.DisplaySize = ImVec2((float)app.GetWindow().GetWidth(), (float)app.GetWindow().GetHeight()); + + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + if(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + GLFWwindow* backup_current_context = glfwGetCurrentContext(); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + glfwMakeContextCurrent(backup_current_context); + } + } + + void ImGuiLayer::OnImGuiRender() + { + //Demo's used to figure out how to do things. + //Should not be on for actual NavProject for + //real use + //static bool show = true; + //ImGui::ShowDemoWindow(&show); + //ImPlot::ShowDemoWindow(); + } +} diff --git a/Specter/src/Specter/ImGui/ImGuiLayer.h b/Specter/src/Specter/ImGui/ImGuiLayer.h new file mode 100644 index 0000000..30ba6a2 --- /dev/null +++ b/Specter/src/Specter/ImGui/ImGuiLayer.h @@ -0,0 +1,36 @@ +/* + ImGuiLayer.h + The layer containing all of the ImGui related setup and calls. Based on the work by @TheCherno in his game engine series. + Should always exist as an overlay in the Application LayerStack. Note that it currently is OpenGL specific based on + ImGui implementation/backends. + + GWM -- Feb 2022 +*/ +#ifndef IMGUI_LAYER_H +#define IMGUI_LAYER_H + +#include "Specter/Core/SpecCore.h" +#include "Specter/Core/Layer.h" + +namespace Specter { + + class ImGuiLayer : public Layer + { + public: + ImGuiLayer(); + ~ImGuiLayer(); + + virtual void OnAttach() override; + virtual void OnDetach() override; + virtual void OnImGuiRender() override; + + void Begin(); + void End(); + + private: + float m_time; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Physics/AnalysisStack.cpp b/Specter/src/Specter/Physics/AnalysisStack.cpp new file mode 100644 index 0000000..6a13d80 --- /dev/null +++ b/Specter/src/Specter/Physics/AnalysisStack.cpp @@ -0,0 +1,35 @@ +/* + AnalysisStack.cpp + Container for the analyses in the PhysicsLayer. Really just a specialized wrapper around std::vector. + + GWM -- Feb 2022 +*/ +#include "AnalysisStack.h" + +namespace Specter { + + AnalysisStack::AnalysisStack() {} + + AnalysisStack::~AnalysisStack() + { + for(AnalysisStage* stage : m_stack) + delete stage; + } + + void AnalysisStack::PushStage(AnalysisStage* stage) + { + m_stack.emplace(m_stack.begin()+m_insertIndex, stage); + m_insertIndex++; + } + + void AnalysisStack::PopStage(AnalysisStage* stage) + { + auto iter = std::find(m_stack.begin(), m_stack.end(), stage); + if(iter != m_stack.end()) + { + m_stack.erase(iter); + m_insertIndex--; + } + } + +} diff --git a/Specter/src/Specter/Physics/AnalysisStack.h b/Specter/src/Specter/Physics/AnalysisStack.h new file mode 100644 index 0000000..943794b --- /dev/null +++ b/Specter/src/Specter/Physics/AnalysisStack.h @@ -0,0 +1,35 @@ +/* + AnalysisStack.h + Container for the analyses in the PhysicsLayer. Really just a specialized wrapper around std::vector. + + GWM -- Feb 2022 +*/ + +#ifndef ANALYSIS_STACK_H +#define ANALYSIS_STACK_H + +#include "AnalysisStage.h" +#include "Specter/Core/SpecCore.h" + +namespace Specter { + + class AnalysisStack + { + public: + AnalysisStack(); + ~AnalysisStack(); + + void PushStage(AnalysisStage* stage); + void PopStage(AnalysisStage* stage); + + std::vector::iterator begin() { return m_stack.begin(); } + std::vector::iterator end() { return m_stack.end(); } + private: + std::vector m_stack; //The analysis stack owns the analysis stages + unsigned int m_insertIndex=0; + }; + +} + + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Physics/AnalysisStage.cpp b/Specter/src/Specter/Physics/AnalysisStage.cpp new file mode 100644 index 0000000..29301a1 --- /dev/null +++ b/Specter/src/Specter/Physics/AnalysisStage.cpp @@ -0,0 +1,19 @@ +/* + AnalysisStage.h + Represents a step in a chain of analyses that are run on a NavEvent. These stages are where NavParameters are set and validated. Users should use this base class + to create their own analyses and inject them into their project. See template NavProject for an example of use. + + GWM -- Feb 2022 +*/ +#include "AnalysisStage.h" + +namespace Specter { + + AnalysisStage::AnalysisStage(const std::string& name) : + m_name(name) + { + } + + AnalysisStage::~AnalysisStage() {} + +} \ No newline at end of file diff --git a/Specter/src/Specter/Physics/AnalysisStage.h b/Specter/src/Specter/Physics/AnalysisStage.h new file mode 100644 index 0000000..182ffc8 --- /dev/null +++ b/Specter/src/Specter/Physics/AnalysisStage.h @@ -0,0 +1,32 @@ +/* + AnalysisStage.h + Represents a step in a chain of analyses that are run on a NavEvent. These stages are where Parameters are set and validated. Users should use this base class + to create their own analyses and inject them into their project. See template NavProject for an example of use. + + GWM -- Feb 2022 +*/ +#ifndef ANALYSIS_STAGE_H +#define ANALYSIS_STAGE_H + +#include "Specter/Core/SpecCore.h" +#include "Specter/Core/Parameter.h" +#include "SpecData.h" + +namespace Specter { + + class AnalysisStage + { + public: + AnalysisStage(const std::string& name="AnalysisStage"); + virtual ~AnalysisStage(); + + virtual void AnalyzePhysicsEvent(const SpecEvent& event) {}; + + inline std::string GetName() { return m_name; } + private: + std::string m_name; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Physics/Caen/CompassFile.cpp b/Specter/src/Specter/Physics/Caen/CompassFile.cpp new file mode 100644 index 0000000..9f265ec --- /dev/null +++ b/Specter/src/Specter/Physics/Caen/CompassFile.cpp @@ -0,0 +1,210 @@ +/* + CompassFile.cpp + Wrapper class around a shared pointer to an ifstream. Here the shared pointer is used + to overcome limitations of the ifstream class, namely that it is written such that ifstream + cannot be modified by move semantics. Contains all information needed to parse a single binary + CompassFile. Currently has a class wide defined buffer size; may want to make this user input + in the future. + + Written by G.W. McCann Oct. 2020 + + Modified for Specter; not really any significant changes. Just some simple changes, removal of unused data. + + GWM -- Feb 2022 + + Update to reflect new CAEN binary data format with headers to indicate data contents. + + GWM -- May 2022 +*/ +#include "CompassFile.h" + +namespace Specter { + + CompassFile::CompassFile() : + m_filename(""), m_bufferIter(nullptr), m_bufferEnd(nullptr), m_smap(nullptr), m_hitUsedFlag(true), m_file(std::make_shared()), m_eofFlag(false) + { + } + + CompassFile::CompassFile(const std::string& filename) : + m_filename(""), m_bufferIter(nullptr), m_bufferEnd(nullptr), m_smap(nullptr), m_hitUsedFlag(true), m_file(std::make_shared()), m_eofFlag(false) + { + Open(filename); + } + + CompassFile::CompassFile(const std::string& filename, int bsize) : + m_filename(""), m_bufferIter(nullptr), m_bufferEnd(nullptr), m_smap(nullptr), m_hitUsedFlag(true), + m_bufsize(bsize), m_file(std::make_shared()), m_eofFlag(false) + { + Open(filename); + } + + CompassFile::~CompassFile() + { + Close(); + } + + void CompassFile::Open(const std::string& filename) + { + SPEC_PROFILE_FUNCTION(); + m_eofFlag = false; + m_hitUsedFlag = true; + m_filename = filename; + m_file->open(m_filename, std::ios::binary | std::ios::in); + + m_file->seekg(0, std::ios_base::end); + m_size = (unsigned int)m_file->tellg(); + if(m_size == 0) + { + m_eofFlag = true; + } + else + { + m_file->seekg(0, std::ios_base::beg); + ReadHeader(); + m_nHits = m_size / m_hitsize; + m_buffersize = m_hitsize * m_bufsize; + m_hitBuffer.resize(m_buffersize); + } + } + + void CompassFile::Close() + { + if(IsOpen()) + m_file->close(); + } + + void CompassFile::ReadHeader() + { + if(!IsOpen()) + { + SPEC_WARN("Unable to get hit size from file {0}, sending invalid value.", m_filename); + return; + } + + char* header = new char[2]; + m_file->read(header, 2); + m_header = *((uint16_t*)header); + m_hitsize = 16; //default hitsize 16 bytes + if (Compass_IsEnergy(m_header)) + m_hitsize += 2; + if (Compass_IsEnergyShort(m_header)) + m_hitsize += 2; + if (Compass_IsEnergyCalibrated(m_header)) + m_hitsize += 8; + if (Compass_IsWaves(m_header)) + { + m_hitsize += 5; + char* firstHit = new char[m_hitsize]; //Read chunk of first hit + m_file->read(firstHit, m_hitsize); + firstHit += m_hitsize - 4; //Move to the Nsamples value + uint32_t nsamples = *((uint32_t*)firstHit); + m_hitsize += nsamples * 2; //Each sample is two bytes + m_file->seekg(0, std::ios_base::beg); + m_file->read(header, 2); + + delete[] firstHit; + } + + delete[] header; + } + + /* + GetNextHit() is the function which... gets the next hit + Has to check if the buffer needs refilled/filled for the first time + Upon pulling a hit, sets the UsedFlag to false, letting the next level know + that the hit should be free game. + + If the file cannot be opened, signals as though file is EOF + */ + bool CompassFile::GetNextHit() + { + SPEC_PROFILE_FUNCTION(); + if(!IsOpen()) return true; + + if((m_bufferIter == nullptr || m_bufferIter == m_bufferEnd) && !IsEOF()) + { + GetNextBuffer(); + } + + if(!IsEOF()) + { + ParseNextHit(); + m_hitUsedFlag = false; + } + + return m_eofFlag; + } + + /* + GetNextBuffer() ... self-explanatory name + Note tht this is where the EOF flag is set. The EOF is only singaled + after the LAST buffer is completely read (i.e literally no more data). ifstream sets its eof + bit upon pulling the last buffer, but this class waits until that entire + last buffer is read to singal EOF (the true end of file). + */ + void CompassFile::GetNextBuffer() + { + SPEC_PROFILE_FUNCTION(); + if(m_file->eof()) + { + m_eofFlag = true; + return; + } + + m_file->read(m_hitBuffer.data(), m_hitBuffer.size()); + + m_bufferIter = m_hitBuffer.data(); + m_bufferEnd = m_bufferIter + m_file->gcount(); //one past the last datum + + } + + void CompassFile::ParseNextHit() + { + SPEC_PROFILE_FUNCTION(); + m_currentHit.board = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + m_currentHit.channel = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + m_currentHit.timestamp = *((uint64_t*)m_bufferIter); + m_bufferIter += 8; + if (Compass_IsEnergy(m_header)) + { + m_currentHit.energy = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + } + if (Compass_IsEnergyCalibrated(m_header)) + { + m_currentHit.energyCalibrated = *((uint64_t*)m_bufferIter); + m_bufferIter += 8; + } + if (Compass_IsEnergyShort(m_header)) + { + m_currentHit.energyShort = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + } + m_currentHit.flags = *((uint32_t*)m_bufferIter); + m_bufferIter += 4; + if (Compass_IsWaves(m_header)) + { + m_currentHit.waveCode = *((uint8_t*)m_bufferIter); + m_bufferIter += 1; + m_currentHit.Ns = *((uint32_t*)m_bufferIter); + m_bufferIter += 4; + if (m_currentHit.samples.size() != m_currentHit.Ns) + m_currentHit.samples.resize(m_currentHit.Ns); + for (size_t i = 0; i < m_currentHit.samples.size(); i++) + { + m_currentHit.samples[i] = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + } + } + + if(m_smap != nullptr) + { //memory safety + int gchan = m_currentHit.channel + m_currentHit.board*16; + m_currentHit.timestamp += m_smap->GetShift(gchan); + } + + } + +} \ No newline at end of file diff --git a/Specter/src/Specter/Physics/Caen/CompassFile.h b/Specter/src/Specter/Physics/Caen/CompassFile.h new file mode 100644 index 0000000..5ec8616 --- /dev/null +++ b/Specter/src/Specter/Physics/Caen/CompassFile.h @@ -0,0 +1,83 @@ +/* + CompassFile.h + Wrapper class around a shared pointer to an ifstream. Here the shared pointer is used + to overcome limitations of the ifstream class, namely that it is written such that ifstream + cannot be modified by move semantics. Contains all information needed to parse a single binary + CompassFile. Currently has a class wide defined buffer size; may want to make this user input + in the future. + + Written by G.W. McCann Oct. 2020 + + Modified for Specter; not really any significant changes. Just some simple changes, removal of unused data. + + GWM -- Feb 2022 + + Update to reflect new CAEN binary data format with headers to indicate data contents. + + GWM -- May 2022 +*/ +#ifndef COMPASSFILE_H +#define COMPASSFILE_H + +#include "Specter/Core/SpecCore.h" +#include "CompassHit.h" +#include "Specter/Physics/ShiftMap.h" + +namespace Specter { + + class CompassFile + { + + public: + CompassFile(); + CompassFile(const std::string& filename); + CompassFile(const std::string& filename, int bsize); + ~CompassFile(); + void Open(const std::string& filename); + void Close(); + bool GetNextHit(); + + inline bool IsOpen() const { return m_file->is_open(); }; + inline CompassHit GetCurrentHit() const { return m_currentHit; } + inline std::string GetName() const { return m_filename; } + inline bool CheckHitHasBeenUsed() const { return m_hitUsedFlag; } //query to find out if we've used the current hit + inline void SetHitHasBeenUsed() { m_hitUsedFlag = true; } //flip the flag to indicate the current hit has been used + inline bool IsEOF() const { return m_eofFlag; } //see if we've read all available data + inline bool* GetUsedFlagPtr() { return &m_hitUsedFlag; } + inline void AttachShiftMap(ShiftMap* map) { m_smap = map; } + inline unsigned int GetSize() const { return m_size; } + inline unsigned int GetNumberOfHits() const { return m_nHits; } + + + private: + void ReadHeader(); + void ParseNextHit(); + void GetNextBuffer(); + + using Buffer = std::vector; + + using FilePointer = std::shared_ptr; //to make this class copy/movable + + std::string m_filename; + Buffer m_hitBuffer; + char* m_bufferIter; + char* m_bufferEnd; + ShiftMap* m_smap; //NOT owned by CompassFile. DO NOT delete + + bool m_hitUsedFlag; + int m_bufsize = 200000; //size of the buffer in hits + int m_hitsize; //size of a CompassHit in bytes (without alignment padding) + uint16_t m_header; + int m_buffersize; + + CompassHit m_currentHit; + FilePointer m_file; + bool m_eofFlag; + unsigned int m_size; //size of the file in bytes + unsigned int m_nHits; //number of hits in the file (m_size/m_hitsize) + + }; + +} + +#endif diff --git a/Specter/src/Specter/Physics/Caen/CompassHit.cpp b/Specter/src/Specter/Physics/Caen/CompassHit.cpp new file mode 100644 index 0000000..4e0b03b --- /dev/null +++ b/Specter/src/Specter/Physics/Caen/CompassHit.cpp @@ -0,0 +1,24 @@ +#include "CompassHit.h" + +namespace Specter { + + bool Compass_IsEnergy(uint16_t header) + { + return (header & CompassHeaders::Energy) != 0; + } + + bool Compass_IsEnergyShort(uint16_t header) + { + return (header & CompassHeaders::EnergyShort) != 0; + } + + bool Compass_IsEnergyCalibrated(uint16_t header) + { + return (header & CompassHeaders::EnergyCalibrated) != 0; + } + + bool Compass_IsWaves(uint16_t header) + { + return (header & CompassHeaders::EnergyCalibrated) != 0; + } +} \ No newline at end of file diff --git a/Specter/src/Specter/Physics/Caen/CompassHit.h b/Specter/src/Specter/Physics/Caen/CompassHit.h new file mode 100644 index 0000000..f77d492 --- /dev/null +++ b/Specter/src/Specter/Physics/Caen/CompassHit.h @@ -0,0 +1,47 @@ +/* + CompassHit.h + Simple struct representing data from the CAEN CoMPASS DAQ. Note here I do not have any of the non-standard data available (calibrated energy, waveform data, etc.) + + GWM -- Feb 2022 + + Update to reflect new CAEN binary data format with headers to indicate data contents. + + GWM -- May 2022 +*/ +#ifndef COMPASS_HIT_H +#define COMPASS_HIT_H + +#include "Specter/Core/SpecCore.h" + +namespace Specter { + + struct CompassHit + { + uint16_t board = 0; + uint16_t channel = 0; + uint64_t timestamp = 0; + uint16_t energy = 0; + uint16_t energyShort = 0; + uint64_t energyCalibrated = 0; + uint32_t flags = 0; + uint8_t waveCode = 0; + uint32_t Ns = 0; + std::vector samples; + }; + + //New to CoMPASS Data Format: Headers indicating what data is present. + enum CompassHeaders + { + Energy = 0x0001, + EnergyShort = 0x0002, + EnergyCalibrated = 0x0004, + Waves = 0x0008 + }; + + bool Compass_IsEnergy(uint16_t header); + bool Compass_IsEnergyShort(uint16_t header); + bool Compass_IsEnergyCalibrated(uint16_t header); + bool Compass_IsWaves(uint16_t header); +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Physics/Caen/CompassOnlineSource.cpp b/Specter/src/Specter/Physics/Caen/CompassOnlineSource.cpp new file mode 100644 index 0000000..890fcf0 --- /dev/null +++ b/Specter/src/Specter/Physics/Caen/CompassOnlineSource.cpp @@ -0,0 +1,149 @@ +/* + CompassOnlineSource.cpp + A data source for online CAEN CoMPASS Data. Uses TCPClient to connect to the CoMPASS server. Data is then converted + from the CAEN CoMPASS format to the native NavData format. Note that here we use syncrhonous since we + need to know if the buffer is/was filled, however we use non-blocking since we don't want the entire process to hang on attempting a connection or waiting + for data to come over the pipe. We handle the case of an un-filled buffer internally. + + IMPORTANT + Specter wants a unqiue ID on each hit. To do this we use the idiom: + id = board_number * nchannels_per_board + channel_number + This requires two things: that the class variable m_nchannels_per_board be set to match your physical digitizers, and that ALL of your + digitizers have the SAME number of channels. By default CompassRun assumes 16 channels per board, as this is what is used with the SE-SPS setup at FoxLab. + If you use a different set of boards, CHANGE THIS VALUE! If you use mixed boards, you will need to invent a new id scheme altogether. + + ADDITIONALLY + CoMPASS servers provide no stream side information on the state of a transfer (verified via communication w/ CAEN). That is: there are no headers or enders on the data transfers. + This forces us to use the size of a single CoMPASS datum (CompassHit) to determine the state of a transfer. If the read buffer size is not a whole multiple of CompassHits, the data + was determined to be fragmented. This has a huge drawback: in general there is no guarantee that the first byte of the transferred data is the first byte of a CompassHit. This means that + Specter MUST be connected to the CoMPASS server BEFORE starting the aquisition. Otherwise the program could endup in a state of scrambled unpacking (also verified w/ CAEN). + Maybe we can get them to change this? Headers reeaaally should exist for transfers like this. + + GWM -- April 2022 +*/ +#include "CompassOnlineSource.h" + +namespace Specter { + + CompassOnlineSource::CompassOnlineSource(const std::string& hostname, const std::string& port, uint16_t header, int channels_per_board) : + DataSource(), m_bufferIter(nullptr), m_bufferEnd(nullptr), m_header(header), m_nchannels_per_board(channels_per_board) + { + InitConnection(hostname, port); + } + + CompassOnlineSource::~CompassOnlineSource() {} + + void CompassOnlineSource::InitConnection(const std::string& hostname, const std::string& port) + { + SPEC_PROFILE_FUNCTION(); + + m_datasize = 16; //base size of CoMPASS data + if (Compass_IsEnergy(m_header)) + m_datasize += 2; + if (Compass_IsEnergyShort(m_header)) + m_datasize += 2; + if (Compass_IsEnergyCalibrated(m_header)) + m_datasize += 8; + if (Compass_IsWaves(m_header)) + SPEC_ERROR("Specter does not support reading CoMPASS wave data for an online source!"); + + m_validFlag = false; + m_connection.Connect(hostname, port); + if (m_connection.IsOpen()) + { + m_validFlag = true; + } + } + + const SpecData& CompassOnlineSource::GetData() + { + SPEC_PROFILE_FUNCTION(); + size_t range = m_bufferEnd - m_bufferIter; //how much buffer we have left + if (!IsValid()) + { + SPEC_ERROR("Attempting to access invalid source at CompassOnlineSource!"); + m_datum = SpecData(); + return m_datum; + } + else if (m_bufferIter == nullptr || range < m_datasize || m_bufferIter == m_bufferEnd) //If no buffer/buffer completely used/buffer fragmented fill + { + FillBuffer(); + } + + if (m_bufferIter != m_bufferEnd && range >= m_datasize)//If buffer and enough data for a hit, get it + GetHit(); + else + { + m_datum = SpecData(); + return m_datum; + } + + m_datum.longEnergy = m_currentHit.energy; + m_datum.shortEnergy = m_currentHit.energyShort; + m_datum.calEnergy = m_currentHit.energyCalibrated; + m_datum.timestamp = m_currentHit.timestamp; + m_datum.id = m_currentHit.board * m_nchannels_per_board + m_currentHit.channel; + + return m_datum; + } + + void CompassOnlineSource::FillBuffer() + { + SPEC_PROFILE_FUNCTION(); + if (!m_connection.IsOpen()) //Make sure connection is still cool + { + m_validFlag = false; + return; + } + + std::vector recieved = m_connection.Read(); + //If we didn't finish the last buffer toss all of the stuff we used and then append the recieved data + //Otherwise, copy over the recieved buffer. Note lack of vector::resize, vector::reserve. Intentional for performance. + //The amount of copying/resizing is best handled by the std (according to multiple references) + if (m_bufferIter != m_bufferEnd) + { + size_t pos = m_bufferEnd - m_bufferIter; + m_currentBuffer.erase(m_currentBuffer.begin(), m_currentBuffer.begin() + (m_currentBuffer.size() - pos)); //remove used bytes + m_currentBuffer.insert(m_currentBuffer.end(), recieved.begin(), recieved.end()); + } + else + m_currentBuffer = recieved; + + m_bufferIter = m_currentBuffer.data(); + m_bufferEnd = m_currentBuffer.data() + m_currentBuffer.size(); + } + + void CompassOnlineSource::ReadHeader() + { + + } + + void CompassOnlineSource::GetHit() + { + SPEC_PROFILE_FUNCTION(); + m_currentHit.board = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + m_currentHit.channel = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + m_currentHit.timestamp = *((uint64_t*)m_bufferIter); + m_bufferIter += 8; + if (Compass_IsEnergy(m_header)) + { + m_currentHit.energy = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + } + if (Compass_IsEnergyCalibrated(m_header)) + { + m_currentHit.energyCalibrated = *((uint16_t*)m_bufferIter); + m_bufferIter += 8; + } + if (Compass_IsEnergyShort(m_header)) + { + m_currentHit.energyShort = *((uint16_t*)m_bufferIter); + m_bufferIter += 2; + } + m_currentHit.flags = *((uint32_t*)m_bufferIter); + m_bufferIter += 4; + } + +} \ No newline at end of file diff --git a/Specter/src/Specter/Physics/Caen/CompassOnlineSource.h b/Specter/src/Specter/Physics/Caen/CompassOnlineSource.h new file mode 100644 index 0000000..b2ae09b --- /dev/null +++ b/Specter/src/Specter/Physics/Caen/CompassOnlineSource.h @@ -0,0 +1,68 @@ +/* + CompassOnlineSource.h + A data source for online CAEN CoMPASS Data. Uses TCPClient to connect to the CoMPASS server. Data is then converted + from the CAEN CoMPASS format to the native NavData format. Note that here we use syncrhonous since we + need to know if the buffer is/was filled, however we use non-blocking since we don't want the entire process to hang on attempting a connection or waiting + for data to come over the pipe. We handle the case of an un-filled buffer internally. + + IMPORTANT + Specter wants a unqiue ID on each hit. To do this we use the idiom: + id = board_number * nchannels_per_board + channel_number + This requires two things: that the class variable m_nchannels_per_board be set to match your physical digitizers, and that ALL of your + digitizers have the SAME number of channels. By default CompassRun assumes 16 channels per board, as this is what is used with the SE-SPS setup at FoxLab. + If you use a different set of boards, CHANGE THIS VALUE! If you use mixed boards, you will need to invent a new id scheme altogether. + + ADDITIONALLY + CoMPASS servers provide no stream side information on the state of a transfer (verified via communication w/ CAEN). That is: there are no headers or enders on the data transfers. + This forces us to use the size of a single CoMPASS datum (CompassHit) to determine the state of a transfer. If the read buffer size is not a whole multiple of CompassHits, the data + was determined to be fragmented. This has a huge drawback: in general there is no guarantee that the first byte of the transferred data is the first byte of a CompassHit. This means that + Specter MUST be connected to the CoMPASS server BEFORE starting the aquisition. Otherwise the program could endup in a state of scrambled unpacking (also verified w/ CAEN). + Maybe we can get them to change this? Headers reeaaally should exist for transfers like this. + + GWM -- April 2022 + + Update to reflect new CAEN binary data format with headers to indicate data contents. Note that as prev. mentioned, no headers, so cannot rely on data stream to indicate state. State must be selected + by user at UI when creating source. Cannot support waves atm. No way to predict size of first event to calibrate the number of samples for the stream (or guarantee that they will be constant for duration + of Specter's runtime). Best to use the CoMPASSPlot for waves. + + GWM -- May 2022 +*/ +#ifndef COMPASS_ONLINE_SOURCE_H +#define COMPASS_ONLINE_SOURCE_H + +#include "Specter/Physics/DataSource.h" +#include "Specter/Utils/TCPClient.h" +#include "CompassHit.h" + +namespace Specter { + + class CompassOnlineSource : public DataSource + { + public: + CompassOnlineSource(const std::string& hostname, const std::string& port, uint16_t header, int channels_per_board=16); + virtual ~CompassOnlineSource() override; + + virtual const SpecData& GetData() override; + + private: + void InitConnection(const std::string& hostname, const std::string& port); + void FillBuffer(); + void GetHit(); + void ReadHeader(); + + std::vector m_currentBuffer; + uint16_t m_header; + int m_datasize; //size of CoMPASS hit in bytes, set by header arg + const int m_nchannels_per_board = 16; //IMPORTANT: Used for ID'ing channels uniquely. If you use boards with 32 or 8 or 64 channels you must change this! If you mix boards with + //different numbers of channels, you will have to find a different id solution. + char* m_bufferIter; + char* m_bufferEnd; + CompassHit m_currentHit; + + TCPClient m_connection; + + }; + +} + +#endif diff --git a/Specter/src/Specter/Physics/Caen/CompassRun.cpp b/Specter/src/Specter/Physics/Caen/CompassRun.cpp new file mode 100644 index 0000000..d81ae59 --- /dev/null +++ b/Specter/src/Specter/Physics/Caen/CompassRun.cpp @@ -0,0 +1,156 @@ +/* + CompassRun.cpp + Class designed as abstraction of a collection of binary files that represent the total data in a single + Compass data run. It handles the user input (shift maps, file collection etc.) and creates a list of + CompassFiles from which to draw data. It then draws data from these files, organizes them in time, + and writes to a ROOT file for further processing. + + Written by G.W. McCann Oct. 2020 + + Updated to also handle scaler data. -- GWM Oct. 2020 + + Modifed and updated for use in Specter. Obviously stripped out any ROOT code. Also, now uses the very nice std::filesystem + library to handle filepathing. Also, removed scalers (for now). + + GWM -- Feb 2022 + + Update to reflect new CAEN binary data format with headers to indicate data contents. + + GWM -- May 2022 +*/ +#include "CompassRun.h" + +namespace Specter { + + CompassRun::CompassRun() : + DataSource(), m_directory(""), m_startIndex(0), m_nchannels_per_board(16) + { + } + + CompassRun::CompassRun(const std::string& dir, int channels_per_board) : + DataSource(), m_directory(dir), m_startIndex(0), m_nchannels_per_board(channels_per_board) + { + CollectFiles(); + } + + CompassRun::~CompassRun() {} + + void CompassRun::CollectFiles() + { + SPEC_PROFILE_FUNCTION(); + int nfiles=0; + for(auto& item : std::filesystem::directory_iterator(m_directory)) + { + if(item.path().extension() == m_extension) + nfiles++; + } + + m_datafiles.clear(); + m_datafiles.reserve(nfiles); + for(auto& item : std::filesystem::directory_iterator(m_directory)) + { + if(item.path().extension() == m_extension) + { + m_datafiles.emplace_back(item.path().string()); + } + } + + long total_hits=0; + for(auto& file : m_datafiles) + { + + if(!file.IsOpen()) + { + SPEC_ERROR("Unable to open file with name {0}", file.GetName()); + m_validFlag = false; + return; + } + + if(m_smap.IsValid()) + file.AttachShiftMap(&m_smap); + + total_hits += file.GetNumberOfHits(); + } + + if(m_datafiles.size() == 0) + { + SPEC_WARN("Unable to find any files with extension {0} in directory {1}. CompassRun killed.", m_extension, m_directory); + m_validFlag = false; + } + else + { + SPEC_INFO("Succesfully opened {0} files with {1} total hits", nfiles, total_hits); + m_validFlag = true; + } + } + + /* + GetHitsFromFiles() is the function which actually retrieves and sorts the data from the individual + files. There are several tricks which allow this to happen. First is that, after sorting, it is impossible + to determine which file the data originally came from (short of parsing the name of the file against board/channel). + However, we need to let the file know that we want it to pull the next hit. To do this, a pointer to the UsedFlag of the file + is retrieved along with the data. This flag is flipped so that on the next hit cycle a new hit is pulled. Second is the use + of a rolling start index. Once a file has gone EOF, we no longer need it. If this is the first file in the list, we can just skip + that index all together. In this way, the loop can go from N times to N-1 times. + */ + bool CompassRun::GetHitsFromFiles() + { + SPEC_PROFILE_FUNCTION(); + std::pair earliestHit = std::make_pair(CompassHit(), nullptr); + for(unsigned int i=m_startIndex; i + +namespace Specter { + + class CompassRun : public DataSource + { + + public: + CompassRun(); + CompassRun(const std::string& dir, int channels_per_board=16); + virtual ~CompassRun(); + virtual const SpecData& GetData() override; + inline void SetDirectory(const std::string& dir) { m_directory = dir; CollectFiles(); } + inline void SetShiftMap(const std::string& filename) { m_smap.SetFile(filename); } + + + private: + void CollectFiles(); + bool GetHitsFromFiles(); + + std::filesystem::path m_directory; + const std::string m_extension = ".bin"; + + std::vector m_datafiles; + unsigned int m_startIndex; //this is the file we start looking at; increases as we finish files. + int m_nchannels_per_board; //IMPORTANT: Used for ID'ing channels uniquely. If you use boards with 32 or 8 or 64 channels you must change this! If you mix boards with + //different numbers of channels, you will have to find a different id solution. + ShiftMap m_smap; + + CompassHit m_hit; + + unsigned int m_totalHits; + }; + +} + +#endif diff --git a/Specter/src/Specter/Physics/DataSource.cpp b/Specter/src/Specter/Physics/DataSource.cpp new file mode 100644 index 0000000..4466710 --- /dev/null +++ b/Specter/src/Specter/Physics/DataSource.cpp @@ -0,0 +1,39 @@ +/* + DataSource.cpp + Abstract data source class. In Specter a DataSource can be either an online (live) source or an offline (file) source. By default, + Specter has classes to handle CAEN CoMPASS data sources (files and online); other sources may be implemented as more use cases for Specter become + apparent. + + GWM -- Feb 2022 +*/ +#include "DataSource.h" +#include "Caen/CompassRun.h" +#include "Caen/CompassOnlineSource.h" + +namespace Specter { + + //loc=either an ip address or a file location, port=address port, or unused in case of file + DataSource* CreateDataSource(const std::string& location, const std::string& port, uint16_t header, int channels_per_board, DataSource::SourceType type) + { + switch(type) + { + case DataSource::SourceType::CompassOffline : return new CompassRun(location, channels_per_board); + case DataSource::SourceType::CompassOnline : return new CompassOnlineSource(location, port, header, channels_per_board); + case DataSource::SourceType::None : return nullptr; + } + SPEC_WARN("Invalid DataSourceType at CreateDataSource!"); + return nullptr; + } + + std::string ConvertDataSourceTypeToString(DataSource::SourceType type) + { + switch(type) + { + case DataSource::SourceType::None: return "None"; + case DataSource::SourceType::CompassOnline : return "CompassOnline"; + case DataSource::SourceType::CompassOffline : return "CompassOffline"; + } + + return "None"; + } +} diff --git a/Specter/src/Specter/Physics/DataSource.h b/Specter/src/Specter/Physics/DataSource.h new file mode 100644 index 0000000..db77669 --- /dev/null +++ b/Specter/src/Specter/Physics/DataSource.h @@ -0,0 +1,46 @@ +/* + DataSource.h + Abstract data source class. In Specter a DataSource can be either an online (live) source or an offline (file) source. By default, + Specter has classes to handle CAEN CoMPASS data sources (files and online); other sources may be implemented as more use cases for Specter become + apparent. + + GWM -- Feb 2022 +*/ +#ifndef DATA_SOURCE_H +#define DATA_SOURCE_H + +#include "Specter/Core/SpecCore.h" +#include "SpecData.h" + +namespace Specter { + + class DataSource + { + public: + enum class SourceType //Need to know what kind of sources are available. + { + None, + CompassOnline, + CompassOffline + }; + + DataSource() : + m_validFlag(false) + { + } + + virtual ~DataSource() {}; + virtual const SpecData& GetData() = 0; + inline bool IsValid() { return m_validFlag; } + + protected: + bool m_validFlag; + SpecData m_datum; + }; + + DataSource* CreateDataSource(const std::string& location, const std::string& port, uint16_t bitflags, int channels_per_board, DataSource::SourceType type); + + std::string ConvertDataSourceTypeToString(DataSource::SourceType type); +} + +#endif diff --git a/Specter/src/Specter/Physics/PhysicsEventBuilder.cpp b/Specter/src/Specter/Physics/PhysicsEventBuilder.cpp new file mode 100644 index 0000000..71fff60 --- /dev/null +++ b/Specter/src/Specter/Physics/PhysicsEventBuilder.cpp @@ -0,0 +1,65 @@ +/* + PhysicsEventBuilder.h + Class for taking in raw NavData and converting into a NavEvent. NavEvent is just a std::vector of NavData, where + the held NavData all falls within a time window called the coincidence window. As soon as a NavData is given that falls outside + of this window, the current event is shifted to the ready event and the AddDatumToEvent function returns true. The ready event can + then be retrieved. The hit that triggered the end of event then is used to start the new event. The current pattern is strongly associated + with digital electronics concepts for nuclear data aquisition systems. + + GWM -- Feb 2022 +*/ +#include "PhysicsEventBuilder.h" + +namespace Specter { + + PhysicsEventBuilder::PhysicsEventBuilder(uint64_t windowSize) : + m_sortFlag(false), m_coincWindow(windowSize), m_bufferIndex(0) + { + } + + PhysicsEventBuilder::~PhysicsEventBuilder() + { + } + + bool PhysicsEventBuilder::AddDatum(const SpecData& datum) + { + SPEC_PROFILE_FUNCTION(); + if (datum.timestamp == 0) //Ignore empty data (need a valid timestamp) + return false; + + m_dataBuffer[m_bufferIndex] = datum; + m_bufferIndex++; + if (m_bufferIndex < s_maxDataBuffer) //If we haven't filled the buffer keep going + return false; + else if (m_sortFlag) + std::sort(m_dataBuffer.begin(), m_dataBuffer.end(), [](SpecData& i, SpecData& j) { return i.timestamp < j.timestamp; }); + + //Generate our ready events + m_readyEvents.clear(); + uint64_t eventStartTime = m_dataBuffer[0].timestamp; + SpecEvent event; + event.push_back(m_dataBuffer[0]); + for (auto& data : m_dataBuffer) + { + if (data.timestamp - eventStartTime < m_coincWindow) //are we within active window + { + event.push_back(data); + } + else // found one that falls outside + { + m_readyEvents.push_back(event); + event.clear(); + eventStartTime = data.timestamp; + event.push_back(data); + } + } + m_bufferIndex = 0; + return true; + } + + const std::vector& PhysicsEventBuilder::GetReadyEvents() const + { + return m_readyEvents; + } + +} \ No newline at end of file diff --git a/Specter/src/Specter/Physics/PhysicsEventBuilder.h b/Specter/src/Specter/Physics/PhysicsEventBuilder.h new file mode 100644 index 0000000..be81fca --- /dev/null +++ b/Specter/src/Specter/Physics/PhysicsEventBuilder.h @@ -0,0 +1,45 @@ +/* + PhysicsEventBuilder.h + Class for taking in raw NavData and converting into a NavEvent. NavEvent is just a std::vector of NavData, where + the held NavData all falls within a time window called the coincidence window. As soon as a NavData is given that falls outside + of this window, the current event is shifted to the ready event and the AddDatumToEvent function returns true. The ready event can + then be retrieved. The hit that triggered the end of event then is used to start the new event. The current pattern is strongly associated + with digital electronics concepts for nuclear data aquisition systems. + + GWM -- Feb 2022 +*/ +#ifndef PHYSICS_EVENT_BUILDER_H +#define PHYSICS_EVENT_BUILDER_H + +#include "SpecData.h" + +namespace Specter { + + class PhysicsEventBuilder + { + public: + PhysicsEventBuilder(uint64_t windowSize); + ~PhysicsEventBuilder(); + inline void SetCoincidenceWindow(uint64_t windowSize) { m_coincWindow = windowSize; } + inline void SetSortFlag(bool flag) { m_sortFlag = flag; } + inline void ClearAll() // reset all internal structures + { + m_bufferIndex = 0; + m_readyEvents.clear(); + } + bool AddDatum(const SpecData& datum); + const std::vector& GetReadyEvents() const; + + private: + bool m_sortFlag; + static constexpr int s_maxDataBuffer = 1000; + std::array m_dataBuffer; + int m_bufferIndex; + std::vector m_readyEvents; + uint64_t m_coincWindow; + + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Physics/PhysicsLayer.cpp b/Specter/src/Specter/Physics/PhysicsLayer.cpp new file mode 100644 index 0000000..742edcf --- /dev/null +++ b/Specter/src/Specter/Physics/PhysicsLayer.cpp @@ -0,0 +1,188 @@ +/* + PhysicsLayer.cpp + The layer of the application representing all physics. This includes the data source and the thread upon which data is piped, event built, and analyzed. + PhysicsLayer receives a PhysicsStartEvent or PhysicsStopEvent to handle changes to the state of the secondary thread. As it handles the thread, it must have synchronization. + This is handled on two levels. The first is a mutex that synchronizes access to the DataSource. The second is an atomic boolean flag which is used to control the state of the physics thread. + PhysicsLayer also owns the AnalysisStack for the application. + + GWM -- Feb 2022 +*/ +#include "PhysicsLayer.h" +#include "Specter/Core/SpectrumManager.h" +#include "SpecData.h" + +namespace Specter { + + PhysicsLayer::PhysicsLayer() : + m_activeFlag(false), m_source(nullptr), m_eventBuilder(0), m_physThread(nullptr) + { + } + + PhysicsLayer::~PhysicsLayer() + { + SPEC_PROFILE_FUNCTION(); + if (m_activeFlag) + { + DetachDataSource(); + DestroyPhysThread(); + } + } + + void PhysicsLayer::OnAttach() + { + /* For debugging + NavParameter par("joseph"); + par.SetValue(8); + NAV_INFO("Does the par exist? {0}", ParameterMap::GetInstance().IsParameterValid("joseph")); + NAV_INFO("What is its value? {0}", ParameterMap::GetInstance().GetParameterValue("joseph")); + */ + } + + void PhysicsLayer::OnDetach() + { + } + + void PhysicsLayer::OnEvent(Event& event) + { + SPEC_PROFILE_FUNCTION(); + EventDispatcher dispatch(event); + dispatch.Dispatch(BIND_EVENT_FUNCTION(PhysicsLayer::OnPhysicsStartEvent)); + dispatch.Dispatch(BIND_EVENT_FUNCTION(PhysicsLayer::OnPhysicsStopEvent)); + } + + bool PhysicsLayer::OnPhysicsStartEvent(PhysicsStartEvent& event) + { + SPEC_PROFILE_FUNCTION(); + //If the Physics thread is active, kill it + if(m_activeFlag) + { + DetachDataSource(); + DestroyPhysThread(); + } + + AttachDataSource(event); + + //If we succesfully attached, fire up a new phys thread + if(m_activeFlag) + { + SPEC_INFO("Starting new analysis thread..."); + m_physThread = new std::thread(&PhysicsLayer::RunSource, std::ref(*this)); + } + return true; + } + + bool PhysicsLayer::OnPhysicsStopEvent(PhysicsStopEvent& event) + { + SPEC_PROFILE_FUNCTION(); + if (m_activeFlag) + { + DetachDataSource(); + DestroyPhysThread(); + } + return true; + } + + void PhysicsLayer::PushStage(AnalysisStage* stage) + { + m_physStack.PushStage(stage); + } + + void PhysicsLayer::OnUpdate(Timestep& step) {} + + /*Threaded functions*/ + + void PhysicsLayer::DestroyPhysThread() + { + SPEC_PROFILE_FUNCTION(); + SPEC_INFO("Destroying the analysis thread..."); + //Join the thread back to the parent (finish up the thread) + if(m_physThread != nullptr && m_physThread->joinable()) + { + m_physThread->join(); + } + + //Free the thread memory + if(m_physThread != nullptr) + { + delete m_physThread; + m_physThread = nullptr; + } + SPEC_INFO("Thread destroyed."); + } + + void PhysicsLayer::AttachDataSource(PhysicsStartEvent& event) + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_sourceMutex); //Shouldn't matter for this, but safety first + m_source.reset(CreateDataSource(event.GetSourceLocation(), event.GetSourcePort(), event.GetBitFlags(), event.GetChannelsPerBoard(), event.GetSourceType())); + m_eventBuilder.SetCoincidenceWindow(event.GetCoincidenceWindow()); + m_eventBuilder.SetSortFlag(event.GetSortFlag()); + m_eventBuilder.ClearAll(); //Protect against stopping mid-event + if (m_source->IsValid()) + { + SPEC_INFO("Attach successful. Enabling data pull..."); + m_activeFlag = true; + } + else + { + SPEC_ERROR("DataSource attach failed... check for error conditions."); + m_source.reset(nullptr); + } + } + + void PhysicsLayer::DetachDataSource() + { + SPEC_PROFILE_FUNCTION(); + std::scoped_lock guard(m_sourceMutex); + SPEC_INFO("Detaching physics data source..."); + m_activeFlag = false; + m_source.reset(nullptr); + SPEC_INFO("Detach succesful."); + } + + void PhysicsLayer::RunSource() + { + SPEC_PROFILE_FUNCTION(); + SpectrumManager& manager = SpectrumManager::GetInstance(); + + std::vector events; + SpecData datum; + while(m_activeFlag) + { + //Scope to encapsulate access to the data source + { + std::scoped_lock guard(m_sourceMutex); + if (m_source == nullptr || !m_source->IsValid()) + { + return; + } + /* + Looks funny, but two conditions lead to !IsValid(). Either source prev. shutdown, + OR we reached end of source, indicated after prev. data grab + */ + datum = m_source->GetData(); + if(!m_source->IsValid()) + { + SPEC_INFO("End of data source."); + return; + } + } + + //Pass data from source to event builder. True from AddDatum indicates events are ready + if (m_eventBuilder.AddDatum(datum)) + { + events = m_eventBuilder.GetReadyEvents(); + for (auto& event : events) + { + for (auto& stage : m_physStack) + stage->AnalyzePhysicsEvent(event); + + //Now that the analysis stack has filled all our NavParameters with data, update the histogram counts + manager.UpdateHistograms(); + //Invalidate all parameters to get ready for next event + manager.InvalidateParameters(); + } + } + } + } +} diff --git a/Specter/src/Specter/Physics/PhysicsLayer.h b/Specter/src/Specter/Physics/PhysicsLayer.h new file mode 100644 index 0000000..57b61f8 --- /dev/null +++ b/Specter/src/Specter/Physics/PhysicsLayer.h @@ -0,0 +1,64 @@ +/* + PhysicsLayer.h + The layer of the application representing all physics. This includes the data source and the thread upon which data is piped, event built, and analyzed. + PhysicsLayer receives a PhysicsStartEvent or PhysicsStopEvent to handle changes to the state of the secondary thread. As it handles the thread, it must have synchronization. + This is handled on two levels. The first is a mutex that synchronizes access to the DataSource. The second is an atomic boolean flag which is used to control the state of the physics thread. + PhysicsLayer also owns the AnalysisStack for the application. + + GWM -- Feb 2022 +*/ +#ifndef PHYSICS_LAYER_H +#define PHYSICS_LAYER_H + +#include "Specter/Core/SpecCore.h" +#include "Specter/Core/Layer.h" +#include "Specter/Events/PhysicsEvent.h" +#include "AnalysisStack.h" +#include "AnalysisStage.h" +#include "DataSource.h" +#include "PhysicsEventBuilder.h" + +#include +#include +#include + +namespace Specter { + + class PhysicsLayer : public Layer + { + public: + PhysicsLayer(); + virtual ~PhysicsLayer(); + + virtual void OnAttach() override; + virtual void OnUpdate(Timestep& step) override; + virtual void OnDetach() override; + virtual void OnImGuiRender() override {}; + virtual void OnEvent(Event& event) override; + + bool OnPhysicsStartEvent(PhysicsStartEvent& event); + bool OnPhysicsStopEvent(PhysicsStopEvent& event); + + void PushStage(AnalysisStage* stage); + + private: + void DestroyPhysThread(); + void AttachDataSource(PhysicsStartEvent& event); + void DetachDataSource(); + void RunSource(); + + AnalysisStack m_physStack; + std::atomic m_activeFlag; //safe read/write across thread, but more expensive + + std::mutex m_sourceMutex; + + std::unique_ptr m_source; + PhysicsEventBuilder m_eventBuilder; + + std::thread* m_physThread; + + }; + +} + +#endif diff --git a/Specter/src/Specter/Physics/ShiftMap.cpp b/Specter/src/Specter/Physics/ShiftMap.cpp new file mode 100644 index 0000000..08bc1f8 --- /dev/null +++ b/Specter/src/Specter/Physics/ShiftMap.cpp @@ -0,0 +1,85 @@ +/* + ShiftMap.h + New class to act a go-between for timestamp shifts to channels. Takes in a + formated file containing data for shifts and then stores them in an unordered_map. + Key is a global compass channel (board#*16 + channel). Shifts in ps. + + Written by G.W. McCann Oct. 2020 + + Not currently implemented for Specter though it could still be useful. Leave this here as a maybe upgrade path. + + GWM -- Feb 2022 +*/ +#include "ShiftMap.h" + +namespace Specter { + + ShiftMap::ShiftMap() : + m_filename(""), m_validFlag(false) + { + } + + ShiftMap::ShiftMap(const std::string& filename) : + m_filename(filename), m_validFlag(false) + { + ParseFile(); + } + + ShiftMap::~ShiftMap() {} + + void ShiftMap::SetFile(const std::string& filename) + { + m_filename = filename; + ParseFile(); + } + + uint64_t ShiftMap::GetShift(int gchan) + { + if(!m_validFlag) + return 0; + + auto iter = m_map.find(gchan); + if(iter == m_map.end()) + return 0; + else + return iter->second; + } + + void ShiftMap::ParseFile() + { + m_validFlag = false; + std::ifstream input(m_filename); + if(!input.is_open()) + return; + + int board, channel, gchan; + uint64_t shift; + std::string junk, temp; + + std::getline(input, junk); + std::getline(input, junk); + + while(input>>board) + { + input>>temp; + input>>shift; + if(temp == "all") //keyword to set all channels in this board to same shift + { + for(int i=0; i<16; i++) + { + gchan = board*16 + i; + m_map[gchan] = shift; + } + } + else + { + channel = stoi(temp); + gchan = channel + board*16; + m_map[gchan] = shift; + } + } + + m_validFlag = true; + } + +} \ No newline at end of file diff --git a/Specter/src/Specter/Physics/ShiftMap.h b/Specter/src/Specter/Physics/ShiftMap.h new file mode 100644 index 0000000..546d0ce --- /dev/null +++ b/Specter/src/Specter/Physics/ShiftMap.h @@ -0,0 +1,43 @@ +/* + ShiftMap.h + New class to act a go-between for timestamp shifts to channels. Takes in a + formated file containing data for shifts and then stores them in an unordered_map. + Key is a global compass channel (board#*16 + channel). Shifts in ps. + + Written by G.W. McCann Oct. 2020 + + Not currently implemented for Specter though it could still be useful. Leave this here as a maybe upgrade path. + + GWM -- Feb 2022 +*/ +#ifndef SHIFTMAP_H +#define SHIFTMAP_H + +#include "Specter/Core/SpecCore.h" + +namespace Specter { + + class ShiftMap + { + public: + ShiftMap(); + ShiftMap(const std::string& filename); + ~ShiftMap(); + void SetFile(const std::string& filename); + inline bool IsValid() { return m_validFlag; } + inline std::string GetFilename() { return m_filename; } + uint64_t GetShift(int gchan); + + private: + void ParseFile(); + + std::string m_filename; + bool m_validFlag; + + std::unordered_map m_map; + + }; + +} + +#endif diff --git a/Specter/src/Specter/Physics/SpecData.h b/Specter/src/Specter/Physics/SpecData.h new file mode 100644 index 0000000..93c8384 --- /dev/null +++ b/Specter/src/Specter/Physics/SpecData.h @@ -0,0 +1,31 @@ +/* + SpecData.h + Simple data structures for the event building process in Specter. Way to create uniform data from different sources. Note that the current paradigm + is very heavily tied to digital data aquisition systems. Any attempt to use Specter for analog systems would require a deep overhaul of the event processing. + Most likely link to something like nscldaq. + + GWM -- Feb 2022 + + Update to reflect new CAEN binary data format with headers to indicate data contents. + + GWM -- May 2022 +*/ +#ifndef SPECDATA_H +#define SPECDATA_H + +namespace Specter { + + struct SpecData + { + uint32_t longEnergy; + uint32_t shortEnergy; + uint64_t calEnergy; + uint64_t timestamp; + uint32_t id; + }; + + using SpecEvent = std::vector; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Renderer/GraphicsContext.h b/Specter/src/Specter/Renderer/GraphicsContext.h new file mode 100644 index 0000000..be16853 --- /dev/null +++ b/Specter/src/Specter/Renderer/GraphicsContext.h @@ -0,0 +1,22 @@ +/* + GraphicsContext.h + Abstraction of graphical rendering context. Exists to allow Specter the felxibility to be backend api agnostic, though currently + only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details. + + GWM -- Feb 2022 +*/ +#ifndef GRAPHICS_CONTEXT_H +#define GRAPHICS_CONTEXT_H + +namespace Specter { + + class GraphicsContext + { + public: + virtual void Init() = 0; + virtual void SwapBuffers() = 0; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Renderer/RenderCommand.cpp b/Specter/src/Specter/Renderer/RenderCommand.cpp new file mode 100644 index 0000000..0767cc9 --- /dev/null +++ b/Specter/src/Specter/Renderer/RenderCommand.cpp @@ -0,0 +1,15 @@ +/* + RenderCommand.cpp + Render call abstraction. Exists to allow Specter the felxibility to be backend api agnostic, though currently + only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details. + + GWM -- Feb 2022 +*/ +#include "RenderCommand.h" +#include "Platform/OpenGL/OpenGLRendererAPI.h" + +namespace Specter { + + RendererAPI* RenderCommand::s_api = new OpenGLRendererAPI(); + +} \ No newline at end of file diff --git a/Specter/src/Specter/Renderer/RenderCommand.h b/Specter/src/Specter/Renderer/RenderCommand.h new file mode 100644 index 0000000..7a3c069 --- /dev/null +++ b/Specter/src/Specter/Renderer/RenderCommand.h @@ -0,0 +1,29 @@ +/* + RenderCommand.h + Render call abstraction. Exists to allow Specter the felxibility to be backend api agnostic, though currently + only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details. + + GWM -- Feb 2022 +*/ +#ifndef RENDER_COMMAND_H +#define RENDER_COMMAND_H + +#include "Specter/Core/SpecCore.h" +#include "RendererAPI.h" + +namespace Specter { + + class RenderCommand + { + public: + inline static void SetClearColor(const glm::vec4& color_array) { s_api->SetClearColor(color_array); } + inline static void Clear() { s_api->Clear(); } + inline static float GetFrameTime() { return s_api->GetFrameTime(); } + + private: + static RendererAPI* s_api; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Renderer/RendererAPI.cpp b/Specter/src/Specter/Renderer/RendererAPI.cpp new file mode 100644 index 0000000..1df3c6a --- /dev/null +++ b/Specter/src/Specter/Renderer/RendererAPI.cpp @@ -0,0 +1,14 @@ +/* + RenderAPI.h + Render API abstraction. Exists to allow Specter the felxibility to be backend api agnostic, though currently + only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details. + + GWM -- Feb 2022 +*/ +#include "RendererAPI.h" + +namespace Specter { + + RendererAPI::API RendererAPI::s_api = RendererAPI::API::OpenGL; + +} \ No newline at end of file diff --git a/Specter/src/Specter/Renderer/RendererAPI.h b/Specter/src/Specter/Renderer/RendererAPI.h new file mode 100644 index 0000000..a09b2c4 --- /dev/null +++ b/Specter/src/Specter/Renderer/RendererAPI.h @@ -0,0 +1,37 @@ +/* + RenderAPI.h + Render API abstraction. Exists to allow Specter the felxibility to be backend api agnostic, though currently + only OpenGL is implemented. Entirely based upon the work done by @TheCherno in his game engine series. See his content for more details. + + GWM -- Feb 2022 +*/ +#ifndef RENDERER_API_H +#define RENDERER_API_H + +#include "Specter/Core/SpecCore.h" +#include "glm/vec4.hpp" + +namespace Specter { + + class RendererAPI + { + public: + enum class API + { + None = 0, + OpenGL = 1 //Currently only have OpenGL + }; + + virtual void Clear() = 0; + virtual void SetClearColor(const glm::vec4& color) = 0; + virtual float GetFrameTime() = 0; + + inline static API GetAPI() { return s_api; } + + private: + static API s_api; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Utils/Instrumentor.h b/Specter/src/Specter/Utils/Instrumentor.h new file mode 100644 index 0000000..8031cd4 --- /dev/null +++ b/Specter/src/Specter/Utils/Instrumentor.h @@ -0,0 +1,260 @@ +/* + Instrumentor.h + Instrumentation classes for generating JSON trace files for use in Google Chrome's tracer. Based entirely upon the work done by @TheCherno in his game engine series. See his Hazel + repository for more details. + + To activate the profiling, switch the SPEC_PROFILE macro to a value greater than 0. By default the instrumentaion is disabled (duh). Should only be used for development cases. + + GWM -- May 2022 +*/ +#ifndef INSTRUMENTOR_H +#define INSTRUMENTOR_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Specter/Core/Logger.h" +#include "Timer.h" + +namespace Specter { + + using FloatingPointMicroseconds = std::chrono::duration; + + struct ProfileResult + { + std::string name; + FloatingPointMicroseconds start; + std::chrono::microseconds elapsedTime; + std::thread::id threadID; + }; + + struct InstrumentationSession + { + std::string name; + }; + + class Instrumentor + { + public: + Instrumentor(const Instrumentor&) = delete; + Instrumentor(Instrumentor&&) = delete; + + void BeginSession(const std::string& name, const std::string filename="inst_results.json") + { + std::scoped_lock guard(m_instMutex); + + if (m_session) + { + /* + * Can only ever have one session rolling. If for whatever reason attempt to open second, handle gracefully + */ + if (Logger::GetLogger()) //Ensure logger exists + { + SPEC_ERROR("Attempting to create new instr. session ({0}) while session ({1}) is running! Setting to session {0}", name, m_session->name); + } + + InternalEndSession(); + } + + m_outputFile.open(filename); + if (m_outputFile.is_open()) + { + m_session = new InstrumentationSession({ name }); + WriteHeader(); + } + else if(Logger::GetLogger()) + { + SPEC_ERROR("Unable to open instr. output file named {0}!", filename); + } + } + + void EndSession() + { + std::scoped_lock guard(m_instMutex); + InternalEndSession(); + } + + void WriteProfile(const ProfileResult& result) + { + std::scoped_lock guard(m_instMutex); + std::stringstream json_string; + json_string << std::setprecision(3) << std::fixed; + json_string << ",{"; + json_string << "\"cat\":\"function\","; + json_string << "\"dur\":" << (result.elapsedTime.count()) << ','; + json_string << "\"name\":\"" << result.name << "\","; + json_string << "\"ph\":\"X\","; + json_string << "\"pid\":0,"; + json_string << "\"tid\":" << result.threadID << ","; + json_string << "\"ts\":" << result.start.count(); + json_string << "}"; + + if (m_session) + { + m_outputFile << json_string.str(); + m_outputFile.flush(); + } + } + + static Instrumentor& Get() + { + static Instrumentor s_instance; + return s_instance; + } + + private: + Instrumentor() : + m_session(nullptr) + { + + } + + ~Instrumentor() + { + EndSession(); + } + + void InternalEndSession() + { + if (m_session) + { + WriteFooter(); + m_outputFile.close(); + delete m_session; + m_session = nullptr; + } + } + + void WriteHeader() + { + m_outputFile << "{\"otherData\": {},\"traceEvents\":[{}"; + m_outputFile.flush(); + } + + void WriteFooter() + { + m_outputFile << "]}"; + m_outputFile.flush(); + } + + std::mutex m_instMutex; + InstrumentationSession* m_session; + std::ofstream m_outputFile; + }; + + class InstrumentationTimer + { + public: + InstrumentationTimer(const char* name) : + m_name(name), m_stopped(false) + { + m_startTime = Clock::now(); + } + + ~InstrumentationTimer() + { + if (!m_stopped) + Stop(); + } + + + void Stop() + { + auto stopTime = Clock::now(); + FloatingPointMicroseconds start = FloatingPointMicroseconds((m_startTime).time_since_epoch()); + auto elapsedTime = std::chrono::time_point_cast(stopTime).time_since_epoch() - std::chrono::time_point_cast(m_startTime).time_since_epoch(); + m_stopped = true; + + Instrumentor::Get().WriteProfile({ m_name, start, elapsedTime, std::this_thread::get_id() }); + } + + private: + using Time = std::chrono::steady_clock::time_point; + using Clock = std::chrono::steady_clock; + + const char* m_name; + Time m_startTime; + bool m_stopped; + }; + + namespace InstrumentorUtils { + + template + struct ChangeResult + { + char data[N]; + }; + + template + constexpr auto CleanupOutputString(const char(&expr)[N], const char(&remove)[M]) + { + ChangeResult result = {}; + + size_t srcIndex = 0; + size_t destIndex = 0; + while (srcIndex < N) + { + size_t matchIndex = 0; + while (matchIndex < M - 1 && srcIndex + matchIndex < N - 1 && expr[srcIndex + matchIndex] == remove[matchIndex]) + matchIndex++; + if (matchIndex == M - 1) + srcIndex += matchIndex; + result.data[destIndex++] = expr[srcIndex] == '"' ? '\'' : expr[srcIndex]; + srcIndex++; + } + return result; + } + + } +} + +//Macros for ease of use + +#define SPEC_PROFILE 0 +#if SPEC_PROFILE + +//Attempt to resolve function signature with precompiler +#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__) +#define SPEC_FUNC_SIG __PRETTY_FUNCTION__ +#elif defined(__DMC__) && (__DMC__ >= 0x810) +#define SPEC_FUNC_SIG __PRETTY_FUNCTION__ +#elif (defined(__FUNCSIG__) || (_MSC_VER)) +#define SPEC_FUNC_SIG __FUNCSIG__ +#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500)) +#define SPEC_FUNC_SIG __FUNCTION__ +#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550) +#define SPEC_FUNC_SIG __FUNC__ +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901) +#define SPEC_FUNC_SIG __func__ +#elif defined(__cplusplus) && (__cplusplus >= 201103) +#define SPEC_FUNC_SIG __func__ +#else +#define SPEC_FUNC_SIG "SPEC_FUNC_SIG unknown!" +#endif + +#define SPEC_PROFILE_BEGIN_SESSION(name, filename) ::Specter::Instrumentor::Get().BeginSession(name, filename); +#define SPEC_PROFILE_END_SESSION() ::Specter::Instrumentor::Get().EndSession(); +#define SPEC_PROFILE_SCOPE_LINE2(name, line) constexpr auto fixedName##line = ::Specter::InstrumentorUtils::CleanupOutputString(name, "__cdecl ");\ + ::Specter::InstrumentationTimer timer##line(fixedName##line.data) +#define SPEC_PROFILE_SCOPE_LINE(name, line) SPEC_PROFILE_SCOPE_LINE2(name, line) +#define SPEC_PROFILE_SCOPE(name) SPEC_PROFILE_SCOPE_LINE(name, __LINE__) +#define SPEC_PROFILE_FUNCTION() SPEC_PROFILE_SCOPE(SPEC_FUNC_SIG) + +#else + +#define SPEC_PROFILE_BEGIN_SESSION(name, filename) +#define SPEC_PROFILE_END_SESSION() +#define SPEC_PROFILE_SCOPE_LINE2(name, line) +#define SPEC_PROFILE_SCOPE_LINE(name, line) +#define SPEC_PROFILE_SCOPE(name) +#define SPEC_PROFILE_FUNCTION() + +#endif + +#endif diff --git a/Specter/src/Specter/Utils/TCPClient.cpp b/Specter/src/Specter/Utils/TCPClient.cpp new file mode 100644 index 0000000..499391c --- /dev/null +++ b/Specter/src/Specter/Utils/TCPClient.cpp @@ -0,0 +1,85 @@ +/* + TCPClient.h + Specter's TCP client rep using asio. Contains very basic ability to read and write from a tcp socket in a non-blocking, synchronous method. + See asio docs for more details and examples on how to use. + + GWM -- April 2022 + + Note: the write functionality has not been verified. Should be fine, but test before using. +*/ +#include "TCPClient.h" + +namespace Specter { + + TCPClient::TCPClient() : + m_socket(m_context) + { + m_readBuffer.resize(s_readBufferSize); + } + + TCPClient::TCPClient(const std::string& host, const std::string& port) : + m_socket(m_context) + { + m_readBuffer.resize(s_readBufferSize); + Connect(host, port); + } + + TCPClient::~TCPClient() + { + Close(); + } + + void TCPClient::Connect(const std::string& host, const std::string& port) + { + try + { + asio::ip::tcp::resolver resolver(m_context); + auto end_points = resolver.resolve(host, port); + asio::connect(m_socket, end_points); + m_socket.non_blocking(true); //Set the connection as non-blocking + } + catch (asio::system_error& e) + { + SPEC_ERROR("Unable to connect to remote port for TCPClient! Error Code: {0}", e.what()); + } + } + + std::vector TCPClient::Read() + { + asio::error_code code; + size_t length = m_socket.read_some(asio::buffer(m_readBuffer, m_readBuffer.size()), code); + if (code == asio::error::eof) + { + SPEC_WARN("Server has closed connection. Closing the TCPClient"); + Close(); + } + else if (code && code != asio::error::would_block) + { + SPEC_ERROR("TCPClient::Read recieved unexpected error from host. Error message: {0}", code.message()); + SPEC_WARN("Closing the socket."); + Close(); + } + return std::vector(m_readBuffer.begin(), m_readBuffer.begin()+length); + } + + //untested, not currently used. + size_t TCPClient::Write(const std::vector& data) + { + asio::error_code code; + m_writeBuffer = data; + size_t length = m_socket.write_some(asio::buffer(m_writeBuffer, m_writeBuffer.size()), code); + if (code == asio::error::eof) + { + SPEC_WARN("Server has closed connection. Closing the TCPClient."); + Close(); + } + else if(code && code != asio::error::would_block) + { + SPEC_ERROR("TCPClient::Write recieved unexpected error from host. Error message: {0}", code.message()); + SPEC_WARN("Closing the socket"); + Close(); + } + + return length; + } +} \ No newline at end of file diff --git a/Specter/src/Specter/Utils/TCPClient.h b/Specter/src/Specter/Utils/TCPClient.h new file mode 100644 index 0000000..d1f84cb --- /dev/null +++ b/Specter/src/Specter/Utils/TCPClient.h @@ -0,0 +1,41 @@ +/* + TCPClient.h + Specter's TCP client rep using asio. Contains very basic ability to read and write from a tcp socket in a non-blocking, synchronous method. + See asio docs for more details and examples on how to use. + + GWM -- April 2022 + + Note: the write functionality has not been verified. Should be fine, but test before using. +*/ +#ifndef TCPCLIENT_H +#define TCPCLIENT_H + +#include + +namespace Specter { + + class TCPClient + { + public: + TCPClient(); + TCPClient(const std::string& host, const std::string& port); + ~TCPClient(); + + void Connect(const std::string& host, const std::string& port); + std::vector Read(); + size_t Write(const std::vector& data); + inline void Close() { if(IsOpen()) m_socket.close(); } + inline bool IsOpen() { return m_socket.is_open(); } + + private: + + std::vector m_readBuffer; + std::vector m_writeBuffer; + + static constexpr size_t s_readBufferSize = 24000; // reserve some space for the read buffer + asio::io_context m_context; + asio::ip::tcp::socket m_socket; + }; +} + +#endif \ No newline at end of file diff --git a/Specter/src/Specter/Utils/TestServerLayer.cpp b/Specter/src/Specter/Utils/TestServerLayer.cpp new file mode 100644 index 0000000..fa71d77 --- /dev/null +++ b/Specter/src/Specter/Utils/TestServerLayer.cpp @@ -0,0 +1,323 @@ +/* + TestServerLayer.h + This set of classes represents a test layer containing a TCP server and client connection class. This is only to be used as a way to debug + online data sources. You can have this server post data to a port on a localhost and then have the analysis thread connect to that port and analyze the + imitated data. Default setup is for an OnlineCompassSource, but of course this could be easily modified for your test of choice. For more details, + see the asio Tutorials, specifically the asynchronous daytime server. + + Here we use async methods, as we do not want the whole project to be hanging on creating a succesfull client/server connection. (Also cause it was fun + to learn) + + GWM -- April 2022 + + NOTE: There are NO security features on this server/connection. Please use with care on a protected network/firewalled machine. +*/ +#include "TestServerLayer.h" + +namespace Specter { + + /* TCPConnection */ + + TCPConnection::TCPConnection(asio::io_context& context) : + m_socket(context), m_buffer(36) + { + } + + //This function is kinda misnamed in our scheme. Should be Write to make more clear. + void TCPConnection::Start() + { + CreateBinaryBuffer(); //Generate Buffer + //CreateBinaryBufferFragmented(); //Generate fragmented buffer + //Actually write the buffer to the socket. Use std::bind to set a callback function for error handling or any other server side actions + asio::async_write(m_socket, asio::buffer(m_buffer, m_buffer.size()), + std::bind(&TCPConnection::HandleWrite, this, std::placeholders::_1, std::placeholders::_2)); + } + + //Server-side connection actions upon attempting write, only on for debugging + void TCPConnection::HandleWrite(const asio::error_code& ec, size_t bytes) + { + /* + static int i = 0; + if(!ec) + SPEC_INFO("Number written: {0}", ++i); + */ + //SPEC_INFO("Writer result: Asio Error -- {0} Amount transferred={1}", ec.message(), bytes); + } + + /* + Create C-style binary buffer from the data struct. This is to mimic the raw data source, + which will have no padding, or any other normal struct related features (and note that the intrisic + ordering from compass ensures that padding would be placed) + */ + void TCPConnection::CreateBinaryBuffer() + { + m_hit.board = 8; + m_hit.channel = 1; + m_hit.timestamp = m_hit.timestamp + s_timestep; + m_hit.energy = 512; + m_hit.energyShort = 0; + m_hit.flags = 0; + m_hit.Ns = 0; + + + char* data_pointer; + int buffer_position = 0; + data_pointer = (char*)&m_hit.board; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.channel; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.timestamp; + for (int i = 0; i < 8; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.energy; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.energyShort; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.flags; + for (int i = 0; i < 4; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.Ns; + for (int i = 0; i < 4; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + } + + /* + Create C-style binary buffer from the data struct. This is to mimic the raw data source, + which will have no padding, or any other normal struct related features (and note that the intrisic + ordering from compass ensures that padding would be placed). Here we also fragment a hit (split over 2 buffers) + to test ability to handle real life conditions + + Note -- as implemented highlights one issue with CAEN CoMPASS sources. No headers are left in the stream, + so one must be CERTAIN to attach Specter to the socket before running, otherwise fragments could lead to + scrambled unpacking order. (i.e. sometimes this will work for the test and sometimes it won't) + */ + void TCPConnection::CreateBinaryBufferFragmented() + { + static std::atomic even = true; + m_hit.board = 8; + m_hit.channel = 1; + m_hit.timestamp = m_hit.timestamp + s_timestep; + m_hit.energy = 512; + m_hit.energyShort = 0; + m_hit.flags = 0; + m_hit.Ns = 0; + + + char* data_pointer; + int buffer_position = 0; + if (even) + { + data_pointer = (char*)&m_hit.board; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.channel; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.timestamp; + for (int i = 0; i < 8; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.energy; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.energyShort; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.flags; + for (int i = 0; i < 4; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.board; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.channel; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + m_hit.timestamp += s_timestep; + data_pointer = (char*)&m_hit.timestamp; + for (int i = 0; i < 8; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + even = false; + } + else + { + data_pointer = (char*)&m_hit.energy; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.energyShort; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.flags; + for (int i = 0; i < 4; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.board; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.channel; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.timestamp; + for (int i = 0; i < 8; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.energy; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.energyShort; + for (int i = 0; i < 2; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + data_pointer = (char*)&m_hit.flags; + for (int i = 0; i < 4; i++) + { + m_buffer[buffer_position] = *(data_pointer + i); + buffer_position++; + } + } + } + + /* TCPServer */ + + TCPServer::TCPServer(asio::io_context& context) : + m_contextRef(context), m_acceptor(context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 51489)), m_isAccepted(false) + { + } + + //Attempt to create a connection + std::shared_ptr TCPServer::StartAccept() + { + std::shared_ptr new_connection = TCPConnection::CreateConnection(m_contextRef); + + m_acceptor.async_accept(new_connection->Socket(), std::bind(&TCPServer::HandleAccept, this, new_connection, std::placeholders::_1)); + + return new_connection; + } + + //If connection successful, attempt to write as a test. + void TCPServer::HandleAccept(std::shared_ptr connection, const asio::error_code& ec) + { + if (!ec) + { + m_isAccepted = true; + connection->Start(); + } + else + { + m_isAccepted = false; + SPEC_INFO("TCPServer HandleAccept found Error: {0}", ec.message()); + } + StartAccept(); + } + + /* TestServerLayer */ + + TestServerLayer::TestServerLayer() : + Layer("TestServer"), m_server(nullptr), m_connection(nullptr) + { + } + + TestServerLayer::~TestServerLayer() + { + if (m_server) + delete m_server; + } + + //Create a server, get the connection, poll actions + void TestServerLayer::OnAttach() + { + try + { + m_server = new TCPServer(m_context); + m_connection = m_server->StartAccept(); + m_context.poll(); + } + catch (std::exception& e) + { + SPEC_INFO("ServerLayer Error: {0}", e.what()); + } + } + + void TestServerLayer::OnDetach() + { + } + + //Tell to write, then poll actions + void TestServerLayer::OnUpdate(Timestep& step) + { + m_connection->Start(); + m_context.poll(); + } + +} \ No newline at end of file diff --git a/Specter/src/Specter/Utils/TestServerLayer.h b/Specter/src/Specter/Utils/TestServerLayer.h new file mode 100644 index 0000000..30dd167 --- /dev/null +++ b/Specter/src/Specter/Utils/TestServerLayer.h @@ -0,0 +1,78 @@ +/* + TestServerLayer.h + This set of classes represents a test layer containing a TCP server and client connection class. This is only to be used as a way to debug + online data sources. You can have this server post data to a port on a localhost and then have the analysis thread connect to that port and analyze the + imitated data. Default setup is for an OnlineCompassSource, but of course this could be easily modified for your test of choice. For more details, + see the asio Tutorials, specifically the asynchronous daytime server. + + Here we use async methods, as we do not want the whole project to be hanging on creating a succesfull client/server connection. (Also cause it was fun + to learn) + + GWM -- April 2022 + + NOTE: There are NO security features on this server/connection. Please use with care on a protected network/firewalled machine. +*/ +#ifndef TEST_SERVER_LAYER_H +#define TEST_SERVER_LAYER_H + +#include "Specter/Core/Layer.h" +#include "Specter/Physics/Caen/CompassHit.h" +#include "asio.hpp" + +namespace Specter { + + /*Server-side TCP Connection to the open port*/ + class TCPConnection + { + public: + TCPConnection(asio::io_context& context); + inline static std::shared_ptr CreateConnection(asio::io_context& context) { return std::make_shared(context); } + inline asio::ip::tcp::socket& Socket() { return m_socket; } + + void Start(); + + private: + void HandleWrite(const asio::error_code& ec, size_t bytes); + void CreateBinaryBuffer(); + void CreateBinaryBufferFragmented(); + + asio::ip::tcp::socket m_socket; + std::vector m_buffer; + CompassHit m_hit; + static constexpr uint64_t s_timestep = 2000000; + }; + + /*Server itself*/ + class TCPServer + { + public: + TCPServer(asio::io_context& context); + inline bool IsAccepted() { return m_isAccepted; } + std::shared_ptr StartAccept(); + + private: + void HandleAccept(std::shared_ptr connection, const asio::error_code& error); + + asio::io_context& m_contextRef; + asio::ip::tcp::acceptor m_acceptor; + bool m_isAccepted; + }; + + class TestServerLayer : public Layer + { + public: + TestServerLayer(); + virtual ~TestServerLayer(); + + virtual void OnAttach() override; + virtual void OnDetach() override; + virtual void OnUpdate(Timestep& step) override; + + private: + asio::io_context m_context; + TCPServer* m_server; + std::shared_ptr m_connection; + }; +} + +#endif diff --git a/Specter/src/Specter/Utils/Timer.h b/Specter/src/Specter/Utils/Timer.h new file mode 100644 index 0000000..f01a852 --- /dev/null +++ b/Specter/src/Specter/Utils/Timer.h @@ -0,0 +1,46 @@ +#ifndef TIMER_H +#define TIMER_H + +#include + +namespace Specter { + + class Timer + { + public: + Timer(const char* name) : + m_name(name), m_stopped(false) + { + m_startTime = Clock::now(); + } + + ~Timer() + { + if (!m_stopped) + Stop(); + } + + + void Stop() + { + auto stopTime = Clock::now(); + int64_t start = std::chrono::time_point_cast(m_startTime).time_since_epoch().count(); + int64_t stop = std::chrono::time_point_cast(stopTime).time_since_epoch().count(); + float duration = (stop - start)*0.001f; + m_stopped = true; + + SPEC_INFO("{1} -- Duration: {0} ms", m_name, duration); + } + + private: + using Time = std::chrono::steady_clock::time_point; + using Clock = std::chrono::steady_clock; + + const char* m_name; + Time m_startTime; + bool m_stopped; + }; + +} + +#endif \ No newline at end of file diff --git a/Specter/src/specpch.cpp b/Specter/src/specpch.cpp new file mode 100644 index 0000000..7d09ffb --- /dev/null +++ b/Specter/src/specpch.cpp @@ -0,0 +1 @@ +#include "specpch.h" \ No newline at end of file diff --git a/Specter/src/specpch.h b/Specter/src/specpch.h new file mode 100644 index 0000000..5584155 --- /dev/null +++ b/Specter/src/specpch.h @@ -0,0 +1,22 @@ +#ifndef SPECPCH_H +#define SPECPCH_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "Specter/Core/Logger.h" +#include "Specter/Utils/Instrumentor.h" + +#endif \ No newline at end of file