From cf11ad7832b31c4bf80ae29700c3854f4f7c14f6 Mon Sep 17 00:00:00 2001
From: Christine Kim <125395064+chkim-usgs@users.noreply.github.com>
Date: Mon, 20 Nov 2023 10:23:45 -0700
Subject: [PATCH] Replace affine6p transformation with numpy solution (#579)

* Replace affine6p transformation with numpy solution

* Remove affine6p dependency

* Update changelog and remove pip from conda-recipe
---
 CHANGELOG.md                     |  4 ++++
 ale/drivers/lo_drivers.py        | 19 +++++++++++++------
 environment.yml                  |  3 ---
 recipe/meta.yaml                 |  1 -
 tests/pytests/test_lo_drivers.py | 19 +++++++++++++++++--
 5 files changed, 34 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31e559e..75abfd9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -45,6 +45,10 @@ release.
 ### Fixed
 - Fixed LRO MiniRF drivers naif keywords focal to pixel and pixel to focal translations to be correct. [#569](https://github.com/DOI-USGS/ale/pull/569)
 
+### Changed
+- Removed the affine6p library and replaced affine6p's affine transformation with a numpy solution [#579](https://github.com/DOI-USGS/ale/pull/579) 
+
+
 ## [0.9.1] - 2023-06-05
 
 ### Changed
diff --git a/ale/drivers/lo_drivers.py b/ale/drivers/lo_drivers.py
index 42a7d2d..7150982 100644
--- a/ale/drivers/lo_drivers.py
+++ b/ale/drivers/lo_drivers.py
@@ -1,6 +1,5 @@
 import spiceypy as spice
 import numpy as np
-import affine6p
 from ale.base.data_naif import NaifSpice
 from ale.base.label_isis import IsisLabel
 from ale.base.type_sensor import Framer
@@ -156,17 +155,25 @@ class LoHighCameraIsisLabelNaifSpiceDriver(Framer, IsisLabel, NaifSpice, LoDisto
           p_fidYCoords = self.label['IsisCube']['Instrument']['FiducialYCoordinates'].value
 
           # Create Affine Transformation
-          
           p_src = [p_fidSamples, p_fidLines]
           p_dst = [p_fidXCoords, p_fidYCoords]
 
-          # format the fiducial coordinatens as [ [x, y], [x, y]...]
+          # Format the fiducial coordinates as [ [x, y], [x, y]...]
           p_src = np.rot90(np.array([p_fidSamples, p_fidLines]))
           p_dst = np.rot90(np.array([p_fidXCoords, p_fidYCoords]))
 
-          # find a best match for the transformation based on source and destination coordinates
-          tr_mat = affine6p.estimate(p_src, p_dst).get_matrix()
+          # Pad data with ones so that the transformation allows translations 
+          pad = lambda x: np.hstack([x, np.ones((x.shape[0], 1))])
+          X = pad(p_src)
+          Y = pad(p_dst)
 
+          # Solve the least squares problem X * A = Y to find our transformation matrix A
+          A, res, rank, s = np.linalg.lstsq(X, Y)
+
+          # Transpose matrix and convert to list
+          tr_mat = np.transpose(A).tolist()
+
+          # Compute inverse of transformation matrix
           tr_mat_inv = np.linalg.inv(tr_mat)
 
           # X and Y, Inverse S and L components of transformation
@@ -175,7 +182,7 @@ class LoHighCameraIsisLabelNaifSpiceDriver(Framer, IsisLabel, NaifSpice, LoDisto
           itranss = tr_mat_inv[0]
           itransl = tr_mat_inv[1]
 
-          # move the last item to the front to get the ordering standard in ISIS
+          # Move the last item to the front to get the ordering standard in ISIS
           transx.insert(0, transx.pop())
           transy.insert(0, transy.pop())
           itranss = np.roll(itranss, 1).tolist()
diff --git a/environment.yml b/environment.yml
index b450dc6..062d3e9 100644
--- a/environment.yml
+++ b/environment.yml
@@ -22,6 +22,3 @@ dependencies:
   - pytest-cov
   - networkx
   - breathe
-  - pip
-  - pip:
-    - affine6p
diff --git a/recipe/meta.yaml b/recipe/meta.yaml
index 3ce001b..80bc112 100644
--- a/recipe/meta.yaml
+++ b/recipe/meta.yaml
@@ -18,7 +18,6 @@ requirements:
     - cmake>=3.15
     - eigen
   host:
-    - pip
     - python
     - nlohmann_json
   run:
diff --git a/tests/pytests/test_lo_drivers.py b/tests/pytests/test_lo_drivers.py
index f1c2bef..50010ad 100644
--- a/tests/pytests/test_lo_drivers.py
+++ b/tests/pytests/test_lo_drivers.py
@@ -98,7 +98,6 @@ class test_high_isis3_naif(unittest.TestCase):
             namfrm.assert_called_with("LO3_HIGH_RESOLUTION_CAMERA")
 
     def test_naif_keywords(self):
-
         with patch('ale.drivers.lo_drivers.LoHighCameraIsisLabelNaifSpiceDriver.ikid', new_callable=PropertyMock) as ikid, \
             patch('ale.base.data_naif.spice.bodvrd', return_value=[1737.4, 1737.4, 1737.4]) as bodvrd:
 
@@ -114,6 +113,22 @@ class test_high_isis3_naif(unittest.TestCase):
                 "INS-533001_ITRANSL" : [4541.692430539061, -0.05845617762411283, 143.95514969883214]
             }
             
-            assert self.driver.naif_keywords == naif_keywords
+            assert self.driver.naif_keywords["BODY_CODE"] == naif_keywords["BODY_CODE"]
+            assert self.driver.naif_keywords["BODY301_RADII"] == naif_keywords["BODY301_RADII"]
+            assert self.driver.naif_keywords["BODY_FRAME_CODE"] == naif_keywords["BODY_FRAME_CODE"]
+
+            np.testing.assert_almost_equal(np.asarray(self.driver.naif_keywords["INS-533001_TRANSX"]), 
+                                           np.asarray(naif_keywords["INS-533001_TRANSX"]), 
+                                           decimal=10)
+            np.testing.assert_almost_equal(np.asarray(self.driver.naif_keywords["INS-533001_TRANSY"]), 
+                                           np.asarray(naif_keywords["INS-533001_TRANSY"]), 
+                                           decimal=10)
+            np.testing.assert_almost_equal(np.asarray(self.driver.naif_keywords["INS-533001_ITRANSS"]), 
+                                           np.asarray(naif_keywords["INS-533001_ITRANSS"]), 
+                                           decimal=10)
+            np.testing.assert_almost_equal(np.asarray(self.driver.naif_keywords["INS-533001_ITRANSL"]), 
+                                           np.asarray(naif_keywords["INS-533001_ITRANSL"]), 
+                                           decimal=10)
+
             bodvrd.assert_called_with('Moon', 'RADII', 3)
   
-- 
GitLab