@@ -573,16 +573,69 @@ def __init__(self, function_domain, function_range, affine):
573
573
#
574
574
###################################################################
575
575
576
- def inverse (self ):
576
+ def inverse (self , preserve_dtype = False ):
577
577
""" Return coordinate map with inverse affine transform or None
578
578
579
- Try to invert our ``affine``, and see if it can be cast to the needed
580
- data type, which is ``self.function_domain.coord_dtype``. We need this
581
- dtype in order for the inverse to preserve the coordinate system dtypes.
579
+ Parameters
580
+ ----------
581
+ preserve_dtype : bool
582
+ If False, return affine mapping from inverting the ``affine``. The
583
+ domain / range dtypes for the inverse may then change as a function
584
+ of the dtype of the inverted ``affine``. If True, try to invert our
585
+ ``affine``, and see if it can be cast to the needed data type, which
586
+ is ``self.function_domain.coord_dtype``. We need this dtype in
587
+ order for the inverse to preserve the coordinate system dtypes.
588
+
589
+ Returns
590
+ -------
591
+ aff_cm_inv : ``AffineTransform`` instance or None
592
+ ``AffineTransform`` mapping from the *range* of input `self` to the
593
+ *domain* of input `self` - the inverse of `self`. If
594
+ ``self.affine`` was not invertible return None. If `preserve_dtype`
595
+ is True, and the inverse of ``self.affine`` cannot be cast to
596
+ ``self.function_domain.coord_dtype``, then return None. Otherwise
597
+ return ``AffineTransform`` inverse mapping. If `preserve_dtype` is
598
+ False, the domain / range dtypes of the return inverse may well be
599
+ different from those of the input `self`.
600
+
601
+ Examples
602
+ --------
603
+ >>> input_cs = CoordinateSystem('ijk', coord_dtype=np.int)
604
+ >>> output_cs = CoordinateSystem('xyz', coord_dtype=np.int)
605
+ >>> affine = np.array([[1,0,0,1],
606
+ ... [0,1,0,1],
607
+ ... [0,0,1,1],
608
+ ... [0,0,0,1]])
609
+ >>> affine_transform = AffineTransform(input_cs, output_cs, affine)
610
+ >>> affine_transform([2,3,4]) #doctest: +IGNORE_DTYPE
611
+ array([3, 4, 5])
612
+
613
+ The inverse transform, by default, generates a floating point inverse
614
+ matrix and therefore floating point output:
615
+
616
+ >>> affine_transform_inv = affine_transform.inverse()
617
+ >>> affine_transform_inv([2, 6, 12])
618
+ array([ 1., 5., 11.])
619
+
620
+ You can force it to preserve the coordinate system dtype with the
621
+ `preserve_dtype` flag:
622
+
623
+ >>> at_inv_preserved = affine_transform.inverse(preserve_dtype=True)
624
+ >>> at_inv_preserved([2, 6, 12]) #doctest: +IGNORE_DTYPE
625
+ array([ 1, 5, 11])
626
+
627
+ If you `preserve_dtype`, and there is no inverse affine preserving the
628
+ dtype, the inverse is None:
629
+
630
+ >>> affine2 = affine.copy()
631
+ >>> affine2[0, 0] = 2 # now inverse can't be integer
632
+ >>> aff_t = AffineTransform(input_cs, output_cs, affine2)
633
+ >>> aff_t.inverse(preserve_dtype=True) is None
634
+ True
582
635
"""
583
636
aff_dt = self .function_range .coord_dtype
584
637
try :
585
- m_inv_in = npl .inv (self .affine )
638
+ m_inv = npl .inv (self .affine )
586
639
except npl .LinAlgError :
587
640
return None
588
641
except TypeError :
@@ -591,10 +644,12 @@ def inverse(self):
591
644
from sympy import Matrix , matrix2numpy
592
645
sym_inv = Matrix (self .affine ).inv ()
593
646
m_inv = matrix2numpy (sym_inv ).astype (aff_dt )
594
- else : # linalg inverse succeeded - can we cast back?
595
- m_inv = m_inv_in .astype (aff_dt )
596
- if (aff_dt != np .object ) and not np .allclose (m_inv_in , m_inv ):
597
- return None
647
+ else : # linalg inverse succeeded
648
+ if preserve_dtype and aff_dt != np .object : # can we cast back?
649
+ m_inv_orig = m_inv
650
+ m_inv = m_inv .astype (aff_dt )
651
+ if not np .allclose (m_inv_orig , m_inv ):
652
+ return None
598
653
return AffineTransform (self .function_range ,
599
654
self .function_domain ,
600
655
m_inv )
@@ -900,9 +955,6 @@ def __call__(self, x):
900
955
>>> affine_transform = AffineTransform(input_cs, output_cs, affine)
901
956
>>> affine_transform([2,3,4]) #doctest: +IGNORE_DTYPE
902
957
array([3, 4, 5])
903
- >>> affine_transform_inv = affine_transform.inverse()
904
- >>> affine_transform_inv([2, 6, 12])
905
- array([ 1, 5, 11])
906
958
"""
907
959
x = np .asanyarray (x )
908
960
out_shape = (self .function_range .ndim ,)
@@ -1567,7 +1619,9 @@ def _function(x):
1567
1619
value += b
1568
1620
return value
1569
1621
1570
- affine_transform_inv = affine_transform .inverse ()
1622
+ # Preserve dtype check because the CoordinateMap expects to generate the
1623
+ # expected dtype and checks this on object creation
1624
+ affine_transform_inv = affine_transform .inverse (preserve_dtype = True )
1571
1625
if affine_transform_inv :
1572
1626
Ainv , binv = to_matvec (affine_transform_inv .affine )
1573
1627
def _inverse_function (x ):
0 commit comments