water-column-sonar-annotation 26.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tests/__init__.py +0 -0
- tests/astronomical/__init__.py +0 -0
- tests/astronomical/test_astronomical_manager.py +148 -0
- tests/conftest.py +60 -0
- tests/cruise/__init__.py +0 -0
- tests/cruise/test_cruise_manager.py +80 -0
- tests/geospatial/__init__.py +0 -0
- tests/geospatial/test_geospatial_manager.py +86 -0
- tests/record/test_echoview_record_manager.py +160 -0
- water_column_sonar_annotation/__init__.py +5 -0
- water_column_sonar_annotation/astronomical/__init__.py +5 -0
- water_column_sonar_annotation/astronomical/astronomical_manager.py +82 -0
- water_column_sonar_annotation/cruise/__init__.py +5 -0
- water_column_sonar_annotation/cruise/cruise_manager.py +104 -0
- water_column_sonar_annotation/geospatial/__init__.py +5 -0
- water_column_sonar_annotation/geospatial/geospatial_manager.py +143 -0
- water_column_sonar_annotation/record/__init__.py +9 -0
- water_column_sonar_annotation/record/echoview_record_manager.py +426 -0
- water_column_sonar_annotation/record/graph_record_manager.py +82 -0
- water_column_sonar_annotation/record/parquet_record_manager.py +83 -0
- water_column_sonar_annotation/shape/__init__.py +5 -0
- water_column_sonar_annotation/shape/shape_manager.py +29 -0
- water_column_sonar_annotation-26.1.8.dist-info/METADATA +109 -0
- water_column_sonar_annotation-26.1.8.dist-info/RECORD +27 -0
- water_column_sonar_annotation-26.1.8.dist-info/WHEEL +5 -0
- water_column_sonar_annotation-26.1.8.dist-info/licenses/LICENSE +21 -0
- water_column_sonar_annotation-26.1.8.dist-info/top_level.txt +2 -0
tests/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from water_column_sonar_annotation.astronomical import AstronomicalManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
#######################################################
|
|
7
|
+
def setup_module():
|
|
8
|
+
print("setup")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def teardown_module():
|
|
12
|
+
print("teardown")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_get_solar_azimuth():
|
|
16
|
+
astronomical_manager = AstronomicalManager()
|
|
17
|
+
# https://www.suncalc.org/#/39.9812,-105.2495,13/2026.01.26/11:52/1/3
|
|
18
|
+
azimuth_noon = astronomical_manager.get_solar_azimuth(
|
|
19
|
+
iso_time="2026-01-26T19:00:00Z", # noon
|
|
20
|
+
latitude=39.9674884, # Boulder
|
|
21
|
+
longitude=-105.2532602,
|
|
22
|
+
)
|
|
23
|
+
assert np.isclose(azimuth_noon, 31.38) # 15)
|
|
24
|
+
|
|
25
|
+
azimuth_sunset = astronomical_manager.get_solar_azimuth(
|
|
26
|
+
iso_time="2026-01-26T00:00:00Z", # sunset
|
|
27
|
+
latitude=39.9674884,
|
|
28
|
+
longitude=-105.2532602,
|
|
29
|
+
)
|
|
30
|
+
assert np.isclose(azimuth_sunset, 1.25) # 27)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_get_solar_azimuth_boulder_2pm():
|
|
34
|
+
# 2026-01-29 @2pm is UTC: "2026-01-29T21:02:23Z"
|
|
35
|
+
astronomical_manager = AstronomicalManager()
|
|
36
|
+
# https://www.suncalc.org/#/39.9812,-105.2495,13/2026.01.26/11:52/1/3
|
|
37
|
+
azimuth_noon = astronomical_manager.get_solar_azimuth(
|
|
38
|
+
iso_time="2026-01-29T21:02:23Z", # 2pm
|
|
39
|
+
latitude=39.9674884, # Boulder
|
|
40
|
+
longitude=-105.2532602,
|
|
41
|
+
)
|
|
42
|
+
assert np.isclose(azimuth_noon, 27.01)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### PHASE OF DAY ###
|
|
46
|
+
# { 'dawn': 1, 'day': 2, 'dusk': 3, 'night': 4 }
|
|
47
|
+
def test_phase_of_day_at_noon():
|
|
48
|
+
astronomical_manager = AstronomicalManager()
|
|
49
|
+
phase = astronomical_manager.phase_of_day(
|
|
50
|
+
iso_time="2026-01-27T19:00:00Z", # noon
|
|
51
|
+
latitude=39.9674884, # Boulder
|
|
52
|
+
longitude=-105.2532602,
|
|
53
|
+
)
|
|
54
|
+
assert phase == 2
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_phase_of_day_at_midnight():
|
|
58
|
+
astronomical_manager = AstronomicalManager()
|
|
59
|
+
phase = astronomical_manager.phase_of_day(
|
|
60
|
+
iso_time="2026-01-28T07:00:00Z",
|
|
61
|
+
latitude=39.9674884, # Boulder
|
|
62
|
+
longitude=-105.2532602,
|
|
63
|
+
)
|
|
64
|
+
assert phase == 4
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_phase_of_day_before_sunset():
|
|
68
|
+
astronomical_manager = AstronomicalManager()
|
|
69
|
+
phase = astronomical_manager.phase_of_day(
|
|
70
|
+
# sunset is at 5:13pm on jan 27th, per https://psl.noaa.gov/boulder/boulder.sunset.html
|
|
71
|
+
iso_time="2026-01-28T00:09:00Z", # sunset @ 5:13pm, nautical sunset @6:16pm
|
|
72
|
+
latitude=39.9674884, # Boulder
|
|
73
|
+
longitude=-105.2532602,
|
|
74
|
+
)
|
|
75
|
+
assert phase == 2 # day
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_phase_of_day_after_sunset():
|
|
79
|
+
astronomical_manager = AstronomicalManager()
|
|
80
|
+
phase = astronomical_manager.phase_of_day(
|
|
81
|
+
# sunset is at 5:13pm on jan 27th, per https://psl.noaa.gov/boulder/boulder.sunset.html
|
|
82
|
+
iso_time="2026-01-28T00:10:00Z", # sunset @ 5:13pm, nautical sunset @6:16pm
|
|
83
|
+
latitude=39.9674884, # Boulder
|
|
84
|
+
longitude=-105.2532602,
|
|
85
|
+
)
|
|
86
|
+
assert phase == 3 # dusk
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_phase_of_day_before_nautical_sunset():
|
|
90
|
+
astronomical_manager = AstronomicalManager()
|
|
91
|
+
# an hour'ish later is nautical sunset
|
|
92
|
+
phase_before_nautical_sunset = astronomical_manager.phase_of_day(
|
|
93
|
+
iso_time="2026-01-28T01:15:00Z", # sunset @5:13pm, nautical sunset @6:16pm
|
|
94
|
+
latitude=39.9674884, # Boulder
|
|
95
|
+
longitude=-105.2532602,
|
|
96
|
+
)
|
|
97
|
+
assert phase_before_nautical_sunset == 3 # dusk
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_phase_of_day_after_nautical_sunset():
|
|
101
|
+
astronomical_manager = AstronomicalManager()
|
|
102
|
+
phase_after_nautical_sunset = astronomical_manager.phase_of_day(
|
|
103
|
+
iso_time="2026-01-28T01:17:00Z", # sunset @5:13pm, nautical sunset @6:16pm
|
|
104
|
+
latitude=39.9674884, # Boulder
|
|
105
|
+
longitude=-105.2532602,
|
|
106
|
+
)
|
|
107
|
+
assert phase_after_nautical_sunset == 4 # night
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_phase_of_day_before_sunrise():
|
|
111
|
+
astronomical_manager = AstronomicalManager()
|
|
112
|
+
phase_at_sunrise = astronomical_manager.phase_of_day(
|
|
113
|
+
iso_time="2026-01-27T14:13:00Z", # sunrise @7:13am, nautical sunrise @6:12am
|
|
114
|
+
latitude=39.9674884, # Boulder
|
|
115
|
+
longitude=-105.2532602,
|
|
116
|
+
)
|
|
117
|
+
assert phase_at_sunrise == 1 # dusk
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_phase_of_day_after_sunrise():
|
|
121
|
+
astronomical_manager = AstronomicalManager()
|
|
122
|
+
phase_at_sunrise = astronomical_manager.phase_of_day(
|
|
123
|
+
iso_time="2026-01-27T14:20:00Z", # sunrise @7:13am, nautical sunrise @6:12am
|
|
124
|
+
latitude=39.9674884, # Boulder
|
|
125
|
+
longitude=-105.2532602,
|
|
126
|
+
)
|
|
127
|
+
assert phase_at_sunrise == 2 # day
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_phase_of_day_before_nautical_sunrise():
|
|
131
|
+
astronomical_manager = AstronomicalManager()
|
|
132
|
+
# about an hour before is nautical sunrise
|
|
133
|
+
phase_before_nautical_sunrise = astronomical_manager.phase_of_day(
|
|
134
|
+
iso_time="2026-01-27T13:12:00Z", # sunrise @7:13am, nautical sunrise @6:12am
|
|
135
|
+
latitude=39.9674884, # Boulder
|
|
136
|
+
longitude=-105.2532602,
|
|
137
|
+
)
|
|
138
|
+
assert phase_before_nautical_sunrise == 4 # night
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_phase_of_day_after_nautical_sunrise():
|
|
142
|
+
astronomical_manager = AstronomicalManager()
|
|
143
|
+
phase = astronomical_manager.phase_of_day(
|
|
144
|
+
iso_time="2026-01-27T14:13:00Z", # sunrise @7:13am, nautical sunrise @6:12am
|
|
145
|
+
latitude=39.9674884, # Boulder
|
|
146
|
+
longitude=-105.2532602,
|
|
147
|
+
)
|
|
148
|
+
assert phase == 1
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pooch
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
HERE = Path(__file__).parent.absolute()
|
|
7
|
+
TEST_DATA_FOLDER = HERE / "test_resources"
|
|
8
|
+
|
|
9
|
+
HB1906_DATA = pooch.create(
|
|
10
|
+
path=pooch.os_cache("water-column-sonar-annotation"),
|
|
11
|
+
base_url="https://github.com/CI-CMG/water-column-sonar-annotation/releases/download/v26.1.0/",
|
|
12
|
+
retry_if_failed=1,
|
|
13
|
+
registry={
|
|
14
|
+
"HB201906_BOTTOMS.zip": "sha256:20609581493ea3326c1084b6868e02aafbb6c0eae871d946f30b8b5f0e7ba059",
|
|
15
|
+
"HB201906_EVR.zip": "sha256:ceed912a25301be8f1b8f91e134d0ca4cff717f52b6623a58677832fd60c2990",
|
|
16
|
+
#
|
|
17
|
+
# "ne_50m_coastline.shp": "sha256:797d675af9613f80b51ab6049fa32e589974d7a97c6497ca56772965f179ed26",
|
|
18
|
+
# "ne_50m_coastline.shx": "sha256:0ff1792f2d16b58246d074215edd9d12fa280880ecaad61a91b9382fee854065",
|
|
19
|
+
#
|
|
20
|
+
"ne_10m_coastline.shp": "sha256:459a4a97c09db19aadf5244026612de9d43748be27f83a360242b99f7fabb3c1",
|
|
21
|
+
"ne_10m_coastline.shx": "sha256:f873afee7f56779ce52253f740ec251c2f12244aea911dc40f0a85d75de8d5f2",
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fetch_raw_files():
|
|
27
|
+
HB1906_DATA.fetch(fname="HB201906_BOTTOMS.zip", progressbar=True)
|
|
28
|
+
HB1906_DATA.fetch(fname="HB201906_EVR.zip", progressbar=True)
|
|
29
|
+
|
|
30
|
+
# HB1906_DATA.fetch(fname="ne_50m_coastline.shp", progressbar=True)
|
|
31
|
+
# HB1906_DATA.fetch(fname="ne_50m_coastline.shx", progressbar=True)
|
|
32
|
+
|
|
33
|
+
HB1906_DATA.fetch(fname="ne_10m_coastline.shp", progressbar=True)
|
|
34
|
+
HB1906_DATA.fetch(fname="ne_10m_coastline.shx", progressbar=True)
|
|
35
|
+
|
|
36
|
+
file_name = HB1906_DATA.fetch(fname="HB201906_EVR.zip", progressbar=True)
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
water-column-sonar-annotation user$ ls /Users/user/Library/Caches/water-column-sonar-annotation
|
|
40
|
+
HB201906_BOTTOMS.zip HB201906_EVR.zip ne_10m_coastline.shp ne_10m_coastline.shx
|
|
41
|
+
"""
|
|
42
|
+
return Path(file_name).parent
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture(scope="session")
|
|
46
|
+
def test_path():
|
|
47
|
+
return {
|
|
48
|
+
"DATA_TEST_PATH": fetch_raw_files(),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# """
|
|
53
|
+
# Folder locations in mac and windows:
|
|
54
|
+
#
|
|
55
|
+
# Windows
|
|
56
|
+
# C:\Users\<user>\AppData\Local\echopype\Cache\2024.12.23.10.10
|
|
57
|
+
#
|
|
58
|
+
# MacOS
|
|
59
|
+
# /Users//Library/Caches/echopype/2024.12.23.10.10
|
|
60
|
+
# """
|
tests/cruise/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from water_column_sonar_annotation.cruise import CruiseManager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#######################################################
|
|
8
|
+
def setup_module():
|
|
9
|
+
print("setup")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def teardown_module():
|
|
13
|
+
print("teardown")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def process_cruise_path(test_path):
|
|
18
|
+
return test_path["DATA_TEST_PATH"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#######################################################
|
|
22
|
+
# def test_get_cruise_bottom_nan(process_cruise_path, tmp_path):
|
|
23
|
+
# cruise_manager = CruiseManager()
|
|
24
|
+
# cruise = cruise_manager.get_cruise()
|
|
25
|
+
# # count of non-nan values np.count_nonzero(~np.isnan(cruise.bottom.values)) / cruise.Sv.shape[1]
|
|
26
|
+
# assert len(cruise.Sv.shape) == 3
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_get_cruise(process_cruise_path, tmp_path):
|
|
30
|
+
cruise_manager = CruiseManager()
|
|
31
|
+
cruise = cruise_manager.get_cruise()
|
|
32
|
+
assert len(cruise.Sv.shape) == 3
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_get_coordinates():
|
|
36
|
+
"""This only gets the depth over the interval, need to calculate the 'altitude'"""
|
|
37
|
+
cruise_manager = CruiseManager()
|
|
38
|
+
lat, lon = cruise_manager.get_coordinates(
|
|
39
|
+
start_time="2019-10-16T16:20:00",
|
|
40
|
+
end_time="2019-10-16T16:30:00",
|
|
41
|
+
)
|
|
42
|
+
assert np.isclose(lat, 41.48177)
|
|
43
|
+
assert np.isclose(lon, -68.50478)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_get_depth():
|
|
47
|
+
"""This only gets the depth over the interval, need to calculate the 'altitude'"""
|
|
48
|
+
cruise_manager = CruiseManager()
|
|
49
|
+
depth_value = cruise_manager.get_depth(
|
|
50
|
+
start_time="2019-10-16T16:20:00",
|
|
51
|
+
end_time="2019-10-16T16:50:00",
|
|
52
|
+
)
|
|
53
|
+
assert np.isclose(depth_value, 96.36) # 96.356674
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_get_altitude():
|
|
57
|
+
"""This gets the distance from EVR to the bottom"""
|
|
58
|
+
cruise_manager = CruiseManager()
|
|
59
|
+
altitude_value = cruise_manager.get_altitude(
|
|
60
|
+
start_time="2019-10-16T16:20:00",
|
|
61
|
+
end_time="2019-10-16T16:50:00",
|
|
62
|
+
bbox_max=80.0,
|
|
63
|
+
)
|
|
64
|
+
# bottom is at 96.356674
|
|
65
|
+
# setting the bbox at 80.
|
|
66
|
+
assert np.isclose(altitude_value, 16.36) # 96.356674
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_get_altitude_nan_bottom():
|
|
70
|
+
"""This gets the distance from EVR to the bottom when there are nan values"""
|
|
71
|
+
cruise_manager = CruiseManager()
|
|
72
|
+
altitude_value = cruise_manager.get_altitude(
|
|
73
|
+
start_time="2019-09-26T10:02:39.03",
|
|
74
|
+
end_time="2019-09-26T10:02:40.00",
|
|
75
|
+
bbox_max=10.0,
|
|
76
|
+
)
|
|
77
|
+
assert np.isclose(altitude_value, 0.0)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# get_gps
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from water_column_sonar_annotation.geospatial import GeospatialManager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#######################################################
|
|
8
|
+
def setup_module():
|
|
9
|
+
print("setup")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def teardown_module():
|
|
13
|
+
print("teardown")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def process_check_distance_from_coastline(test_path):
|
|
18
|
+
return test_path["DATA_TEST_PATH"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#######################################################
|
|
22
|
+
def test_check_distance_from_coastline(process_check_distance_from_coastline, tmp_path):
|
|
23
|
+
geospatial_manager = GeospatialManager()
|
|
24
|
+
# Point in middle of atlantic https://wktmap.com/?ab28cbae
|
|
25
|
+
distance = geospatial_manager.check_distance_from_coastline(
|
|
26
|
+
latitude=51.508742,
|
|
27
|
+
longitude=-30.410156,
|
|
28
|
+
shapefile_path=process_check_distance_from_coastline,
|
|
29
|
+
)
|
|
30
|
+
# assert np.isclose(distance, 1_236_212.37356) # 1,200 km
|
|
31
|
+
assert np.isclose(distance, 1_233_910.720702243)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_check_distance_from_coastline_woods_hole(
|
|
35
|
+
process_check_distance_from_coastline, tmp_path
|
|
36
|
+
):
|
|
37
|
+
geospatial_manager = GeospatialManager()
|
|
38
|
+
# Point in middle of woods hole vineyard sound: https://wktmap.com/?9b405aa9
|
|
39
|
+
distance = geospatial_manager.check_distance_from_coastline(
|
|
40
|
+
latitude=41.494692,
|
|
41
|
+
longitude=-70.647926,
|
|
42
|
+
shapefile_path=process_check_distance_from_coastline,
|
|
43
|
+
)
|
|
44
|
+
# The sound is 5 km across
|
|
45
|
+
# assert np.isclose(distance, 4_457.0347) # 4.5 km --> should be 2.5 km?
|
|
46
|
+
assert np.isclose(distance, 3_093) # 3_093.3015 km is close enough
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_get_local_time():
|
|
50
|
+
geospatial_manager = GeospatialManager()
|
|
51
|
+
local_time = geospatial_manager.get_local_time(
|
|
52
|
+
iso_time="2026-01-26T20:35:00Z",
|
|
53
|
+
latitude=51.508742,
|
|
54
|
+
longitude=-30.410156,
|
|
55
|
+
)
|
|
56
|
+
assert local_time == "2026-01-26T18:35:00-02:00"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_get_local_hour_of_day():
|
|
60
|
+
geospatial_manager = GeospatialManager()
|
|
61
|
+
local_hour_of_day = geospatial_manager.get_local_hour_of_day(
|
|
62
|
+
iso_time="2026-01-26T20:35:00Z",
|
|
63
|
+
latitude=51.508742,
|
|
64
|
+
longitude=-30.410156,
|
|
65
|
+
)
|
|
66
|
+
assert local_hour_of_day == 18
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_get_month_of_year_january():
|
|
70
|
+
geospatial_manager = GeospatialManager()
|
|
71
|
+
month_of_year = geospatial_manager.get_month_of_year(
|
|
72
|
+
iso_time="2026-01-26T20:35:00Z",
|
|
73
|
+
latitude=51.508742,
|
|
74
|
+
longitude=-30.410156,
|
|
75
|
+
)
|
|
76
|
+
assert month_of_year == 1
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_get_month_of_year_july():
|
|
80
|
+
geospatial_manager = GeospatialManager()
|
|
81
|
+
month_of_year = geospatial_manager.get_month_of_year(
|
|
82
|
+
iso_time="2026-07-13T12:00:00Z",
|
|
83
|
+
latitude=51.508742,
|
|
84
|
+
longitude=-30.410156,
|
|
85
|
+
)
|
|
86
|
+
assert month_of_year == 7
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from water_column_sonar_annotation.record import EchoviewRecordManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
#######################################################
|
|
7
|
+
def setup_module():
|
|
8
|
+
print("setup")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def teardown_module():
|
|
12
|
+
print("teardown")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def process_evr_file_path(test_path):
|
|
17
|
+
return test_path["DATA_TEST_PATH"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
#######################################################
|
|
21
|
+
# @pytest.mark.skip(reason="todo implement this")
|
|
22
|
+
# def test_process_evr_file(process_evr_file_path, tmp_path):
|
|
23
|
+
# echoview_record_manager = EchoviewRecordManager()
|
|
24
|
+
# echoview_record_manager.process_point_data()
|
|
25
|
+
# assert True
|
|
26
|
+
|
|
27
|
+
record_header = """EVRG 7 11.0.244.39215
|
|
28
|
+
11"""
|
|
29
|
+
|
|
30
|
+
possible_herring_example = """13 4 7 0 3 -1 1 20190925 2247242130 24.1290795746 20190925 2247362460 35.1668500183
|
|
31
|
+
0
|
|
32
|
+
0
|
|
33
|
+
possible_herring
|
|
34
|
+
20190925 2247242130 24.1290795746 20190925 2247242130 35.1668500183 20190925 2247362460 35.1668500183 20190925 2247362460 24.1290795746 1
|
|
35
|
+
100"""
|
|
36
|
+
|
|
37
|
+
fish_school_example = """13 30 8 0 7 -1 1 20190925 1749451605 20.2800268439 20190925 1749501645 26.3028953087
|
|
38
|
+
0
|
|
39
|
+
10
|
|
40
|
+
School detected with:
|
|
41
|
+
Minimum data threshold: -66.00
|
|
42
|
+
Maximum data threshold: (none)
|
|
43
|
+
Distance mode: GPS distance
|
|
44
|
+
Minimum total school height (meters): 4.00
|
|
45
|
+
Minimum candidate length (meters): 1.00
|
|
46
|
+
Minimum candidate height (meters): 2.00
|
|
47
|
+
Maximum vertical linking distance (meters): 2.00
|
|
48
|
+
Maximum horizontal linking distance (meters): 20.00
|
|
49
|
+
Minimum total school length (meters): 4.00
|
|
50
|
+
fish_school
|
|
51
|
+
20190925 1749451605 22.0286015595 20190925 1749451605 25.1371788317 20190925 1749461355 25.1371788317 20190925 1749461605 25.1420359837 20190925 1749461605 25.7200370702 20190925 1749471370 25.7200370702 20190925 1749471620 25.7248942222 20190925 1749471620 26.1086092292 20190925 1749481385 26.1086092292 20190925 1749481635 26.1134663812 20190925 1749481635 26.3028953087 20190925 1749491640 26.3028953087 20190925 1749491640 25.1371788317 20190925 1749501645 25.1371788317 20190925 1749501645 24.5543205932 20190925 1749491640 24.5543205932 20190925 1749491640 24.3600345136 20190925 1749501645 24.3600345136 20190925 1749501645 21.6400294005 20190925 1749491640 21.6400294005 20190925 1749491640 21.0571711620 20190925 1749481635 21.0571711620 20190925 1749481635 20.2800268439 20190925 1749471620 20.2800268439 20190925 1749471620 20.8580279305 20190925 1749471370 20.8628850825 20190925 1749461605 20.8628850825 20190925 1749461605 21.8294583280 20190925 1749461355 21.8343154800 20190925 1749451605 21.8343154800 1
|
|
52
|
+
Region 8"""
|
|
53
|
+
|
|
54
|
+
unclassified_regions_example = """13 12 1 0 2 -1 1 20190925 2053458953 9.2818 20190925 2054119318 11.5333
|
|
55
|
+
0
|
|
56
|
+
0
|
|
57
|
+
Unclassified regions
|
|
58
|
+
20190925 2053458953 9.6034489515 20190925 2053521545 11.1197829964 20190925 2054046730 11.5333286451 20190925 2054064248 11.5333286451 20190925 2054079263 11.4414296120 20190925 2054116810 10.8440858974 20190925 2054119318 10.4764897652 20190925 2054116810 9.6953479845 20190925 2054111800 9.4196508854 20190925 2054091775 9.2818023359 20190925 2054076760 9.2818023359 20190925 2053483995 9.5574994350 0
|
|
59
|
+
100"""
|
|
60
|
+
|
|
61
|
+
krill_schools_example = """13 23 49 0 7 -1 1 20191020 0536541560 13.0000000000 20191020 0536591655 17.0000000000
|
|
62
|
+
0
|
|
63
|
+
10
|
|
64
|
+
School detected with:
|
|
65
|
+
Minimum data threshold: -80.00
|
|
66
|
+
Maximum data threshold: (none)
|
|
67
|
+
Distance mode: GPS distance
|
|
68
|
+
Minimum total school height (meters): 4.00
|
|
69
|
+
Minimum candidate length (meters): 1.00
|
|
70
|
+
Minimum candidate height (meters): 2.00
|
|
71
|
+
Maximum vertical linking distance (meters): 2.00
|
|
72
|
+
Maximum horizontal linking distance (meters): 20.00
|
|
73
|
+
Minimum total school length (meters): 4.00
|
|
74
|
+
krill_schools
|
|
75
|
+
20191020 0536551605 15.0250000000 20191020 0536551605 17.0000000000 20191020 0536561605 17.0000000000 20191020 0536561605 16.0000000000 20191020 0536571635 16.0000000000 20191020 0536571635 15.0000000000 20191020 0536581405 15.0000000000 20191020 0536581655 15.0250000000 20191020 0536581655 16.0000000000 20191020 0536591655 16.0000000000 20191020 0536591655 15.0000000000 20191020 0536581655 15.0000000000 20191020 0536581655 14.0000000000 20191020 0536571635 14.0000000000 20191020 0536571635 13.0000000000 20191020 0536561605 13.0000000000 20191020 0536561605 14.9750000000 20191020 0536561355 15.0000000000 20191020 0536551605 15.0000000000 20191020 0536551605 14.0000000000 20191020 0536541560 14.0000000000 20191020 0536541560 15.0000000000 20191020 0536551355 15.0000000000 1
|
|
76
|
+
Region 49"""
|
|
77
|
+
|
|
78
|
+
ah_school_example1 = """13 5 23 0 7 -1 1 20191106 1314583780 25.3008882713 20191106 1314593790 30.2941528987
|
|
79
|
+
0
|
|
80
|
+
10
|
|
81
|
+
School detected with:
|
|
82
|
+
Minimum data threshold: -66.00
|
|
83
|
+
Maximum data threshold: (none)
|
|
84
|
+
Distance mode: GPS distance
|
|
85
|
+
Minimum total school height (meters): 4.00
|
|
86
|
+
Minimum candidate length (meters): 1.00
|
|
87
|
+
Minimum candidate height (meters): 2.00
|
|
88
|
+
Maximum vertical linking distance (meters): 2.00
|
|
89
|
+
Maximum horizontal linking distance (meters): 20.00
|
|
90
|
+
Minimum total school length (meters): 4.00
|
|
91
|
+
AH_School
|
|
92
|
+
20191106 1314583780 25.4929369108 20191106 1314583780 30.2941528987 20191106 1314593790 30.2941528987 20191106 1314593790 25.3008882713 20191106 1314583780 25.3008882713 1
|
|
93
|
+
Region 23"""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
ah_school_example2 = """13 16 28 0 7 -1 1 20191106 1317305715 31.8305420148 20191106 1317335745 35.8635634446
|
|
97
|
+
0
|
|
98
|
+
10
|
|
99
|
+
School detected with:
|
|
100
|
+
Minimum data threshold: -66.00
|
|
101
|
+
Maximum data threshold: (none)
|
|
102
|
+
Distance mode: GPS distance
|
|
103
|
+
Minimum total school height (meters): 4.00
|
|
104
|
+
Minimum candidate length (meters): 1.00
|
|
105
|
+
Minimum candidate height (meters): 2.00
|
|
106
|
+
Maximum vertical linking distance (meters): 2.00
|
|
107
|
+
Maximum horizontal linking distance (meters): 20.00
|
|
108
|
+
Minimum total school length (meters): 4.00
|
|
109
|
+
AH_School
|
|
110
|
+
20191106 1317315725 34.3319755445 20191106 1317315725 35.8635634446 20191106 1317325735 35.8635634446 20191106 1317325735 35.4794661656 20191106 1317335745 35.4794661656 20191106 1317335745 34.5192229680 20191106 1317325735 34.5192229680 20191106 1317325735 34.3271743285 20191106 1317335745 34.3271743285 20191106 1317335745 31.8305420148 20191106 1317315725 31.8305420148 20191106 1317315725 34.1303244730 20191106 1317315475 34.1351256890 20191106 1317305715 34.1351256890 20191106 1317305715 34.3271743285 20191106 1317315475 34.3271743285 1
|
|
111
|
+
Region 28"""
|
|
112
|
+
|
|
113
|
+
atlantic_herring_example = """13 70 2 0 2 -1 1 20191014 0011274130 26.8060139347 20191014 0017599670 76.3659699303
|
|
114
|
+
0
|
|
115
|
+
0
|
|
116
|
+
atlantic_herring
|
|
117
|
+
20191014 0011274130 32.7282728273 20191014 0011339205 48.0014668133 20191014 0011474370 54.2354235424 20191014 0011499420 54.2354235424 20191014 0011544520 54.2354235424 20191014 0012260060 57.6640997433 20191014 0012310115 57.6640997433 20191014 0012350165 57.6640997433 20191014 0012390210 57.6640997433 20191014 0013370930 70.7554088742 20191014 0013420980 71.6905023836 20191014 0013506080 71.6905023836 20191014 0013541115 71.3788045471 20191014 0013586170 71.3788045471 20191014 0014011185 70.7554088742 20191014 0015067420 75.7425742574 20191014 0015107470 76.0542720939 20191014 0015187560 76.3659699303 20191014 0015282670 76.3659699303 20191014 0015307700 76.0542720939 20191014 0015337730 75.7425742574 20191014 0016278420 75.1191785845 20191014 0016383575 75.1191785845 20191014 0016473695 75.1191785845 20191014 0016528780 74.4957829116 20191014 0016568850 73.2489915658 20191014 0017129040 68.2618261826 20191014 0017154065 66.7033370004 20191014 0017184115 64.8331499817 20191014 0017199150 62.9629629630 20191014 0017339330 56.4173083975 20191014 0017384400 55.7939127246 20191014 0017439460 55.1705170517 20191014 0017479510 53.6120278695 20191014 0017599670 49.5599559956 20191014 0017599670 48.0014668133 20191014 0017584660 44.8844884488 20191014 0017559630 44.5727906124 20191014 0017309300 44.5727906124 20191014 0017284270 44.2610927759 20191014 0017259240 44.2610927759 20191014 0017214180 43.3259992666 20191014 0017179110 43.0143014301 20191014 0016323505 43.0143014301 20191014 0016288450 43.0143014301 20191014 0016223340 42.0792079208 20191014 0016188310 42.0792079208 20191014 0016143230 41.1441144114 20191014 0015467870 39.2739273927 20191014 0015402790 38.0271360469 20191014 0015327710 36.7803447011 20191014 0014351845 34.9101576824 20191014 0014306720 34.5984598460 20191014 0014261660 33.9750641731 20191014 0014221575 33.3516685002 20191014 0014191530 33.3516685002 20191014 0014161465 33.3516685002 20191014 0014116410 33.3516685002 20191014 0013150670 30.2346901357 20191014 0013060570 28.6762009534 20191014 0013020530 27.4294096076 20191014 0012555450 26.8060139347 20191014 0012485320 26.8060139347 20191014 0012455280 26.8060139347 20191014 0012134870 29.6112944628 20191014 0012074750 29.9229922992 20191014 0011534490 31.4814814815 20191014 0011429315 32.4165749908 20191014 0011339205 32.7282728273 20191014 0011314180 32.7282728273 1
|
|
118
|
+
100"""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_process_evr_record_possible_herring():
|
|
122
|
+
echoview_record_manager = EchoviewRecordManager()
|
|
123
|
+
echoview_record_manager.process_evr_record(
|
|
124
|
+
evr_record=possible_herring_example, filename="test.evr"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_process_evr_record_fish_school():
|
|
129
|
+
echoview_record_manager = EchoviewRecordManager()
|
|
130
|
+
echoview_record_manager.process_evr_record(
|
|
131
|
+
evr_record=fish_school_example, filename="test.evr"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_process_evr_record_unclassified_regions():
|
|
136
|
+
echoview_record_manager = EchoviewRecordManager()
|
|
137
|
+
echoview_record_manager.process_evr_record(
|
|
138
|
+
evr_record=unclassified_regions_example, filename="test.evr"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_process_evr_record_krill_schools():
|
|
143
|
+
echoview_record_manager = EchoviewRecordManager()
|
|
144
|
+
echoview_record_manager.process_evr_record(
|
|
145
|
+
evr_record=krill_schools_example, filename="test.evr"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_process_evr_record_ah_school_1():
|
|
150
|
+
echoview_record_manager = EchoviewRecordManager()
|
|
151
|
+
echoview_record_manager.process_evr_record(
|
|
152
|
+
evr_record=ah_school_example1, filename="test.evr"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_process_evr_record_ah_school_2():
|
|
157
|
+
echoview_record_manager = EchoviewRecordManager()
|
|
158
|
+
echoview_record_manager.process_evr_record(
|
|
159
|
+
evr_record=ah_school_example2, filename="test.evr"
|
|
160
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import pvlib
|
|
4
|
+
|
|
5
|
+
from water_column_sonar_annotation.geospatial import GeospatialManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AstronomicalManager:
|
|
9
|
+
#######################################################
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
):
|
|
13
|
+
self.DECIMAL_PRECISION = 6
|
|
14
|
+
# https://github.com/CI-CMG/water-column-sonar-annotation/issues/6
|
|
15
|
+
self.SUNRISE_DEGREES = 0.0
|
|
16
|
+
self.CIVIL_TWILIGHT_DEGREES = -6.0
|
|
17
|
+
self.NAUTICAL_TWILIGHT_DEGREES = -12.0 # Requested metric to calculate
|
|
18
|
+
self.ASTRONOMICAL_TWILIGHT_DEGREES = -18.0
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def get_solar_azimuth(
|
|
22
|
+
iso_time: str = "2026-01-26T00:06:00Z",
|
|
23
|
+
latitude: float = 39.9674884, # boulder gps coordinates
|
|
24
|
+
longitude: float = -105.2532602,
|
|
25
|
+
) -> float:
|
|
26
|
+
"""
|
|
27
|
+
Good reference for calculating: https://www.suncalc.org/#/39.9812,-105.2495,13/2026.01.26/11:52/1/3
|
|
28
|
+
utc time now: '2026-01-25T18:42:00Z' # 11:43am
|
|
29
|
+
7:14 am↑ (sunrise)
|
|
30
|
+
(Timestamp('2026-01-25 18:42:00+0000', tz='UTC'), Timestamp('2026-01-25 14:15:07.145030400+0000', tz='UTC'))
|
|
31
|
+
5:13 pm↑ (sunset)
|
|
32
|
+
(Timestamp('2026-01-25 18:42:00+0000', tz='UTC'), Timestamp('2026-01-26 00:10:51.244243200+0000', tz='UTC'))
|
|
33
|
+
solar altitude should be: 31.26°, azimuth should be: 174.01°
|
|
34
|
+
"""
|
|
35
|
+
solar_position = pvlib.solarposition.get_solarposition(
|
|
36
|
+
time=pd.DatetimeIndex([iso_time]),
|
|
37
|
+
latitude=latitude,
|
|
38
|
+
longitude=longitude,
|
|
39
|
+
)
|
|
40
|
+
# 'elevation' is analogous to 'altitude' in suncalc
|
|
41
|
+
elevation = solar_position.elevation.iloc[0]
|
|
42
|
+
### The altitude aka elevation is the angle between horizon and the center of the sun including refraction ###
|
|
43
|
+
return np.round(elevation, 2)
|
|
44
|
+
|
|
45
|
+
def phase_of_day(
|
|
46
|
+
self,
|
|
47
|
+
iso_time: str,
|
|
48
|
+
latitude: float,
|
|
49
|
+
longitude: float,
|
|
50
|
+
) -> int:
|
|
51
|
+
"""
|
|
52
|
+
Returns whether the time/gps references a Nautical Daylight time
|
|
53
|
+
Going to need to verify the az is correctly computed
|
|
54
|
+
{ 'night': 4, 'dawn': 1, 'day': 2, 'dusk': 3 }
|
|
55
|
+
"""
|
|
56
|
+
# categories = {"night": 4, "dawn": 1, "day": 2, "dusk": 3}
|
|
57
|
+
solar_azimuth = self.get_solar_azimuth(iso_time, latitude, longitude)
|
|
58
|
+
geospatial_manager = GeospatialManager()
|
|
59
|
+
local_hour = geospatial_manager.get_local_hour_of_day(
|
|
60
|
+
iso_time=iso_time,
|
|
61
|
+
latitude=latitude,
|
|
62
|
+
longitude=longitude,
|
|
63
|
+
)
|
|
64
|
+
if solar_azimuth < self.NAUTICAL_TWILIGHT_DEGREES:
|
|
65
|
+
return 4 # night
|
|
66
|
+
if solar_azimuth >= 0.0:
|
|
67
|
+
return 2 # day
|
|
68
|
+
if local_hour < 12:
|
|
69
|
+
return 1 # dawn
|
|
70
|
+
return 3 # dusk
|
|
71
|
+
|
|
72
|
+
# def get_moon_phase(self):
|
|
73
|
+
# # TODO: add method for getting the moon phase
|
|
74
|
+
# pass
|
|
75
|
+
|
|
76
|
+
# TODO: calculate moonrise and moonset
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# if __name__ == "__main__":
|
|
80
|
+
# astronomical_manager = AstronomicalManager()
|
|
81
|
+
# azimuth = astronomical_manager.get_solar_azimuth()
|
|
82
|
+
# print(azimuth)
|