Skip to content

Commit ccc8395

Browse files
committed
Add ConvertDetectorAngleErrToPositionAngleErr Functor
Test position angle error prop Fix Fix functor Test new functor Without debuging Logging for testing
1 parent 7fc987c commit ccc8395

File tree

2 files changed

+161
-2
lines changed

2 files changed

+161
-2
lines changed

python/lsst/pipe/tasks/functors.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,55 @@ def getPositionAngleFromDetectorAngle(self, theta, cd11, cd12, cd21, cd22):
13941394
# Position angle of vector from (RA1, Dec1) to (RA2, Dec2)
13951395
return np.rad2deg(self.computePositionAngle(ra1, dec1, ra2, dec2))
13961396

1397+
def getPositionAngleErrFromDetectorAngleErr(self, theta, theta_err, cd11, cd12, cd21, cd22):
1398+
"""Compute position angle error (E of N) from detector angle error.
1399+
1400+
Parameters
1401+
----------
1402+
theta : `float`
1403+
detector angle [radian]
1404+
theta_err : `float`
1405+
detector angle err [radian]
1406+
cd11 : `float`
1407+
[1, 1] element of the local Wcs affine transform.
1408+
cd12 : `float`
1409+
[1, 2] element of the local Wcs affine transform.
1410+
cd21 : `float`
1411+
[2, 1] element of the local Wcs affine transform.
1412+
cd22 : `float`
1413+
[2, 2] element of the local Wcs affine transform.
1414+
Returns
1415+
-------
1416+
Position Angle Error: `~pandas.Series`
1417+
Position angle error in degrees
1418+
"""
1419+
# Need to compute abs(dPA/dtheta)*theta_Err to get propogated errors
1420+
1421+
# Get unit direction
1422+
dx = np.cos(theta)
1423+
dy = np.sin(theta)
1424+
1425+
# Transform it using WCS?
1426+
u = dx * cd11 + dy * cd12
1427+
v = dx * cd21 + dy * cd22
1428+
# Now we are computing the tangent
1429+
ratio = u / v
1430+
1431+
# Get derivative of theta
1432+
du_dtheta = -np.sin(theta) * cd11 + np.cos(theta) * cd12
1433+
dv_dtheta = -np.sin(theta) * cd21 + np.cos(theta) * cd22
1434+
1435+
# Get derivative of tangent
1436+
d_ratio_dtheta = (v * du_dtheta - u * dv_dtheta) / v ** 2
1437+
dPA_dtheta = (1 / (1 + ratio ** 2)) * d_ratio_dtheta
1438+
1439+
pa_err = np.rad2deg(np.abs(dPA_dtheta) * theta_err)
1440+
1441+
logging.info("PA Error: %s" % pa_err)
1442+
logging.info("theta_err: %s" % theta_err)
1443+
1444+
return pa_err
1445+
13971446

13981447
class ComputePixelScale(LocalWcs):
13991448
"""Compute the local pixel scale from the stored CDMatrix.
@@ -1554,6 +1603,53 @@ def _func(self, df):
15541603
)
15551604

15561605

1606+
class ConvertDetectorAngleErrToPositionAngleErr(LocalWcs):
1607+
"""Compute a position angle error from a detector angle error and the
1608+
stored CDMatrix.
1609+
1610+
Returns
1611+
-------
1612+
position angle error : degrees
1613+
"""
1614+
1615+
name = "PositionAngleErr"
1616+
1617+
def __init__(
1618+
self,
1619+
theta_col,
1620+
theta_err_col,
1621+
colCD_1_1,
1622+
colCD_1_2,
1623+
colCD_2_1,
1624+
colCD_2_2,
1625+
**kwargs
1626+
):
1627+
self.theta_col = theta_col
1628+
self.theta_err_col = theta_err_col
1629+
super().__init__(colCD_1_1, colCD_1_2, colCD_2_1, colCD_2_2, **kwargs)
1630+
1631+
@property
1632+
def columns(self):
1633+
return [
1634+
self.theta_col,
1635+
self.theta_err_col,
1636+
self.colCD_1_1,
1637+
self.colCD_1_2,
1638+
self.colCD_2_1,
1639+
self.colCD_2_2
1640+
]
1641+
1642+
def _func(self, df):
1643+
return self.getPositionAngleErrFromDetectorAngleErr(
1644+
df[self.theta_col],
1645+
df[self.theta_err_col],
1646+
df[self.colCD_1_1],
1647+
df[self.colCD_1_2],
1648+
df[self.colCD_2_1],
1649+
df[self.colCD_2_2]
1650+
)
1651+
1652+
15571653
class ReferenceBand(Functor):
15581654
"""Return the band used to seed multiband forced photometry.
15591655

