oskarastrom commited on
Commit
523a361
1 Parent(s): 2027cf1

Full Commit

Browse files
lib/fish_eye/.gitignore ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+ MANIFEST
27
+
28
+ # PyInstaller
29
+ # Usually these files are written by a python script from a template
30
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ .hypothesis/
48
+ .pytest_cache/
49
+
50
+ # Translations
51
+ *.mo
52
+ *.pot
53
+
54
+ # Django stuff:
55
+ *.log
56
+ local_settings.py
57
+ db.sqlite3
58
+
59
+ # Flask stuff:
60
+ instance/
61
+ .webassets-cache
62
+
63
+ # Scrapy stuff:
64
+ .scrapy
65
+
66
+ # Sphinx documentation
67
+ docs/_build/
68
+
69
+ # PyBuilder
70
+ target/
71
+
72
+ # Jupyter Notebook
73
+ .ipynb_checkpoints
74
+
75
+ # pyenv
76
+ .python-version
77
+
78
+ # celery beat schedule file
79
+ celerybeat-schedule
80
+
81
+ # SageMath parsed files
82
+ *.sage.py
83
+
84
+ # Environments
85
+ .env
86
+ .venv
87
+ env/
88
+ venv/
89
+ ENV/
90
+ env.bak/
91
+ venv.bak/
92
+
93
+ # Spyder project settings
94
+ .spyderproject
95
+ .spyproject
96
+
97
+ # Rope project settings
98
+ .ropeproject
99
+
100
+ # mkdocs documentation
101
+ /site
102
+
103
+ # mypy
104
+ .mypy_cache/
lib/fish_eye/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Grant Van Horn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
lib/fish_eye/README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Converting ARIS into image zipfiles that can be provided to annotators:
2
+ ```
3
+ gen_clips.py --aris_dir [location of source files]
4
+ --clip_dir [location to output unzipped images]
5
+ --dump_dir [location to output json dumps of created clips]
6
+ --river_name [eg. kenai]
7
+ --river_location [eg. ak]
8
+ --zip_location [location to output zipped images for annotation]
9
+ ```
10
+
11
+ # Annotate zip files:
12
+ https://kulits.github.io/vatic.js/index.html
13
+
14
+ # Convert outputs of annotation tool to common format:
15
+ ```
16
+ convert_annotations.py --json_dump_path [path to json dump of annotated files created in the previous step]
17
+ --xml_dir [location of annotations created in the previous step]
18
+ --output_path [location of place to put converted annotations]
19
+ ```
lib/fish_eye/beam_widths/ARIS1800_1200_48.csv ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ beam_num,beam_center,beam_left,beam_right
2
+ 0,-13.5735,-13.7893,-13.3577
3
+ 1,-13.1419,-13.3577,-12.9211
4
+ 2,-12.7003,-12.9211,-12.4723
5
+ 3,-12.2442,-12.4723,-12.0082
6
+ 4,-11.7721,-12.0082,-11.5301
7
+ 5,-11.2881,-11.5301,-11.0401
8
+ 6,-10.7921,-11.0401,-10.5373
9
+ 7,-10.2824,-10.5373,-10.0211
10
+ 8,-9.7597,-10.0211,-9.4902
11
+ 9,-9.2206,-9.4902,-8.9418
12
+ 10,-8.663,-8.9418,-8.3765
13
+ 11,-8.0899,-8.3765,-7.7963
14
+ 12,-7.5026,-7.7963,-7.2023
15
+ 13,-6.9019,-7.2023,-6.5958
16
+ 14,-6.2897,-6.5958,-5.9779
17
+ 15,-5.6661,-5.9779,-5.3476
18
+ 16,-5.0291,-5.3476,-4.7057
19
+ 17,-4.3823,-4.7057,-4.0544
20
+ 18,-3.7264,-4.0544,-3.3948
21
+ 19,-3.0632,-3.3948,-2.7282
22
+ 20,-2.3931,-2.7282,-2.0551
23
+ 21,-1.717,-2.0551,-1.3757
24
+ 22,-1.0343,-1.3757,-0.6902
25
+ 23,-0.3461,-0.6902,0.0
26
+ 24,0.3461,0.0,0.6902
27
+ 25,1.0343,0.6902,1.3757
28
+ 26,1.717,1.3757,2.0551
29
+ 27,2.3931,2.0551,2.7282
30
+ 28,3.0632,2.7282,3.3948
31
+ 29,3.7264,3.3948,4.0544
32
+ 30,4.3823,4.0544,4.7057
33
+ 31,5.0291,4.7057,5.3476
34
+ 32,5.6661,5.3476,5.9779
35
+ 33,6.2897,5.9779,6.5958
36
+ 34,6.9019,6.5958,7.2023
37
+ 35,7.5026,7.2023,7.7963
38
+ 36,8.0899,7.7963,8.3765
39
+ 37,8.663,8.3765,8.9418
40
+ 38,9.2206,8.9418,9.4902
41
+ 39,9.7597,9.4902,10.0211
42
+ 40,10.2824,10.0211,10.5373
43
+ 41,10.7921,10.5373,11.0401
44
+ 42,11.2881,11.0401,11.5301
45
+ 43,11.7721,11.5301,12.0082
46
+ 44,12.2442,12.0082,12.4723
47
+ 45,12.7003,12.4723,12.9211
48
+ 46,13.1419,12.9211,13.3577
49
+ 47,13.5735,13.3577,13.7893
lib/fish_eye/beam_widths/ARIS1800_96.csv ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ beam_num,beam_center,beam_left,beam_right
2
+ 0,-13.749,-13.8562,-13.6419
3
+ 1,-13.5347,-13.6419,-13.4261
4
+ 2,-13.3174,-13.4261,-13.2075
5
+ 3,-13.0975,-13.2075,-12.9867
6
+ 4,-12.8758,-12.9867,-12.7628
7
+ 5,-12.6498,-12.7628,-12.5348
8
+ 6,-12.4197,-12.5348,-12.3026
9
+ 7,-12.1855,-12.3026,-12.0666
10
+ 8,-11.9476,-12.0666,-11.8273
11
+ 9,-11.7069,-11.8273,-11.5853
12
+ 10,-11.4636,-11.5853,-11.3405
13
+ 11,-11.2173,-11.3405,-11.0925
14
+ 12,-10.9676,-11.0925,-10.841
15
+ 13,-10.7144,-10.841,-10.5862
16
+ 14,-10.4579,-10.5862,-10.3282
17
+ 15,-10.1985,-10.3282,-10.0669
18
+ 16,-9.9352,-10.0669,-9.8016
19
+ 17,-9.668,-9.8016,-9.5321
20
+ 18,-9.3961,-9.5321,-9.2578
21
+ 19,-9.1195,-9.2578,-8.979
22
+ 20,-8.8385,-8.979,-8.696
23
+ 21,-8.5535,-8.696,-8.4095
24
+ 22,-8.2654,-8.4095,-8.1196
25
+ 23,-7.9738,-8.1196,-7.826
26
+ 24,-7.6781,-7.826,-7.5287
27
+ 25,-7.3793,-7.5287,-7.2284
28
+ 26,-7.0774,-7.2284,-6.9251
29
+ 27,-6.7727,-6.9251,-6.619
30
+ 28,-6.4652,-6.619,-6.3101
31
+ 29,-6.1549,-6.3101,-5.9983
32
+ 30,-5.8416,-5.9983,-5.6831
33
+ 31,-5.5246,-5.6831,-5.3646
34
+ 32,-5.2046,-5.3646,-5.0435
35
+ 33,-4.8824,-5.0435,-4.7201
36
+ 34,-4.5578,-4.7201,-4.3944
37
+ 35,-4.231,-4.3944,-4.0665
38
+ 36,-3.9019,-4.0665,-3.7365
39
+ 37,-3.5711,-3.7365,-3.4049
40
+ 38,-3.2387,-3.4049,-3.0716
41
+ 39,-2.9044,-3.0716,-2.7365
42
+ 40,-2.5686,-2.7365,-2.4
43
+ 41,-2.2314,-2.4,-2.062
44
+ 42,-1.8925,-2.062,-1.7222
45
+ 43,-1.5519,-1.7222,-1.3809
46
+ 44,-1.2098,-1.3809,-1.0381
47
+ 45,-0.8664,-1.0381,-0.694
48
+ 46,-0.5216,-0.694,-0.3487
49
+ 47,-0.1757,-0.3487,-0.0026
50
+ 48,0.1706,-0.0026,0.3438
51
+ 49,0.5169,0.3438,0.6899
52
+ 50,0.8628,0.6899,1.0352
53
+ 51,1.2076,1.0352,1.3793
54
+ 52,1.551,1.3793,1.7221
55
+ 53,1.8931,1.7221,2.0634
56
+ 54,2.2337,2.0634,2.4032
57
+ 55,2.5726,2.4032,2.7412
58
+ 56,2.9098,2.7412,3.0777
59
+ 57,3.2456,3.0777,3.4128
60
+ 58,3.5799,3.4128,3.7461
61
+ 59,3.9123,3.7461,4.0777
62
+ 60,4.2431,4.0777,4.4077
63
+ 61,4.5722,4.4077,4.7356
64
+ 62,4.899,4.7356,5.0613
65
+ 63,5.2236,5.0613,5.3847
66
+ 64,5.5458,5.3847,5.7058
67
+ 65,5.8658,5.7058,6.0243
68
+ 66,6.1828,6.0243,6.3395
69
+ 67,6.4961,6.3395,6.6513
70
+ 68,6.8064,6.6513,6.9602
71
+ 69,7.1139,6.9602,7.2663
72
+ 70,7.4186,7.2663,7.5696
73
+ 71,7.7205,7.5696,7.8699
74
+ 72,8.0193,7.8699,8.1672
75
+ 73,8.315,8.1672,8.4608
76
+ 74,8.6066,8.4608,8.7507
77
+ 75,8.8947,8.7507,9.0372
78
+ 76,9.1797,9.0372,9.3202
79
+ 77,9.4607,9.3202,9.599
80
+ 78,9.7373,9.599,9.8733
81
+ 79,10.0092,9.8733,10.1428
82
+ 80,10.2764,10.1428,10.4081
83
+ 81,10.5397,10.4081,10.6694
84
+ 82,10.7991,10.6694,10.9274
85
+ 83,11.0556,10.9274,11.1822
86
+ 84,11.3088,11.1822,11.4337
87
+ 85,11.5585,11.4337,11.6817
88
+ 86,11.8048,11.6817,11.9265
89
+ 87,12.0481,11.9265,12.1685
90
+ 88,12.2888,12.1685,12.4078
91
+ 89,12.5267,12.4078,12.6438
92
+ 90,12.7609,12.6438,12.876
93
+ 91,12.991,12.876,13.104
94
+ 92,13.217,13.104,13.3279
95
+ 93,13.4387,13.3279,13.5487
96
+ 94,13.6586,13.5487,13.7673
97
+ 95,13.8759,13.7673,13.9831
lib/fish_eye/beam_widths/ARIS3000_128.csv ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ beam_num,beam_center,beam_left,beam_right
2
+ 0,-15.0068,-15.1218,-14.8918
3
+ 1,-14.7768,-14.8918,-14.6615
4
+ 2,-14.5462,-14.6615,-14.4306
5
+ 3,-14.315,-14.4306,-14.1992
6
+ 4,-14.0833,-14.1992,-13.9672
7
+ 5,-13.8511,-13.9672,-13.7349
8
+ 6,-13.6186,-13.7349,-13.5022
9
+ 7,-13.3858,-13.5022,-13.2693
10
+ 8,-13.1528,-13.2693,-13.0362
11
+ 9,-12.9196,-13.0362,-12.8029
12
+ 10,-12.6861,-12.8029,-12.5692
13
+ 11,-12.4523,-12.5692,-12.3353
14
+ 12,-12.2182,-12.3353,-12.101
15
+ 13,-11.9838,-12.101,-11.8665
16
+ 14,-11.7491,-11.8665,-11.6316
17
+ 15,-11.5141,-11.6316,-11.3965
18
+ 16,-11.2789,-11.3965,-11.1612
19
+ 17,-11.0435,-11.1612,-10.9257
20
+ 18,-10.8079,-10.9257,-10.69
21
+ 19,-10.5721,-10.69,-10.4541
22
+ 20,-10.3361,-10.4541,-10.218
23
+ 21,-10.0999,-10.218,-9.9817
24
+ 22,-9.8635,-9.9817,-9.7452
25
+ 23,-9.6269,-9.7452,-9.5086
26
+ 24,-9.3902,-9.5086,-9.2718
27
+ 25,-9.1534,-9.2718,-9.035
28
+ 26,-8.9165,-9.035,-8.798
29
+ 27,-8.6795,-8.798,-8.561
30
+ 28,-8.4424,-8.561,-8.3239
31
+ 29,-8.2053,-8.3239,-8.0868
32
+ 30,-7.9682,-8.0868,-7.8496
33
+ 31,-7.731,-7.8496,-7.6124
34
+ 32,-7.4938,-7.6124,-7.3752
35
+ 33,-7.2566,-7.3752,-7.138
36
+ 34,-7.0193,-7.138,-6.9007
37
+ 35,-6.782,-6.9007,-6.6633
38
+ 36,-6.5446,-6.6633,-6.4259
39
+ 37,-6.3072,-6.4259,-6.1885
40
+ 38,-6.0698,-6.1885,-5.9511
41
+ 39,-5.8324,-5.9511,-5.7137
42
+ 40,-5.5949,-5.7137,-5.4762
43
+ 41,-5.3574,-5.4762,-5.2387
44
+ 42,-5.1199,-5.2387,-5.0011
45
+ 43,-4.8823,-5.0011,-4.7635
46
+ 44,-4.6447,-4.7635,-4.5259
47
+ 45,-4.4071,-4.5259,-4.2883
48
+ 46,-4.1695,-4.2883,-4.0507
49
+ 47,-3.9318,-4.0507,-3.813
50
+ 48,-3.6941,-3.813,-3.5753
51
+ 49,-3.4564,-3.5753,-3.3376
52
+ 50,-3.2187,-3.3376,-3.0998
53
+ 51,-2.9809,-3.0998,-2.862
54
+ 52,-2.743,-2.862,-2.624
55
+ 53,-2.505,-2.624,-2.386
56
+ 54,-2.2669,-2.386,-2.1478
57
+ 55,-2.0287,-2.1478,-1.9096
58
+ 56,-1.7904,-1.9096,-1.6712
59
+ 57,-1.552,-1.6712,-1.4328
60
+ 58,-1.3135,-1.4328,-1.1942
61
+ 59,-1.0749,-1.1942,-0.9556
62
+ 60,-0.8362,-0.9556,-0.7168
63
+ 61,-0.5974,-0.7168,-0.478
64
+ 62,-0.3585,-0.478,-0.2391
65
+ 63,-0.1196,-0.2391,-0.0001
66
+ 64,0.1194,-0.0001,0.2389
67
+ 65,0.3584,0.2389,0.4779
68
+ 66,0.5973,0.4779,0.7168
69
+ 67,0.8362,0.7168,0.9556
70
+ 68,1.075,0.9556,1.1944
71
+ 69,1.3137,1.1944,1.433
72
+ 70,1.5523,1.433,1.6716
73
+ 71,1.7908,1.6716,1.91
74
+ 72,2.0292,1.91,2.1484
75
+ 73,2.2675,2.1484,2.3866
76
+ 74,2.5057,2.3866,2.6248
77
+ 75,2.7438,2.6248,2.8628
78
+ 76,2.9818,2.8628,3.1008
79
+ 77,3.2197,3.1008,3.3386
80
+ 78,3.4575,3.3386,3.5764
81
+ 79,3.6952,3.5764,3.8141
82
+ 80,3.9329,3.8141,4.0518
83
+ 81,4.1706,4.0518,4.2895
84
+ 82,4.4083,4.2895,4.5271
85
+ 83,4.6459,4.5271,4.7647
86
+ 84,4.8835,4.7647,5.0023
87
+ 85,5.1211,5.0023,5.2399
88
+ 86,5.3587,5.2399,5.4775
89
+ 87,5.5962,5.4775,5.715
90
+ 88,5.8337,5.715,5.9525
91
+ 89,6.0712,5.9525,6.1899
92
+ 90,6.3086,6.1899,6.4273
93
+ 91,6.546,6.4273,6.6647
94
+ 92,6.7834,6.6647,6.9021
95
+ 93,7.0208,6.9021,7.1395
96
+ 94,7.2581,7.1395,7.3768
97
+ 95,7.4954,7.3768,7.614
98
+ 96,7.7326,7.614,7.8512
99
+ 97,7.9698,7.8512,8.0884
100
+ 98,8.207,8.0884,8.3256
101
+ 99,8.4441,8.3256,8.5627
102
+ 100,8.6812,8.5627,8.7998
103
+ 101,8.9183,8.7998,9.0368
104
+ 102,9.1553,9.0368,9.2738
105
+ 103,9.3922,9.2738,9.5106
106
+ 104,9.629,9.5106,9.7474
107
+ 105,9.8657,9.7474,9.984
108
+ 106,10.1023,9.984,10.2205
109
+ 107,10.3387,10.2205,10.4568
110
+ 108,10.5749,10.4568,10.6929
111
+ 109,10.8109,10.6929,10.9288
112
+ 110,11.0467,10.9288,11.1645
113
+ 111,11.2823,11.1645,11.4
114
+ 112,11.5177,11.4,11.6353
115
+ 113,11.7529,11.6353,11.8704
116
+ 114,11.9879,11.8704,12.1053
117
+ 115,12.2226,12.1053,12.3398
118
+ 116,12.457,12.3398,12.5741
119
+ 117,12.6911,12.5741,12.808
120
+ 118,12.9249,12.808,13.0417
121
+ 119,13.1584,13.0417,13.275
122
+ 120,13.3916,13.275,13.5081
123
+ 121,13.6246,13.5081,13.741
124
+ 122,13.8574,13.741,13.9737
125
+ 123,14.0899,13.9737,14.206
126
+ 124,14.3221,14.206,14.438
127
+ 125,14.5538,14.438,14.6694
128
+ 126,14.785,14.6694,14.9003
129
+ 127,15.0156,14.9003,15.1306
lib/fish_eye/beam_widths/ARIS3000_64.csv ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ beam_num,beam_center,beam_left,beam_right
2
+ 0,-14.8872,-15.1175,-14.6569
3
+ 1,-14.4266,-14.6569,-14.1952
4
+ 2,-13.9637,-14.1952,-13.7314
5
+ 3,-13.499,-13.7314,-13.2661
6
+ 4,-13.0332,-13.2661,-12.7999
7
+ 5,-12.5665,-12.7999,-12.3326
8
+ 6,-12.0986,-12.3326,-11.8641
9
+ 7,-11.6295,-11.8641,-11.3944
10
+ 8,-11.1593,-11.3944,-10.9238
11
+ 9,-10.6883,-10.9238,-10.4524
12
+ 10,-10.2165,-10.4524,-9.9802
13
+ 11,-9.7439,-9.9802,-9.5073
14
+ 12,-9.2706,-9.5073,-9.0338
15
+ 13,-8.7969,-9.0338,-8.5599
16
+ 14,-8.3228,-8.5599,-8.0857
17
+ 15,-7.8486,-8.0857,-7.6114
18
+ 16,-7.3742,-7.6114,-7.137
19
+ 17,-6.8997,-7.137,-6.6624
20
+ 18,-6.425,-6.6624,-6.1876
21
+ 19,-5.9502,-6.1876,-5.7128
22
+ 20,-5.4753,-5.7128,-5.2378
23
+ 21,-5.0003,-5.2378,-4.7627
24
+ 22,-4.5251,-4.7627,-4.2875
25
+ 23,-4.0499,-4.2875,-3.8122
26
+ 24,-3.5745,-3.8122,-3.3368
27
+ 25,-3.0991,-3.3368,-2.8613
28
+ 26,-2.6234,-2.8613,-2.3854
29
+ 27,-2.1473,-2.3854,-1.9091
30
+ 28,-1.6708,-1.9091,-1.4324
31
+ 29,-1.1939,-1.4324,-0.9553
32
+ 30,-0.7166,-0.9553,-0.4778
33
+ 31,-0.2389,-0.4778,0.0
34
+ 32,0.2389,0.0,0.4778
35
+ 33,0.7166,0.4778,0.9553
36
+ 34,1.1939,0.9553,1.4324
37
+ 35,1.6708,1.4324,1.9091
38
+ 36,2.1473,1.9091,2.3854
39
+ 37,2.6234,2.3854,2.8613
40
+ 38,3.0991,2.8613,3.3368
41
+ 39,3.5745,3.3368,3.8122
42
+ 40,4.0499,3.8122,4.2875
43
+ 41,4.5251,4.2875,4.7627
44
+ 42,5.0003,4.7627,5.2378
45
+ 43,5.4753,5.2378,5.7128
46
+ 44,5.9502,5.7128,6.1876
47
+ 45,6.425,6.1876,6.6624
48
+ 46,6.8997,6.6624,7.137
49
+ 47,7.3742,7.137,7.6114
50
+ 48,7.8486,7.6114,8.0857
51
+ 49,8.3228,8.0857,8.5599
52
+ 50,8.7969,8.5599,9.0338
53
+ 51,9.2706,9.0338,9.5073
54
+ 52,9.7439,9.5073,9.9802
55
+ 53,10.2165,9.9802,10.4524
56
+ 54,10.6883,10.4524,10.9238
57
+ 55,11.1593,10.9238,11.3944
58
+ 56,11.6295,11.3944,11.8641
59
+ 57,12.0986,11.8641,12.3326
60
+ 58,12.5665,12.3326,12.7999
61
+ 59,13.0332,12.7999,13.2661
62
+ 60,13.499,13.2661,13.7314
63
+ 61,13.9637,13.7314,14.1952
64
+ 62,14.4266,14.1952,14.6569
65
+ 63,14.8872,14.6569,15.1175
lib/fish_eye/beam_widths/ARIS_Telephoto_48.csv ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ beam_num,beam_center,beam_left,beam_right
2
+ 0,-7.339,-7.487,-7.345
3
+ 1,-7.043,-7.193,-7.048
4
+ 2,-6.744,-6.895,-6.749
5
+ 3,-6.443,-6.595,-6.447
6
+ 4,-6.14,-6.293,-6.144
7
+ 5,-5.835,-5.988,-5.839
8
+ 6,-5.529,-5.683,-5.533
9
+ 7,-5.222,-5.377,-5.225
10
+ 8,-4.913,-5.069,-4.915
11
+ 9,-4.602,-4.758,-4.604
12
+ 10,-4.29,-4.448,-4.291
13
+ 11,-3.975,-4.133,-3.976
14
+ 12,-3.659,-3.818,-3.66
15
+ 13,-3.342,-3.501,-3.343
16
+ 14,-3.025,-3.184,-3.026
17
+ 15,-2.708,-2.867,-2.709
18
+ 16,-2.391,-2.55,-2.392
19
+ 17,-2.074,-2.233,-2.075
20
+ 18,-1.756,-1.915,-1.757
21
+ 19,-1.438,-1.598,-1.438
22
+ 20,-1.119,-1.279,-1.119
23
+ 21,-0.8,-0.96,-0.8
24
+ 22,-0.48,-0.64,-0.48
25
+ 23,-0.16,-0.32,-0.16
26
+ 24,0.16,0.16,0.32
27
+ 25,0.48,0.48,0.64
28
+ 26,0.8,0.8,0.96
29
+ 27,1.119,1.119,1.279
30
+ 28,1.438,1.438,1.598
31
+ 29,1.756,1.757,1.915
32
+ 30,2.074,2.075,2.233
33
+ 31,2.391,2.392,2.55
34
+ 32,2.708,2.709,2.867
35
+ 33,3.025,3.026,3.184
36
+ 34,3.342,3.343,3.501
37
+ 35,3.659,3.66,3.818
38
+ 36,3.975,3.976,4.133
39
+ 37,4.29,4.291,4.448
40
+ 38,4.602,4.604,4.758
41
+ 39,4.913,4.915,5.069
42
+ 40,5.222,5.225,5.377
43
+ 41,5.529,5.533,5.683
44
+ 42,5.835,5.839,5.988
45
+ 43,6.14,6.144,6.293
46
+ 44,6.443,6.447,6.595
47
+ 45,6.744,6.749,6.895
48
+ 46,7.043,7.048,7.193
49
+ 47,7.339,7.345,7.487
lib/fish_eye/beam_widths/ARIS_Telephoto_96.csv ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ beam_num,beam_center,beam_left,beam_right
2
+ 0,-7.419,-7.493,-7.345
3
+ 1,-7.271,-7.345,-7.197
4
+ 2,-7.123,-7.197,-7.048
5
+ 3,-6.974,-7.048,-6.899
6
+ 4,-6.824,-6.899,-6.749
7
+ 5,-6.674,-6.749,-6.598
8
+ 6,-6.523,-6.598,-6.447
9
+ 7,-6.372,-6.447,-6.296
10
+ 8,-6.22,-6.296,-6.144
11
+ 9,-6.068,-6.144,-5.992
12
+ 10,-5.915,-5.992,-5.839
13
+ 11,-5.763,-5.839,-5.686
14
+ 12,-5.609,-5.686,-5.533
15
+ 13,-5.456,-5.533,-5.379
16
+ 14,-5.302,-5.379,-5.225
17
+ 15,-5.148,-5.225,-5.07
18
+ 16,-4.993,-5.07,-4.915
19
+ 17,-4.838,-4.915,-4.76
20
+ 18,-4.682,-4.76,-4.604
21
+ 19,-4.526,-4.604,-4.448
22
+ 20,-4.37,-4.448,-4.291
23
+ 21,-4.213,-4.291,-4.134
24
+ 22,-4.055,-4.134,-3.976
25
+ 23,-3.897,-3.976,-3.818
26
+ 24,-3.739,-3.818,-3.66
27
+ 25,-3.581,-3.66,-3.501
28
+ 26,-3.422,-3.501,-3.343
29
+ 27,-3.264,-3.343,-3.185
30
+ 28,-3.105,-3.185,-3.026
31
+ 29,-2.947,-3.026,-2.868
32
+ 30,-2.788,-2.868,-2.709
33
+ 31,-2.63,-2.709,-2.55
34
+ 32,-2.471,-2.55,-2.392
35
+ 33,-2.313,-2.392,-2.233
36
+ 34,-2.154,-2.233,-2.075
37
+ 35,-1.995,-2.075,-1.916
38
+ 36,-1.836,-1.916,-1.757
39
+ 37,-1.677,-1.757,-1.598
40
+ 38,-1.518,-1.598,-1.438
41
+ 39,-1.359,-1.438,-1.279
42
+ 40,-1.199,-1.279,-1.119
43
+ 41,-1.039,-1.119,-0.96
44
+ 42,-0.88,-0.96,-0.8
45
+ 43,-0.72,-0.8,-0.64
46
+ 44,-0.56,-0.64,-0.48
47
+ 45,-0.4,-0.48,-0.32
48
+ 46,-0.24,-0.32,-0.16
49
+ 47,-0.08,-0.16,0.0
50
+ 48,0.08,0.0,0.16
51
+ 49,0.24,0.16,0.32
52
+ 50,0.4,0.32,0.48
53
+ 51,0.56,0.48,0.64
54
+ 52,0.72,0.64,0.8
55
+ 53,0.88,0.8,0.96
56
+ 54,1.039,0.96,1.119
57
+ 55,1.199,1.119,1.279
58
+ 56,1.359,1.279,1.438
59
+ 57,1.518,1.438,1.598
60
+ 58,1.677,1.598,1.757
61
+ 59,1.836,1.757,1.916
62
+ 60,1.995,1.916,2.075
63
+ 61,2.154,2.075,2.233
64
+ 62,2.313,2.233,2.392
65
+ 63,2.471,2.392,2.55
66
+ 64,2.63,2.55,2.709
67
+ 65,2.788,2.709,2.868
68
+ 66,2.947,2.868,3.026
69
+ 67,3.105,3.026,3.185
70
+ 68,3.264,3.185,3.343
71
+ 69,3.422,3.343,3.501
72
+ 70,3.581,3.501,3.66
73
+ 71,3.739,3.66,3.818
74
+ 72,3.897,3.818,3.976
75
+ 73,4.055,3.976,4.134
76
+ 74,4.213,4.134,4.291
77
+ 75,4.37,4.291,4.448
78
+ 76,4.526,4.448,4.605
79
+ 77,4.682,4.605,4.76
80
+ 78,4.838,4.76,4.916
81
+ 79,4.993,4.916,5.07
82
+ 80,5.148,5.07,5.225
83
+ 81,5.302,5.225,5.379
84
+ 82,5.456,5.379,5.533
85
+ 83,5.609,5.533,5.686
86
+ 84,5.763,5.686,5.839
87
+ 85,5.915,5.839,5.992
88
+ 86,6.068,5.992,6.144
89
+ 87,6.22,6.144,6.296
90
+ 88,6.372,6.296,6.448
91
+ 89,6.523,6.448,6.599
92
+ 90,6.674,6.599,6.749
93
+ 91,6.824,6.749,6.899
94
+ 92,6.974,6.899,7.049
95
+ 93,7.123,7.049,7.199
96
+ 94,7.271,7.199,7.345
97
+ 95,7.419,7.345,7.493
lib/fish_eye/beams.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utility functions for converting the beam width header files into
3
+ python arrays.
4
+
5
+ See : https://github.com/SoundMetrics/aris-file-sdk/tree/master/beam-width-metrics
6
+
7
+ """
8
+
9
+ import glob
10
+ import os
11
+ import re
12
+
13
+ import pandas as pd
14
+
15
+ def parse_beam_header_file(fp):
16
+ """ Utility to parse the beam width header files.
17
+ """
18
+
19
+ # example of what we are trying to parse:
20
+ # DEFINE_BEAMWIDTH3(0, -13.5735, -13.7893, -13.3577)
21
+
22
+ pattern = re.compile(r"^DEFINE_BEAMWIDTH3\D*(\d+), ([-+]?\d*\.\d+), ([-+]?\d*\.\d+), ([-+]?\d*\.\d+)")
23
+
24
+ beam_angles = []
25
+
26
+ with open(fp) as f:
27
+ for line in f:
28
+ m = pattern.match(line)
29
+ if m:
30
+
31
+ beam_num = int(m.group(1))
32
+ beam_center = float(m.group(2))
33
+ beam_left = float(m.group(3))
34
+ beam_right = float(m.group(4))
35
+
36
+ beam_angles.append([beam_num, beam_center, beam_left, beam_right])
37
+
38
+ # Simple sanity check
39
+ for i in range(len(beam_angles)):
40
+ assert beam_angles[i][0] == i
41
+
42
+ beam_angles = pd.DataFrame(beam_angles, columns=['beam_num', 'beam_center', 'beam_left', 'beam_right'])
43
+
44
+ return beam_angles
45
+
46
+
47
+ def convert_beam_header_files(beam_dir):
48
+ """ Convert the aris-file-sdk/beam-width-metrics directory to pandas data frames.
49
+ """
50
+
51
+ system_beam_angles = {}
52
+
53
+ for fp in glob.glob(os.path.join(beam_dir, "BeamWidths*")):
54
+
55
+ beam_angles = parse_beam_header_file(fp)
56
+ name = os.path.splitext(os.path.basename(fp))[0]
57
+ system_type = name.split("BeamWidths_")[1]
58
+
59
+ system_beam_angles[system_type] = beam_angles
60
+
61
+ return system_beam_angles
62
+
63
+
64
+ def make_csv_files_for_beam_widths(beam_dir, output_dir):
65
+ """ Convert the aris-file-sdk/beam-width-metrics directory to pandas data frames
66
+ and save them as csv files.
67
+ """
68
+
69
+ system_beam_angles = convert_beam_header_files(beam_dir)
70
+
71
+ for system_type, beam_angles in system_beam_angles.items():
72
+
73
+ beam_angles.to_csv(os.path.join(output_dir, system_type + ".csv"), index=False)
74
+
75
+
76
+
77
+ def load_beam_width_data(frame, beam_width_dir):
78
+ """ Load in the beam spacing file that corresponds to the correct ARIS setup for this frame.
79
+ """
80
+
81
+ system_type = frame.thesystemtype
82
+ beam_count = frame.BeamCount
83
+ used_telephoto = frame.largelens
84
+
85
+ beam_width_fn = None
86
+
87
+ # ARIS 1800
88
+ if system_type == 0:
89
+
90
+ if beam_count == 48:
91
+
92
+ if used_telephoto:
93
+ # ARIS_Telephoto_48
94
+ beam_width_fn = 'ARIS_Telephoto_48.csv'
95
+ else:
96
+ # ARIS1800_1200_48
97
+ beam_width_fn = 'ARIS1800_1200_48.csv'
98
+
99
+ elif beam_count == 96:
100
+
101
+ if used_telephoto:
102
+ # ARIS_Telephoto_96
103
+ beam_width_fn = 'ARIS_Telephoto_96.csv'
104
+ else:
105
+ # ARIS1800_96
106
+ beam_width_fn = 'ARIS1800_96.csv'
107
+
108
+ else:
109
+ raise ValueEror("Invalid Beam Count %d for ARIS 1800" % (beam_count,))
110
+
111
+ # ARIS 3000
112
+ elif system_type == 1:
113
+
114
+ if used_telephoto:
115
+ raise ValueEror("Don't know telephoto beam widths for ARIS 3000")
116
+
117
+ if beam_count == 64:
118
+ # ARIS3000_64
119
+ beam_width_fn = 'ARIS3000_64.csv'
120
+
121
+ elif beam_count == 128:
122
+ # ARIS3000_128
123
+ beam_width_fn = 'ARIS3000_128.csv'
124
+
125
+ else:
126
+ raise ValueEror("Invalid Beam Count %d for ARIS 3000" % (beam_count,))
127
+
128
+ # ARIS 1200
129
+ elif system_type == 2:
130
+
131
+ if beam_count != 48:
132
+ raise ValueEror("Invalid Beam Count %d for ARIS 1200" % (beam_count,))
133
+
134
+ if used_telephoto:
135
+ # ARIS_Telephoto_48
136
+ beam_width_fn = 'ARIS_Telephoto_48.csv'
137
+ else:
138
+ # ARIS1800_1200_48
139
+ beam_width_fn = 'ARIS1800_1200_48.csv'
140
+
141
+ else:
142
+ raise ValueError("Unknown System Type: %s" % (system_type,))
143
+
144
+
145
+ beam_width_fp = os.path.join(beam_width_dir, beam_width_fn)
146
+
147
+ # return pd.read_csv(beam_width_fp)
148
+ return (pd.read_csv(beam_width_fp), beam_width_fn.replace('.csv', ''))
149
+
150
+
151
+
152
+
153
+
154
+ # /Users/GVH/Code/soundmetrics/aris-file-sdk/beam-width-metrics
lib/fish_eye/convert_annotations.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from absl import app
2
+ from absl import flags
3
+ from colorsys import hls_to_rgb
4
+ import json
5
+ import numpy as np
6
+ import os
7
+ import xml.etree.ElementTree as ET
8
+
9
+ from fish_length import Fish_Length
10
+ from tracker import Tracker
11
+
12
+ flags.DEFINE_string(
13
+ 'json_dump_path', None, 'Path to json containing annotated clip info.'
14
+ )
15
+ flags.DEFINE_string(
16
+ 'xml_dir', None, 'Directory containing xml annotation files.'
17
+ )
18
+ flags.DEFINE_string(
19
+ 'output_path', None, 'Directory to output clip annotation jsons.'
20
+ )
21
+ flags.mark_flag_as_required('json_dump_path')
22
+ flags.mark_flag_as_required('xml_dir')
23
+ flags.mark_flag_as_required('output_path')
24
+ FLAGS = flags.FLAGS
25
+
26
+ def get_annotation_from_clip(clip):
27
+ root = ET.parse(os.path.join(FLAGS.xml_dir, clip['clip_name']+'.xml')).getroot()
28
+
29
+ data = {}
30
+ data['clip_id'] = clip['clip_id']
31
+ data['aris_filename'] = clip['aris_filename']
32
+ data['start_frame'] = clip['start_frame']
33
+ data['end_frame'] = clip['end_frame']
34
+ data['upstream_direction'] = clip['upstream_direction']
35
+ data['image_meter_width'] = clip['aris_info']['pixel_meter_size']*clip['aris_info']['xdim']
36
+
37
+ # Create image entries
38
+ frames = []
39
+ for i in range(int(root.findall('object')[0].findtext('startFrame')), 1 + int(root.findall('object')[0].findtext('endFrame'))):
40
+ frame = {
41
+ 'frame_num': clip['start_frame'] + i,
42
+ 'fish': []
43
+ }
44
+ frames.append(frame)
45
+
46
+ fishes = []
47
+ # Populate images with bboxes
48
+ for track_id, object in enumerate(root.findall('object')):
49
+ fish = {}
50
+ fish['id'] = track_id
51
+ fish['length'] = -1
52
+ fish['direction'] = 'N/A'
53
+ fish['start_frame_index'] = -1
54
+ fish['end_frame_index'] = -1
55
+ fish['color'] = Tracker.selectColor(track_id)
56
+
57
+ fishes.append(fish)
58
+
59
+ last_drawn = None
60
+ stat_interp = []
61
+ lengths = []
62
+ for polygon in object.findall('polygon'):
63
+ if int(polygon.findtext('pt/l')) == -1:
64
+ continue
65
+
66
+ index = int(polygon.find('t').text)
67
+
68
+ frame = frames[index]
69
+
70
+ frame_entry = {}
71
+ frame_entry['fish_id'] = track_id
72
+ frame_entry['bbox'] = None
73
+ frame_entry['visible'] = 1
74
+ frame_entry['human_labeled'] = int(polygon.findtext('pt/l'))
75
+
76
+ frame['fish'].append(frame_entry)
77
+
78
+ # Determine if polygon is stationary
79
+ if polygon.findtext('s') is not None and int(polygon.findtext('s')):
80
+ stat_interp.append(frame_entry)
81
+ else:
82
+ frame_entry['bbox'] = list(np.array([int(polygon.findtext('pt/x'))/clip['aris_info']['xdim'], int(polygon.findtext('pt/y'))/clip['aris_info']['ydim'],
83
+ int(polygon.findall('pt/x')[2].text)/clip['aris_info']['xdim'], int(polygon.findall('pt/y')[1].text)/clip['aris_info']['ydim']]))
84
+
85
+ # Coordinates of greater than 1.1 will cause training to fail
86
+ if (np.array(frame_entry['bbox']) > 1.1).any():
87
+ print('Error: Invalid bbox.')
88
+ frame['fish'].pop()
89
+ continue
90
+
91
+ lengths.append(int(polygon.findall('pt/x')[2].text)-int(polygon.findtext('pt/x')))
92
+
93
+ # Interpolate if there are stationary boxes
94
+ if stat_interp:
95
+ bbox_interp = last_drawn + np.dot(1 + np.array(range(len(stat_interp)))[:,np.newaxis], (np.array(frame_entry['bbox']) - np.array(last_drawn))[np.newaxis])/(len(stat_interp) + 1)
96
+ for frame_entry, bbox in zip(stat_interp, bbox_interp):
97
+ frame_entry['bbox'] = list(bbox)
98
+ stat_interp = []
99
+ last_drawn = frame_entry['bbox']
100
+
101
+ if fish['start_frame_index'] == -1:
102
+ fish['start_frame_index'] = index
103
+ fish['end_frame_index'] = index
104
+
105
+ if stat_interp:
106
+ for frame_entry in stat_interp:
107
+ frame_entry['bbox'] = last_drawn
108
+
109
+ data['frames'] = frames
110
+ data['fish'] = fishes
111
+
112
+ # Add track
113
+ for fish in fishes:
114
+ for frame_entry in frames[fish['start_frame_index']]['fish']:
115
+ if frame_entry['fish_id'] == fish['id']:
116
+ start_bbox = frame_entry['bbox']
117
+ break
118
+ else:
119
+ raise RuntimeWarning(f'Start box of fish {fish["id"]} in {clip["clip_name"]} is not defined.')
120
+
121
+ for frame_entry in frames[fish['end_frame_index']]['fish']:
122
+ if frame_entry['fish_id'] == fish['id']:
123
+ end_bbox = frame_entry['bbox']
124
+ break
125
+ else:
126
+ raise RuntimeWarning(f'End box of fish {fish["id"]} in {clip["clip_name"]} is not defined.')
127
+
128
+ fish['direction'] = Tracker.get_direction(start_bbox, end_bbox)
129
+
130
+ return Fish_Length.add_lengths(data)
131
+
132
+ def main(argv):
133
+ with open(FLAGS.json_dump_path) as json_file:
134
+ json_dump = json.load(json_file)
135
+
136
+ for clip in json_dump:
137
+ data = get_annotation_from_clip(clip)
138
+
139
+ with open(os.path.join(FLAGS.output_path, f'{clip["clip_name"]}.json'), 'w') as output_file:
140
+ json.dump(data, output_file, indent=2)
141
+
142
+ if __name__ == '__main__':
143
+ app.run(main)
lib/fish_eye/data_format.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ We are typically given an ARIS file and a "count file" that contains the human counts of fish at some temporal resolution. The "count file" might be a csv file, a text file, or something else. We'll create a json file that stores this information in a more convenient/universal format:
3
+
4
+ ```
5
+ { // Information for a single clip
6
+ "clip_id" : // Unique id for this clip (perhaps a UUID?)
7
+ "aris_filename" : // Path to the ARIS file
8
+ "clip_name" : // Basename of annotation files
9
+ "start_frame" : // Start frame for this "count event", used to index into the ARIS file
10
+ "end_frame" : // End frame for this "count event", used to index into the ARIS file
11
+ "start_time" : // Start time in for this "count event" (this should be the sonartimestamp of the start_frame )
12
+ "end_time " : // End time for this "count event" (this should be the sonartimestamp of the end_frame )
13
+ "upstream_direction" : // Either `left` or `right`
14
+ "fish": [ // Should have one entry for each fish
15
+ {
16
+ "frame" : , // Manual marking frame number
17
+ "direction" : , // Either `left`, `right`, or `undefined`
18
+ "length" : , // Length in meters
19
+ "x" : , // x position of marking
20
+ "y" : , // y position of marking
21
+ }
22
+ ],
23
+ "aris_info" : { // Needed for generating a warped image from the raw ARIS samples
24
+ "camera_type" : , // ARIS camera type
25
+ "framerate" : , // Frames per second
26
+ "pixel_meter_size" : , // The size of a pixel in meters
27
+ "xdim" : , // The width of the warped image
28
+ "ydim" : , // The height of the warped image
29
+ "x_meter_start" : , // x start in meters
30
+ "y_meter_start" : , // y start in meters
31
+ "x_meter_stop" : , // x stop in meters
32
+ "y_meter_stop' : , // y stop in meters
33
+ }
34
+ }
35
+ ```
36
+
37
+
38
+ When a sequence of frames are annotated from an ARIS file (i.e. a "clip"), we will produce the following json file:
39
+
40
+ ```
41
+ { // Annotation information for a single "clip"
42
+ "clip_id" : // a unique clip id, should match the `clip_id` in the clip info dictionary above.
43
+ "aris_filename" : // the name of the associated aris file
44
+ "start_frame" : // Start frame for this "clip", used to index into the ARIS file
45
+ "end_frame" : // End frame for this "clip", used to index into the ARIS file
46
+ "upstream_direction" : // Either `left` or `right`
47
+ "clip_meter_width" : // Width of a frame in meters
48
+ "clip_meter_height" : // Height of a frame in meters
49
+ "frames" : [ // Should have one entry for each frame
50
+ {
51
+ "frame_num" : , // the frame number from the ARIS file
52
+ "fish" : [ // should have one entry for each fish that is present in this frame
53
+ {
54
+ "fish_id" : // fish track id
55
+ "box" : [xmin, ymin, xmax, ymax] // in normalized coordinates (multiply by `xdim` and `ydim` to get unnormalized coordinates)
56
+ "visible" : // 0 means not visible (is this necessary?), 1 means visible
57
+ "human_labeled" : // 1 or 0 for whether a human did the annotation or if it was interpolated
58
+ }
59
+ ]
60
+ }
61
+ ],
62
+ "fish" : [ // Should have one entry for each fish
63
+ {
64
+ "id" : // fish track id (should be unique) (0 based, not unique across clips)
65
+ "length" : // computed fish length in meters
66
+ "direction" : // computed swimming direction {left, right, none}
67
+ "start_frame_index" : // first frame this fish appears in
68
+ "end_frame_index" : // last frame this fish appears in
69
+ "color" : // a unique hex color value for this fish
70
+ }
71
+ ]
72
+ }
lib/fish_eye/fish_length.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import defaultdict
2
+ from copy import deepcopy
3
+ import json
4
+ import numpy as np
5
+
6
+ class Fish_Length:
7
+ @staticmethod
8
+ def mean_length(tracks, constant, aux=-1):
9
+ return [np.mean(track[2] - track[0])*constant for track in tracks]
10
+
11
+ @staticmethod
12
+ def quantile_length(tracks, constant, aux=-1):
13
+ return [np.quantile(track[2] - track[0], aux)*constant for track in tracks]
14
+
15
+ @staticmethod
16
+ def quantile_diagonal(tracks, constant, aux=-1):
17
+ return [np.quantile(np.sqrt((track[2] - track[0])**2 + (track[3] - track[1])**2), aux)*constant for track in tracks]
18
+
19
+ @staticmethod
20
+ def add_lengths(json_data, length_fn=quantile_length.__func__, constant=0.8348286633599985, aux=0.8773333335319834, output_path=None):
21
+ json_data = deepcopy(json_data)
22
+
23
+ tracks = defaultdict(list)
24
+ for frame in json_data['frames']:
25
+ for frame_entry in frame['fish']:
26
+ tracks[frame_entry['fish_id']].append(np.array(frame_entry['bbox']))
27
+ tracks = [np.array(track).T for _, track in sorted(tracks.items())]
28
+
29
+ lengths = np.array(length_fn(tracks, constant*json_data['image_meter_width'], aux=aux))
30
+
31
+ for fish, fish_length in zip(sorted(json_data['fish'], key=lambda k: k['id']), lengths):
32
+ fish['length'] = fish_length
33
+
34
+ if output_path is not None:
35
+ with open(output_path,'w') as output:
36
+ json.dump(json_data, output, indent=2)
37
+
38
+ return json_data
lib/fish_eye/pyARIS.py ADDED
@@ -0,0 +1,1009 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ===================================================
4
+ A python interface for ARIS files
5
+ ===================================================
6
+
7
+ Last modified on: January 31, 2017
8
+ The most recent version can be found at: https://github.com/EminentCodfish/pyARIS
9
+
10
+ @author: Chris Rillahan
11
+ """
12
+
13
+ import struct, array, pytz, datetime, tqdm
14
+ import os
15
+ import subprocess as sp
16
+ from matplotlib import cm as colormap
17
+ from PIL import Image, ImageFont, ImageDraw
18
+ import numpy as np
19
+ from beams import load_beam_width_data
20
+
21
+
22
+ class ARIS_File:
23
+ 'This is a class container for the ARIS file headers'
24
+
25
+ def __init__(self, filename, version_number, FrameCount, FrameRate, HighResolution, NumRawBeams, SampleRate, SamplesPerChannel, ReceiverGain,
26
+ WindowStart, WindowLength, Reverse, SN, strDate, strHeaderID, UserID1, UserID2, UserID3, UserID4, StartFrame,EndFrame,
27
+ TimeLapse, RecordInterval, RadioSeconds, FrameInterval, Flags, AuxFlags, Sspd, Flags3D, SoftwareVersion, WaterTemp,
28
+ Salinity, PulseLength, TxMode, VersionFGPA, VersionPSuC, ThumbnailFI, FileSize, OptionalHeaderSize, OptionalTailSize,
29
+ VersionMinor, LargeLens):
30
+ self.filename = filename #Name of the ARIS file
31
+ self.version_number = version_number #File format version DDF_05 = 0x05464444
32
+ #OBSOLETE: Calculate the number of frames from file size & beams*samples.
33
+ self.FrameCount = FrameCount #Total frames in file
34
+ #OBSOLETE: See frame header instead.
35
+ self.FrameRate = FrameRate #Initial recorded frame rate
36
+ #OBSOLETE: See frame header instead.
37
+ self.HighResolution = HighResolution #Non-zero if HF, zero if LF
38
+ #OBSOLETE: See frame header instead.
39
+ self.NumRawBeams = NumRawBeams #ARIS 3000 = 128/64, ARIS 1800 = 96/48, ARIS 1200 = 48
40
+ #OBSOLETE: See frame header instead.
41
+ self.SampleRate = SampleRate #1/Sample Period
42
+ #OBSOLETE: See frame header instead.
43
+ self.SamplesPerChannel = SamplesPerChannel #Number of range samples in each beam
44
+ #OBSOLETE: See frame header instead.
45
+ self.ReceiverGain = ReceiverGain #Relative gain in dB: 0 - 40
46
+ #OBSOLETE: See frame header instead.
47
+ self.WindowStart = WindowStart #Image window start range in meters (code [0..31] in DIDSON)
48
+ #OBSOLETE: See frame header instead.
49
+ self.WindowLength = WindowLength #Image window length in meters (code [0..3] in DIDSON)
50
+ #OBSOLETE: See frame header instead.
51
+ self.Reverse = Reverse #Non-zero = lens down (DIDSON) or lens up (ARIS), zero = opposite
52
+ self.SN = SN #Sonar serial number
53
+ self.strDate = strDate #Date that file was recorded
54
+ self.strHeaderID = strHeaderID #User input to identify file in 256 characters
55
+ self.UserID1 = UserID1 #User-defined integer quantity
56
+ self.UserID2 = UserID2 #User-defined integer quantity
57
+ self.UserID3 = UserID3 #User-defined integer quantity
58
+ self.UserID4 = UserID4 #User-defined integer quantity
59
+ self.StartFrame = StartFrame #First frame number from source file (for DIDSON snippet files)
60
+ self.EndFrame = EndFrame #Last frame number from source file (for DIDSON snippet files)
61
+ self.TimeLapse = TimeLapse #Non-zero indicates time lapse recording
62
+ self.RecordInterval = RecordInterval #Number of frames/seconds between recorded frames
63
+ self.RadioSeconds = RadioSeconds #Frames or seconds interval
64
+ self.FrameInterval = FrameInterval #Record every Nth frame
65
+ self.Flags = Flags #See DDF_04 file format document (OBSOLETE)
66
+ self.AuxFlags = AuxFlags #See DDF_04 file format document
67
+ #OBSOLETE: See frame header instead.
68
+ self.Sspd = Sspd #Sound velocity in water
69
+ self.Flags3D = Flags3D #See DDF_04 file format document
70
+ self.SoftwareVersion = SoftwareVersion #DIDSON software version that recorded the file
71
+ self.WaterTemp = WaterTemp #Water temperature code: 0 = 5-15C, 1 = 15-25C, 2 = 25-35C
72
+ self.Salinity = Salinity #Salinity code: 0 = fresh, 1 = brackish, 2 = salt
73
+ self.PulseLength = PulseLength #Added for ARIS but not used
74
+ self.TxMode = TxMode #Added for ARIS but not used
75
+ self.VersionFGPA = VersionFGPA #Reserved for future use
76
+ self.VersionPSuC = VersionPSuC #Reserved for future use
77
+ self.ThumbnailFI = ThumbnailFI #Frame index of frame used for thumbnail image of file
78
+ #OBSOLETE: Do not use; query your filesystem instead.
79
+ self.FileSize = FileSize #Total file size in bytes
80
+ self.OptionalHeaderSize = OptionalHeaderSize#Reserved for future use (Obsolete, not used)
81
+ self.OptionalTailSize = OptionalTailSize #Reserved for future use (Obsolete, not used)
82
+ self.VersionMinor = VersionMinor #DIDSON_ADJUSTED_VERSION_MINOR (Obsolete)
83
+ self.LargeLens = LargeLens #Non-zero if telephoto lens (large lens, hi-res lens, big lens) is present
84
+
85
+ def __len__(self):
86
+ return self.FrameCount
87
+
88
+ def __repr__(self):
89
+ return 'ARIS File: ' + self.filename
90
+
91
+ def info(self):
92
+ print('Filename: ' + str(self.filename))
93
+ print('Software Version: ' + str(self.SoftwareVersion))
94
+ print('ARIS S/N: ' + str(self.SN))
95
+ print('File size: ' + str(self.FileSize))
96
+ print('Number of Frames: ' + str(self.FrameCount))
97
+ print('Beam Count: ' + str(self.NumRawBeams))
98
+ print('Samples/Beam: ' + str(self.SamplesPerChannel))
99
+
100
+ class ARIS_Frame(ARIS_File):
101
+ """This is a class container for the ARIS frame dataPI"""
102
+
103
+ def __init__(self, frameindex, frametime, version, status, sonartimestamp, tsday, tshour, tsminute, tssecond, tshsecond, transmitmode,
104
+ windowstart, windowlength, threshold, intensity, receivergain, degc1, degc2, humidity, focus, battery, uservalue1, uservalue2,
105
+ uservalue3, uservalue4, uservalue5, uservalue6, uservalue7, uservalue8, velocity, depth, altitude, pitch, pitchrate, roll,
106
+ rollrate, heading, headingrate, compassheading, compasspitch, compassroll, latitude, longitude, sonarposition, configflags,
107
+ beamtilt, targetrange, targetbearing, targetpresent, firmwarerevision, flags, sourceframe, watertemp, timerperiod, sonarx,
108
+ sonary, sonarz, sonarpan, sonartilt, sonarroll, panpnnl, tiltpnnl, rollpnnl, vehicletime, timeggk, dateggk, qualityggk, numsatsggk,
109
+ dopggk, ehtggk, heavetss, yeargps, monthgps, daygps, hourgps, minutegps, secondgps, hsecondgps, sonarpanoffset, sonartiltoffset,
110
+ sonarrolloffset, sonarxoffset, sonaryoffset, sonarzoffset, tmatrix, samplerate, accellx, accelly, accellz, pingmode, frequencyhilow,
111
+ pulsewidth, cycleperiod, sampleperiod, transmitenable, framerate, soundspeed, samplesperbeam, enable150v, samplestartdelay, largelens,
112
+ thesystemtype, sonarserialnumber, encryptedkey, ariserrorflagsuint, missedpackets, arisappversion, available2, reorderedsamples,
113
+ salinity, pressure, batteryvoltage, mainvoltage, switchvoltage, focusmotormoving, voltagechanging, focustimeoutfault, focusovercurrentfault,
114
+ focusnotfoundfault, focusstalledfault, fpgatimeoutfault, fpgabusyfault, fpgastuckfault, cputempfault, psutempfault, watertempfault,
115
+ humidityfault, pressurefault, voltagereadfault, voltagewritefault, focuscurrentposition, targetpan, targettilt, targetroll, panmotorerrorcode,
116
+ tiltmotorerrorcode, rollmotorerrorcode, panabsposition, tiltabsposition, rollabsposition, panaccelx, panaccely, panaccelz, tiltaccelx,
117
+ tiltaccely, tiltaccelz, rollaccelx, rollaccely, rollaccelz, appliedsettings, constrainedsettings, invalidsettings, enableinterpacketdelay,
118
+ interpacketdelayperiod, uptime, arisappversionmajor, arisappversionminor, gotime, panvelocity, tiltvelocity, rollvelocity, sentinel):
119
+
120
+ self.frameindex = frameindex #Frame number in file
121
+ self.frametime = frametime #PC time stamp when recorded; microseconds since epoch (Jan 1st 1970)
122
+ self.version = version #ARIS file format version = 0x05464444
123
+ self.status = status
124
+ self.sonartimestamp = sonartimestamp #On-sonar microseconds since epoch (Jan 1st 1970)
125
+ self.tsday = tsday
126
+ self.tshour = tshour
127
+ self.tsminute = tsminute
128
+ self.tssecond = tssecond
129
+ self.tshsecond = tshsecond
130
+ self.transmitmode = transmitmode
131
+ self.windowstart = windowstart #Window start in meters
132
+ self.windowlength = windowlength #Window length in meters
133
+ self.threshold = threshold
134
+ self.intensity = intensity
135
+ self.receivergain = receivergain #Note: 0-24 dB
136
+ self.degc1 = degc1 #CPU temperature (C)
137
+ self.degc2 = degc2 #Power supply temperature (C)
138
+ self.humidity = humidity #% relative humidity
139
+ self.focus = focus #Focus units 0-1000
140
+ self.battery = battery #OBSOLETE: Unused.
141
+ self.uservalue1 = uservalue1
142
+ self.uservalue2 = uservalue2
143
+ self.uservalue3 = uservalue3
144
+ self.uservalue4 = uservalue4
145
+ self.uservalue5 = uservalue5
146
+ self.uservalue6 = uservalue6
147
+ self.uservalue7 = uservalue7
148
+ self.uservalue8 = uservalue8
149
+ self.velocity = velocity # Platform velocity from AUV integration
150
+ self.depth = depth # Platform depth from AUV integration
151
+ self.altitude = altitude # Platform altitude from AUV integration
152
+ self.pitch = pitch # Platform pitch from AUV integration
153
+ self.pitchrate = pitchrate # Platform pitch rate from AUV integration
154
+ self.roll = roll # Platform roll from AUV integration
155
+ self.rollrate = rollrate # Platform roll rate from AUV integration
156
+ self.heading = heading # Platform heading from AUV integration
157
+ self.headingrate = headingrate # Platform heading rate from AUV integration
158
+ self.compassheading = compassheading # Sonar compass heading output
159
+ self.compasspitch = compasspitch # Sonar compass pitch output
160
+ self.compassroll = compassroll # Sonar compass roll output
161
+ self.latitude = latitude # from auxiliary GPS sensor
162
+ self.longitude = longitude # from auxiliary GPS sensor
163
+ self.sonarposition = sonarposition # special for PNNL
164
+ self.configflags = configflags
165
+ self.beamtilt = beamtilt
166
+ self.targetrange = targetrange
167
+ self.targetbearing = targetbearing
168
+ self.targetpresent = targetpresent
169
+ self.firmwarerevision = firmwarerevision #OBSOLETE: Unused.
170
+ self.flags = flags
171
+ self.sourceframe = sourceframe # Source file frame number for CSOT output files
172
+ self.watertemp = watertemp # Water temperature from housing temperature sensor
173
+ self.timerperiod = timerperiod
174
+ self.sonarx = sonarx # Sonar X location for 3D processing
175
+ self.sonary = sonary # Sonar Y location for 3D processing
176
+ self.sonayz = sonarz # Sonar Z location for 3D processing
177
+ self.sonarpan = sonarpan # X2 pan output
178
+ self.sonartilt = sonartilt # X2 tilt output
179
+ self.sonarroll = sonarroll # X2 roll output **** End of DDF_03 frame header data ****
180
+ self.panpnnl = panpnnl
181
+ self.tiltpnnl = tiltpnnl
182
+ self.rollpnnl = rollpnnl
183
+ self.vehicletime = vehicletime # special for Bluefin Robotics HAUV or other AUV integration
184
+ self.timeggk = timeggk # GPS output from NMEA GGK message
185
+ self.dateggk = dateggk # GPS output from NMEA GGK message
186
+ self.qualityggk = qualityggk # GPS output from NMEA GGK message
187
+ self.numsatsggk = numsatsggk # GPS output from NMEA GGK message
188
+ self.dopggk = dopggk # GPS output from NMEA GGK message
189
+ self.ehtggk = ehtggk # GPS output from NMEA GGK message
190
+ self.heavetss = heavetss # external sensor
191
+ self.yeargps = yeargps # GPS year output
192
+ self.monthgps = monthgps # GPS month output
193
+ self.daygps = daygps # GPS day output
194
+ self.hourgps = hourgps # GPS hour output
195
+ self.minutegps = minutegps # GPS minute output
196
+ self.secondgps = secondgps # GPS second output
197
+ self.hsecondgps = hsecondgps # GPS 1/100th second output
198
+ self.sonarpanoffset = sonarpanoffset # Sonar mount location pan offset for 3D processing
199
+ self.sonartiltoffset = sonartiltoffset # Sonar mount location tilt offset for 3D processing
200
+ self.sonarrolloffset = sonarrolloffset # Sonar mount location roll offset for 3D processing
201
+ self.sonarxoffset = sonarxoffset # Sonar mount location X offset for 3D processing
202
+ self.sonaryoffset = sonaryoffset # Sonar mount location Y offset for 3D processing
203
+ self.sonarzoffset = sonarzoffset # Sonar mount location Z offset for 3D processing
204
+ self.tmatirx = tmatrix # 3D processing transformation matrix
205
+ self.samplerate = samplerate # Calculated as 1e6/SamplePeriod
206
+ self.accellx = accellx # X-axis sonar acceleration
207
+ self.accelly = accelly # Y-axis sonar acceleration
208
+ self.accellz = accellz # Z-axis sonar acceleration
209
+ self.pingmode = pingmode # ARIS ping mode [1..12]
210
+ self.frequencyhilow = frequencyhilow # 1 = HF, 0 = LF
211
+ self.pulsewidth = pulsewidth # Width of transmit pulse in usec, [4..100]
212
+ self.cycleperiod = cycleperiod # Ping cycle time in usec, [1802..65535]
213
+ self.sampleperiod = sampleperiod # Downrange sample rate in usec, [4..100]
214
+ self.tranmitenable = transmitenable # 1 = Transmit ON, 0 = Transmit OFF
215
+ self.framerate = framerate # Instantaneous frame rate between frame N and frame N-1
216
+ self.soundspeed = soundspeed # Sound velocity in water calculated from water temperature and salinity setting
217
+ self.samplesperbeam = samplesperbeam # Number of downrange samples in each beam
218
+ self.enable150v = enable150v # 1 = 150V ON (Max Power), 0 = 150V OFF (Min Power, 12V)
219
+ self.samplestartdelay = samplestartdelay # Delay from transmit until start of sampling (window start) in usec, [930..65535]
220
+ self.largelens = largelens # 1 = telephoto lens (large lens, big lens, hi-res lens) present
221
+ self.thesystemtype = thesystemtype # 1 = ARIS 3000, 0 = ARIS 1800, 2 = ARIS 1200
222
+ self.sonarserialnumber = sonarserialnumber # Sonar serial number as labeled on housing
223
+ self.encryptedkey = encryptedkey # Reserved for future use
224
+ self.ariserrorflagsuint = ariserrorflagsuint # Error flag code bits
225
+ self.missedpackets = missedpackets # Missed packet count for Ethernet statistics reporting
226
+ self.arisappversion = arisappversion # Version number of ArisApp sending frame data
227
+ self.available2 = available2 # Reserved for future use
228
+ self.reorderedsamples = reorderedsamples # 1 = frame data already ordered into [beam,sample] array, 0 = needs reordering
229
+ self.salinity = salinity # Water salinity code: 0 = fresh, 15 = brackish, 35 = salt
230
+ self.pressure = pressure # Depth sensor output in meters (psi)
231
+ self.batteryvoltage = batteryvoltage # Battery input voltage before power steering
232
+ self.mainvoltage = mainvoltage # Main cable input voltage before power steering
233
+ self.switchvoltage = switchvoltage # Input voltage after power steering
234
+ self.focusmotormoving = focusmotormoving # Added 14-Aug-2012 for AutomaticRecording
235
+ self.voltagechanging = voltagechanging # Added 16-Aug (first two bits = 12V, second two bits = 150V, 00 = not changing, 01 = turning on, 10 = turning off)
236
+ self.focustimeoutfault = focustimeoutfault
237
+ self.focusovercurrentfault = focusovercurrentfault
238
+ self.focusnotfoundfault = focusnotfoundfault
239
+ self.focusstalledfault = focusstalledfault
240
+ self.fpgatimeoutfault = fpgatimeoutfault
241
+ self.fpgabusyfault = fpgabusyfault
242
+ self.fpgastuckfault = fpgastuckfault
243
+ self.cputempfault = cputempfault
244
+ self.psutempfault = psutempfault
245
+ self.watertempfault = watertempfault
246
+ self.humidityfault = humidityfault
247
+ self.pressurefault = pressurefault
248
+ self.voltagereadfault = voltagereadfault
249
+ self.voltagewritefault = voltagewritefault
250
+ self.focuscurrentposition = focuscurrentposition # Focus shaft current position in motor units [0.1000]
251
+ self.targetpan = targetpan # Commanded pan position
252
+ self.targettilt = targettilt # Commanded tilt position
253
+ self.targetroll = targetroll # Commanded roll position
254
+ self.panmotorerrorcode = panmotorerrorcode
255
+ self.tiltmotorerrorcode = tiltmotorerrorcode
256
+ self.rollmotorerrorcode = rollmotorerrorcode
257
+ self.panabsposition = panabsposition # Low-resolution magnetic encoder absolute pan position
258
+ self.tiltabsposition = tiltabsposition # Low-resolution magnetic encoder absolute tilt position
259
+ self.rollabsposition = rollabsposition # Low-resolution magnetic encoder absolute roll position
260
+ self.panaccelx = panaccelx # Accelerometer outputs from AR2 CPU board sensor
261
+ self.panaccely = panaccely
262
+ self.panaccelz = panaccelz
263
+ self.tiltaccelx = tiltaccelx
264
+ self.tiltaccely = tiltaccely
265
+ self.tiltaccelz = tiltaccelz
266
+ self.rollaccelx = rollaccelx
267
+ self.rollaccely = rollaccely
268
+ self.rollccelz = rollaccelz
269
+ self.appliedsettings = appliedsettings # Cookie indices for command acknowlege in frame header
270
+ self.constrainedsettings = constrainedsettings
271
+ self.invalidsettings = invalidsettings
272
+ self.enableinterpacketdelay = enableinterpacketdelay # If true delay is added between sending out image data packets
273
+ self.interpacketdelayperiod = interpacketdelayperiod # packet delay factor in us (does not include function overhead time)
274
+ self.uptime = uptime # Total number of seconds sonar has been running
275
+ self.arisappverionmajor = arisappversionmajor # Major version number
276
+ self.arisappversionminor = arisappversionminor # Minor version number
277
+ self.gotime = gotime # Sonar time when frame cycle is initiated in hardware
278
+ self.panvelocity = panvelocity # AR2 pan velocity in degrees/second
279
+ self.tiltvelocity = tiltvelocity # AR2 tilt velocity in degrees/second
280
+ self.rollvelocity = rollvelocity # AR2 roll velocity in degrees/second
281
+ self.sentinel = sentinel # Used to measure the frame header size
282
+
283
+ def __repr__(self):
284
+ return 'ARIS Frame Number: ' + str(self.frameindex)
285
+
286
+ def info(self):
287
+ print('Frame Number: ' + str(self.frameindex))
288
+ print('Frame Time: ' + str(datetime.datetime.fromtimestamp(self.sonartimestamp/1000000, pytz.timezone('UTC')).strftime('%Y-%m-%d %H:%M:%S.%f')))
289
+ print('Frame Rate: ' + str(self.framerate))
290
+ print('Window Start: ' + str(self.windowstart))
291
+ print('Window Length: ' + str(self.windowlength))
292
+ print('Ping Mode: ' + str(self.pingmode))
293
+ print('Frequency: ' + str(self.frequencyhilow))
294
+
295
+
296
+ def DataImport(filename, startFrame = 1, frameBuffer = 0):
297
+ """DataImport reads in the file specified by the filename. The function populates
298
+ a ARIS_File data structure. This function then calls the FrameRead() method
299
+ to load a starting frame.
300
+
301
+ Parameters
302
+ -----------
303
+ filename : Input file (*.aris)
304
+ startFrame : The first frame to be populated into the data structure
305
+ frameBuffer : This parameter is passed into the FrameRead method. It adds a
306
+ specified number of pixels around the edges of the remapped frame.
307
+
308
+ Returns
309
+ -------
310
+ output_data : a ARIS_File data structure
311
+ frame : An ARIS_Frame data structure
312
+
313
+ Notes
314
+ -------
315
+ Basic frame attributes can be found by calling the file.info() method.
316
+ A list of all the frames attributes can be found by using dir(file), some
317
+ of these may or may not be used by the ARIS.
318
+ """
319
+
320
+ try:
321
+ data = open(filename, 'rb')
322
+ except:
323
+ print('File Error: An error occurred trying to read the file.')
324
+ raise
325
+
326
+ #Start reading file header
327
+ version_number = struct.unpack('I', data.read(4))[0]
328
+ FrameCount = struct.unpack('I', data.read(4))[0]
329
+ FrameRate = struct.unpack('I', data.read(4))[0]
330
+ HighResolution = struct.unpack('I', data.read(4))[0]
331
+ NumRawBeams = struct.unpack('I', data.read(4))[0]
332
+ SampleRate = struct.unpack('f', data.read(4))[0]
333
+ SamplesPerChannel = struct.unpack('I', data.read(4))[0]
334
+ ReceiverGain = struct.unpack('I', data.read(4))[0]
335
+ WindowStart = struct.unpack('f', data.read(4))[0]
336
+ WindowLength = struct.unpack('f', data.read(4))[0]
337
+ Reverse = struct.unpack('I', data.read(4))[0]
338
+ SN = struct.unpack('I', data.read(4))[0]
339
+ strDate = struct.unpack('32s', data.read(32))[0]
340
+ strHeaderID = struct.unpack('256s', data.read(256))[0]
341
+ UserID1 = struct.unpack('i', data.read(4))[0]
342
+ UserID2 = struct.unpack('i', data.read(4))[0]
343
+ UserID3 = struct.unpack('i', data.read(4))[0]
344
+ UserID4 = struct.unpack('i', data.read(4))[0]
345
+ StartFrame = struct.unpack('I', data.read(4))[0]
346
+ EndFrame = struct.unpack('I', data.read(4))[0]
347
+ TimeLapse = struct.unpack('I', data.read(4))[0]
348
+ RecordInterval = struct.unpack('I', data.read(4))[0]
349
+ RadioSeconds = struct.unpack('I', data.read(4))[0]
350
+ FrameInterval = struct.unpack('I', data.read(4))[0]
351
+ Flags = struct.unpack('I', data.read(4))[0]
352
+ AuxFlags = struct.unpack('I', data.read(4))[0]
353
+ Sspd = struct.unpack('I', data.read(4))[0]
354
+ Flags3D = struct.unpack('I', data.read(4))[0]
355
+ SoftwareVersion = struct.unpack('I', data.read(4))[0]
356
+ WaterTemp = struct.unpack('I', data.read(4))[0]
357
+ Salinity = struct.unpack('I', data.read(4))[0]
358
+ PulseLength = struct.unpack('I', data.read(4))[0]
359
+ TxMode = struct.unpack('I', data.read(4))[0]
360
+ VersionFGPA = struct.unpack('I', data.read(4))[0]
361
+ VersionPSuC = struct.unpack('I', data.read(4))[0]
362
+ ThumbnailFI = struct.unpack('I', data.read(4))[0]
363
+ FileSize = struct.unpack('Q', data.read(8))[0]
364
+ OptionalHeaderSize = struct.unpack('Q', data.read(8))[0]
365
+ OptionalTailSize = struct.unpack('Q', data.read(8))[0]
366
+ VersionMinor = struct.unpack('I', data.read(4))[0]
367
+ LargeLens = struct.unpack('I', data.read(4))[0]
368
+
369
+ #Create data structure
370
+ output_data = ARIS_File(filename, version_number, FrameCount, FrameRate, HighResolution, NumRawBeams, SampleRate, SamplesPerChannel, ReceiverGain,
371
+ WindowStart, WindowLength, Reverse, SN, strDate, strHeaderID, UserID1, UserID2, UserID3, UserID4, StartFrame,EndFrame,
372
+ TimeLapse, RecordInterval, RadioSeconds, FrameInterval, Flags, AuxFlags, Sspd, Flags3D, SoftwareVersion, WaterTemp,
373
+ Salinity, PulseLength, TxMode, VersionFGPA, VersionPSuC, ThumbnailFI, FileSize, OptionalHeaderSize, OptionalTailSize,
374
+ VersionMinor, LargeLens)
375
+
376
+ #Close data file
377
+ data.close()
378
+
379
+ #Create an empty container for the lookup table
380
+ output_data.LUP = None
381
+
382
+ #Load the first frame
383
+ frame = FrameRead(output_data, startFrame)
384
+
385
+ #Return the data structure
386
+ return output_data, frame
387
+
388
+ def FrameRead(ARIS_data, frameIndex, frameBuffer = None):
389
+ """The FrameRead function loads in the specified frame data from the raw ARIS data.
390
+ The function then calls the remapARIS() function which remaps the raw data into
391
+ a 2D real world projection.
392
+
393
+ Parameters
394
+ -----------
395
+ ARIS_data : ARIS data structure returned via pyARIS.DataImport()
396
+ frameIndex : frame number
397
+ frameBuffer : This parameter add a specified number of pixels around the edges
398
+ of the remapped frame.
399
+
400
+ Returns
401
+ -------
402
+ output : a frame data structure
403
+
404
+ Notes
405
+ -------
406
+ Basic frame attributes can be found by calling the frame.info() method.
407
+ A list of all the frames attributes can be found by using dir(frame), some
408
+ of these may or may not be used by the ARIS.
409
+ """
410
+
411
+ FrameSize = ARIS_data.NumRawBeams*ARIS_data.SamplesPerChannel
412
+
413
+ frameoffset = (1024+(frameIndex*(1024+(FrameSize))))
414
+
415
+ data = open(ARIS_data.filename, 'rb')
416
+ data.seek(frameoffset, 0)
417
+
418
+ frameindex = struct.unpack('I', data.read(4))[0] #Frame number in file
419
+ frametime = struct.unpack('Q', data.read(8))[0] #PC time stamp when recorded; microseconds since epoch (Jan 1st 1970)
420
+ version = struct.unpack('I', data.read(4))[0] #ARIS file format version = 0x05464444
421
+ status = struct.unpack('I', data.read(4))[0]
422
+ sonartimestamp = struct.unpack('Q', data.read(8))[0] #On-sonar microseconds since epoch (Jan 1st 1970)
423
+ tsday = struct.unpack('I', data.read(4))[0]
424
+ tshour = struct.unpack('I', data.read(4))[0]
425
+ tsminute = struct.unpack('I', data.read(4))[0]
426
+ tssecond = struct.unpack('I', data.read(4))[0]
427
+ tshsecond = struct.unpack('I', data.read(4))[0]
428
+ transmitmode = struct.unpack('I', data.read(4))[0]
429
+ windowstart = struct.unpack('f', data.read(4))[0] #Window start in meters
430
+ windowlength = struct.unpack('f', data.read(4))[0] #Window length in meters
431
+ threshold = struct.unpack('I', data.read(4))[0]
432
+ intensity = struct.unpack('i', data.read(4))[0]
433
+ receivergain = struct.unpack('I', data.read(4))[0] #Note: 0-24 dB
434
+ degc1 = struct.unpack('I', data.read(4))[0] #CPU temperature (C)
435
+ degc2 = struct.unpack('I', data.read(4))[0] #Power supply temperature (C)
436
+ humidity = struct.unpack('I', data.read(4))[0] #% relative humidity
437
+ focus = struct.unpack('I', data.read(4))[0] #Focus units 0-1000
438
+ battery = struct.unpack('I', data.read(4))[0] #OBSOLETE: Unused.
439
+ uservalue1 = struct.unpack('f', data.read(4))[0]
440
+ uservalue2 = struct.unpack('f', data.read(4))[0]
441
+ uservalue3 = struct.unpack('f', data.read(4))[0]
442
+ uservalue4 = struct.unpack('f', data.read(4))[0]
443
+ uservalue5 = struct.unpack('f', data.read(4))[0]
444
+ uservalue6 = struct.unpack('f', data.read(4))[0]
445
+ uservalue7 = struct.unpack('f', data.read(4))[0]
446
+ uservalue8 = struct.unpack('f', data.read(4))[0]
447
+ velocity = struct.unpack('f', data.read(4))[0] # Platform velocity from AUV integration
448
+ depth = struct.unpack('f', data.read(4))[0] # Platform depth from AUV integration
449
+ altitude = struct.unpack('f', data.read(4))[0] # Platform altitude from AUV integration
450
+ pitch = struct.unpack('f', data.read(4))[0] # Platform pitch from AUV integration
451
+ pitchrate = struct.unpack('f', data.read(4))[0] # Platform pitch rate from AUV integration
452
+ roll = struct.unpack('f', data.read(4))[0] # Platform roll from AUV integration
453
+ rollrate = struct.unpack('f', data.read(4))[0] # Platform roll rate from AUV integration
454
+ heading = struct.unpack('f', data.read(4))[0] # Platform heading from AUV integration
455
+ headingrate = struct.unpack('f', data.read(4))[0] # Platform heading rate from AUV integration
456
+ compassheading = struct.unpack('f', data.read(4))[0] # Sonar compass heading output
457
+ compasspitch = struct.unpack('f', data.read(4))[0] # Sonar compass pitch output
458
+ compassroll = struct.unpack('f', data.read(4))[0] # Sonar compass roll output
459
+ latitude = struct.unpack('d', data.read(8))[0] # from auxiliary GPS sensor
460
+ longitude = struct.unpack('d', data.read(8))[0] # from auxiliary GPS sensor
461
+ sonarposition = struct.unpack('f', data.read(4))[0] # special for PNNL
462
+ configflags = struct.unpack('I', data.read(4))[0]
463
+ beamtilt = struct.unpack('f', data.read(4))[0]
464
+ targetrange = struct.unpack('f', data.read(4))[0]
465
+ targetbearing = struct.unpack('f', data.read(4))[0]
466
+ targetpresent = struct.unpack('I', data.read(4))[0]
467
+ firmwarerevision = struct.unpack('I', data.read(4))[0] #OBSOLETE: Unused.
468
+ flags = struct.unpack('I', data.read(4))[0]
469
+ sourceframe = struct.unpack('I', data.read(4))[0] # Source file frame number for CSOT output files
470
+ watertemp = struct.unpack('f', data.read(4))[0] # Water temperature from housing temperature sensor
471
+ timerperiod = struct.unpack('I', data.read(4))[0]
472
+ sonarx = struct.unpack('f', data.read(4))[0] # Sonar X location for 3D processing
473
+ sonary = struct.unpack('f', data.read(4))[0] # Sonar Y location for 3D processing
474
+ sonarz = struct.unpack('f', data.read(4))[0] # Sonar Z location for 3D processing
475
+ sonarpan = struct.unpack('f', data.read(4))[0] # X2 pan output
476
+ sonartilt = struct.unpack('f', data.read(4))[0] # X2 tilt output
477
+ sonarroll = struct.unpack('f', data.read(4))[0] # X2 roll output **** End of DDF_03 frame header data ****
478
+ panpnnl = struct.unpack('f', data.read(4))[0]
479
+ tiltpnnl = struct.unpack('f', data.read(4))[0]
480
+ rollpnnl = struct.unpack('f', data.read(4))[0]
481
+ vehicletime = struct.unpack('d', data.read(8))[0] # special for Bluefin Robotics HAUV or other AUV integration
482
+ timeggk = struct.unpack('f', data.read(4))[0] # GPS output from NMEA GGK message
483
+ dateggk = struct.unpack('I', data.read(4))[0] # GPS output from NMEA GGK message
484
+ qualityggk = struct.unpack('I', data.read(4))[0] # GPS output from NMEA GGK message
485
+ numsatsggk = struct.unpack('I', data.read(4))[0] # GPS output from NMEA GGK message
486
+ dopggk = struct.unpack('f', data.read(4))[0] # GPS output from NMEA GGK message
487
+ ehtggk = struct.unpack('f', data.read(4))[0] # GPS output from NMEA GGK message
488
+ heavetss = struct.unpack('f', data.read(4))[0] # external sensor
489
+ yeargps = struct.unpack('I', data.read(4))[0] # GPS year output
490
+ monthgps = struct.unpack('I', data.read(4))[0] # GPS month output
491
+ daygps = struct.unpack('I', data.read(4))[0] # GPS day output
492
+ hourgps = struct.unpack('I', data.read(4))[0] # GPS hour output
493
+ minutegps = struct.unpack('I', data.read(4))[0] # GPS minute output
494
+ secondgps = struct.unpack('I', data.read(4))[0] # GPS second output
495
+ hsecondgps = struct.unpack('I', data.read(4))[0] # GPS 1/100th second output
496
+ sonarpanoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location pan offset for 3D processing
497
+ sonartiltoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location tilt offset for 3D processing
498
+ sonarrolloffset = struct.unpack('f', data.read(4))[0] # Sonar mount location roll offset for 3D processing
499
+ sonarxoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location X offset for 3D processing
500
+ sonaryoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location Y offset for 3D processing
501
+ sonarzoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location Z offset for 3D processing
502
+ tmatrix = array.array('f') # 3D processing transformation matrix
503
+ for i in range(16):
504
+ tmatrix.append(struct.unpack('f', data.read(4))[0])
505
+ samplerate = struct.unpack('f', data.read(4))[0] # Calculated as 1e6/SamplePeriod
506
+ accellx = struct.unpack('f', data.read(4))[0] # X-axis sonar acceleration
507
+ accelly = struct.unpack('f', data.read(4))[0] # Y-axis sonar acceleration
508
+ accellz = struct.unpack('f', data.read(4))[0] # Z-axis sonar acceleration
509
+ pingmode = struct.unpack('I', data.read(4))[0] # ARIS ping mode [1..12]
510
+ frequencyhilow = struct.unpack('I', data.read(4))[0] # 1 = HF, 0 = LF
511
+ pulsewidth = struct.unpack('I', data.read(4))[0] # Width of transmit pulse in usec, [4..100]
512
+ cycleperiod = struct.unpack('I', data.read(4))[0] # Ping cycle time in usec, [1802..65535]
513
+ sampleperiod = struct.unpack('I', data.read(4))[0] # Downrange sample rate in usec, [4..100]
514
+ transmitenable = struct.unpack('I', data.read(4))[0] # 1 = Transmit ON, 0 = Transmit OFF
515
+ framerate = struct.unpack('f', data.read(4))[0] # Instantaneous frame rate between frame N and frame N-1
516
+ soundspeed = struct.unpack('f', data.read(4))[0] # Sound velocity in water calculated from water temperature and salinity setting
517
+ samplesperbeam = struct.unpack('I', data.read(4))[0] # Number of downrange samples in each beam
518
+ enable150v = struct.unpack('I', data.read(4))[0] # 1 = 150V ON (Max Power), 0 = 150V OFF (Min Power, 12V)
519
+ samplestartdelay = struct.unpack('I', data.read(4))[0] # Delay from transmit until start of sampling (window start) in usec, [930..65535]
520
+ largelens = struct.unpack('I', data.read(4))[0] # 1 = telephoto lens (large lens, big lens, hi-res lens) present
521
+ thesystemtype = struct.unpack('I', data.read(4))[0] # 1 = ARIS 3000, 0 = ARIS 1800, 2 = ARIS 1200
522
+ sonarserialnumber = struct.unpack('I', data.read(4))[0] # Sonar serial number as labeled on housing
523
+ encryptedkey = struct.unpack('Q', data.read(8))[0] # Reserved for future use
524
+ ariserrorflagsuint = struct.unpack('I', data.read(4))[0] # Error flag code bits
525
+ missedpackets = struct.unpack('I', data.read(4))[0] # Missed packet count for Ethernet statistics reporting
526
+ arisappversion = struct.unpack('I', data.read(4))[0] # Version number of ArisApp sending frame data
527
+ available2 = struct.unpack('I', data.read(4))[0] # Reserved for future use
528
+ reorderedsamples = struct.unpack('I', data.read(4))[0] # 1 = frame data already ordered into [beam,sample] array, 0 = needs reordering
529
+ salinity = struct.unpack('I', data.read(4))[0] # Water salinity code: 0 = fresh, 15 = brackish, 35 = salt
530
+ pressure = struct.unpack('f', data.read(4))[0] # Depth sensor output in meters (psi)
531
+ batteryvoltage = struct.unpack('f', data.read(4))[0] # Battery input voltage before power steering
532
+ mainvoltage = struct.unpack('f', data.read(4))[0] # Main cable input voltage before power steering
533
+ switchvoltage = struct.unpack('f', data.read(4))[0] # Input voltage after power steering
534
+ focusmotormoving = struct.unpack('I', data.read(4))[0] # Added 14-Aug-2012 for AutomaticRecording
535
+ voltagechanging = struct.unpack('I', data.read(4))[0] # Added 16-Aug (first two bits = 12V, second two bits = 150V, 00 = not changing, 01 = turning on, 10 = turning off)
536
+ focustimeoutfault = struct.unpack('I', data.read(4))[0]
537
+ focusovercurrentfault = struct.unpack('I', data.read(4))[0]
538
+ focusnotfoundfault = struct.unpack('I', data.read(4))[0]
539
+ focusstalledfault = struct.unpack('I', data.read(4))[0]
540
+ fpgatimeoutfault = struct.unpack('I', data.read(4))[0]
541
+ fpgabusyfault = struct.unpack('I', data.read(4))[0]
542
+ fpgastuckfault = struct.unpack('I', data.read(4))[0]
543
+ cputempfault = struct.unpack('I', data.read(4))[0]
544
+ psutempfault = struct.unpack('I', data.read(4))[0]
545
+ watertempfault = struct.unpack('I', data.read(4))[0]
546
+ humidityfault = struct.unpack('I', data.read(4))[0]
547
+ pressurefault = struct.unpack('I', data.read(4))[0]
548
+ voltagereadfault = struct.unpack('I', data.read(4))[0]
549
+ voltagewritefault = struct.unpack('I', data.read(4))[0]
550
+ focuscurrentposition = struct.unpack('I', data.read(4))[0] # Focus shaft current position in motor units [0.1000]
551
+ targetpan = struct.unpack('f', data.read(4))[0] # Commanded pan position
552
+ targettilt = struct.unpack('f', data.read(4))[0] # Commanded tilt position
553
+ targetroll = struct.unpack('f', data.read(4))[0] # Commanded roll position
554
+ panmotorerrorcode = struct.unpack('I', data.read(4))[0]
555
+ tiltmotorerrorcode = struct.unpack('I', data.read(4))[0]
556
+ rollmotorerrorcode = struct.unpack('I', data.read(4))[0]
557
+ panabsposition = struct.unpack('f', data.read(4))[0] # Low-resolution magnetic encoder absolute pan position
558
+ tiltabsposition = struct.unpack('f', data.read(4))[0] # Low-resolution magnetic encoder absolute tilt position
559
+ rollabsposition = struct.unpack('f', data.read(4))[0] # Low-resolution magnetic encoder absolute roll position
560
+ panaccelx = struct.unpack('f', data.read(4))[0] # Accelerometer outputs from AR2 CPU board sensor
561
+ panaccely = struct.unpack('f', data.read(4))[0]
562
+ panaccelz = struct.unpack('f', data.read(4))[0]
563
+ tiltaccelx = struct.unpack('f', data.read(4))[0]
564
+ tiltaccely = struct.unpack('f', data.read(4))[0]
565
+ tiltaccelz = struct.unpack('f', data.read(4))[0]
566
+ rollaccelx = struct.unpack('f', data.read(4))[0]
567
+ rollaccely = struct.unpack('f', data.read(4))[0]
568
+ rollaccelz = struct.unpack('f', data.read(4))[0]
569
+ appliedsettings = struct.unpack('I', data.read(4))[0] # Cookie indices for command acknowlege in frame header
570
+ constrainedsettings = struct.unpack('I', data.read(4))[0]
571
+ invalidsettings = struct.unpack('I', data.read(4))[0]
572
+ enableinterpacketdelay = struct.unpack('I', data.read(4))[0] # If true delay is added between sending out image data packets
573
+ interpacketdelayperiod = struct.unpack('I', data.read(4))[0] # packet delay factor in us (does not include function overhead time)
574
+ uptime = struct.unpack('I', data.read(4))[0] # Total number of seconds sonar has been running
575
+ arisappversionmajor = struct.unpack('H', data.read(2))[0] # Major version number
576
+ arisappversionminor = struct.unpack('H', data.read(2))[0] # Minor version number
577
+ gotime = struct.unpack('Q', data.read(8))[0] # Sonar time when frame cycle is initiated in hardware
578
+ panvelocity = struct.unpack('f', data.read(4))[0] # AR2 pan velocity in degrees/second
579
+ tiltvelocity = struct.unpack('f', data.read(4))[0] # AR2 tilt velocity in degrees/second
580
+ rollvelocity = struct.unpack('f', data.read(4))[0] # AR2 roll velocity in degrees/second
581
+ sentinel = struct.unpack('I', data.read(4))[0] # Used to measure the frame header size
582
+
583
+ #Create the ARIS_frame data structure and add the meta-data
584
+ output = ARIS_Frame(frameindex, frametime, version, status, sonartimestamp, tsday, tshour, tsminute, tssecond, tshsecond, transmitmode,
585
+ windowstart, windowlength, threshold, intensity, receivergain, degc1, degc2, humidity, focus, battery, uservalue1, uservalue2,
586
+ uservalue3, uservalue4, uservalue5, uservalue6, uservalue7, uservalue8, velocity, depth, altitude, pitch, pitchrate, roll,
587
+ rollrate, heading, headingrate, compassheading, compasspitch, compassroll, latitude, longitude, sonarposition, configflags,
588
+ beamtilt, targetrange, targetbearing, targetpresent, firmwarerevision, flags, sourceframe, watertemp, timerperiod, sonarx,
589
+ sonary, sonarz, sonarpan, sonartilt, sonarroll, panpnnl, tiltpnnl, rollpnnl, vehicletime, timeggk, dateggk, qualityggk, numsatsggk,
590
+ dopggk, ehtggk, heavetss, yeargps, monthgps, daygps, hourgps, minutegps, secondgps, hsecondgps, sonarpanoffset, sonartiltoffset,
591
+ sonarrolloffset, sonarxoffset, sonaryoffset, sonarzoffset, tmatrix, samplerate, accellx, accelly, accellz, pingmode, frequencyhilow,
592
+ pulsewidth, cycleperiod, sampleperiod, transmitenable, framerate, soundspeed, samplesperbeam, enable150v, samplestartdelay, largelens,
593
+ thesystemtype, sonarserialnumber, encryptedkey, ariserrorflagsuint, missedpackets, arisappversion, available2, reorderedsamples,
594
+ salinity, pressure, batteryvoltage, mainvoltage, switchvoltage, focusmotormoving, voltagechanging, focustimeoutfault, focusovercurrentfault,
595
+ focusnotfoundfault, focusstalledfault, fpgatimeoutfault, fpgabusyfault, fpgastuckfault, cputempfault, psutempfault, watertempfault,
596
+ humidityfault, pressurefault, voltagereadfault, voltagewritefault, focuscurrentposition, targetpan, targettilt, targetroll, panmotorerrorcode,
597
+ tiltmotorerrorcode, rollmotorerrorcode, panabsposition, tiltabsposition, rollabsposition, panaccelx, panaccely, panaccelz, tiltaccelx,
598
+ tiltaccely, tiltaccelz, rollaccelx, rollaccely, rollaccelz, appliedsettings, constrainedsettings, invalidsettings, enableinterpacketdelay,
599
+ interpacketdelayperiod, uptime, arisappversionmajor, arisappversionminor, gotime, panvelocity, tiltvelocity, rollvelocity, sentinel)
600
+
601
+
602
+ #Add the frame data
603
+ if pingmode in [1,2]:
604
+ ARIS_Frame.BeamCount = 48
605
+ if pingmode in [3,4,5]:
606
+ ARIS_Frame.BeamCount = 96
607
+ if pingmode in [6,7,8]:
608
+ ARIS_Frame.BeamCount = 64
609
+ if pingmode in [9,10,11,12]:
610
+ ARIS_Frame.BeamCount = 128
611
+
612
+ data.seek(frameoffset+1024, 0)
613
+ frame = np.empty([samplesperbeam, ARIS_Frame.BeamCount], dtype=float)
614
+ for r in range(len(frame)):
615
+ for c in range(len(frame[r])):
616
+ frame[r][c] = struct.unpack('B', data.read(1))[0]
617
+ frame = np.fliplr(frame)
618
+
619
+ #Remap the data from 0-255 to 0-80 dB
620
+ #remap = lambda t: (t * 80)/255
621
+ #vfunc = np.vectorize(remap)
622
+ #frame = vfunc(frame)
623
+
624
+ output.frame_data = frame
625
+ output.WinStart = output.samplestartdelay * 0.000001 * output.soundspeed / 2
626
+
627
+ #Close the data file
628
+ data.close()
629
+
630
+ return output
631
+
632
+
633
+ def get_box_for_sample(beam_num, bin_num, frame, beam_data):
634
+ """ Get the box coordinates (in meters) for a sample.
635
+ This is a non-axis aligned box.
636
+ Returns:
637
+ back left, back right, front right, front left
638
+ """
639
+
640
+ sample_start_delay = frame.samplestartdelay # usec
641
+ sound_speed = frame.soundspeed # meters / sec
642
+ sample_period = frame.sampleperiod # usec
643
+
644
+ WindowStart = sample_start_delay * 1e-6 * sound_speed / 2 # meters
645
+ sample_length = sample_period * 1e-6 * sound_speed / 2. # meters
646
+
647
+ bin_front_edge_distance = WindowStart + sample_length * bin_num
648
+ bin_back_edge_distance = WindowStart + sample_length* (bin_num + 1)
649
+
650
+ beam_angles = beam_data[beam_data['beam_num'] == beam_num]
651
+ a1 = beam_angles['beam_left'].iloc[0]
652
+ a2 = beam_angles['beam_right'].iloc[0]
653
+ c = beam_angles['beam_center'].iloc[0]
654
+
655
+ # I can't figure out whats going on with the beam spacing in the files.
656
+ # Once the center point crosses 0, the ordering of the left and right angles swap...
657
+ # For now I'll assume the y axis is the common line. Positive angles go to the left,
658
+ # negative angles go to the right
659
+ left = max(a1, a2)
660
+ right = min(a1, a2)
661
+
662
+ # Left Edge
663
+ beam_left_angle = np.deg2rad(left)
664
+ rot_matrix = np.array([[np.cos(beam_left_angle), -np.sin(beam_left_angle)], [np.sin(beam_left_angle), np.cos(beam_left_angle)]])
665
+
666
+ vec = np.array([0, bin_back_edge_distance])
667
+ bin_left_back_point = np.matmul(rot_matrix, vec)
668
+
669
+ vec = np.array([0, bin_front_edge_distance])
670
+ bin_left_front_point = np.matmul(rot_matrix, vec)
671
+
672
+ # Right Edge
673
+ beam_right_angle = np.deg2rad(right)
674
+ rot_matrix = np.array([[np.cos(beam_right_angle), -np.sin(beam_right_angle)], [np.sin(beam_right_angle), np.cos(beam_right_angle)]])
675
+
676
+ vec = np.array([0, bin_front_edge_distance])
677
+ bin_right_front_point = np.matmul(rot_matrix, vec)
678
+
679
+ vec = np.array([0, bin_back_edge_distance])
680
+ bin_right_back_point = np.matmul(rot_matrix, vec)
681
+
682
+
683
+ return bin_left_back_point, bin_right_back_point, bin_right_front_point, bin_left_front_point
684
+
685
+
686
+ def xy_to_sample(x, y, frame, beam_data):
687
+ """ Convert an x,y location (in meters) to a beam sample.
688
+ Returns:
689
+ beam num
690
+ bin num
691
+ """
692
+
693
+ # Get the angle
694
+ angle = np.rad2deg(np.arctan(x / y))
695
+
696
+ beam_num = beam_data[(beam_data['beam_left'] <= angle) & (angle <= beam_data['beam_right'])]
697
+
698
+ if beam_num.shape[0] == 0:
699
+ return None, None
700
+
701
+ beam_num = beam_num['beam_num'].iloc[0]
702
+
703
+ # Get the distance
704
+ hyp = y / np.cos(np.arctan(x / y))
705
+
706
+ # Take into account the window start
707
+ hyp -= frame.WinStart
708
+
709
+ # Sample length
710
+ bin_length = frame.sampleperiod * 0.000001 * frame.soundspeed / 2.
711
+
712
+ # Convert to bins
713
+ bin_num = int(hyp / bin_length)
714
+
715
+ if bin_num < 0 or bin_num > (frame.BeamCount - 1):
716
+ return None, None
717
+
718
+ return beam_num, bin_num
719
+
720
+
721
+ def get_minimum_pixel_meter_size(frame, beam_width_data):
722
+ """ Compute the smallest pixel size that will bound a sample.
723
+ """
724
+
725
+ all_widths = []
726
+ all_heights = []
727
+
728
+ for beam_num in range(frame.BeamCount):
729
+ bl, br, fr, fl = get_box_for_sample(beam_num, 0, frame, beam_width_data)
730
+
731
+ # determine the axis aligned box around this sample box.
732
+ xs = [bl[0], br[0], fr[0], fl[0]]
733
+ ys = [bl[1], br[1], fr[1], fl[1]]
734
+ min_x = min(xs)
735
+ max_x = max(xs)
736
+ min_y = min(ys)
737
+ max_y = max(ys)
738
+
739
+ width = max_x - min_x
740
+ height = max_y - min_y
741
+
742
+ all_widths.append(width)
743
+ all_heights.append(height)
744
+
745
+ min_width = min(all_widths)
746
+ min_height = min(all_heights)
747
+
748
+ return min(min_width, min_height)
749
+
750
+ def compute_image_bounds(pixel_meter_size, frame, beam_width_data, additional_pixel_padding_x=0, additional_pixel_padding_y=0):
751
+ """ Given the size of a pixel in meters, compute the bounds of an image that will contain the frame.
752
+ """
753
+
754
+ # Compute the projected locations of all samples so that we can get the extent
755
+ all_bl = []
756
+ all_br = []
757
+ all_fr = []
758
+ all_fl = []
759
+
760
+ for beam_num in [0, frame.BeamCount / 2, frame.BeamCount - 1]:
761
+ for bin_num in [0, frame.samplesperbeam - 1]:
762
+ bl, br, fr, fl = get_box_for_sample(beam_num, bin_num, frame, beam_width_data)
763
+
764
+ all_bl.append(bl)
765
+ all_br.append(br)
766
+ all_fr.append(fr)
767
+ all_fl.append(fl)
768
+
769
+ all_bl = np.array(all_bl)
770
+ all_br = np.array(all_br)
771
+ all_fr = np.array(all_fr)
772
+ all_fl = np.array(all_fl)
773
+
774
+ # Get the xdim extent
775
+ min_back_left = np.min(all_bl[:,0])
776
+ min_back_right = np.min(all_br[:,0])
777
+ min_front_left = np.min(all_fl[:,0])
778
+ min_front_right = np.min(all_fr[:,0])
779
+ assert min_back_left < min_back_right
780
+ assert min_back_left < min_front_left
781
+ assert min_back_left < min_front_right
782
+
783
+ max_back_left = np.max(all_bl[:,0])
784
+ max_back_right = np.max(all_br[:,0])
785
+ max_front_left = np.max(all_fl[:,0])
786
+ max_front_right = np.max(all_fr[:,0])
787
+ assert max_back_right > max_back_left
788
+ assert max_back_right > max_front_left
789
+ assert max_back_right > max_front_right
790
+
791
+ xdim_extent = np.array([min_back_left, max_back_right])
792
+
793
+
794
+ # Get the ydim extent
795
+ min_back_left = np.min(all_bl[:,1])
796
+ min_back_right = np.min(all_br[:,1])
797
+ min_front_left = np.min(all_fl[:,1])
798
+ min_front_right = np.min(all_fr[:,1])
799
+ min_front = min(min_front_left, min_front_right)
800
+ assert min_front < min_back_right
801
+ assert min_front < min_back_left
802
+
803
+
804
+ max_back_left = np.max(all_bl[:,1])
805
+ max_back_right = np.max(all_br[:,1])
806
+ max_front_left = np.max(all_fl[:,1])
807
+ max_front_right = np.max(all_fr[:,1])
808
+ max_back = max(max_back_left, max_back_right)
809
+ assert max_back > max_front_right
810
+ assert max_back > max_front_left
811
+
812
+ ydim_extent = np.array([min_front, max_back])
813
+
814
+ # Determine which meter location corresponds to our "target center"
815
+ bl, br, fr, fl = get_box_for_sample(frame.BeamCount / 2, 0, frame, beam_width_data)
816
+ target_center_x = (fl[0] + fr[0]) / 2.
817
+ target_center_y = (bl[1] + fl[1]) / 2.
818
+
819
+ # Determine the x dimension size and what this corresponds to in meters
820
+ extra_padding_x = pixel_meter_size + pixel_meter_size * additional_pixel_padding_x
821
+
822
+ # X Min
823
+ xmin_len = target_center_x - xdim_extent[0]
824
+ xp = xmin_len % pixel_meter_size
825
+ xmin_padded = xdim_extent[0] - (extra_padding_x - xp)
826
+ xmin_len = target_center_x - xmin_padded
827
+ x_min_cells = np.abs(xmin_len / pixel_meter_size)
828
+ x_min_meters = target_center_x - xmin_len
829
+ assert x_min_meters <= xdim_extent[0]
830
+
831
+
832
+ # X Max
833
+ xmax_len = xdim_extent[1] - target_center_x
834
+ xp = xmax_len % pixel_meter_size
835
+ xmax_padded = xdim_extent[1] + (extra_padding_x - xp)
836
+ xmax_len = xmax_padded - target_center_x
837
+ x_max_cells = np.abs(xmax_len / pixel_meter_size)
838
+ x_max_meters = target_center_x + xmax_len
839
+ assert x_max_meters >= xdim_extent[1]
840
+
841
+
842
+ # if we want a specific beam to be the in the middle of the image then we should take the max?
843
+ xdim = int(x_min_cells + x_max_cells)
844
+ x_meter_start = x_min_meters
845
+ x_meter_stop = x_max_meters
846
+
847
+ # Determine the y dimension size and what this corresponds to in meters
848
+ extra_padding_y = pixel_meter_size + pixel_meter_size * additional_pixel_padding_y
849
+
850
+ # Y Min
851
+ ymin_len = target_center_y - ydim_extent[0]
852
+ yp = ymin_len % pixel_meter_size
853
+ ymin_padded = ydim_extent[0] - ( extra_padding_y - yp)
854
+ ymin_len = target_center_y - ymin_padded
855
+ y_min_cells = np.abs(ymin_len / pixel_meter_size)
856
+ y_min_meters = target_center_y - ymin_len
857
+ assert y_min_meters <= ydim_extent[0]
858
+
859
+ # Y Max
860
+ ymax_len = ydim_extent[1] - target_center_y
861
+ yp = ymax_len % pixel_meter_size
862
+ ymax_padded = ydim_extent[1] + (extra_padding_y - yp)
863
+ ymax_len = ymax_padded - target_center_y
864
+ y_max_cells = np.abs(ymax_len / pixel_meter_size)
865
+ y_max_meters = target_center_y + ymax_len
866
+ assert y_max_meters >= ydim_extent[1]
867
+
868
+ ydim = int(y_min_cells + y_max_cells)
869
+ y_meter_start = y_max_meters
870
+ y_meter_stop = y_min_meters
871
+
872
+ return xdim, ydim, x_meter_start, y_meter_start, x_meter_stop, y_meter_stop
873
+
874
+
875
+ def compute_mapping_from_sample_to_image(pixel_meter_size, xdim, ydim, x_meter_start, y_meter_start, frame, beam_width_data):
876
+
877
+ x_meter_values = np.array([x_meter_start + i * pixel_meter_size for i in range(xdim)])
878
+ y_meter_values = np.array([y_meter_start - i * pixel_meter_size for i in range(ydim)])
879
+
880
+ YY, XX = np.meshgrid(y_meter_values, x_meter_values, indexing='ij')
881
+ XYpairs = np.vstack([ XX.reshape(-1), YY.reshape(-1) ]).T
882
+
883
+ II, JJ = np.meshgrid(np.arange(ydim), np.arange(xdim), indexing='ij')
884
+ IJpairs = np.vstack([ II.reshape(-1), JJ.reshape(-1)]).T
885
+
886
+ # Get the angle of the xy pairs
887
+ angles = np.rad2deg(np.arctan(XYpairs[:,0] / XYpairs[:,1]))
888
+
889
+ # Discard pairs that have an angle that is out of range
890
+ min_angle = beam_width_data['beam_left'].min()
891
+ max_angle = beam_width_data['beam_right'].max()
892
+ valid_pairs = (angles > min_angle) & (angles < max_angle)
893
+ angles = angles[valid_pairs]
894
+ XYpairs = XYpairs[valid_pairs]
895
+ IJpairs = IJpairs[valid_pairs]
896
+
897
+ # Get the distance of the xy pairs
898
+ hyp = XYpairs[:,1] / np.cos(np.arctan(XYpairs[:,0] / XYpairs[:,1]))
899
+
900
+ # Take into account the window start
901
+ hyp -= frame.WinStart
902
+
903
+ # Sample length
904
+ bin_length = frame.sampleperiod * 0.000001 * frame.soundspeed / 2.
905
+
906
+ # Convert to bins
907
+ bin_nums = (hyp / bin_length).astype(int)
908
+
909
+ # Discard pairs that have a distance that is out of range
910
+ valid_pairs = (bin_nums >= 0) & (bin_nums < frame.samplesperbeam)
911
+ bin_nums = bin_nums[valid_pairs]
912
+ angles = angles[valid_pairs]
913
+ XYpairs = XYpairs[valid_pairs]
914
+ IJpairs = IJpairs[valid_pairs]
915
+
916
+
917
+ beam_edges = beam_width_data['beam_left'].to_numpy()
918
+ beam_nums = np.digitize(angles, beam_edges) - 1
919
+
920
+ # For each valid x,y pair compute which beam it falls into
921
+ write_to = [] # (i, j) of image
922
+ read_from = [] # (bin_num, beam_num) of frame data
923
+ for index in range(XYpairs.shape[0]):
924
+
925
+ bin_num = bin_nums[index]
926
+ beam_num = beam_nums[index]
927
+
928
+ write_to.append(IJpairs[index])
929
+ read_from.append((bin_num, beam_num))
930
+
931
+ read_from = np.array(read_from)
932
+ read_from_rows = np.array(read_from[:,0])
933
+ read_from_cols = np.array(read_from[:,1])
934
+
935
+ write_to = np.array(write_to)
936
+ write_to_rows = np.array(write_to[:,0])
937
+ write_to_cols = np.array(write_to[:,1])
938
+
939
+ return read_from_rows, read_from_cols, write_to_rows, write_to_cols
940
+
941
+
942
+ def make_video(data,
943
+ xdim, ydim, sample_read_rows, sample_read_cols, image_write_rows, image_write_cols,
944
+ directory, filename, fps = 24.0, start_frame = 1, end_frame = None, timestamp = False, fontsize = 30, ts_pos = (0,0), save_raw = False):
945
+ """Output video using the ffmpeg pipeline. The current implementation
946
+ outputs compresses png files and outputs a mp4.
947
+
948
+ Parameters
949
+ -----------
950
+ data : (Str) ARIS data structure returned via pyARIS.DataImport()
951
+ filename : (Str) output filename. Must include file extension (i.e. 'video.mp4')
952
+ fps : (Float) Output video frame rate (frames per second). Default = 24 fps
953
+ start_frame, end_frame : (Int) Range of frames included in the output video
954
+ timestamp : (Bool) Add the timestamp from the sonar to the video frames
955
+ fontsize : (Int) Size of timestamp font
956
+ ts_pos : (Tuple) (x,y) location of the timestamp
957
+
958
+ Returns
959
+ -------
960
+ Returns a video into the current working directory
961
+
962
+ Notes
963
+ ------
964
+ Currently this function looks for ffmpeg.exe in the current working directory.
965
+ Must have the '*.mp4' file extension.
966
+ Uses the tqdm package to display a status bar.
967
+
968
+ Example
969
+ -------
970
+ >>> pyARIS.VideoExport(data, 'test_video.mp4', fps = 24)
971
+
972
+ """
973
+
974
+ #Command to send via the command prompt which specifies the pipe parameters
975
+ # command = ['ffmpeg',
976
+ # '-y', # (optional) overwrite output file if it exists
977
+ # '-f', 'image2pipe',
978
+ # '-vcodec', 'mjpeg', #'mjpeg',
979
+ # '-r', '1',
980
+ # '-r', str(fps), # frames per second
981
+ # '-i', '-', # The input comes from a pipe
982
+ # '-an', # Tells FFMPEG not to expect any audio
983
+ # '-vcodec', 'mpeg4',
984
+ # '-b:v', '5000k',
985
+ # directory + filename + "/"+filename+".mp4",
986
+ # '-hide_banner',
987
+ # '-loglevel', 'panic']
988
+
989
+ # Create directories if they don't exist
990
+ if not os.path.exists(os.path.join(directory, filename, 'frames/')):
991
+ os.makedirs(os.path.join(directory, filename, 'frames/'))
992
+ if save_raw and not os.path.exists(os.path.join(directory, filename, 'frames-raw/')):
993
+ os.makedirs(os.path.join(directory, filename, 'frames-raw/'))
994
+
995
+ if end_frame == None:
996
+ end_frame = data.FrameCount
997
+
998
+ cm = colormap.get_cmap('viridis')
999
+
1000
+ for i, frame_offset in enumerate(tqdm.tqdm(range(start_frame, end_frame))):
1001
+ frame = FrameRead(data, frame_offset)
1002
+ frame_image = np.zeros([ydim, xdim], dtype=np.uint8)
1003
+ frame_image[image_write_rows, image_write_cols] = frame.frame_data[sample_read_rows, sample_read_cols]
1004
+
1005
+ rgb_im = Image.fromarray(cm(frame_image, bytes=True)).convert('RGB')
1006
+ rgb_im.save(os.path.join(directory, filename, 'frames/', f'{i}.jpg'), 'JPEG')
1007
+
1008
+ if save_raw:
1009
+ Image.fromarray(np.uint8(frame.frame_data), mode='L').save(os.path.join(directory, filename, 'frames-raw/', f'{i}.jpg'), 'JPEG')
lib/fish_eye/sort.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SORT: A Simple, Online and Realtime Tracker
3
+ Copyright (C) 2016-2020 Alex Bewley alex@bewley.ai
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ """
18
+ from filterpy.kalman import KalmanFilter
19
+ import numpy as np
20
+
21
+ def linear_assignment(cost_matrix):
22
+ try:
23
+ import lap
24
+ _, x, y = lap.lapjv(cost_matrix, extend_cost=True)
25
+ return np.array([[y[i],i] for i in x if i >= 0]) #
26
+ except ImportError:
27
+ from scipy.optimize import linear_sum_assignment
28
+ x, y = linear_sum_assignment(cost_matrix)
29
+ return np.array(list(zip(x, y)))
30
+
31
+
32
+ def iou_batch(bb_test, bb_gt):
33
+ """
34
+ From SORT: Computes IOU between two bboxes in the form [l,t,w,h]
35
+ """
36
+ bb_gt = np.expand_dims(bb_gt, 0)
37
+ bb_test = np.expand_dims(bb_test, 1)
38
+
39
+ xx1 = np.maximum(bb_test[..., 0], bb_gt[..., 0])
40
+ yy1 = np.maximum(bb_test[..., 1], bb_gt[..., 1])
41
+ xx2 = np.minimum(bb_test[..., 2], bb_gt[..., 2])
42
+ yy2 = np.minimum(bb_test[..., 3], bb_gt[..., 3])
43
+ w = np.maximum(0., xx2 - xx1)
44
+ h = np.maximum(0., yy2 - yy1)
45
+ wh = w * h
46
+ o = wh / ((bb_test[..., 2] - bb_test[..., 0]) * (bb_test[..., 3] - bb_test[..., 1])
47
+ + (bb_gt[..., 2] - bb_gt[..., 0]) * (bb_gt[..., 3] - bb_gt[..., 1]) - wh)
48
+ return(o)
49
+
50
+
51
+ def convert_bbox_to_z(bbox):
52
+ """
53
+ Takes a bounding box in the form [x1,y1,x2,y2] and returns z in the form
54
+ [x,y,s,r] where x,y is the centre of the box and s is the scale/area and r is
55
+ the aspect ratio
56
+ """
57
+ w = bbox[2] - bbox[0]
58
+ h = bbox[3] - bbox[1]
59
+ x = bbox[0] + w/2.
60
+ y = bbox[1] + h/2.
61
+ s = w * h #scale is just area
62
+ r = w / float(h)
63
+ return np.array([x, y, s, r]).reshape((4, 1))
64
+
65
+
66
+ def convert_x_to_bbox(x,score=None):
67
+ """
68
+ Takes a bounding box in the centre form [x,y,s,r] and returns it in the form
69
+ [x1,y1,x2,y2] where x1,y1 is the top left and x2,y2 is the bottom right
70
+ """
71
+ w = np.sqrt(x[2] * x[3])
72
+ h = x[2] / w
73
+ if(score==None):
74
+ return np.array([x[0]-w/2.,x[1]-h/2.,x[0]+w/2.,x[1]+h/2.]).reshape((1,4))
75
+ else:
76
+ return np.array([x[0]-w/2.,x[1]-h/2.,x[0]+w/2.,x[1]+h/2.,score]).reshape((1,5))
77
+
78
+
79
+ class KalmanBoxTracker(object):
80
+ """
81
+ This class represents the internal state of individual tracked objects observed as bbox.
82
+ """
83
+ count = 0
84
+ def __init__(self,bbox):
85
+ """
86
+ Initialises a tracker using initial bounding box.
87
+ """
88
+ #define constant velocity model
89
+ self.kf = KalmanFilter(dim_x=7, dim_z=4)
90
+ self.kf.F = np.array([[1,0,0,0,1,0,0],[0,1,0,0,0,1,0],[0,0,1,0,0,0,1],[0,0,0,1,0,0,0], [0,0,0,0,1,0,0],[0,0,0,0,0,1,0],[0,0,0,0,0,0,1]])
91
+ self.kf.H = np.array([[1,0,0,0,0,0,0],[0,1,0,0,0,0,0],[0,0,1,0,0,0,0],[0,0,0,1,0,0,0]])
92
+
93
+ self.kf.R[2:,2:] *= 10.
94
+ self.kf.P[4:,4:] *= 1000. #give high uncertainty to the unobservable initial velocities
95
+ self.kf.P *= 10.
96
+ self.kf.Q[-1,-1] *= 0.01
97
+ self.kf.Q[4:,4:] *= 0.01
98
+
99
+ self.kf.x[:4] = convert_bbox_to_z(bbox)
100
+ self.time_since_update = 0
101
+ self.id = KalmanBoxTracker.count
102
+ KalmanBoxTracker.count += 1
103
+ self.history = []
104
+ self.hits = 0
105
+ self.hit_streak = 0
106
+ self.age = 0
107
+
108
+ def update(self,bbox):
109
+ """
110
+ Updates the state vector with observed bbox.
111
+ """
112
+ self.time_since_update = 0
113
+ self.history = []
114
+ self.hits += 1
115
+ self.hit_streak += 1
116
+ self.kf.update(convert_bbox_to_z(bbox))
117
+
118
+ def predict(self):
119
+ """
120
+ Advances the state vector and returns the predicted bounding box estimate.
121
+ """
122
+ if((self.kf.x[6]+self.kf.x[2])<=0):
123
+ self.kf.x[6] *= 0.0
124
+ self.kf.predict()
125
+ self.age += 1
126
+ if(self.time_since_update>0):
127
+ self.hit_streak = 0
128
+ self.time_since_update += 1
129
+ self.history.append(convert_x_to_bbox(self.kf.x))
130
+ return self.history[-1]
131
+
132
+ def get_state(self):
133
+ """
134
+ Returns the current bounding box estimate.
135
+ """
136
+ return convert_x_to_bbox(self.kf.x)
137
+
138
+
139
+ def associate_detections_to_trackers(detections,trackers,iou_threshold = 0.3):
140
+ """
141
+ Assigns detections to tracked object (both represented as bounding boxes)
142
+
143
+ Returns 3 lists of matches, unmatched_detections and unmatched_trackers
144
+ """
145
+ if(len(trackers)==0):
146
+ return np.empty((0,2),dtype=int), np.arange(len(detections)), np.empty((0,5),dtype=int)
147
+
148
+ iou_matrix = iou_batch(detections, trackers)
149
+
150
+ if min(iou_matrix.shape) > 0:
151
+ a = (iou_matrix > iou_threshold).astype(np.int32)
152
+ if a.sum(1).max() == 1 and a.sum(0).max() == 1:
153
+ matched_indices = np.stack(np.where(a), axis=1)
154
+ else:
155
+ matched_indices = linear_assignment(-iou_matrix)
156
+ else:
157
+ matched_indices = np.empty(shape=(0,2))
158
+
159
+ unmatched_detections = []
160
+ for d, _ in enumerate(detections):
161
+ if(d not in matched_indices[:,0]):
162
+ unmatched_detections.append(d)
163
+ unmatched_trackers = []
164
+ for t, _ in enumerate(trackers):
165
+ if(t not in matched_indices[:,1]):
166
+ unmatched_trackers.append(t)
167
+
168
+ #filter out matched with low IOU
169
+ matches = []
170
+ for m in matched_indices:
171
+ if(iou_matrix[m[0], m[1]]<iou_threshold):
172
+ unmatched_detections.append(m[0])
173
+ unmatched_trackers.append(m[1])
174
+ else:
175
+ matches.append(m.reshape(1,2))
176
+ if(len(matches)==0):
177
+ matches = np.empty((0,2),dtype=int)
178
+ else:
179
+ matches = np.concatenate(matches,axis=0)
180
+
181
+ return matches, np.array(unmatched_detections), np.array(unmatched_trackers)
182
+
183
+
184
+ class Sort(object):
185
+ def __init__(self, max_age=1, min_hits=3, iou_threshold=0.3):
186
+ """
187
+ Sets key parameters for SORT
188
+ """
189
+ self.max_age = max_age
190
+ self.min_hits = min_hits
191
+ self.iou_threshold = iou_threshold
192
+ self.trackers = []
193
+ self.frame_count = 0
194
+
195
+ def update(self, dets=np.empty((0, 5))):
196
+ """
197
+ Params:
198
+ dets - a numpy array of detections in the format [[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]
199
+ Requires: this method must be called once for each frame even with empty detections (use np.empty((0, 5)) for frames without detections).
200
+ Returns the a similar array, where the last column is the object ID.
201
+
202
+ NOTE: The number of objects returned may differ from the number of detections provided.
203
+ """
204
+ self.frame_count += 1
205
+ # get predicted locations from existing trackers.
206
+ trks = np.zeros((len(self.trackers), 5))
207
+ to_del = []
208
+ ret = []
209
+ for t, trk in enumerate(trks):
210
+ pos = self.trackers[t].predict()[0]
211
+ trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]
212
+ if np.any(np.isnan(pos)):
213
+ to_del.append(t)
214
+ trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
215
+ for t in reversed(to_del):
216
+ self.trackers.pop(t)
217
+ matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets,trks, self.iou_threshold)
218
+
219
+ # update matched trackers with assigned detections
220
+ for m in matched:
221
+ self.trackers[m[1]].update(dets[m[0], :])
222
+
223
+ # create and initialise new trackers for unmatched detections
224
+ for i in unmatched_dets:
225
+ trk = KalmanBoxTracker(dets[i,:])
226
+ self.trackers.append(trk)
227
+ i = len(self.trackers)
228
+ for trk in reversed(self.trackers):
229
+ d = trk.get_state()[0]
230
+ if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):
231
+ ret.append(np.concatenate((d,[trk.id+1])).reshape(1,-1)) # +1 as MOT benchmark requires positive
232
+ i -= 1
233
+ # remove dead tracklet
234
+ if(trk.time_since_update > self.max_age):
235
+ self.trackers.pop(i)
236
+ if(len(ret)>0):
237
+ return np.concatenate(ret)
238
+ return np.empty((0,5))
lib/fish_eye/tracker.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import Counter
2
+ from colorsys import hls_to_rgb
3
+ from copy import deepcopy
4
+ import json
5
+ import numpy as np
6
+
7
+ from fish_length import Fish_Length
8
+ from sort import Sort
9
+
10
+ class Tracker:
11
+ def __init__(self, clip_info, algorithm=Sort, args={'max_age':1, 'min_hits':0, 'iou_threshold':0.05}, min_hits=3):
12
+ self.algorithm = algorithm(**args)
13
+ self.fish_ids = Counter()
14
+ self.min_hits = min_hits
15
+ self.json_data = deepcopy(clip_info)
16
+ self.frame_id = self.json_data['start_frame']
17
+ self.json_data['frames'] = []
18
+
19
+ # Boxes should be given in normalized [x1,y1,x2,y2,c]
20
+ def update(self, dets=np.empty((0, 5))):
21
+ new_frame_entries = []
22
+ for track in self.algorithm.update(dets):
23
+ self.fish_ids[int(track[4])] += 1
24
+ new_frame_entries.append({
25
+ 'fish_id': int(track[4]),
26
+ 'bbox': list(track[:4]),
27
+ 'visible': 1,
28
+ 'human_labeled': 0
29
+ })
30
+ new_frame_entries = sorted(new_frame_entries, key=lambda k: k['fish_id'])
31
+
32
+ self.json_data['frames'].append(
33
+ {
34
+ 'frame_num': self.frame_id,
35
+ 'fish': new_frame_entries
36
+ })
37
+ self.frame_id += 1
38
+
39
+ def finalize(self, output_path=None, min_length=-1.0): # vert_margin=0.0
40
+ json_data = deepcopy(self.json_data)
41
+
42
+ # map (valid) fish IDs to 0, 1, 2, ...
43
+ fish_id_map = {}
44
+ for fish_id, count in self.fish_ids.items():
45
+ if count >= self.min_hits:
46
+ fish_id_map[fish_id] = len(fish_id_map)
47
+
48
+ # separate frame boxes into tracks, keyed by mapped IDs
49
+ # each track is a list of tuples ( bbox, frame_num )
50
+ tracks = { v : [] for _, v in fish_id_map.items() }
51
+ for frame in json_data['frames']:
52
+ for bbox in frame['fish']:
53
+ # check if valid
54
+ if bbox['fish_id'] in fish_id_map.keys():
55
+ track_id = fish_id_map[bbox['fish_id']]
56
+ tracks[track_id].append((bbox['bbox'], frame['frame_num']))
57
+
58
+ # map IDs and keep frame['fish'] sorted by ID
59
+ for i, frame in enumerate(json_data['frames']):
60
+ new_frame_entries = []
61
+ for frame_entry in frame['fish']:
62
+ if frame_entry['fish_id'] in fish_id_map:
63
+ frame_entry['fish_id'] = fish_id_map[frame_entry['fish_id']]
64
+ new_frame_entries.append(frame_entry)
65
+ frame['fish'] = sorted(new_frame_entries, key=lambda k: k['fish_id'])
66
+
67
+ # create summary 'fish' entry for json data
68
+ json_data['fish'] = []
69
+ for track_id, boxes in tracks.items():
70
+ fish_entry = {}
71
+ fish_entry['id'] = track_id
72
+ fish_entry['length'] = -1
73
+
74
+ # top = False
75
+ # bottom = False
76
+ # for frame in json_data['frames']:
77
+ # for frame_entry in frame['fish']:
78
+ # if frame_entry['fish_id'] == track_id:
79
+ # if frame_entry['bbox'][3] > vert_margin:
80
+ # top = True
81
+ # if frame_entry['bbox'][1] < 1 - vert_margin:
82
+ # bottom = True
83
+ # break
84
+
85
+ # if not top or not bottom:
86
+ # continue
87
+
88
+ start_bbox = boxes[0][0]
89
+ end_bbox = boxes[-1][0]
90
+ fish_entry['direction'] = Tracker.get_direction(start_bbox, end_bbox)
91
+
92
+ fish_entry['start_frame_index'] = boxes[0][1]
93
+ fish_entry['end_frame_index'] = boxes[-1][1]
94
+ fish_entry['color'] = Tracker.selectColor(track_id)
95
+
96
+ json_data['fish'].append(fish_entry)
97
+
98
+ # filter 'fish' field by fish length
99
+ json_data = Fish_Length.add_lengths(json_data)
100
+ invalid_ids = []
101
+ if min_length != -1.0:
102
+ new_fish = []
103
+ for fish in json_data['fish']:
104
+ if fish['length'] > min_length:
105
+ new_fish.append(fish)
106
+ else:
107
+ invalid_ids.append(fish['id'])
108
+ json_data['fish'] = new_fish
109
+
110
+ # filter 'frames' field by fish length
111
+ if len(invalid_ids):
112
+ for frame in json_data['frames']:
113
+ new_fish = []
114
+ for fish in frame['fish']:
115
+ if fish['fish_id'] not in invalid_ids:
116
+ new_fish.append(fish)
117
+ frame['fish'] = new_fish
118
+
119
+ if output_path is not None:
120
+ with open(output_path,'w') as output:
121
+ json.dump(json_data, output, indent=2)
122
+
123
+ return json_data
124
+
125
+ def state(self, output_path=None):
126
+ json_data = deepcopy(self.json_data)
127
+
128
+ if output_path is not None:
129
+ with open(output_path,'w') as output:
130
+ json.dump(json_data, output, indent=2)
131
+
132
+ return json_data
133
+
134
+ @staticmethod
135
+ def selectColor(number):
136
+ hue = ((number * 137.508 + 60) % 360) / 360
137
+ return '#{0:02x}{1:02x}{2:02x}'.format(*(int(n * 255) for n in hls_to_rgb(hue, 0.5, 0.75)))
138
+
139
+ @staticmethod
140
+ def get_direction(start_bbox, end_bbox):
141
+ start_center = (start_bbox[2] + start_bbox[0])/2
142
+ end_center = (end_bbox[2] + end_bbox[0])/2
143
+ if start_center < 0.5 and end_center >= 0.5:
144
+ return 'right'
145
+ elif start_center >= 0.5 and end_center < 0.5:
146
+ return 'left'
147
+ else:
148
+ return 'none'
149
+
150
+ @staticmethod
151
+ def count_dirs(json_data):
152
+ right = 0
153
+ left = 0
154
+ none = 0
155
+ for fish_entry in json_data['fish']:
156
+ if fish_entry['direction'] == 'right':
157
+ right += 1
158
+ elif fish_entry['direction'] == 'left':
159
+ left += 1
160
+ else:
161
+ none += 1
162
+ return (right, left, none)
models/download_model.sh ADDED
@@ -0,0 +1 @@
 
 
1
+ aws s3 cp s3://fishcounting/Models/2021-10-yolov5/v5m_896_300best.pt .
models/dump.rdb ADDED
Binary file (3.51 kB). View file
 
models/v5m_896_300best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:14e26883b08b5f07854c67d4ee17b5521eb98f7e0b88d25716542a5cf6dde10f
3
+ size 167806440
scripts/infer.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import project_path
2
+ import argparse
3
+ from predict import predict_task
4
+ from datetime import datetime
5
+
6
+
7
+ def main(args):
8
+ predict_task(args.aris, weights=args.weights)
9
+
10
+ def argument_parser():
11
+ parser = argparse.ArgumentParser()
12
+ parser.add_argument("--aris", required=True, help="Path to ARIS file. Required.")
13
+ parser.add_argument("--weights", default='../models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
14
+ return parser
15
+
16
+ if __name__ == "__main__":
17
+ args = argument_parser().parse_args()
18
+ main(args)
scripts/project_path.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+
4
+ current_dir = os.path.dirname(os.path.realpath(__file__))
5
+ pardir = os.path.dirname(current_dir)
6
+ for d in [pardir, current_dir, os.path.join(pardir, "lib/fish_eye/"), os.path.join(pardir, "lib/"), os.path.join(pardir, "lib/yolov5/")]:
7
+ if d not in sys.path:
8
+ sys.path.append(d)
static/css/style.css ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --fisheye_color: #58df94;
3
+ --caltech_orange: #FF6C0C;
4
+ }
5
+
6
+ html,
7
+ body {
8
+ height: 100%;
9
+ }
10
+
11
+ body {
12
+ /* padding-top: 40px; */
13
+ padding-bottom: 40px;
14
+ background-color: #f5f5f5;
15
+ }
16
+
17
+ #content {
18
+ padding-top: 40px;
19
+ }
20
+
21
+ #home_banner * {
22
+ transition: all .2s ease-in-out;
23
+ }
24
+ #home_banner:hover > svg {
25
+ transform: scale(1.3) rotate(180deg);
26
+ }
27
+ #home_banner:hover #banner_svg_eye {
28
+ fill: var(--fisheye_color)
29
+ }
30
+ #home_banner:hover #banner_svg_slit {
31
+ fill: #231F20;
32
+ }
33
+ #home_banner:hover #banner_svg_pupil {
34
+ fill: #FFFFFF;
35
+ }
36
+ #home_banner > span {
37
+ color: black;
38
+ font-style: italic;
39
+ }
40
+ #home_banner:hover > span {
41
+ transform: translateX(6px);
42
+ }
43
+ #home_banner:hover #banner_text_caltech {
44
+ /*color: var(--caltech_orange);*/
45
+ }
46
+ #home_banner:hover #banner_text_fisheye {
47
+ /*color: var(--fisheye_color);*/
48
+ }
49
+
50
+ .mode_screen {
51
+ width: calc(100% - 280px);
52
+ display: none;
53
+ max-height: 100vh;
54
+ overflow-y: scroll;
55
+ position: absolute;
56
+ top: 0px;
57
+ left: 280px;
58
+ }
59
+ .page_image {
60
+ max-width: min(95%, 600px);
61
+ }
62
+ .result-download {
63
+ color:gray;
64
+ font-size: 20px;
65
+ }
66
+ .result-download:hover {
67
+ color:black;
68
+ }
69
+ .result-analysis {
70
+ width: 25px;
71
+ margin-top: -5px;
72
+ }
73
+ .result-analysis path {
74
+ fill:gray;
75
+ }
76
+ .result-analysis:hover path {
77
+ fill:black;
78
+ }
79
+
80
+
81
+ #analysis_table {
82
+ width: 50%;
83
+ margin-left: 25%
84
+ }
85
+ #analysis_table th {
86
+ padding: 2px;
87
+ width: 200px;
88
+ text-align: right;
89
+ padding-right: 10px;
90
+ }
91
+ #analysis_table td {
92
+ padding: 2px;
93
+ width: 200px;
94
+ text-align: left;
95
+ padding-left: 10px;
96
+ }
97
+
98
+ #sidebar {
99
+ width: 280px;
100
+ height: 100vh;
101
+ border: lightgray;
102
+ border-right-style: solid;
103
+ }
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+ analysis-fishstick {
116
+ width: 80%;
117
+ height: 40px;
118
+ display: block;
119
+ margin-left: 10%;
120
+ position: relative;
121
+ }
122
+ .fishstick_line {
123
+ position: absolute;
124
+ width: 100%;
125
+ height: 8px;
126
+ top: 50%;
127
+ translate: 0 -50%;
128
+ background-color: blue;
129
+ }
130
+ .fishstick_token {
131
+ position: absolute;
132
+ width: 20px;
133
+ height: 20px;
134
+ top: 50%;
135
+ border-radius: 50%;
136
+ background-color: orange;
137
+ translate: -50% -50%;
138
+ }
static/images/Github:fisheye_flask.jpeg ADDED
static/images/fish.png ADDED
static/images/fisheye.png ADDED
static/images/salmon_640.jpeg ADDED
static/images/some images from pixabay.com.txt ADDED
File without changes
static/svg/book-open-svgrepo-com.svg ADDED
static/svg/chemistry-flask-svgrepo-com.svg ADDED
static/svg/done-1477-svgrepo-com.svg ADDED
static/svg/eye-svgrepo-com.svg ADDED
static/svg/house-svgrepo-com.svg ADDED
static/svg/svgs from svgrepo.com.txt ADDED
File without changes
static/svg/upload-svgrepo-com.svg ADDED