summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/graph.py58
-rw-r--r--lib/great_circle_arc.py262
-rw-r--r--lib/polygon.py13
-rw-r--r--lib/skyline.py108
4 files changed, 232 insertions, 209 deletions
diff --git a/lib/graph.py b/lib/graph.py
index 491f2fa..a32c3be 100644
--- a/lib/graph.py
+++ b/lib/graph.py
@@ -60,14 +60,14 @@ class Graph:
.. note::
This class is not meant to be used directly. Instead, use
- `sphere.polygon.SphericalPolygon.union` and
- `sphere.polygon.SphericalPolygon.intersection`.
+ `~sphere.polygon.SphericalPolygon.union` and
+ `~sphere.polygon.SphericalPolygon.intersection`.
"""
class Node:
- """
- A `Node` represents a single point, connected by an arbitrary
- number of `Edge` objects to other `Node` objects.
+ """
+ A `~Graph.Node` represents a single point, connected by an arbitrary
+ number of `~Graph.Edge` objects to other `~Graph.Node` objects.
"""
def __init__(self, point, source_polygons=[]):
"""
@@ -91,12 +91,12 @@ class Graph:
Parameters
----------
- edge : `Edge` instance
+ edge : `~Graph.Edge` instance
The edge to follow away from.
Returns
-------
- other : `Edge` instance
+ other : `~Graph.Edge` instance
The other edge.
"""
edges = list(self._edges)
@@ -109,11 +109,14 @@ class Graph:
def equals(self, other, thres=2e-8):
"""
Returns `True` if the location of this and the *other*
- `Node` are the same.
+ `~Graph.Node` are the same.
Parameters
----------
- thres: float
+ other : `~Graph.Node` instance
+ The other node.
+
+ thres : float
If difference is smaller than this, points are equal.
The default value of 2e-8 radians is set based on
emphirical test cases. Relative threshold based on
@@ -126,14 +129,14 @@ class Graph:
class Edge:
"""
- An `Edge` represents a connection between exactly two `Node`
- objects. This `Edge` class has no direction.
+ An `~Graph.Edge` represents a connection between exactly two
+ `~Graph.Node` objects. This `~Graph.Edge` class has no direction.
"""
def __init__(self, A, B, source_polygons=[]):
"""
Parameters
----------
- A, B : `Node` instances
+ A, B : `~Graph.Node` instances
source_polygon : `~sphere.polygon.SphericalPolygon` instance, optional
The polygon this edge came from. Used for bookkeeping.
@@ -154,11 +157,11 @@ class Graph:
Parameters
----------
- node : `Node` instance
+ node : `~Graph.Node` instance
Returns
-------
- other : `Node` instance
+ other : `~Graph.Node` instance
"""
nodes = self._nodes
try:
@@ -168,11 +171,11 @@ class Graph:
def equals(self, other):
"""
- Returns True if the other edge is between the same two nodes.
+ Returns `True` if the other edge is between the same two nodes.
Parameters
----------
- other : `Edge` instance
+ other : `~Graph.Edge` instance
Returns
-------
@@ -261,7 +264,7 @@ class Graph:
Returns
-------
- node : `Node` instance
+ node : `~Graph.Node` instance
The new node
"""
new_node = self.Node(point, source_polygons)
@@ -285,7 +288,7 @@ class Graph:
Parameters
----------
- node : `Node` instance
+ node : `~Graph.Node` instance
"""
assert node in self._nodes
@@ -304,14 +307,14 @@ class Graph:
Parameters
----------
- A, B : `Node` instances
+ A, B : `~Graph.Node` instances
source_polygons : `~sphere.polygon.SphericalPolygon` instance, optional
The polygon(s) this edge came from. Used for bookkeeping.
Returns
-------
- edge : `Edge` instance
+ edge : `~Graph.Edge` instance
The new edge
"""
assert A in self._nodes
@@ -338,7 +341,7 @@ class Graph:
Parameters
----------
- edge : `Edge` instance
+ edge : `~Graph.Edge` instance
"""
assert edge in self._edges
@@ -354,21 +357,22 @@ class Graph:
def split_edge(self, edge, node):
"""
- Splits an `Edge` *edge* at `Node` *node*, removing *edge* and
- replacing it with two new `Edge` instances. It is intended
- that *E* is along the original edge, but that is not enforced.
+ Splits an `~Graph.Edge` *edge* at `~Graph.Node` *node*, removing
+ *edge* and replacing it with two new `~Graph.Edge` instances.
+ It is intended that *E* is along the original edge, but that is
+ not enforced.
Parameters
----------
- edge : `Edge` instance
+ edge : `~Graph.Edge` instance
The edge to split
- node : `Node` instance
+ node : `~Graph.Node` instance
The node to insert
Returns
-------
- edgeA, edgeB : `Edge` instances
+ edgeA, edgeB : `~Graph.Edge` instances
The two new edges on either side of *node*.
"""
assert edge in self._edges
diff --git a/lib/great_circle_arc.py b/lib/great_circle_arc.py
index 399cb3d..d5e78f2 100644
--- a/lib/great_circle_arc.py
+++ b/lib/great_circle_arc.py
@@ -53,138 +53,145 @@ try:
except ImportError:
HAS_C_UFUNCS = False
+if HAS_C_UFUNCS:
+ inner1d = math_util.inner1d
+else:
+ from numpy.core.umath_tests import inner1d
+
__all__ = ['angle', 'intersection', 'intersects', 'length', 'midpoint',
'interpolate']
-if not HAS_C_UFUNCS:
- from numpy.core.umath_tests import inner1d
+def _fast_cross(a, b):
+ """
+ This is a reimplementation of `numpy.cross` that only does 3D x
+ 3D, and is therefore faster since it doesn't need any
+ conditionals.
+ """
+ if HAS_C_UFUNCS:
+ return math_util.cross(a, b)
+
+ cp = np.empty(np.broadcast(a, b).shape)
+ aT = a.T
+ bT = b.T
+ cpT = cp.T
+
+ cpT[0] = aT[1]*bT[2] - aT[2]*bT[1]
+ cpT[1] = aT[2]*bT[0] - aT[0]*bT[2]
+ cpT[2] = aT[0]*bT[1] - aT[1]*bT[0]
+
+ return cp
+
+def _cross_and_normalize(A, B):
+ if HAS_C_UFUNCS:
+ return math_util.cross_and_norm(A, B)
+
+ T = _fast_cross(A, B)
+ # Normalization
+ l = np.sqrt(np.sum(T ** 2, axis=-1))
+ l = np.expand_dims(l, 2)
+ # Might get some divide-by-zeros, but we don't care
+ with np.errstate(invalid='ignore'):
+ TN = T / l
+ return TN
- def _fast_cross(a, b):
- """
- This is a reimplementation of `numpy.cross` that only does 3D x
- 3D, and is therefore faster since it doesn't need any
- conditionals.
- """
- cp = np.empty(np.broadcast(a, b).shape)
- aT = a.T
- bT = b.T
- cpT = cp.T
-
- cpT[0] = aT[1]*bT[2] - aT[2]*bT[1]
- cpT[1] = aT[2]*bT[0] - aT[0]*bT[2]
- cpT[2] = aT[0]*bT[1] - aT[1]*bT[0]
-
- return cp
-
- def _cross_and_normalize(A, B):
- T = _fast_cross(A, B)
- # Normalization
- l = np.sqrt(np.sum(T ** 2, axis=-1))
- l = np.expand_dims(l, 2)
- # Might get some divide-by-zeros, but we don't care
- with np.errstate(invalid='ignore'):
- TN = T / l
- return TN
-
- def intersection(A, B, C, D):
- r"""
- Returns the point of intersection between two great circle arcs.
- The arcs are defined between the points *AB* and *CD*. Either *A*
- and *B* or *C* and *D* may be arrays of points, but not both.
-
- Parameters
- ----------
- A, B : (*x*, *y*, *z*) triples or Nx3 arrays of triples
- Endpoints of the first great circle arc.
-
- C, D : (*x*, *y*, *z*) triples or Nx3 arrays of triples
- Endpoints of the second great circle arc.
-
- Returns
- -------
- T : (*x*, *y*, *z*) triples or Nx3 arrays of triples
- If the given arcs intersect, the intersection is returned. If
- the arcs do not intersect, the triple is set to all NaNs.
-
- Notes
- -----
- The basic intersection is computed using linear algebra as follows
- [1]_:
-
- .. math::
-
- T = \lVert(A × B) × (C × D)\rVert
-
- To determine the correct sign (i.e. hemisphere) of the
- intersection, the following four values are computed:
-
- .. math::
-
- s_1 = ((A × B) × A) · T
-
- s_2 = (B × (A × B)) · T
-
- s_3 = ((C × D) × C) · T
-
- s_4 = (D × (C × D)) · T
-
- For :math:`s_n`, if all positive :math:`T` is returned as-is. If
- all negative, :math:`T` is multiplied by :math:`-1`. Otherwise
- the intersection does not exist and is undefined.
-
- References
- ----------
-
- .. [1] Method explained in an `e-mail
- <http://www.mathworks.com/matlabcentral/newsreader/view_thread/276271>`_
- by Roger Stafford.
-
- http://www.mathworks.com/matlabcentral/newsreader/view_thread/276271
- """
- A = np.asanyarray(A)
- B = np.asanyarray(B)
- C = np.asanyarray(C)
- D = np.asanyarray(D)
-
- A, B = np.broadcast_arrays(A, B)
- C, D = np.broadcast_arrays(C, D)
-
- ABX = _fast_cross(A, B)
- CDX = _fast_cross(C, D)
- T = _cross_and_normalize(ABX, CDX)
- T_ndim = len(T.shape)
-
- if T_ndim > 1:
- s = np.zeros(T.shape[0])
- else:
- s = np.zeros(1)
- s += np.sign(inner1d(_fast_cross(ABX, A), T))
- s += np.sign(inner1d(_fast_cross(B, ABX), T))
- s += np.sign(inner1d(_fast_cross(CDX, C), T))
- s += np.sign(inner1d(_fast_cross(D, CDX), T))
- if T_ndim > 1:
- s = np.expand_dims(s, 2)
-
- cross = np.where(s == -4, -T, np.where(s == 4, T, np.nan))
-
- # If they share a common point, it's not an intersection. This
- # gets around some rounding-error/numerical problems with the
- # above.
- equals = (np.all(A == C, axis = -1) |
- np.all(A == D, axis = -1) |
- np.all(B == C, axis = -1) |
- np.all(B == D, axis = -1))
-
- equals = np.expand_dims(equals, 2)
-
- return np.where(equals, np.nan, cross)
-else:
- inner1d = math_util.inner1d
- _fast_cross = math_util.cross
- _cross_and_normalize = math_util.cross_and_norm
- intersection = math_util.intersection
+
+def intersection(A, B, C, D):
+ r"""
+ Returns the point of intersection between two great circle arcs.
+ The arcs are defined between the points *AB* and *CD*. Either *A*
+ and *B* or *C* and *D* may be arrays of points, but not both.
+
+ Parameters
+ ----------
+ A, B : (*x*, *y*, *z*) triples or Nx3 arrays of triples
+ Endpoints of the first great circle arc.
+
+ C, D : (*x*, *y*, *z*) triples or Nx3 arrays of triples
+ Endpoints of the second great circle arc.
+
+ Returns
+ -------
+ T : (*x*, *y*, *z*) triples or Nx3 arrays of triples
+ If the given arcs intersect, the intersection is returned. If
+ the arcs do not intersect, the triple is set to all NaNs.
+
+ Notes
+ -----
+ The basic intersection is computed using linear algebra as follows
+ [1]_:
+
+ .. math::
+
+ T = \lVert(A × B) × (C × D)\rVert
+
+ To determine the correct sign (i.e. hemisphere) of the
+ intersection, the following four values are computed:
+
+ .. math::
+
+ s_1 = ((A × B) × A) · T
+
+ s_2 = (B × (A × B)) · T
+
+ s_3 = ((C × D) × C) · T
+
+ s_4 = (D × (C × D)) · T
+
+ For :math:`s_n`, if all positive :math:`T` is returned as-is. If
+ all negative, :math:`T` is multiplied by :math:`-1`. Otherwise
+ the intersection does not exist and is undefined.
+
+ References
+ ----------
+
+ .. [1] Method explained in an `e-mail
+ <http://www.mathworks.com/matlabcentral/newsreader/view_thread/276271>`_
+ by Roger Stafford.
+
+ http://www.mathworks.com/matlabcentral/newsreader/view_thread/276271
+ """
+ if HAS_C_UFUNCS:
+ return math_util.intersection(A, B, C, D)
+
+ A = np.asanyarray(A)
+ B = np.asanyarray(B)
+ C = np.asanyarray(C)
+ D = np.asanyarray(D)
+
+ A, B = np.broadcast_arrays(A, B)
+ C, D = np.broadcast_arrays(C, D)
+
+ ABX = _fast_cross(A, B)
+ CDX = _fast_cross(C, D)
+ T = _cross_and_normalize(ABX, CDX)
+ T_ndim = len(T.shape)
+
+ if T_ndim > 1:
+ s = np.zeros(T.shape[0])
+ else:
+ s = np.zeros(1)
+ s += np.sign(inner1d(_fast_cross(ABX, A), T))
+ s += np.sign(inner1d(_fast_cross(B, ABX), T))
+ s += np.sign(inner1d(_fast_cross(CDX, C), T))
+ s += np.sign(inner1d(_fast_cross(D, CDX), T))
+ if T_ndim > 1:
+ s = np.expand_dims(s, 2)
+
+ cross = np.where(s == -4, -T, np.where(s == 4, T, np.nan))
+
+ # If they share a common point, it's not an intersection. This
+ # gets around some rounding-error/numerical problems with the
+ # above.
+ equals = (np.all(A == C, axis = -1) |
+ np.all(A == D, axis = -1) |
+ np.all(B == C, axis = -1) |
+ np.all(B == D, axis = -1))
+
+ equals = np.expand_dims(equals, 2)
+
+ return np.where(equals, np.nan, cross)
def length(A, B, degrees=True):
@@ -261,7 +268,8 @@ def angle(A, B, C, degrees=True):
Parameters
----------
- A, B, C : (*x*, *y*, *z*) triples or Nx3 arrays of triples.
+ A, B, C : (*x*, *y*, *z*) triples or Nx3 arrays of triples
+ Points on sphere.
degrees : bool, optional
If `True` (default) the result is returned in decimal degrees,
diff --git a/lib/polygon.py b/lib/polygon.py
index 8047ab1..fd67042 100644
--- a/lib/polygon.py
+++ b/lib/polygon.py
@@ -115,12 +115,14 @@ class SphericalPolygon(object):
return self._inside
def to_radec(self):
- """Convert `SphericalPolygon` footprint to RA and DEC.
+ """
+ Convert `SphericalPolygon` footprint to RA and DEC.
Returns
-------
- List of RA and DEC pairs (in degrees) corresponding to
- `self.points`.
+ zip(ra,dec) : list of tuples
+ List of (*ra*, *dec*) pairs in degrees corresponding
+ to `points`.
"""
ra, dec = vector.vector_to_radec(self.points[:,0], self.points[:,1],
self.points[:,2], degrees=True)
@@ -314,7 +316,7 @@ class SphericalPolygon(object):
Parameters
----------
- preserve_order: bool
+ preserve_order : bool
Preserve original order? If `True`, polygon is
rotated around min point. If `False`, all points
are sorted in ascending order.
@@ -361,7 +363,8 @@ class SphericalPolygon(object):
Returns
-------
- `True` or `False`
+ is_eq : bool
+ `True` or `False`.
"""
self_n = len(self.points)
diff --git a/lib/skyline.py b/lib/skyline.py
index 113192e..db4d6b8 100644
--- a/lib/skyline.py
+++ b/lib/skyline.py
@@ -33,11 +33,9 @@
# OF THE POSSIBILITY OF SUCH DAMAGE.
"""
-Manage outlines on the sky.
-
This module provides support for working with footprints
on the sky. Primary use case would use the following
-generalized steps::
+generalized steps:
#. Initialize `SkyLine` objects for each input image.
This object would be the union of all the input
@@ -72,16 +70,6 @@ This process will work reasonably fast as most operations
are performed using the `SkyLine` objects and WCS information
solely, not image data itself.
-.. note:: Requires Python 2.7 or later.
-
-:Authors: Pey Lian Lim, Warren Hack, Michael Droettboom
-
-:Organization: Space Telescope Science Institute
-
-:History:
- * 2012-05-25 PLL started coding. Original class
- structure by WJH. Parent class by MD.
-
"""
from __future__ import division, print_function, absolute_import
@@ -96,26 +84,28 @@ from stwcs.distortion.utils import output_wcs
# LOCAL
from .polygon import SphericalPolygon
-__all__ = ['SkyLine']
+__all__ = ['SkyLineMember', 'SkyLine']
__version__ = '0.3a'
__vdate__ = '20-Jun-2012'
class SkyLineMember(object):
+ """
+ Container for `SkyLine` members with these attributes:
+
+ * `fname`: Image name (with path if given)
+ * `ext`: Extension read
+ * `wcs`: `HSTWCS` object the data data
+ * `polygon`: `~sphere.polygon.SphericalPolygon` object of the data
+ """
def __init__(self, fname, ext):
"""
- Container for `SkyLine` members.
-
- Given FITS image and extension, will store its
- `SphericalPolygon` instance from WCS under
- `polygon`.
-
Parameters
----------
- fname: str
+ fname : str
FITS image.
- ext: int
+ ext : int
Image extension.
"""
@@ -145,17 +135,22 @@ class SkyLineMember(object):
return self._polygon
class SkyLine(object):
+ """
+ Manage outlines on the sky.
+
+ Each `SkyLine` has a list of `~SkyLine.members` and
+ a composite `~SkyLine.polygon` with all the
+ functionalities of `~sphere.polygon.SphericalPolygon`.
+ """
def __init__(self, fname, extname='SCI'):
"""
- Initialize `SkyLine` object instance.
-
Parameters
----------
- fname: str
+ fname : str
FITS image. `None` to create empty `SkyLine`.
- extname: str
+ extname : str
EXTNAME to use. SCI is recommended for normal
HST images. PRIMARY if image is single ext.
@@ -179,7 +174,7 @@ class SkyLine(object):
self.polygon = SphericalPolygon([])
def __getattr__(self, what):
- """Control attribute access to `SphericalPolygon`."""
+ """Control attribute access to `~sphere.polygon.SphericalPolygon`."""
if what in ('from_radec', 'from_cone', 'from_wcs',
'multi_union', 'multi_intersection',
'_find_new_inside',):
@@ -197,23 +192,31 @@ class SkyLine(object):
@property
def polygon(self):
- """`SphericalPolygon` portion of `SkyLine`."""
+ """
+ `~sphere.polygon.SphericalPolygon` portion of `SkyLine`
+ that contains the composite skyline from `members`
+ belonging to *self*.
+
+ """
return self._polygon
@polygon.setter
def polygon(self, value):
- """Deep copy a `SphericalPolygon`."""
assert isinstance(value, SphericalPolygon)
- self._polygon = copy(value)
+ self._polygon = copy(value) # Deep copy
@property
def members(self):
- """List of `SkyLineMember` objects."""
+ """
+ List of `SkyLineMember` objects that belong to *self*.
+ Duplicate members are discarded. Members are kept in
+ the order of their additions to *self*.
+
+ """
return self._members
@members.setter
def members(self, values):
- """Make sure `SkyLineMember` entries are unique."""
self._members = []
# Not using set to preserve order
@@ -226,8 +229,8 @@ class SkyLine(object):
def to_wcs(self):
"""
- Combine WCS objects from all `members` and return a
- new WCS object. If no `members`, return `None`.
+ Combine `HSTWCS` objects from all `members` and return
+ a new `HSTWCS` object. If no `members`, return `None`.
"""
wcs = None
@@ -244,15 +247,15 @@ class SkyLine(object):
Parameters
----------
- self: obj
+ self : obj
`SkyLine` instance.
- given_members: list
+ given_members : list
List of `SkyLineMember` to consider.
Returns
-------
- new_members: list
+ new_members : list
List of `SkyLineMember` belonging to *self*.
"""
@@ -310,16 +313,16 @@ class SkyLine(object):
Parameters
----------
- skylines: list
+ skylines : list
A list of `SkyLine` instances.
Returns
-------
- max_skyline: `SkyLine` instance or `None`
+ max_skyline : `SkyLine` instance or `None`
`SkyLine` that overlaps the most or `None` if no
overlap found. This is *not* a copy.
- max_overlap_area: float
+ max_overlap_area : float
Area of intersection.
"""
@@ -342,13 +345,13 @@ class SkyLine(object):
Parameters
----------
- skylines: list
+ skylines : list
A list of `SkyLine` instances.
Returns
-------
- max_pair: tuple
- Pair of `SkyLine` instances with max overlap
+ max_pair : tuple
+ Pair of `SkyLine` objects with max overlap
among given *skylines*. If no overlap found,
return `None`. These are *not* copies.
@@ -371,24 +374,29 @@ class SkyLine(object):
"""
Mosaic all overlapping *skylines*.
+ A pair of skylines with the most overlap is used as
+ a starting point. Then a skyline that overlaps the
+ most with the mosaic is used, and so forth until no
+ overlapping skyline is found.
+
Parameters
----------
- skylines: list
- A list of `SkyLine` instances.
+ skylines : list
+ A list of `SkyLine` objects.
Returns
-------
- mosaic: `SkyLine` instance or `None`
+ mosaic : `SkyLine` instance or `None`
Union of all overlapping *skylines*, or `None` if
no overlap found.
- included: list
- List of image names added to `mosaic` in the order
+ included : list
+ List of image names added to mosaic in the order
of addition.
- excluded: list
+ excluded : list
List of image names excluded because they do not
- overlap with `mosaic`.
+ overlap with mosaic.
"""
out_order = []