From a7da97a09aca4a3c37cc2af9ec4b48521061cf34 Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Fri, 25 Nov 2016 13:38:28 +1100 Subject: [PATCH 1/9] Commented out modules --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index dca4055cd..c6af51595 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ pytest-cov pycodestyle pyephem wcsaxes -readline -gcloud +#readline +#gcloud ccdproc astroplan From b655fbb61f8aeb0429c8459df05137cd7a42c9da Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Mon, 6 Mar 2017 10:53:13 +1100 Subject: [PATCH 2/9] Added a new horizon test file --- pocs/tests/test_horizon_limits.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 pocs/tests/test_horizon_limits.py diff --git a/pocs/tests/test_horizon_limits.py b/pocs/tests/test_horizon_limits.py new file mode 100644 index 000000000..bb377e899 --- /dev/null +++ b/pocs/tests/test_horizon_limits.py @@ -0,0 +1 @@ +import pytest \ No newline at end of file From 760752b8acc38771a7223e19e0221c3d9c46887a Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Mon, 3 Apr 2017 09:44:40 +1000 Subject: [PATCH 3/9] Working on testing the horizon class. --- pocs/images.py | 1 + pocs/scheduler/constraint.py | 132 ++++++++++++++++++++++- pocs/tests/test_constraints.py | 27 +++++ pocs/tests/test_horizon_limits.py | 169 +++++++++++++++++++++++++++++- 4 files changed, 324 insertions(+), 5 deletions(-) diff --git a/pocs/images.py b/pocs/images.py index 3f5dc527e..ebc22dd08 100644 --- a/pocs/images.py +++ b/pocs/images.py @@ -166,6 +166,7 @@ def get_header_pointing(self): dec=float(self.header['DEC-MNT']) * u.degree) self.header_RA = self.header_pointing.ra.to(u.hourangle) + self.header_Dec = self.header_pointing.dec.to(u.degree) # Precess to the current equinox otherwise the RA - LST method will be off. diff --git a/pocs/scheduler/constraint.py b/pocs/scheduler/constraint.py index eee355183..cd249d8e8 100644 --- a/pocs/scheduler/constraint.py +++ b/pocs/scheduler/constraint.py @@ -22,9 +22,11 @@ def __init__(self, weight=1.0, default_score=0.0, *args, **kwargs): super(BaseConstraint, self).__init__(*args, **kwargs) assert isinstance(weight, float), \ - self.logger.error("Constraint weight must be a float greater than 0.0") + self.logger.error( + "Constraint weight must be a float greater than 0.0") assert weight >= 0.0, \ - self.logger.error("Constraint weight must be a float greater than 0.0") + self.logger.error( + "Constraint weight must be a float greater than 0.0") self.weight = weight self._score = default_score @@ -103,7 +105,8 @@ def get_score(self, time, observer, observation, **kwargs): # If target can't meet minimum duration before flip, veto if time + observation.minimum_duration > target_meridian: - self.logger.debug("Observation minimum can't be met before meridian flip") + self.logger.debug( + "Observation minimum can't be met before meridian flip") veto = True # else: @@ -115,7 +118,8 @@ def get_score(self, time, observer, observation, **kwargs): # If end_of_night happens before target sets, use end_of_night if target_end_time > end_of_night: - self.logger.debug("Target sets past end_of_night, using end_of_night") + self.logger.debug( + "Target sets past end_of_night, using end_of_night") target_end_time = end_of_night # Total seconds is score @@ -159,3 +163,123 @@ def get_score(self, time, observer, observation, **kwargs): def __str__(self): return "Moon Avoidance" + + +class Horizon(BaseConstraint): + + #@obstruction_points = [] #How exactly do I use decorators to declare properties/do I need to use a decorator? + def __init__(self, obstruction_points, *args, **kwargs): # Constructor + super().__init__(*args, **kwargs) # Calls parent's (BaseConstraint's) constructor + + # assert the validation conditions + + self.obstruction_points = obstruction_points + + # Process the horizon_image to generate the obstruction_points list + # Segment regions of high contrast using scikit image + # Image Segmentation with Watershed Algorithm + # def process_image(): + + # Get the user to input az, el coordinates + # After a horizon instant has been instantiated this method can be called + # to populate the obstruction_points from user input + + def enter_coords(): + + import ast + + valid = False + while(valid == False): + + print("Enter a list of points. For example (0,0), (0,1), (1,1), (1,0)") + + points = input() + + try: + if isinstance(points, tuple) + valid = True + else + print( + "Input type error. Please enter the coordinates in the format mentioned") + except SyntaxError: + print( + "Syntax error. Please enter the coordinates in the format mentioned") + + return points + + def interpolate(A, B, az): + + # input validation assertions + assert len(A) == 2 + assert len(B) == 2 + assert type(az) == float or type(az) == int + assert type(A[0]) == float or type(A[0]) == int + assert type(A[1]) == float or type(A[1]) == int + assert type(B[0]) == float or type(B[0]) == int + assert type(B[1]) == float or type(B[1]) == int + assert az >= A[0] + assert az <= B[0] + assert az < 90 + + if B[0] == A[0]: + el = B[1] + else: + m = ((B[1] - A[1]) / (B[0] - A[0])) + # Same as y=mx+b + el = m * az + A[1] + + assert el < 90 + + return el + + # Search the base constraint for the adjacent pair of tuples that contains the target azimuth + # Its possible that a single tuple will have a matching azimuth to the target azimuth - special case + #Pass in (x1, y1), (x2, y2), target.az + # Return elevation + + def determine_el(az): + el = 0 + prior_point = obstruction_points[0] + i = 1 + found = False + while(i < len(obstruction_points) and found == False): + next_point = obstruction_points[i] + if az >= prior_point[0] and az <= next_point[0]: + el = interpolate(prior_point, next_point, az) + found = True + else: + i += 1 + prior_point = next_point + return el + + # Determine if the target altitude is above or below the determined + # minimum elevation for that azimuth - inside get_score + + def get_score(self, time, observer, observation, **kwargs): + + target = observation.field + # Is this using the astropy units to declare the constraint? + #"A decorator for validating the units of arguments to functions." + veto = False + score = self._score + + az = observer.altaz(time, target=target).az + alt = observer.altaz(time, target=target).alt + + el = determine_el(az) + + # Determine if the target altitude is above or below the determined minimum elevation for that azimuth + # Note the image is 10 by 15, so I want it to be 7.5 below the target's + # elevation + + if alt - 7.5 > el + veto = True + else: + score = 100 + + # Once the equation is found put the target azimuth into the equation to determine the minimum altitude for that azimuth + # assume everything has a default score of 100 + return veto, score * self.weight + + def __str__(self): + return "Horizon {}".format(self.minimum) diff --git a/pocs/tests/test_constraints.py b/pocs/tests/test_constraints.py index f620453bb..b93d9b9dc 100644 --- a/pocs/tests/test_constraints.py +++ b/pocs/tests/test_constraints.py @@ -197,3 +197,30 @@ def test_moon_avoidance(observer): assert veto1 is False and veto2 is False assert score2 > score1 + +def test_horizon(observer): + h = Horizon() + + time = Time('2016-08-13 10:00:00') + + observation1 = Observation(Field('HD189733', '20h00m43.7135s +22d42m39.0645s')) # HD189733 + observation2 = Observation(Field('Hat-P-16', '00h38m17.59s +42d27m47.2s')) # Hat-P-16 + + veto1, score1 = h.get_score(time, observer, observation1) + veto2, score2 = h.get_score(time, observer, observation2) + + assert veto1 is False and veto2 is False + assert score2 > score1 #Are these scores still relevant? How can I test scores for my class as its either 100 or 0? + +def test_horizon_veto(observer): + h = Horizon() + + time = Time('2016-08-13 10:00:00') + + observation1 = Observation(Field('Sabik', '17h10m23s -15d43m30s')) # Sabik + + veto1, score1 = h.get_score(time, observer, observation1) + + assert veto1 is True + + diff --git a/pocs/tests/test_horizon_limits.py b/pocs/tests/test_horizon_limits.py index bb377e899..de1394860 100644 --- a/pocs/tests/test_horizon_limits.py +++ b/pocs/tests/test_horizon_limits.py @@ -1 +1,168 @@ -import pytest \ No newline at end of file +import pytest + +from pocs.scheduler.constraint import Horizon + +""" +Validation tests, fail if: +1. If there are no points +2. If there is only one point +3. If any point doesnt have two components (az and el) +4. If any point is the incorrect data type +5. If any point isnt in the azimuth range (0<=azimuth<359,59,59) +6. If any point isnt in the elevation range (0<=elevation<90) +7. If any inputted azimuth isnt in the correct sequence low and increasing order, so if a subsequent azimuth is less than a previous one +8. If multiple data points have the same azimuth thrice or more if two azimuths are equal it defines a vertical line +9. Make the obstructions are a reasonable size - no zero to 360 case + +Test case Test no. Test Case Validation pass/fail +1 1 No points F +2 2 One point (10, 50) F +3 3 Two points (100, 50), (120) F +4 3 Two points (100, 50, 50) (120, 60) F +5 3 Two points (100), (120, 60) F +6 3 Two points (100), (120) F +7 4 A string: test F +8 4 A boolean: true F +9 4 (x,20), (120, 60) F +10 4 (10, 50), (30, 10y) F +11 5 Two points: (-2, 60), (-1, 70) F +12 5 Two points: (-1, 60), (1, 70) F +13 6 Two points: (20, -2), (40, -1) F +14 6 Two points: (20, -2), (40, 70) F +15 7 Two points: (50, 60), (40, 70) F +16 8 Three points: (50, 60), (50, 70), (50, 80) F +""" + +#1. If there are no points +""" +def test_validation_1(obstruction_points): + #Using the implicit booleanness of the empty list + assert obstruction_points +""" + +def test_validation_1(obstruction_points): + #Using the implicit booleanness of the empty list + return len(obstruction_points)>0 + +#2. If there is only one point +def test_validation_2(obstruction_points): + assert len(obstruction_points)>=2 + +#3. If any point doesnt have two components (az and el) +def test_validation_3(obstruction_points): + for i in obstruction_points: + assert len(i)==2 + +#4. If any point is the incorrect data type +def test_validation_4(obstruction_points): + assert type(obstruction_points)==list + for i in obstruction_points: + assert type(i[0])==float + assert type(i[1])==float + +#5. If any point isnt in the azimuth range (0<=azimuth<359,59,59) +def test_validation_5(obstruction_points): + for i in obstruction_points: + az = i[0] + assert az>=0 and az<2*math.pi #degrees are astropy units degrees - radians + +#6. If any point isnt in the elevation range (0<=elevation<90) +def test_validation_6(obstruction_points): + for i in obstruction_points: + el = i[1] + assert el>=0 and el<(math.pi)/2 + +#7. If any inputted azimuth isnt in the correct sequence low and increasing order +def test_validation_7(obstruction_points): + az_list = [] + for i in obstruction_points: + az_list.append(i[0]) + assert sorted(az_list) == az_list + +#8. If multiple data points have the same azimuth thrice or more if two azimuths are equal it defines a vertical line +def test_validation_8(obstruction_points): + from itertools import groupby + az_list = [] + for i in obstruction_points: + az_list.append(i[0]) + assert [len(list(group)) for key, group in groupby(az_list)]<=2 + +optc1=[] +optc2=[(10, 50)] +optc3=[(100, 50), (120)] +optc4=[(100, 50, 50), (120, 60)] +optc5=[(100), (120, 60)] +optc6=[(100), (120)] +optc7=["test"] +optc8=[True] +optc9=[("x",20), (120, 60)] +optc10=[(10, 50), (30, "10y")] +optc11=[(-2, 60), (-1, 70)] +optc12=[(-1, 60), (1, 70)] +optc13=[(20, -2), (40, -1)] +optc14=[(20, -2), (40, 70)] +optc15=[(50, 60), (40, 70)] +optc16=[(50, 60), (50, 70), (50, 80)] + +obstruction_points_test_cases=[optc1, optc2, optc3, optc4, optc5, optc6, optc7, optc8, optc9, + optc10, optc11, optc12, optc13, optc14, optc15, optc16] + + +assert test_validation_1(optc1) == False + +#test_validation_2(optc1) + +""" +[ +[(x0, y0), (x1, y1), (x1, y2)] +[(x0, y0), (x1, y1), (x1, y2)] +] +""" + +""" +def test_validations(): + test_validation_1() + test_validation_2() + test_validation_3() + test_validation_4() + test_validation_5() + test_validation_6() + test_validation_7() + test_validation_8() +""" + +def test_interpolate(): + + Horizon1 = Horizon() + + #Testing if the azimuth is already an obstruction point + assert Horizon1.interpolate((20, 20), (25, 20), 25) == 20 + + #Testing if the azimuth isn't an obstruction point (using interpolate) + assert Horizon1.interpolate((20, 20), (25, 25), 22) == 22 + + #Testing if the azimuth isn't an obstruction point (using interpolate) + assert Horizon1.interpolate((20, 20), (25, 25), 22) == 0 + +def test_determine_el(): + + Horizon1 = Horizon() + + #Testing if the azimuth is already an obstruction point (2 points) + assert Horizon1.determine_el([(20, 20), (25, 20), (30, 30)], 25) == 20 + + #Testing if the azimuth is already an obstruction point (3 points) + assert Horizon1.determine_el([(20, 20), (25, 20), (30, 30)], 25) == 20 + + #Testing if the azimuth isn't an obstruction point (using interpolate) + assert Horizon1.determine_el([(20, 20), (25, 25)], 22) == 22 + +def test_horizon_limits(): + #test_validations() + test_interpolate() + test_determine_el() + + + + + From 863b18658e7809ecebe54ee81b16cfaa43c08de7 Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Mon, 3 Apr 2017 11:47:56 +1000 Subject: [PATCH 4/9] Fixed some pep8 style issues. Added the process_image method. --- pocs/scheduler/constraint.py | 53 +++++-- pocs/tests/test_horizon_limits.py | 234 ++++++++++++++---------------- 2 files changed, 148 insertions(+), 139 deletions(-) diff --git a/pocs/scheduler/constraint.py b/pocs/scheduler/constraint.py index cd249d8e8..271b13513 100644 --- a/pocs/scheduler/constraint.py +++ b/pocs/scheduler/constraint.py @@ -167,7 +167,7 @@ def __str__(self): class Horizon(BaseConstraint): - #@obstruction_points = [] #How exactly do I use decorators to declare properties/do I need to use a decorator? + # @obstruction_points = [] # How exactly do I use decorators to declare properties/do I need to use a decorator? def __init__(self, obstruction_points, *args, **kwargs): # Constructor super().__init__(*args, **kwargs) # Calls parent's (BaseConstraint's) constructor @@ -180,27 +180,48 @@ def __init__(self, obstruction_points, *args, **kwargs): # Constructor # Image Segmentation with Watershed Algorithm # def process_image(): - # Get the user to input az, el coordinates - # After a horizon instant has been instantiated this method can be called - # to populate the obstruction_points from user input + def process_image(image_filename): + """ + bottom_left is a tuple, top_right is a tuple, each tuple has az, el + to allow for incomplete horizon images + """ - def enter_coords(): + from skimage.io import imread + from skimage.filters import threshold_otsu + from skimage import feature + import numpy as np + + image = imread(image_filename, flatten=True) + thresh = threshold_otsu(image) + binary = image > thresh + + # Compute the Canny filter + edges1 = feature.canny(binary, low_threshold=0.1, high_threshold=0.5) - import ast + # Turn into array + np.set_printoptions(threshold=np.nan) + print(edges1.astype(np.float)) + + # Convert into az, el coords using bottom_left, top_right + + # Get the user to input az, el coordinates + # After a horizon instant has been instantiated this method can be called + # to populate the obstruction_points from user input + + def enter_coords(): valid = False - while(valid == False): + while(valid is False): print("Enter a list of points. For example (0,0), (0,1), (1,1), (1,0)") points = input() try: - if isinstance(points, tuple) + if isinstance(points, tuple): valid = True - else - print( - "Input type error. Please enter the coordinates in the format mentioned") + else: + print("Input type error. Please enter the coordinates in the format mentioned") except SyntaxError: print( "Syntax error. Please enter the coordinates in the format mentioned") @@ -234,7 +255,7 @@ def interpolate(A, B, az): # Search the base constraint for the adjacent pair of tuples that contains the target azimuth # Its possible that a single tuple will have a matching azimuth to the target azimuth - special case - #Pass in (x1, y1), (x2, y2), target.az + # Pass in (x1, y1), (x2, y2), target.az # Return elevation def determine_el(az): @@ -242,7 +263,7 @@ def determine_el(az): prior_point = obstruction_points[0] i = 1 found = False - while(i < len(obstruction_points) and found == False): + while(i < len(obstruction_points) and found is False): next_point = obstruction_points[i] if az >= prior_point[0] and az <= next_point[0]: el = interpolate(prior_point, next_point, az) @@ -259,7 +280,7 @@ def get_score(self, time, observer, observation, **kwargs): target = observation.field # Is this using the astropy units to declare the constraint? - #"A decorator for validating the units of arguments to functions." + # "A decorator for validating the units of arguments to functions." veto = False score = self._score @@ -272,12 +293,12 @@ def get_score(self, time, observer, observation, **kwargs): # Note the image is 10 by 15, so I want it to be 7.5 below the target's # elevation - if alt - 7.5 > el + if alt - 7.5 > el: veto = True else: score = 100 - # Once the equation is found put the target azimuth into the equation to determine the minimum altitude for that azimuth + # After interpolation put the target az into the equation to determine el # assume everything has a default score of 100 return veto, score * self.weight diff --git a/pocs/tests/test_horizon_limits.py b/pocs/tests/test_horizon_limits.py index de1394860..ab98ae277 100644 --- a/pocs/tests/test_horizon_limits.py +++ b/pocs/tests/test_horizon_limits.py @@ -2,167 +2,155 @@ from pocs.scheduler.constraint import Horizon +import math + """ -Validation tests, fail if: -1. If there are no points -2. If there is only one point -3. If any point doesnt have two components (az and el) -4. If any point is the incorrect data type -5. If any point isnt in the azimuth range (0<=azimuth<359,59,59) -6. If any point isnt in the elevation range (0<=elevation<90) -7. If any inputted azimuth isnt in the correct sequence low and increasing order, so if a subsequent azimuth is less than a previous one -8. If multiple data points have the same azimuth thrice or more if two azimuths are equal it defines a vertical line +Validation tests, fail if: +1. If there are no points +2. If there is only one point +3. If any point doesnt have two components (az and el) +4. If any point is the incorrect data type +5. If any point isnt in the azimuth range (0<=azimuth<359,59,59) +6. If any point isnt in the elevation range (0<=elevation<90) +7. If any inputted azimuth isnt in the correct sequence low and increasing order +8. If multiple data points have the same azimuth thrice or more if two azimuths are equal it defines a vertical line 9. Make the obstructions are a reasonable size - no zero to 360 case -Test case Test no. Test Case Validation pass/fail -1 1 No points F -2 2 One point (10, 50) F -3 3 Two points (100, 50), (120) F -4 3 Two points (100, 50, 50) (120, 60) F -5 3 Two points (100), (120, 60) F -6 3 Two points (100), (120) F -7 4 A string: test F -8 4 A boolean: true F -9 4 (x,20), (120, 60) F -10 4 (10, 50), (30, 10y) F -11 5 Two points: (-2, 60), (-1, 70) F -12 5 Two points: (-1, 60), (1, 70) F -13 6 Two points: (20, -2), (40, -1) F -14 6 Two points: (20, -2), (40, 70) F -15 7 Two points: (50, 60), (40, 70) F -16 8 Three points: (50, 60), (50, 70), (50, 80) F +Test case Test no. Test Case Validation pass/fail +1 1 No points F +2 2 One point (10, 50) F +3 3 Two points (100, 50), (120) F +4 3 Two points (100, 50, 50) (120, 60) F +5 3 Two points (100), (120, 60) F +6 3 Two points (100), (120) F +7 4 A string: test F +8 4 A boolean: true F +9 4 (x,20), (120, 60) F +10 4 (10, 50), (30, 10y) F +11 5 Two points: (-2, 60), (-1, 70) F +12 5 Two points: (-1, 60), (1, 70) F +13 6 Two points: (20, -2), (40, -1) F +14 6 Two points: (20, -2), (40, 70) F +15 7 Two points: (50, 60), (40, 70) F +16 8 Three points: (50, 60), (50, 70), (50, 80) F """ -#1. If there are no points +# 1. If there are no points """ def test_validation_1(obstruction_points): - #Using the implicit booleanness of the empty list - assert obstruction_points + #Using the implicit booleanness of the empty list + assert obstruction_points """ + def test_validation_1(obstruction_points): - #Using the implicit booleanness of the empty list - return len(obstruction_points)>0 + # Using the implicit booleanness of the empty list + return len(obstruction_points) > 0 + -#2. If there is only one point +# 2. If there is only one point def test_validation_2(obstruction_points): - assert len(obstruction_points)>=2 + assert len(obstruction_points) >= 2 -#3. If any point doesnt have two components (az and el) + +# 3. If any point doesnt have two components (az and el) def test_validation_3(obstruction_points): - for i in obstruction_points: - assert len(i)==2 - -#4. If any point is the incorrect data type + for i in obstruction_points: + assert len(i) == 2 + + +# 4. If any point is the incorrect data type def test_validation_4(obstruction_points): - assert type(obstruction_points)==list - for i in obstruction_points: - assert type(i[0])==float - assert type(i[1])==float + assert type(obstruction_points) == list + for i in obstruction_points: + assert type(i[0]) == float + assert type(i[1]) == float -#5. If any point isnt in the azimuth range (0<=azimuth<359,59,59) + +# 5. If any point isnt in the azimuth range (0<=azimuth<359,59,59) def test_validation_5(obstruction_points): - for i in obstruction_points: - az = i[0] - assert az>=0 and az<2*math.pi #degrees are astropy units degrees - radians + for i in obstruction_points: + az = i[0] + assert az >= 0 and az < 2 * math.pi # degrees are astropy units degrees - radians + -#6. If any point isnt in the elevation range (0<=elevation<90) +# 6. If any point isnt in the elevation range (0<=elevation<90) def test_validation_6(obstruction_points): - for i in obstruction_points: - el = i[1] - assert el>=0 and el<(math.pi)/2 + for i in obstruction_points: + el = i[1] + assert el >= 0 and el < (math.pi) / 2 + -#7. If any inputted azimuth isnt in the correct sequence low and increasing order +# 7. If any inputted azimuth isnt in the correct sequence low and increasing order def test_validation_7(obstruction_points): - az_list = [] - for i in obstruction_points: - az_list.append(i[0]) - assert sorted(az_list) == az_list + az_list = [] + for i in obstruction_points: + az_list.append(i[0]) + assert sorted(az_list) == az_list -#8. If multiple data points have the same azimuth thrice or more if two azimuths are equal it defines a vertical line + +# 8. If multiple data points have the same azimuth thrice or more if two azimuths are equal def test_validation_8(obstruction_points): - from itertools import groupby - az_list = [] - for i in obstruction_points: - az_list.append(i[0]) - assert [len(list(group)) for key, group in groupby(az_list)]<=2 - -optc1=[] -optc2=[(10, 50)] -optc3=[(100, 50), (120)] -optc4=[(100, 50, 50), (120, 60)] -optc5=[(100), (120, 60)] -optc6=[(100), (120)] -optc7=["test"] -optc8=[True] -optc9=[("x",20), (120, 60)] -optc10=[(10, 50), (30, "10y")] -optc11=[(-2, 60), (-1, 70)] -optc12=[(-1, 60), (1, 70)] -optc13=[(20, -2), (40, -1)] -optc14=[(20, -2), (40, 70)] -optc15=[(50, 60), (40, 70)] -optc16=[(50, 60), (50, 70), (50, 80)] - -obstruction_points_test_cases=[optc1, optc2, optc3, optc4, optc5, optc6, optc7, optc8, optc9, - optc10, optc11, optc12, optc13, optc14, optc15, optc16] - - -assert test_validation_1(optc1) == False - -#test_validation_2(optc1) + from itertools import groupby + az_list = [] + for i in obstruction_points: + az_list.append(i[0]) + assert [len(list(group)) for key, group in groupby(az_list)] <= 2 -""" -[ -[(x0, y0), (x1, y1), (x1, y2)] -[(x0, y0), (x1, y1), (x1, y2)] -] -""" -""" -def test_validations(): - test_validation_1() - test_validation_2() - test_validation_3() - test_validation_4() - test_validation_5() - test_validation_6() - test_validation_7() - test_validation_8() -""" +optc1 = [] +optc2 = [(10, 50)] +optc3 = [(100, 50), (120)] +optc4 = [(100, 50, 50), (120, 60)] +optc5 = [(100), (120, 60)] +optc6 = [(100), (120)] +optc7 = ["test"] +optc8 = [True] +optc9 = [("x", 20), (120, 60)] +optc10 = [(10, 50), (30, "10y")] +optc11 = [(-2, 60), (-1, 70)] +optc12 = [(-1, 60), (1, 70)] +optc13 = [(20, -2), (40, -1)] +optc14 = [(20, -2), (40, 70)] +optc15 = [(50, 60), (40, 70)] +optc16 = [(50, 60), (50, 70), (50, 80)] + +obstruction_points_test_cases = [optc1, optc2, optc3, optc4, optc5, optc6, optc7, optc8, optc9, + optc10, optc11, optc12, optc13, optc14, optc15, optc16] -def test_interpolate(): - Horizon1 = Horizon() +assert test_validation_1(optc1) is False - #Testing if the azimuth is already an obstruction point - assert Horizon1.interpolate((20, 20), (25, 20), 25) == 20 - #Testing if the azimuth isn't an obstruction point (using interpolate) - assert Horizon1.interpolate((20, 20), (25, 25), 22) == 22 +def test_interpolate(): - #Testing if the azimuth isn't an obstruction point (using interpolate) - assert Horizon1.interpolate((20, 20), (25, 25), 22) == 0 + Horizon1 = Horizon() -def test_determine_el(): + # Testing if the azimuth is already an obstruction point + assert Horizon1.interpolate((20, 20), (25, 20), 25) == 20 - Horizon1 = Horizon() + # Testing if the azimuth isn't an obstruction point (using interpolate) + assert Horizon1.interpolate((20, 20), (25, 25), 22) == 22 - #Testing if the azimuth is already an obstruction point (2 points) - assert Horizon1.determine_el([(20, 20), (25, 20), (30, 30)], 25) == 20 + # Testing if the azimuth isn't an obstruction point (using interpolate) + assert Horizon1.interpolate((20, 20), (25, 25), 22) == 0 - #Testing if the azimuth is already an obstruction point (3 points) - assert Horizon1.determine_el([(20, 20), (25, 20), (30, 30)], 25) == 20 - #Testing if the azimuth isn't an obstruction point (using interpolate) - assert Horizon1.determine_el([(20, 20), (25, 25)], 22) == 22 +def test_determine_el(): -def test_horizon_limits(): - #test_validations() - test_interpolate() - test_determine_el() + Horizon1 = Horizon() + # Testing if the azimuth is already an obstruction point (2 points) + assert Horizon1.determine_el([(20, 20), (25, 20), (30, 30)], 25) == 20 + # Testing if the azimuth is already an obstruction point (3 points) + assert Horizon1.determine_el([(20, 20), (25, 20), (30, 30)], 25) == 20 + # Testing if the azimuth isn't an obstruction point (using interpolate) + assert Horizon1.determine_el([(20, 20), (25, 25)], 22) == 22 +def test_horizon_limits(): + # test_validations() + test_interpolate() + test_determine_el() From 007a4c92a84cbdb27be420d55629f58e6af20161 Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Mon, 10 Apr 2017 11:00:16 +1000 Subject: [PATCH 5/9] Improved the obstruction points validation tests --- pocs/scheduler/constraint.py | 30 ++-- pocs/tests/test_horizon_limits.py | 271 ++++++++++++++++++++---------- 2 files changed, 193 insertions(+), 108 deletions(-) diff --git a/pocs/scheduler/constraint.py b/pocs/scheduler/constraint.py index 271b13513..326129a8d 100644 --- a/pocs/scheduler/constraint.py +++ b/pocs/scheduler/constraint.py @@ -166,8 +166,8 @@ def __str__(self): class Horizon(BaseConstraint): - # @obstruction_points = [] # How exactly do I use decorators to declare properties/do I need to use a decorator? + def __init__(self, obstruction_points, *args, **kwargs): # Constructor super().__init__(*args, **kwargs) # Calls parent's (BaseConstraint's) constructor @@ -209,24 +209,18 @@ def process_image(image_filename): # to populate the obstruction_points from user input def enter_coords(): + """ + Enters a coordinate list from the user and validates it. + """ + from test_horizon_limits.py import obstruction_points_valid + print("Enter a list of azimuth elevation tuples with increasing azimuths.") + print("For example (10,10), (20,20), (340,70), (350,80)") - valid = False - while(valid is False): - - print("Enter a list of points. For example (0,0), (0,1), (1,1), (1,0)") - - points = input() - - try: - if isinstance(points, tuple): - valid = True - else: - print("Input type error. Please enter the coordinates in the format mentioned") - except SyntaxError: - print( - "Syntax error. Please enter the coordinates in the format mentioned") - - return points + points = input() + if obstruction_points_valid(points): + return points + else: + return [] def interpolate(A, B, az): diff --git a/pocs/tests/test_horizon_limits.py b/pocs/tests/test_horizon_limits.py index ab98ae277..2c63678e6 100644 --- a/pocs/tests/test_horizon_limits.py +++ b/pocs/tests/test_horizon_limits.py @@ -2,124 +2,215 @@ from pocs.scheduler.constraint import Horizon -import math - -""" -Validation tests, fail if: -1. If there are no points -2. If there is only one point -3. If any point doesnt have two components (az and el) -4. If any point is the incorrect data type -5. If any point isnt in the azimuth range (0<=azimuth<359,59,59) -6. If any point isnt in the elevation range (0<=elevation<90) -7. If any inputted azimuth isnt in the correct sequence low and increasing order -8. If multiple data points have the same azimuth thrice or more if two azimuths are equal it defines a vertical line -9. Make the obstructions are a reasonable size - no zero to 360 case - -Test case Test no. Test Case Validation pass/fail -1 1 No points F -2 2 One point (10, 50) F -3 3 Two points (100, 50), (120) F -4 3 Two points (100, 50, 50) (120, 60) F -5 3 Two points (100), (120, 60) F -6 3 Two points (100), (120) F -7 4 A string: test F -8 4 A boolean: true F -9 4 (x,20), (120, 60) F -10 4 (10, 50), (30, 10y) F -11 5 Two points: (-2, 60), (-1, 70) F -12 5 Two points: (-1, 60), (1, 70) F -13 6 Two points: (20, -2), (40, -1) F -14 6 Two points: (20, -2), (40, 70) F -15 7 Two points: (50, 60), (40, 70) F -16 8 Three points: (50, 60), (50, 70), (50, 80) F -""" - -# 1. If there are no points -""" -def test_validation_1(obstruction_points): - #Using the implicit booleanness of the empty list - assert obstruction_points -""" - +# If there is only one point def test_validation_1(obstruction_points): - # Using the implicit booleanness of the empty list - return len(obstruction_points) > 0 - - -# 2. If there is only one point -def test_validation_2(obstruction_points): assert len(obstruction_points) >= 2 -# 3. If any point doesnt have two components (az and el) -def test_validation_3(obstruction_points): +# If any point doesnt have two components (az and el) +def test_validation_2(obstruction_points): for i in obstruction_points: assert len(i) == 2 -# 4. If any point is the incorrect data type -def test_validation_4(obstruction_points): +# If any point is the incorrect data type +def test_validation_3(obstruction_points): assert type(obstruction_points) == list for i in obstruction_points: - assert type(i[0]) == float - assert type(i[1]) == float + assert type(i[0]) == float or type(i[0]) == int + assert type(i[1]) == float or type(i[1]) == int -# 5. If any point isnt in the azimuth range (0<=azimuth<359,59,59) -def test_validation_5(obstruction_points): +# If any point isnt in the azimuth range (0<=azimuth<359,59,59) +def test_validation_4(obstruction_points): for i in obstruction_points: az = i[0] - assert az >= 0 and az < 2 * math.pi # degrees are astropy units degrees - radians + assert az >= 0 and az < 360 # degrees are astropy units degrees -# 6. If any point isnt in the elevation range (0<=elevation<90) -def test_validation_6(obstruction_points): +# If any point isnt in the elevation range (0<=elevation<90) +def test_validation_5(obstruction_points): for i in obstruction_points: el = i[1] - assert el >= 0 and el < (math.pi) / 2 + assert el >= 0 and el <= 90 -# 7. If any inputted azimuth isnt in the correct sequence low and increasing order -def test_validation_7(obstruction_points): +# If any inputted azimuth isnt in the correct sequence low and increasing order +def test_validation_6(obstruction_points): az_list = [] for i in obstruction_points: az_list.append(i[0]) assert sorted(az_list) == az_list -# 8. If multiple data points have the same azimuth thrice or more if two azimuths are equal -def test_validation_8(obstruction_points): - from itertools import groupby - az_list = [] +# If multiple data points have the same azimuth thrice or more +# This works assuming that the array is sorted as per test_validation_6 +def test_validation_7(obstruction_points): + azp1 = -1 + azp2 = -1 for i in obstruction_points: - az_list.append(i[0]) - assert [len(list(group)) for key, group in groupby(az_list)] <= 2 - - -optc1 = [] -optc2 = [(10, 50)] -optc3 = [(100, 50), (120)] -optc4 = [(100, 50, 50), (120, 60)] -optc5 = [(100), (120, 60)] -optc6 = [(100), (120)] -optc7 = ["test"] -optc8 = [True] -optc9 = [("x", 20), (120, 60)] -optc10 = [(10, 50), (30, "10y")] -optc11 = [(-2, 60), (-1, 70)] -optc12 = [(-1, 60), (1, 70)] -optc13 = [(20, -2), (40, -1)] -optc14 = [(20, -2), (40, 70)] -optc15 = [(50, 60), (40, 70)] -optc16 = [(50, 60), (50, 70), (50, 80)] - -obstruction_points_test_cases = [optc1, optc2, optc3, optc4, optc5, optc6, optc7, optc8, optc9, - optc10, optc11, optc12, optc13, optc14, optc15, optc16] - - -assert test_validation_1(optc1) is False + az = i[0] + assert az != azp1 or azp1 != azp2 + azp2 = azp1 + azp1 = az + +# Return True if it passes False if it fails + + +def obstruction_points_valid(obstruction_points): + v = True + try: + test_validation_1(obstruction_points) + except AssertionError: + print("obstruction_points should have at least 2 sets of points") + v = False + try: + test_validation_2(obstruction_points) + except AssertionError: + print("Each point should have 2 of components (az and el") + v = False + try: + test_validation_3(obstruction_points) + except AssertionError: + print("obstruction_points should be a list of tuples where each element is a float or an int") + v = False + try: + test_validation_4(obstruction_points) + except AssertionError: + print("obstruction_points should be in the azimuth range of 0 <= azimuth < 60") + v = False + try: + test_validation_5(obstruction_points) + except AssertionError: + print("obstruction_points should be in the elevation range of 0 <= elevation < 90") + v = False + try: + test_validation_6(obstruction_points) + except AssertionError: + print("obstruction_points azimuth's should be in an increasing order") + v = False + try: + test_validation_7(obstruction_points) + except AssertionError: + print("obstruction_points should not consecutively have the same azimuth thrice or more") + v = False + assert v + return v + + +# Unit tests for each of the evaluations +def test_validations(): + + test_validation_1([(20, 10), (40, 70)]) + test_validation_1([("x", 10), (40, 70)]) + test_validation_1([(20), (40, 70)]) + with pytest.raises(AssertionError): + test_validation_1([]) + with pytest.raises(AssertionError): + test_validation_1([10]) + with pytest.raises(AssertionError): + test_validation_1([(30, 20)]) + + test_validation_2([]) + test_validation_2([(20, 10)]) + test_validation_2([(10, 10), (40, 70)]) + test_validation_2([("x", 10), (40, 70)]) + test_validation_2([("x", 10), (40, 70), (50, 80)]) + with pytest.raises(TypeError): + test_validation_2([(100), (120, 60)]) + with pytest.raises(TypeError): + test_validation_2([(120, 60), (100)]) + with pytest.raises(AssertionError): + test_validation_2([(120, 60, 300)]) + with pytest.raises(AssertionError): + test_validation_2([(120, 60, 300), (120, 60)]) + + test_validation_3([(120, 300)]) + test_validation_3([(10, 10), (40, 70)]) + with pytest.raises(AssertionError): + test_validation_3([("x", 300)]) + with pytest.raises(AssertionError): + test_validation_3([(200, False)]) + with pytest.raises(AssertionError): + test_validation_3([[(200, 99)]]) + with pytest.raises(AssertionError): + test_validation_3([((1, 2), 2)]) + + test_validation_4([(20, -2), (40, 70)]) + test_validation_4([(20, 20), (40, 70)]) + test_validation_4([(359.9, 20), (40, 70)]) + test_validation_4([(20, 20), (40, 0.01)]) + test_validation_4([(0, 20), (40, 50)]) + with pytest.raises(AssertionError): + test_validation_4([(-1, 65), (1, 70)]) + with pytest.raises(AssertionError): + test_validation_4([(50, 60), (-10, 70)]) + with pytest.raises(AssertionError): + test_validation_4([(370, 60), (1, 70)]) + with pytest.raises(AssertionError): + test_validation_4([(350, 60), (800, 70)]) + with pytest.raises(AssertionError): + test_validation_4([(360.01, 60), (200, 70)]) + with pytest.raises(AssertionError): + test_validation_4([(-0.01, 60), (200, 70)]) + with pytest.raises(AssertionError): + test_validation_4([(360, 60), (200, 70)]) + + test_validation_5([(40, 70), (-20, 2)]) + test_validation_5([(40, 70), (20, 20)]) + test_validation_5([(40, 70), (359.9, 20)]) + test_validation_5([(40, 0.01), (20, 20)]) + test_validation_5([(40, 50), (0, 20)]) + with pytest.raises(AssertionError): + test_validation_5([(1, 70), (1, -65)]) + with pytest.raises(AssertionError): + test_validation_5([(10, -70), (50, 60)]) + with pytest.raises(AssertionError): + test_validation_5([(1, 70), (350, 370)]) + with pytest.raises(AssertionError): + test_validation_5([(80, 700), (350, 60)]) + with pytest.raises(AssertionError): + test_validation_5([(200, 70), (359, 360.01)]) + with pytest.raises(AssertionError): + test_validation_5([(200, 70), (60, -0.01)]) + with pytest.raises(AssertionError): + test_validation_5([(200, 70), (350, 360)]) + + test_validation_6([(10, 20)]) + test_validation_6([(40, 70), (50, 60)]) + test_validation_6([(40, 70), (40, 60)]) + test_validation_6([(50, 60), (60, 70), (70, 50)]) + with pytest.raises(AssertionError): + test_validation_6([(50, 60), (40, 70)]) + with pytest.raises(AssertionError): + test_validation_6([(50, 60), (60, 70), (40, 50)]) + + test_validation_7([(50, 60), (60, 70), (70, 50)]) + test_validation_7([(50, 60), (60, 70)]) + with pytest.raises(AssertionError): + test_validation_7([(50, 60), (50, 70), (50, 80)]) + with pytest.raises(AssertionError): + test_validation_7([(50, 60), (50, 70), (50, 80), (50, 80)]) + + obstruction_points_valid([(20, 10), (40, 70)]) + obstruction_points_valid([(50, 60), (60, 70), (70, 50)]) + obstruction_points_valid([(10, 10), (40, 70), (50, 30), (65, 10)]) + obstruction_points_valid([(10, 10), (40, 70), (50, 30), (65, 10), (85, 85)]) + with pytest.raises(AssertionError): + obstruction_points_valid([]) + with pytest.raises(TypeError): + obstruction_points_valid([(100), (120, 60)]) + with pytest.raises(AssertionError): + obstruction_points_valid([("x", 300)]) + with pytest.raises(AssertionError): + obstruction_points_valid([(-1, 65), (1, 70)]) + with pytest.raises(AssertionError): + obstruction_points_valid([(1, 70), (1, -65)]) + with pytest.raises(AssertionError): + obstruction_points_valid([(50, 60), (40, 70)]) + with pytest.raises(AssertionError): + obstruction_points_valid([(50, 60), (50, 70), (50, 80)]) def test_interpolate(): From 259549100e4a32e53156467d8ade323825e0f356 Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Fri, 28 Apr 2017 10:44:11 +1000 Subject: [PATCH 6/9] Fixed the testing. --- pocs/scheduler/constraint.py | 68 +++++++++++++++++++------------ pocs/tests/test_horizon_limits.py | 46 ++++++++++++++++----- 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/pocs/scheduler/constraint.py b/pocs/scheduler/constraint.py index 326129a8d..71dbbb6a1 100644 --- a/pocs/scheduler/constraint.py +++ b/pocs/scheduler/constraint.py @@ -166,21 +166,22 @@ def __str__(self): class Horizon(BaseConstraint): - # @obstruction_points = [] # How exactly do I use decorators to declare properties/do I need to use a decorator? - def __init__(self, obstruction_points, *args, **kwargs): # Constructor + def __init__(self, *args, **kwargs): # Constructor super().__init__(*args, **kwargs) # Calls parent's (BaseConstraint's) constructor - # assert the validation conditions - - self.obstruction_points = obstruction_points + self.obstruction_points = [] # Process the horizon_image to generate the obstruction_points list # Segment regions of high contrast using scikit image # Image Segmentation with Watershed Algorithm # def process_image(): - def process_image(image_filename): + def set_obstruction_points(self, op): + self.obstruction_points = op + # call validation within setter + + def process_image(self, image_filename): """ bottom_left is a tuple, top_right is a tuple, each tuple has az, el to allow for incomplete horizon images @@ -208,23 +209,34 @@ def process_image(image_filename): # After a horizon instant has been instantiated this method can be called # to populate the obstruction_points from user input - def enter_coords(): + def enter_coords(self): """ Enters a coordinate list from the user and validates it. + If valid sets up a value for obstruction_points, otherwise leaves it empty + """ + from test_horizon_limits.py import obstruction_points_valid print("Enter a list of azimuth elevation tuples with increasing azimuths.") print("For example (10,10), (20,20), (340,70), (350,80)") - points = input() - if obstruction_points_valid(points): - return points - else: - return [] + self.obstruction_points = input() + if not obstruction_points_valid(self.obstruction_points): + self.obstruction_points = [] - def interpolate(A, B, az): + def interpolate(self, A, B, az): + """ + Determine the line equation between two points to return the elevation for a given azimuth + + Keyword arguments: + A - tuple (azimuth, elevation) + B - tuple (azimuth, elevation) + az - float or int - # input validation assertions + return el - elevation float or int + """ + + # Input validation assertions. assert len(A) == 2 assert len(B) == 2 assert type(az) == float or type(az) == int @@ -236,12 +248,17 @@ def interpolate(A, B, az): assert az <= B[0] assert az < 90 - if B[0] == A[0]: - el = B[1] + x1 = A[0] + y1 = A[1] + x2 = B[0] + y2 = B[1] + + if x2 == x1: # Vertical Line + el = max(y1, y2) else: - m = ((B[1] - A[1]) / (B[0] - A[0])) - # Same as y=mx+b - el = m * az + A[1] + m = ((y2 - y1) / (x2 - x1)) + b = y1 - m * x1 + el = m * az + b assert el < 90 @@ -251,16 +268,17 @@ def interpolate(A, B, az): # Its possible that a single tuple will have a matching azimuth to the target azimuth - special case # Pass in (x1, y1), (x2, y2), target.az # Return elevation + # Default el of 0 when the azimuth is outside an obstruction - def determine_el(az): + def determine_el(self, az): el = 0 - prior_point = obstruction_points[0] + prior_point = self.obstruction_points[0] i = 1 found = False - while(i < len(obstruction_points) and found is False): - next_point = obstruction_points[i] + while(i < len(self.obstruction_points) and found is False): + next_point = self.obstruction_points[i] if az >= prior_point[0] and az <= next_point[0]: - el = interpolate(prior_point, next_point, az) + el = self.interpolate(prior_point, next_point, az) found = True else: i += 1 @@ -281,7 +299,7 @@ def get_score(self, time, observer, observation, **kwargs): az = observer.altaz(time, target=target).az alt = observer.altaz(time, target=target).alt - el = determine_el(az) + el = self.determine_el(az) # Determine if the target altitude is above or below the determined minimum elevation for that azimuth # Note the image is 10 by 15, so I want it to be 7.5 below the target's diff --git a/pocs/tests/test_horizon_limits.py b/pocs/tests/test_horizon_limits.py index 2c63678e6..1a3e9ce1e 100644 --- a/pocs/tests/test_horizon_limits.py +++ b/pocs/tests/test_horizon_limits.py @@ -4,17 +4,21 @@ # If there is only one point +@pytest.fixture(scope='module') def test_validation_1(obstruction_points): assert len(obstruction_points) >= 2 -# If any point doesnt have two components (az and el) +# If any point isn't a tuple, if any tuple isn't of length 2 +@pytest.fixture(scope='module') def test_validation_2(obstruction_points): for i in obstruction_points: + assert type(i) == tuple assert len(i) == 2 # If any point is the incorrect data type +@pytest.fixture(scope='module') def test_validation_3(obstruction_points): assert type(obstruction_points) == list for i in obstruction_points: @@ -23,6 +27,7 @@ def test_validation_3(obstruction_points): # If any point isnt in the azimuth range (0<=azimuth<359,59,59) +@pytest.fixture(scope='module') def test_validation_4(obstruction_points): for i in obstruction_points: az = i[0] @@ -30,6 +35,7 @@ def test_validation_4(obstruction_points): # If any point isnt in the elevation range (0<=elevation<90) +@pytest.fixture(scope='module') def test_validation_5(obstruction_points): for i in obstruction_points: el = i[1] @@ -37,6 +43,7 @@ def test_validation_5(obstruction_points): # If any inputted azimuth isnt in the correct sequence low and increasing order +@pytest.fixture(scope='module') def test_validation_6(obstruction_points): az_list = [] for i in obstruction_points: @@ -46,6 +53,7 @@ def test_validation_6(obstruction_points): # If multiple data points have the same azimuth thrice or more # This works assuming that the array is sorted as per test_validation_6 +@pytest.fixture(scope='module') def test_validation_7(obstruction_points): azp1 = -1 azp2 = -1 @@ -117,9 +125,9 @@ def test_validations(): test_validation_2([(10, 10), (40, 70)]) test_validation_2([("x", 10), (40, 70)]) test_validation_2([("x", 10), (40, 70), (50, 80)]) - with pytest.raises(TypeError): + with pytest.raises(AssertionError): test_validation_2([(100), (120, 60)]) - with pytest.raises(TypeError): + with pytest.raises(AssertionError): test_validation_2([(120, 60), (100)]) with pytest.raises(AssertionError): test_validation_2([(120, 60, 300)]) @@ -193,15 +201,16 @@ def test_validations(): with pytest.raises(AssertionError): test_validation_7([(50, 60), (50, 70), (50, 80), (50, 80)]) + # The following tests test the whole test suite and are designed to pass obstruction_points_valid([(20, 10), (40, 70)]) obstruction_points_valid([(50, 60), (60, 70), (70, 50)]) obstruction_points_valid([(10, 10), (40, 70), (50, 30), (65, 10)]) obstruction_points_valid([(10, 10), (40, 70), (50, 30), (65, 10), (85, 85)]) + + # The following tests test the whole test suite and are designed to fail with pytest.raises(AssertionError): obstruction_points_valid([]) with pytest.raises(TypeError): - obstruction_points_valid([(100), (120, 60)]) - with pytest.raises(AssertionError): obstruction_points_valid([("x", 300)]) with pytest.raises(AssertionError): obstruction_points_valid([(-1, 65), (1, 70)]) @@ -220,28 +229,43 @@ def test_interpolate(): # Testing if the azimuth is already an obstruction point assert Horizon1.interpolate((20, 20), (25, 20), 25) == 20 - # Testing if the azimuth isn't an obstruction point (using interpolate) + # Testing if the azimuth is already an obstruction point + assert Horizon1.interpolate((20, 20), (25, 20), 25) == 20 + + # Testing if the azimuth is between 2 obstruction points (using interpolate) assert Horizon1.interpolate((20, 20), (25, 25), 22) == 22 # Testing if the azimuth isn't an obstruction point (using interpolate) - assert Horizon1.interpolate((20, 20), (25, 25), 22) == 0 + with pytest.raises(AssertionError): + assert Horizon1.interpolate((20, 20), (25, 25), 22) == 0 def test_determine_el(): Horizon1 = Horizon() + Horizon1.set_obstruction_points([(20, 20), (25, 20)]) # Testing if the azimuth is already an obstruction point (2 points) - assert Horizon1.determine_el([(20, 20), (25, 20), (30, 30)], 25) == 20 + assert Horizon1.determine_el(25) == 20 + + Horizon1.set_obstruction_points([(20, 20), (25, 20), (30, 30)]) # Testing if the azimuth is already an obstruction point (3 points) - assert Horizon1.determine_el([(20, 20), (25, 20), (30, 30)], 25) == 20 + assert Horizon1.determine_el(25) == 20 + + # Testing if the azimuth is an obstruction point (using interpolate) + assert Horizon1.determine_el(22) == 20 + + # Testing an azimuth before the first obstruction point + assert Horizon1.determine_el(10) == 0 # Testing if the azimuth isn't an obstruction point (using interpolate) - assert Horizon1.determine_el([(20, 20), (25, 25)], 22) == 22 + with pytest.raises(AssertionError): + assert Horizon1.determine_el(23) == 100 +@pytest.fixture(scope='module') def test_horizon_limits(): - # test_validations() + test_validations() test_interpolate() test_determine_el() From b4f0f2f5b3fa09d139f5240d142ef6b4a346c468 Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Fri, 5 May 2017 14:34:12 +1000 Subject: [PATCH 7/9] Fixed py.test, docstrings. --- pocs/scheduler/constraint.py | 61 +++++------- pocs/tests/test_horizon_limits.py | 156 ++++++++++++++---------------- 2 files changed, 94 insertions(+), 123 deletions(-) diff --git a/pocs/scheduler/constraint.py b/pocs/scheduler/constraint.py index 71dbbb6a1..33f121ea6 100644 --- a/pocs/scheduler/constraint.py +++ b/pocs/scheduler/constraint.py @@ -167,24 +167,26 @@ def __str__(self): class Horizon(BaseConstraint): - def __init__(self, *args, **kwargs): # Constructor - super().__init__(*args, **kwargs) # Calls parent's (BaseConstraint's) constructor + """ Implements horizon and obstruction limits""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.obstruction_points = [] - # Process the horizon_image to generate the obstruction_points list - # Segment regions of high contrast using scikit image - # Image Segmentation with Watershed Algorithm - # def process_image(): - def set_obstruction_points(self, op): self.obstruction_points = op - # call validation within setter def process_image(self, image_filename): """ + + Process the horizon_image to generate the obstruction_points list + Segment regions of high contrast using scikit image + Image Segmentation with Watershed Algorithm bottom_left is a tuple, top_right is a tuple, each tuple has az, el to allow for incomplete horizon images + + Args: + image_filename (file): The horizon panorama image to be processed """ from skimage.io import imread @@ -203,17 +205,11 @@ def process_image(self, image_filename): np.set_printoptions(threshold=np.nan) print(edges1.astype(np.float)) - # Convert into az, el coords using bottom_left, top_right - - # Get the user to input az, el coordinates - # After a horizon instant has been instantiated this method can be called - # to populate the obstruction_points from user input - def enter_coords(self): + """ - Enters a coordinate list from the user and validates it. + Enters a coordinate list from the user and validates it If valid sets up a value for obstruction_points, otherwise leaves it empty - """ from test_horizon_limits.py import obstruction_points_valid @@ -228,12 +224,10 @@ def interpolate(self, A, B, az): """ Determine the line equation between two points to return the elevation for a given azimuth - Keyword arguments: - A - tuple (azimuth, elevation) - B - tuple (azimuth, elevation) - az - float or int - - return el - elevation float or int + Args: + A (tuple): obstruction point A + B (tuple): obstruction point B + az (float or int): the target azimuth """ # Input validation assertions. @@ -264,13 +258,16 @@ def interpolate(self, A, B, az): return el - # Search the base constraint for the adjacent pair of tuples that contains the target azimuth - # Its possible that a single tuple will have a matching azimuth to the target azimuth - special case - # Pass in (x1, y1), (x2, y2), target.az - # Return elevation - # Default el of 0 when the azimuth is outside an obstruction def determine_el(self, az): + + """ + # Determine if the target altitude is above or below the determined minimum elevation for that azimuth + + Args: + az (float or int): the target azimuth + """ + el = 0 prior_point = self.obstruction_points[0] i = 1 @@ -285,14 +282,10 @@ def determine_el(self, az): prior_point = next_point return el - # Determine if the target altitude is above or below the determined - # minimum elevation for that azimuth - inside get_score def get_score(self, time, observer, observation, **kwargs): target = observation.field - # Is this using the astropy units to declare the constraint? - # "A decorator for validating the units of arguments to functions." veto = False score = self._score @@ -302,16 +295,10 @@ def get_score(self, time, observer, observation, **kwargs): el = self.determine_el(az) # Determine if the target altitude is above or below the determined minimum elevation for that azimuth - # Note the image is 10 by 15, so I want it to be 7.5 below the target's - # elevation - if alt - 7.5 > el: veto = True else: score = 100 - - # After interpolation put the target az into the equation to determine el - # assume everything has a default score of 100 return veto, score * self.weight def __str__(self): diff --git a/pocs/tests/test_horizon_limits.py b/pocs/tests/test_horizon_limits.py index 1a3e9ce1e..04c286501 100644 --- a/pocs/tests/test_horizon_limits.py +++ b/pocs/tests/test_horizon_limits.py @@ -4,22 +4,19 @@ # If there is only one point -@pytest.fixture(scope='module') -def test_validation_1(obstruction_points): +def validation_1(obstruction_points): assert len(obstruction_points) >= 2 # If any point isn't a tuple, if any tuple isn't of length 2 -@pytest.fixture(scope='module') -def test_validation_2(obstruction_points): +def validation_2(obstruction_points): for i in obstruction_points: assert type(i) == tuple assert len(i) == 2 # If any point is the incorrect data type -@pytest.fixture(scope='module') -def test_validation_3(obstruction_points): +def validation_3(obstruction_points): assert type(obstruction_points) == list for i in obstruction_points: assert type(i[0]) == float or type(i[0]) == int @@ -27,24 +24,21 @@ def test_validation_3(obstruction_points): # If any point isnt in the azimuth range (0<=azimuth<359,59,59) -@pytest.fixture(scope='module') -def test_validation_4(obstruction_points): +def validation_4(obstruction_points): for i in obstruction_points: az = i[0] assert az >= 0 and az < 360 # degrees are astropy units degrees # If any point isnt in the elevation range (0<=elevation<90) -@pytest.fixture(scope='module') -def test_validation_5(obstruction_points): +def validation_5(obstruction_points): for i in obstruction_points: el = i[1] assert el >= 0 and el <= 90 # If any inputted azimuth isnt in the correct sequence low and increasing order -@pytest.fixture(scope='module') -def test_validation_6(obstruction_points): +def validation_6(obstruction_points): az_list = [] for i in obstruction_points: az_list.append(i[0]) @@ -52,9 +46,8 @@ def test_validation_6(obstruction_points): # If multiple data points have the same azimuth thrice or more -# This works assuming that the array is sorted as per test_validation_6 -@pytest.fixture(scope='module') -def test_validation_7(obstruction_points): +# This works assuming that the array is sorted as per validation_6 +def validation_7(obstruction_points): azp1 = -1 azp2 = -1 for i in obstruction_points: @@ -63,43 +56,41 @@ def test_validation_7(obstruction_points): azp2 = azp1 azp1 = az -# Return True if it passes False if it fails - def obstruction_points_valid(obstruction_points): v = True try: - test_validation_1(obstruction_points) + validation_1(obstruction_points) except AssertionError: print("obstruction_points should have at least 2 sets of points") v = False try: - test_validation_2(obstruction_points) + validation_2(obstruction_points) except AssertionError: print("Each point should have 2 of components (az and el") v = False try: - test_validation_3(obstruction_points) + validation_3(obstruction_points) except AssertionError: print("obstruction_points should be a list of tuples where each element is a float or an int") v = False try: - test_validation_4(obstruction_points) + validation_4(obstruction_points) except AssertionError: print("obstruction_points should be in the azimuth range of 0 <= azimuth < 60") v = False try: - test_validation_5(obstruction_points) + validation_5(obstruction_points) except AssertionError: print("obstruction_points should be in the elevation range of 0 <= elevation < 90") v = False try: - test_validation_6(obstruction_points) + validation_6(obstruction_points) except AssertionError: print("obstruction_points azimuth's should be in an increasing order") v = False try: - test_validation_7(obstruction_points) + validation_7(obstruction_points) except AssertionError: print("obstruction_points should not consecutively have the same azimuth thrice or more") v = False @@ -110,96 +101,96 @@ def obstruction_points_valid(obstruction_points): # Unit tests for each of the evaluations def test_validations(): - test_validation_1([(20, 10), (40, 70)]) - test_validation_1([("x", 10), (40, 70)]) - test_validation_1([(20), (40, 70)]) + validation_1([(20, 10), (40, 70)]) + validation_1([("x", 10), (40, 70)]) + validation_1([(20), (40, 70)]) with pytest.raises(AssertionError): - test_validation_1([]) + validation_1([]) with pytest.raises(AssertionError): - test_validation_1([10]) + validation_1([10]) with pytest.raises(AssertionError): - test_validation_1([(30, 20)]) + validation_1([(30, 20)]) - test_validation_2([]) - test_validation_2([(20, 10)]) - test_validation_2([(10, 10), (40, 70)]) - test_validation_2([("x", 10), (40, 70)]) - test_validation_2([("x", 10), (40, 70), (50, 80)]) + validation_2([]) + validation_2([(20, 10)]) + validation_2([(10, 10), (40, 70)]) + validation_2([("x", 10), (40, 70)]) + validation_2([("x", 10), (40, 70), (50, 80)]) with pytest.raises(AssertionError): - test_validation_2([(100), (120, 60)]) + validation_2([(100), (120, 60)]) with pytest.raises(AssertionError): - test_validation_2([(120, 60), (100)]) + validation_2([(120, 60), (100)]) with pytest.raises(AssertionError): - test_validation_2([(120, 60, 300)]) + validation_2([(120, 60, 300)]) with pytest.raises(AssertionError): - test_validation_2([(120, 60, 300), (120, 60)]) + validation_2([(120, 60, 300), (120, 60)]) - test_validation_3([(120, 300)]) - test_validation_3([(10, 10), (40, 70)]) + validation_3([(120, 300)]) + validation_3([(10, 10), (40, 70)]) with pytest.raises(AssertionError): - test_validation_3([("x", 300)]) + validation_3([("x", 300)]) with pytest.raises(AssertionError): - test_validation_3([(200, False)]) + validation_3([(200, False)]) with pytest.raises(AssertionError): - test_validation_3([[(200, 99)]]) + validation_3([[(200, 99)]]) with pytest.raises(AssertionError): - test_validation_3([((1, 2), 2)]) + validation_3([((1, 2), 2)]) - test_validation_4([(20, -2), (40, 70)]) - test_validation_4([(20, 20), (40, 70)]) - test_validation_4([(359.9, 20), (40, 70)]) - test_validation_4([(20, 20), (40, 0.01)]) - test_validation_4([(0, 20), (40, 50)]) + validation_4([(20, -2), (40, 70)]) + validation_4([(20, 20), (40, 70)]) + validation_4([(359.9, 20), (40, 70)]) + validation_4([(20, 20), (40, 0.01)]) + validation_4([(0, 20), (40, 50)]) with pytest.raises(AssertionError): - test_validation_4([(-1, 65), (1, 70)]) + validation_4([(-1, 65), (1, 70)]) with pytest.raises(AssertionError): - test_validation_4([(50, 60), (-10, 70)]) + validation_4([(50, 60), (-10, 70)]) with pytest.raises(AssertionError): - test_validation_4([(370, 60), (1, 70)]) + validation_4([(370, 60), (1, 70)]) with pytest.raises(AssertionError): - test_validation_4([(350, 60), (800, 70)]) + validation_4([(350, 60), (800, 70)]) with pytest.raises(AssertionError): - test_validation_4([(360.01, 60), (200, 70)]) + validation_4([(360.01, 60), (200, 70)]) with pytest.raises(AssertionError): - test_validation_4([(-0.01, 60), (200, 70)]) + validation_4([(-0.01, 60), (200, 70)]) with pytest.raises(AssertionError): - test_validation_4([(360, 60), (200, 70)]) + validation_4([(360, 60), (200, 70)]) - test_validation_5([(40, 70), (-20, 2)]) - test_validation_5([(40, 70), (20, 20)]) - test_validation_5([(40, 70), (359.9, 20)]) - test_validation_5([(40, 0.01), (20, 20)]) - test_validation_5([(40, 50), (0, 20)]) + validation_5([(40, 70), (-20, 2)]) + validation_5([(40, 70), (20, 20)]) + validation_5([(40, 70), (359.9, 20)]) + validation_5([(40, 0.01), (20, 20)]) + validation_5([(40, 50), (0, 20)]) with pytest.raises(AssertionError): - test_validation_5([(1, 70), (1, -65)]) + validation_5([(1, 70), (1, -65)]) with pytest.raises(AssertionError): - test_validation_5([(10, -70), (50, 60)]) + validation_5([(10, -70), (50, 60)]) with pytest.raises(AssertionError): - test_validation_5([(1, 70), (350, 370)]) + validation_5([(1, 70), (350, 370)]) with pytest.raises(AssertionError): - test_validation_5([(80, 700), (350, 60)]) + validation_5([(80, 700), (350, 60)]) with pytest.raises(AssertionError): - test_validation_5([(200, 70), (359, 360.01)]) + validation_5([(200, 70), (359, 360.01)]) with pytest.raises(AssertionError): - test_validation_5([(200, 70), (60, -0.01)]) + validation_5([(200, 70), (60, -0.01)]) with pytest.raises(AssertionError): - test_validation_5([(200, 70), (350, 360)]) + validation_5([(200, 70), (350, 360)]) - test_validation_6([(10, 20)]) - test_validation_6([(40, 70), (50, 60)]) - test_validation_6([(40, 70), (40, 60)]) - test_validation_6([(50, 60), (60, 70), (70, 50)]) + validation_6([(10, 20)]) + validation_6([(40, 70), (50, 60)]) + validation_6([(40, 70), (40, 60)]) + validation_6([(50, 60), (60, 70), (70, 50)]) with pytest.raises(AssertionError): - test_validation_6([(50, 60), (40, 70)]) + validation_6([(50, 60), (40, 70)]) with pytest.raises(AssertionError): - test_validation_6([(50, 60), (60, 70), (40, 50)]) + validation_6([(50, 60), (60, 70), (40, 50)]) - test_validation_7([(50, 60), (60, 70), (70, 50)]) - test_validation_7([(50, 60), (60, 70)]) + validation_7([(50, 60), (60, 70), (70, 50)]) + validation_7([(50, 60), (60, 70)]) with pytest.raises(AssertionError): - test_validation_7([(50, 60), (50, 70), (50, 80)]) + validation_7([(50, 60), (50, 70), (50, 80)]) with pytest.raises(AssertionError): - test_validation_7([(50, 60), (50, 70), (50, 80), (50, 80)]) + validation_7([(50, 60), (50, 70), (50, 80), (50, 80)]) # The following tests test the whole test suite and are designed to pass obstruction_points_valid([(20, 10), (40, 70)]) @@ -262,10 +253,3 @@ def test_determine_el(): # Testing if the azimuth isn't an obstruction point (using interpolate) with pytest.raises(AssertionError): assert Horizon1.determine_el(23) == 100 - - -@pytest.fixture(scope='module') -def test_horizon_limits(): - test_validations() - test_interpolate() - test_determine_el() From 81aecffe0926725b3e9376452159860775a70b96 Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Mon, 22 May 2017 08:54:01 +1000 Subject: [PATCH 8/9] Successfully implemented the config files --- conf_files/pocs.yaml | 7 +++++- pocs/scheduler/constraint.py | 39 ++++++++++++++++++++++--------- pocs/tests/test_horizon_limits.py | 8 +++++++ 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/conf_files/pocs.yaml b/conf_files/pocs.yaml index 8e2f3206e..c7ba9faf8 100644 --- a/conf_files/pocs.yaml +++ b/conf_files/pocs.yaml @@ -6,7 +6,12 @@ location: longitude: -155.58 # Degrees elevation: 3400.0 # Meters utc_offset: -10.00 # Hours - horizon: 30 # Degrees + horizon: 30 #Degrees + horizon_constraint: + - !!python/tuple [10,10] + - !!python/tuple [20,20] + - !!python/tuple [340,70] + - !!python/tuple [350,80] #List of azimuth, elevation tuples (Degrees) twilight_horizon: -18 # Degrees timezone: US/Hawaii directories: diff --git a/pocs/scheduler/constraint.py b/pocs/scheduler/constraint.py index 33f121ea6..3f8fd8104 100644 --- a/pocs/scheduler/constraint.py +++ b/pocs/scheduler/constraint.py @@ -1,6 +1,8 @@ from astropy import units as u -from .. import PanBase +# Changed from "from .. import PanBase" + +from pocs import PanBase class BaseConstraint(PanBase): @@ -167,12 +169,16 @@ def __str__(self): class Horizon(BaseConstraint): - """ Implements horizon and obstruction limits""" + """ Implements horizon and obstruction limits""" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.obstruction_points = [] + print("Horizon.__init__") + for i in self.config: + print(i, self.config[i]) + def set_obstruction_points(self, op): self.obstruction_points = op @@ -205,14 +211,28 @@ def process_image(self, image_filename): np.set_printoptions(threshold=np.nan) print(edges1.astype(np.float)) - def enter_coords(self): + def get_config_coords(self): + """ + Retrieves the coordinate list from the config file and validates it + If valid sets up a value for obstruction_points, otherwise leaves it empty + """ + + from pocs.tests.test_horizon_limits import obstruction_points_valid + + self.obstruction_points = self.config['location']['horizon_constraint'] + print("get_config_coords", "obstruction_points", self.obstruction_points) + + if not obstruction_points_valid(self.obstruction_points): + self.obstruction_points = [] + + def enter_coords(self): """ Enters a coordinate list from the user and validates it If valid sets up a value for obstruction_points, otherwise leaves it empty """ - from test_horizon_limits.py import obstruction_points_valid + from pocs.tests.test_horizon_limits import obstruction_points_valid print("Enter a list of azimuth elevation tuples with increasing azimuths.") print("For example (10,10), (20,20), (340,70), (350,80)") @@ -225,7 +245,7 @@ def interpolate(self, A, B, az): Determine the line equation between two points to return the elevation for a given azimuth Args: - A (tuple): obstruction point A + A (tuple): obstruction point A B (tuple): obstruction point B az (float or int): the target azimuth """ @@ -258,11 +278,9 @@ def interpolate(self, A, B, az): return el - def determine_el(self, az): - """ - # Determine if the target altitude is above or below the determined minimum elevation for that azimuth + # Determine if the target altitude is above or below the determined minimum elevation for that azimuth Args: az (float or int): the target azimuth @@ -282,7 +300,6 @@ def determine_el(self, az): prior_point = next_point return el - def get_score(self, time, observer, observation, **kwargs): target = observation.field diff --git a/pocs/tests/test_horizon_limits.py b/pocs/tests/test_horizon_limits.py index 04c286501..ed39f6786 100644 --- a/pocs/tests/test_horizon_limits.py +++ b/pocs/tests/test_horizon_limits.py @@ -253,3 +253,11 @@ def test_determine_el(): # Testing if the azimuth isn't an obstruction point (using interpolate) with pytest.raises(AssertionError): assert Horizon1.determine_el(23) == 100 + + +def test_get_config_coords(): + + Horizon1 = Horizon() + Horizon1.get_config_coords() + + assert Horizon1.obstruction_points == [(10, 10), (20, 20), (340, 70), (350, 80)] From 8b2e0f723d3db59609cd4927f837cd390721dc0e Mon Sep 17 00:00:00 2001 From: Brendan Orenstein Date: Mon, 5 Jun 2017 11:16:02 +1000 Subject: [PATCH 9/9] Put the automating horizon limits notebook (and corresponding panaroma images) into the pocs examples notebooks folder. --- pocs/scheduler/constraint.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pocs/scheduler/constraint.py b/pocs/scheduler/constraint.py index 3f8fd8104..4e7eb2639 100644 --- a/pocs/scheduler/constraint.py +++ b/pocs/scheduler/constraint.py @@ -184,13 +184,15 @@ def set_obstruction_points(self, op): def process_image(self, image_filename): """ - Process the horizon_image to generate the obstruction_points list Segment regions of high contrast using scikit image Image Segmentation with Watershed Algorithm bottom_left is a tuple, top_right is a tuple, each tuple has az, el to allow for incomplete horizon images + Note: + Incomplete method, further work to be done to automate the horizon limits + Args: image_filename (file): The horizon panorama image to be processed """