Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Horizon features #85

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion conf_files/pocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
gmt_offset: -600
Expand Down
173 changes: 168 additions & 5 deletions pocs/scheduler/constraint.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -22,9 +24,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
Expand Down Expand Up @@ -103,7 +107,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:
Expand All @@ -115,7 +120,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
Expand Down Expand Up @@ -159,3 +165,160 @@ def get_score(self, time, observer, observation, **kwargs):

def __str__(self):
return "Moon Avoidance"


class Horizon(BaseConstraint):

""" Implements horizon and obstruction limits"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.obstruction_points = []

print("Horizon.__init__")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to remove all print statements and use the self.logger.debug (or .info, .warning, etc) system.

for i in self.config:
print(i, self.config[i])

def set_obstruction_points(self, op):
self.obstruction_points = 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
"""

from skimage.io import imread
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import statements should always be at the top of the file

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)

# Turn into array
np.set_printoptions(threshold=np.nan)
print(edges1.astype(np.float))

def get_config_coords(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is reading the config file but not necessarily "getting" them since it doesn't return anything. I would rename to lookup_config_coords or something.

"""
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to top.


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 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)")

self.obstruction_points = input()
if not obstruction_points_valid(self.obstruction_points):
self.obstruction_points = []

def interpolate(self, A, B, az):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable names should be lowercase and a and b are a little ambiguous since this is a tuple of points. Let's name them point_a and point_b or something more descriptive.

"""
Determine the line equation between two points to return the elevation for a given azimuth

Args:
A (tuple): obstruction point A
B (tuple): obstruction point B
az (float or int): the target azimuth
"""

# 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

x1 = A[0]
y1 = A[1]
x2 = B[0]
y2 = B[1]

if x2 == x1: # Vertical Line
el = max(y1, y2)
else:
m = ((y2 - y1) / (x2 - x1))
b = y1 - m * x1
el = m * az + b

assert el < 90

return el

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
found = False
while(i < len(self.obstruction_points) and found is False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while loops are generally used when you have continuous looping until some breaking condition. Here you have a set number of points you want to loop over so might as well loop them directly, something like:

for i, point in enumerate(self.obstruction_points):
    if found:
        break
    ....

Then just use point instead of next_point assignment below.

next_point = self.obstruction_points[i]
if az >= prior_point[0] and az <= next_point[0]:
el = self.interpolate(prior_point, next_point, az)
found = True
else:
i += 1
prior_point = next_point
return el

def get_score(self, time, observer, observation, **kwargs):

target = observation.field
veto = False
score = self._score

az = observer.altaz(time, target=target).az
alt = observer.altaz(time, target=target).alt

el = self.determine_el(az)

# Determine if the target altitude is above or below the determined minimum elevation for that azimuth
if alt - 7.5 > el:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this magic number?

veto = True
else:
score = 100
return veto, score * self.weight

def __str__(self):
return "Horizon {}".format(self.minimum)
27 changes: 27 additions & 0 deletions pocs/tests/test_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just test one observation at at time and see if score1 > 0 or something. Depends on what you are actually trying to test. Let me know if that doesn't seem to make sense.


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


Loading