tests/test_functors.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,10 @@
4949
ConvertDetectorAngleToPositionAngle,
5050
HtmIndex20, Ebv, MomentsIuuSky, MomentsIvvSky, MomentsIuvSky,
5151
SemimajorAxisFromMoments, SemiminorAxisFromMoments,
52-
PositionAngleFromMoments,
5352
CorrelationIuuSky, CorrelationIvvSky, CorrelationIuvSky,
5453
SemimajorAxisFromCorrelation, SemiminorAxisFromCorrelation,
5554
PositionAngleFromCorrelation
56-
)
55+
PositionAngleFromMoments, ConvertDetectorAngleErrToPositionAngleErr)
5756

5857
ROOT = os.path.abspath(os.path.dirname(__file__))
5958

@@ -762,6 +761,70 @@ def testConvertDetectorAngleToPositionAngle(self):
762761

763762
np.testing.assert_allclose(coord_diff, 0, rtol=0, atol=5e-6)
764763

764+
# Test position angle error propogation
765+
def testConvertDetectorAngleErrToPositionAngleErr(self):
766+
"""Test conversion of position angle err in detector degrees to
767+
position angle erron sky
768+
"""
769+
dipoleSep = 10
770+
ixx = 10
771+
testPixelDeltas = np.random.uniform(-100, 100, size=(self.nRecords, 2))
772+
# testConvertPixelToArcSecond uses a meas_base LocalWcsPlugin
773+
# but we're using a simple WCS as our example, so this doesn't really matter
774+
# and we'll just use the WCS directly
775+
for dec in np.linspace(-90, 90, 10):
776+
for theta in (-45, 0, 90):
777+
for x, y in zip(np.random.uniform(2 * 1109.99981456774, size=10),
778+
np.random.uniform(2 * 560.018167811613, size=10)):
779+
wcs = self._makeWcs(dec=dec, theta=theta)
780+
cd_matrix = wcs.getCdMatrix()
781+
782+
self.dataDict["dipoleSep"] = np.full(self.nRecords, dipoleSep)
783+
self.dataDict["ixx"] = np.full(self.nRecords, ixx)
784+
self.dataDict["slot_Centroid_x"] = np.full(self.nRecords, x)
785+
self.dataDict["slot_Centroid_y"] = np.full(self.nRecords, y)
786+
self.dataDict["someCentroid_x"] = x + testPixelDeltas[:, 0]
787+
self.dataDict["someCentroid_y"] = y + testPixelDeltas[:, 1]
788+
self.dataDict["orientation"] = np.arctan2(
789+
self.dataDict["someCentroid_y"] - self.dataDict["slot_Centroid_y"],
790+
self.dataDict["someCentroid_x"] - self.dataDict["slot_Centroid_x"],
791+
)
792+
self.dataDict["orientation_err"] = np.arctan2(
793+
self.dataDict["someCentroid_y"] - self.dataDict[ "slot_Centroid_y"],
794+
self.dataDict["someCentroid_x"] - self.dataDict["slot_Centroid_x"],
795+
)*.001
796+
797+
self.dataDict["base_LocalWcs_CDMatrix_1_1"] = np.full(self.nRecords,
798+
cd_matrix[0, 0])
799+
self.dataDict["base_LocalWcs_CDMatrix_1_2"] = np.full(self.nRecords,
800+
cd_matrix[0, 1])
801+
self.dataDict["base_LocalWcs_CDMatrix_2_1"] = np.full(self.nRecords,
802+
cd_matrix[1, 0])
803+
self.dataDict["base_LocalWcs_CDMatrix_2_2"] = np.full(self.nRecords,
804+
cd_matrix[1, 1])
805+
df = self.getMultiIndexDataFrame(self.dataDict)
806+
807+
# Test detector angle to position angle conversion
808+
func = ConvertDetectorAngleErrToPositionAngleErr(
809+
"orientation",
810+
"orientation_err",
811+
"base_LocalWcs_CDMatrix_1_1",
812+
"base_LocalWcs_CDMatrix_1_2",
813+
"base_LocalWcs_CDMatrix_2_1",
814+
"base_LocalWcs_CDMatrix_2_2"
815+
)
816+
817+
func_pa = ConvertDetectorAngleToPositionAngle(
818+
"orientation",
819+
"base_LocalWcs_CDMatrix_1_1",
820+
"base_LocalWcs_CDMatrix_1_2",
821+
"base_LocalWcs_CDMatrix_2_1",
822+
"base_LocalWcs_CDMatrix_2_2"
823+
)
824+
val = self._funcVal(func, df)
825+
val_pa = self._funcVal(func_pa, df)
826+
827+
765828
def testConvertPixelToArcseconds(self):
766829
"""Test calculations of the pixel scale, conversions of pixel to
767830
arcseconds.

0 commit comments

Comments
 (0)