diff --git a/gen/python/sym/__init__.py b/gen/python/sym/__init__.py index de8c975ef..421ee0f3d 100644 --- a/gen/python/sym/__init__.py +++ b/gen/python/sym/__init__.py @@ -1,21 +1,14 @@ # ----------------------------------------------------------------------------- # This file was autogenerated by symforce from template: -# geo_package/__init__.py.jinja +# function/namespace_init.py.jinja # Do NOT modify by hand. # ----------------------------------------------------------------------------- """ Python runtime geometry package. """ -from .atan_camera_cal import ATANCameraCal -from .double_sphere_camera_cal import DoubleSphereCameraCal -from .equirectangular_camera_cal import EquirectangularCameraCal -from .linear_camera_cal import LinearCameraCal -from .polynomial_camera_cal import PolynomialCameraCal -from .pose2 import Pose2 -from .pose3 import Pose3 -from .rot2 import Rot2 -from .rot3 import Rot3 -from .spherical_camera_cal import SphericalCameraCal -epsilon = 2.220446049250313e-15 +# Make package a namespace package by adding other portions to the __path__ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore[has-type] +# https://github.com/python/mypy/issues/1422 +from ._init import * diff --git a/gen/python/sym/_init.py b/gen/python/sym/_init.py new file mode 100644 index 000000000..de8c975ef --- /dev/null +++ b/gen/python/sym/_init.py @@ -0,0 +1,21 @@ +# ----------------------------------------------------------------------------- +# This file was autogenerated by symforce from template: +# geo_package/__init__.py.jinja +# Do NOT modify by hand. +# ----------------------------------------------------------------------------- + +""" +Python runtime geometry package. +""" +from .atan_camera_cal import ATANCameraCal +from .double_sphere_camera_cal import DoubleSphereCameraCal +from .equirectangular_camera_cal import EquirectangularCameraCal +from .linear_camera_cal import LinearCameraCal +from .polynomial_camera_cal import PolynomialCameraCal +from .pose2 import Pose2 +from .pose3 import Pose3 +from .rot2 import Rot2 +from .rot3 import Rot3 +from .spherical_camera_cal import SphericalCameraCal + +epsilon = 2.220446049250313e-15 diff --git a/gen/python/sym/atan_camera_cal.py b/gen/python/sym/atan_camera_cal.py index 327ef4150..f3f000d36 100644 --- a/gen/python/sym/atan_camera_cal.py +++ b/gen/python/sym/atan_camera_cal.py @@ -9,6 +9,7 @@ import numpy from .ops import atan_camera_cal as ops +from .util import check_size_and_reshape class ATANCameraCal(object): @@ -93,7 +94,7 @@ def principal_point(self): return ops.CameraOps.principal_point(self) def pixel_from_camera_point(self, point, epsilon): - # type: (ATANCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -105,7 +106,7 @@ def pixel_from_camera_point(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point(self, point, epsilon) def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (ATANCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -119,7 +120,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point_with_jacobians(self, point, epsilon) def camera_ray_from_pixel(self, pixel, epsilon): - # type: (ATANCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -133,7 +134,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): return ops.CameraOps.camera_ray_from_pixel(self, pixel, epsilon) def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): - # type: (ATANCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -187,13 +188,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> ATANCameraCal - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> ATANCameraCal return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -201,13 +196,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> ATANCameraCal - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> ATANCameraCal return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): diff --git a/gen/python/sym/double_sphere_camera_cal.py b/gen/python/sym/double_sphere_camera_cal.py index b762cd429..71d6b7609 100644 --- a/gen/python/sym/double_sphere_camera_cal.py +++ b/gen/python/sym/double_sphere_camera_cal.py @@ -9,6 +9,7 @@ import numpy from .ops import double_sphere_camera_cal as ops +from .util import check_size_and_reshape class DoubleSphereCameraCal(object): @@ -106,7 +107,7 @@ def principal_point(self): return ops.CameraOps.principal_point(self) def pixel_from_camera_point(self, point, epsilon): - # type: (DoubleSphereCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -118,7 +119,7 @@ def pixel_from_camera_point(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point(self, point, epsilon) def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (DoubleSphereCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -132,7 +133,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point_with_jacobians(self, point, epsilon) def camera_ray_from_pixel(self, pixel, epsilon): - # type: (DoubleSphereCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -146,7 +147,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): return ops.CameraOps.camera_ray_from_pixel(self, pixel, epsilon) def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): - # type: (DoubleSphereCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -200,13 +201,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> DoubleSphereCameraCal - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> DoubleSphereCameraCal return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -214,13 +209,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> DoubleSphereCameraCal - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> DoubleSphereCameraCal return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): diff --git a/gen/python/sym/equirectangular_camera_cal.py b/gen/python/sym/equirectangular_camera_cal.py index 70cbe6502..267d8f1d0 100644 --- a/gen/python/sym/equirectangular_camera_cal.py +++ b/gen/python/sym/equirectangular_camera_cal.py @@ -9,6 +9,7 @@ import numpy from .ops import equirectangular_camera_cal as ops +from .util import check_size_and_reshape class EquirectangularCameraCal(object): @@ -88,7 +89,7 @@ def principal_point(self): return ops.CameraOps.principal_point(self) def pixel_from_camera_point(self, point, epsilon): - # type: (EquirectangularCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -100,7 +101,7 @@ def pixel_from_camera_point(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point(self, point, epsilon) def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (EquirectangularCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -114,7 +115,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point_with_jacobians(self, point, epsilon) def camera_ray_from_pixel(self, pixel, epsilon): - # type: (EquirectangularCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -128,7 +129,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): return ops.CameraOps.camera_ray_from_pixel(self, pixel, epsilon) def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): - # type: (EquirectangularCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -182,13 +183,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> EquirectangularCameraCal - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> EquirectangularCameraCal return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -196,13 +191,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> EquirectangularCameraCal - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> EquirectangularCameraCal return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): diff --git a/gen/python/sym/linear_camera_cal.py b/gen/python/sym/linear_camera_cal.py index 0c686ea70..4307b30bc 100644 --- a/gen/python/sym/linear_camera_cal.py +++ b/gen/python/sym/linear_camera_cal.py @@ -9,6 +9,7 @@ import numpy from .ops import linear_camera_cal as ops +from .util import check_size_and_reshape class LinearCameraCal(object): @@ -88,7 +89,7 @@ def principal_point(self): return ops.CameraOps.principal_point(self) def pixel_from_camera_point(self, point, epsilon): - # type: (LinearCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -100,7 +101,7 @@ def pixel_from_camera_point(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point(self, point, epsilon) def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (LinearCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -114,7 +115,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point_with_jacobians(self, point, epsilon) def camera_ray_from_pixel(self, pixel, epsilon): - # type: (LinearCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -128,7 +129,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): return ops.CameraOps.camera_ray_from_pixel(self, pixel, epsilon) def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): - # type: (LinearCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -182,13 +183,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> LinearCameraCal - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> LinearCameraCal return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -196,13 +191,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> LinearCameraCal - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> LinearCameraCal return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): diff --git a/gen/python/sym/ops/atan_camera_cal/camera_ops.py b/gen/python/sym/ops/atan_camera_cal/camera_ops.py index 8d5a6bac4..7543d6f5e 100644 --- a/gen/python/sym/ops/atan_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/atan_camera_cal/camera_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class CameraOps(object): @@ -59,7 +60,7 @@ def principal_point(self): @staticmethod def pixel_from_camera_point(self, point, epsilon): - # type: (sym.ATANCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -72,14 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (4) _tmp0 = max(epsilon, point[2, 0]) @@ -96,7 +90,7 @@ def pixel_from_camera_point(self, point, epsilon): @staticmethod def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (sym.ATANCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -111,14 +105,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (46) _tmp0 = 0.5 * _self[4] @@ -197,7 +184,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): @staticmethod def camera_ray_from_pixel(self, pixel, epsilon): - # type: (sym.ATANCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -212,14 +199,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): - pixel = pixel.reshape((2, 1)) - elif pixel.shape != (2, 1): - raise IndexError( - "pixel is expected to have shape (2, 1) or (2,); instead had shape {}".format( - pixel.shape - ) - ) + pixel = check_size_and_reshape(pixel, "pixel", (2, 1)) # Intermediate terms (5) _tmp0 = -_self[2] + pixel[0, 0] @@ -245,7 +225,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): @staticmethod def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): - # type: (sym.ATANCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -260,14 +240,7 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): - pixel = pixel.reshape((2, 1)) - elif pixel.shape != (2, 1): - raise IndexError( - "pixel is expected to have shape (2, 1) or (2,); instead had shape {}".format( - pixel.shape - ) - ) + pixel = check_size_and_reshape(pixel, "pixel", (2, 1)) # Intermediate terms (54) _tmp0 = -_self[2] + pixel[0, 0] diff --git a/gen/python/sym/ops/atan_camera_cal/group_ops.py b/gen/python/sym/ops/atan_camera_cal/group_ops.py index 2f5deb73f..b40975149 100644 --- a/gen/python/sym/ops/atan_camera_cal/group_ops.py +++ b/gen/python/sym/ops/atan_camera_cal/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/atan_camera_cal/lie_group_ops.py b/gen/python/sym/ops/atan_camera_cal/lie_group_ops.py index 2f9f2ae96..9260e42a9 100644 --- a/gen/python/sym/ops/atan_camera_cal/lie_group_ops.py +++ b/gen/python/sym/ops/atan_camera_cal/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.ATANCameraCal + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.ATANCameraCal # Total ops: 0 # Input arrays - if vec.shape == (5,): - vec = vec.reshape((5, 1)) - elif vec.shape != (5, 1): - raise IndexError( - "vec is expected to have shape (5, 1) or (5,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (5, 1)) # Intermediate terms (0) @@ -66,20 +60,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.ATANCameraCal, numpy.ndarray, float) -> sym.ATANCameraCal + # type: (sym.ATANCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.ATANCameraCal # Total ops: 5 # Input arrays _a = a.data - if vec.shape == (5,): - vec = vec.reshape((5, 1)) - elif vec.shape != (5, 1): - raise IndexError( - "vec is expected to have shape (5, 1) or (5,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (5, 1)) # Intermediate terms (0) diff --git a/gen/python/sym/ops/double_sphere_camera_cal/camera_ops.py b/gen/python/sym/ops/double_sphere_camera_cal/camera_ops.py index 1cffcab5c..d0428f8a7 100644 --- a/gen/python/sym/ops/double_sphere_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/double_sphere_camera_cal/camera_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class CameraOps(object): @@ -59,7 +60,7 @@ def principal_point(self): @staticmethod def pixel_from_camera_point(self, point, epsilon): - # type: (sym.DoubleSphereCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -72,14 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (13) _tmp0 = epsilon ** 2 + point[0, 0] ** 2 + point[1, 0] ** 2 @@ -149,7 +143,7 @@ def pixel_from_camera_point(self, point, epsilon): @staticmethod def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (sym.DoubleSphereCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -164,14 +158,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (40) _tmp0 = epsilon ** 2 + point[0, 0] ** 2 + point[1, 0] ** 2 @@ -284,7 +271,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): @staticmethod def camera_ray_from_pixel(self, pixel, epsilon): - # type: (sym.DoubleSphereCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -299,14 +286,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): - pixel = pixel.reshape((2, 1)) - elif pixel.shape != (2, 1): - raise IndexError( - "pixel is expected to have shape (2, 1) or (2,); instead had shape {}".format( - pixel.shape - ) - ) + pixel = check_size_and_reshape(pixel, "pixel", (2, 1)) # Intermediate terms (12) _tmp0 = -_self[2] + pixel[0, 0] @@ -337,7 +317,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): @staticmethod def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): - # type: (sym.DoubleSphereCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -352,14 +332,7 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): - pixel = pixel.reshape((2, 1)) - elif pixel.shape != (2, 1): - raise IndexError( - "pixel is expected to have shape (2, 1) or (2,); instead had shape {}".format( - pixel.shape - ) - ) + pixel = check_size_and_reshape(pixel, "pixel", (2, 1)) # Intermediate terms (111) _tmp0 = -_self[2] + pixel[0, 0] diff --git a/gen/python/sym/ops/double_sphere_camera_cal/group_ops.py b/gen/python/sym/ops/double_sphere_camera_cal/group_ops.py index d7fbab30f..4a3189cbf 100644 --- a/gen/python/sym/ops/double_sphere_camera_cal/group_ops.py +++ b/gen/python/sym/ops/double_sphere_camera_cal/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/double_sphere_camera_cal/lie_group_ops.py b/gen/python/sym/ops/double_sphere_camera_cal/lie_group_ops.py index 6e61fd260..50f2c2bde 100644 --- a/gen/python/sym/ops/double_sphere_camera_cal/lie_group_ops.py +++ b/gen/python/sym/ops/double_sphere_camera_cal/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.DoubleSphereCameraCal + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.DoubleSphereCameraCal # Total ops: 0 # Input arrays - if vec.shape == (6,): - vec = vec.reshape((6, 1)) - elif vec.shape != (6, 1): - raise IndexError( - "vec is expected to have shape (6, 1) or (6,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (6, 1)) # Intermediate terms (0) @@ -68,20 +62,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.DoubleSphereCameraCal, numpy.ndarray, float) -> sym.DoubleSphereCameraCal + # type: (sym.DoubleSphereCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.DoubleSphereCameraCal # Total ops: 6 # Input arrays _a = a.data - if vec.shape == (6,): - vec = vec.reshape((6, 1)) - elif vec.shape != (6, 1): - raise IndexError( - "vec is expected to have shape (6, 1) or (6,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (6, 1)) # Intermediate terms (0) diff --git a/gen/python/sym/ops/equirectangular_camera_cal/camera_ops.py b/gen/python/sym/ops/equirectangular_camera_cal/camera_ops.py index 87ec9b98d..84de019f7 100644 --- a/gen/python/sym/ops/equirectangular_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/equirectangular_camera_cal/camera_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class CameraOps(object): @@ -59,7 +60,7 @@ def principal_point(self): @staticmethod def pixel_from_camera_point(self, point, epsilon): - # type: (sym.EquirectangularCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -72,14 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (1) _tmp0 = point[0, 0] ** 2 + point[2, 0] ** 2 @@ -104,7 +98,7 @@ def pixel_from_camera_point(self, point, epsilon): @staticmethod def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (sym.EquirectangularCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -119,14 +113,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (10) _tmp0 = ( @@ -168,7 +155,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): @staticmethod def camera_ray_from_pixel(self, pixel, epsilon): - # type: (sym.EquirectangularCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -183,14 +170,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): - pixel = pixel.reshape((2, 1)) - elif pixel.shape != (2, 1): - raise IndexError( - "pixel is expected to have shape (2, 1) or (2,); instead had shape {}".format( - pixel.shape - ) - ) + pixel = check_size_and_reshape(pixel, "pixel", (2, 1)) # Intermediate terms (3) _tmp0 = (-_self[3] + pixel[1, 0]) / _self[1] @@ -217,7 +197,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): @staticmethod def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): - # type: (sym.EquirectangularCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -232,14 +212,7 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): - pixel = pixel.reshape((2, 1)) - elif pixel.shape != (2, 1): - raise IndexError( - "pixel is expected to have shape (2, 1) or (2,); instead had shape {}".format( - pixel.shape - ) - ) + pixel = check_size_and_reshape(pixel, "pixel", (2, 1)) # Intermediate terms (21) _tmp0 = -_self[3] + pixel[1, 0] diff --git a/gen/python/sym/ops/equirectangular_camera_cal/group_ops.py b/gen/python/sym/ops/equirectangular_camera_cal/group_ops.py index a00db1ecc..40351acd4 100644 --- a/gen/python/sym/ops/equirectangular_camera_cal/group_ops.py +++ b/gen/python/sym/ops/equirectangular_camera_cal/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/equirectangular_camera_cal/lie_group_ops.py b/gen/python/sym/ops/equirectangular_camera_cal/lie_group_ops.py index 4bf096e55..d9752a870 100644 --- a/gen/python/sym/ops/equirectangular_camera_cal/lie_group_ops.py +++ b/gen/python/sym/ops/equirectangular_camera_cal/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.EquirectangularCameraCal + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.EquirectangularCameraCal # Total ops: 0 # Input arrays - if vec.shape == (4,): - vec = vec.reshape((4, 1)) - elif vec.shape != (4, 1): - raise IndexError( - "vec is expected to have shape (4, 1) or (4,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (4, 1)) # Intermediate terms (0) @@ -64,20 +58,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.EquirectangularCameraCal, numpy.ndarray, float) -> sym.EquirectangularCameraCal + # type: (sym.EquirectangularCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.EquirectangularCameraCal # Total ops: 4 # Input arrays _a = a.data - if vec.shape == (4,): - vec = vec.reshape((4, 1)) - elif vec.shape != (4, 1): - raise IndexError( - "vec is expected to have shape (4, 1) or (4,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (4, 1)) # Intermediate terms (0) diff --git a/gen/python/sym/ops/linear_camera_cal/camera_ops.py b/gen/python/sym/ops/linear_camera_cal/camera_ops.py index 948d979e4..64b56a3c8 100644 --- a/gen/python/sym/ops/linear_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/linear_camera_cal/camera_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class CameraOps(object): @@ -59,7 +60,7 @@ def principal_point(self): @staticmethod def pixel_from_camera_point(self, point, epsilon): - # type: (sym.LinearCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -72,14 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (1) _tmp0 = 1 / max(epsilon, point[2, 0]) @@ -93,7 +87,7 @@ def pixel_from_camera_point(self, point, epsilon): @staticmethod def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (sym.LinearCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -108,14 +102,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (5) _tmp0 = max(epsilon, point[2, 0]) @@ -156,7 +143,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): @staticmethod def camera_ray_from_pixel(self, pixel, epsilon): - # type: (sym.LinearCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -171,14 +158,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): - pixel = pixel.reshape((2, 1)) - elif pixel.shape != (2, 1): - raise IndexError( - "pixel is expected to have shape (2, 1) or (2,); instead had shape {}".format( - pixel.shape - ) - ) + pixel = check_size_and_reshape(pixel, "pixel", (2, 1)) # Intermediate terms (0) @@ -192,7 +172,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): @staticmethod def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): - # type: (sym.LinearCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Backproject a 2D pixel coordinate into a 3D ray in the camera frame. @@ -207,14 +187,7 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): - pixel = pixel.reshape((2, 1)) - elif pixel.shape != (2, 1): - raise IndexError( - "pixel is expected to have shape (2, 1) or (2,); instead had shape {}".format( - pixel.shape - ) - ) + pixel = check_size_and_reshape(pixel, "pixel", (2, 1)) # Intermediate terms (4) _tmp0 = -_self[2] + pixel[0, 0] diff --git a/gen/python/sym/ops/linear_camera_cal/group_ops.py b/gen/python/sym/ops/linear_camera_cal/group_ops.py index dd7dde66c..ceeb6c1cc 100644 --- a/gen/python/sym/ops/linear_camera_cal/group_ops.py +++ b/gen/python/sym/ops/linear_camera_cal/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/linear_camera_cal/lie_group_ops.py b/gen/python/sym/ops/linear_camera_cal/lie_group_ops.py index 52dd5dd5b..e8072b697 100644 --- a/gen/python/sym/ops/linear_camera_cal/lie_group_ops.py +++ b/gen/python/sym/ops/linear_camera_cal/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.LinearCameraCal + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.LinearCameraCal # Total ops: 0 # Input arrays - if vec.shape == (4,): - vec = vec.reshape((4, 1)) - elif vec.shape != (4, 1): - raise IndexError( - "vec is expected to have shape (4, 1) or (4,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (4, 1)) # Intermediate terms (0) @@ -64,20 +58,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.LinearCameraCal, numpy.ndarray, float) -> sym.LinearCameraCal + # type: (sym.LinearCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.LinearCameraCal # Total ops: 4 # Input arrays _a = a.data - if vec.shape == (4,): - vec = vec.reshape((4, 1)) - elif vec.shape != (4, 1): - raise IndexError( - "vec is expected to have shape (4, 1) or (4,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (4, 1)) # Intermediate terms (0) diff --git a/gen/python/sym/ops/polynomial_camera_cal/camera_ops.py b/gen/python/sym/ops/polynomial_camera_cal/camera_ops.py index ca39d96d8..0b81877ad 100644 --- a/gen/python/sym/ops/polynomial_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/polynomial_camera_cal/camera_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class CameraOps(object): @@ -59,7 +60,7 @@ def principal_point(self): @staticmethod def pixel_from_camera_point(self, point, epsilon): - # type: (sym.PolynomialCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.PolynomialCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -72,14 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (4) _tmp0 = max(epsilon, point[2, 0]) @@ -108,7 +102,7 @@ def pixel_from_camera_point(self, point, epsilon): @staticmethod def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (sym.PolynomialCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.PolynomialCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -123,14 +117,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (35) _tmp0 = point[1, 0] ** 2 diff --git a/gen/python/sym/ops/polynomial_camera_cal/group_ops.py b/gen/python/sym/ops/polynomial_camera_cal/group_ops.py index a5c9f9a06..800f9b407 100644 --- a/gen/python/sym/ops/polynomial_camera_cal/group_ops.py +++ b/gen/python/sym/ops/polynomial_camera_cal/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/polynomial_camera_cal/lie_group_ops.py b/gen/python/sym/ops/polynomial_camera_cal/lie_group_ops.py index cbfd7276d..b8f97fe2e 100644 --- a/gen/python/sym/ops/polynomial_camera_cal/lie_group_ops.py +++ b/gen/python/sym/ops/polynomial_camera_cal/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.PolynomialCameraCal + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.PolynomialCameraCal # Total ops: 0 # Input arrays - if vec.shape == (8,): - vec = vec.reshape((8, 1)) - elif vec.shape != (8, 1): - raise IndexError( - "vec is expected to have shape (8, 1) or (8,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (8, 1)) # Intermediate terms (0) @@ -72,20 +66,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.PolynomialCameraCal, numpy.ndarray, float) -> sym.PolynomialCameraCal + # type: (sym.PolynomialCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.PolynomialCameraCal # Total ops: 8 # Input arrays _a = a.data - if vec.shape == (8,): - vec = vec.reshape((8, 1)) - elif vec.shape != (8, 1): - raise IndexError( - "vec is expected to have shape (8, 1) or (8,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (8, 1)) # Intermediate terms (0) diff --git a/gen/python/sym/ops/pose2/group_ops.py b/gen/python/sym/ops/pose2/group_ops.py index 91e33d778..9d103ec7b 100644 --- a/gen/python/sym/ops/pose2/group_ops.py +++ b/gen/python/sym/ops/pose2/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/pose2/lie_group_ops.py b/gen/python/sym/ops/pose2/lie_group_ops.py index e49bbb270..2a8625003 100644 --- a/gen/python/sym/ops/pose2/lie_group_ops.py +++ b/gen/python/sym/ops/pose2/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.Pose2 + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.Pose2 # Total ops: 2 # Input arrays - if vec.shape == (3,): - vec = vec.reshape((3, 1)) - elif vec.shape != (3, 1): - raise IndexError( - "vec is expected to have shape (3, 1) or (3,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (3, 1)) # Intermediate terms (0) @@ -65,20 +59,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.Pose2, numpy.ndarray, float) -> sym.Pose2 + # type: (sym.Pose2, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.Pose2 # Total ops: 10 # Input arrays _a = a.data - if vec.shape == (3,): - vec = vec.reshape((3, 1)) - elif vec.shape != (3, 1): - raise IndexError( - "vec is expected to have shape (3, 1) or (3,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (3, 1)) # Intermediate terms (2) _tmp0 = math.sin(vec[0, 0]) diff --git a/gen/python/sym/ops/pose3/group_ops.py b/gen/python/sym/ops/pose3/group_ops.py index a471a64e5..597f9dae8 100644 --- a/gen/python/sym/ops/pose3/group_ops.py +++ b/gen/python/sym/ops/pose3/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/pose3/lie_group_ops.py b/gen/python/sym/ops/pose3/lie_group_ops.py index 112a3c868..63dec50d6 100644 --- a/gen/python/sym/ops/pose3/lie_group_ops.py +++ b/gen/python/sym/ops/pose3/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.Pose3 + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.Pose3 # Total ops: 15 # Input arrays - if vec.shape == (6,): - vec = vec.reshape((6, 1)) - elif vec.shape != (6, 1): - raise IndexError( - "vec is expected to have shape (6, 1) or (6,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (6, 1)) # Intermediate terms (3) _tmp0 = math.sqrt(epsilon ** 2 + vec[0, 0] ** 2 + vec[1, 0] ** 2 + vec[2, 0] ** 2) @@ -79,20 +73,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.Pose3, numpy.ndarray, float) -> sym.Pose3 + # type: (sym.Pose3, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.Pose3 # Total ops: 47 # Input arrays _a = a.data - if vec.shape == (6,): - vec = vec.reshape((6, 1)) - elif vec.shape != (6, 1): - raise IndexError( - "vec is expected to have shape (6, 1) or (6,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (6, 1)) # Intermediate terms (8) _tmp0 = math.sqrt(epsilon ** 2 + vec[0, 0] ** 2 + vec[1, 0] ** 2 + vec[2, 0] ** 2) diff --git a/gen/python/sym/ops/rot2/group_ops.py b/gen/python/sym/ops/rot2/group_ops.py index 0367e4bfb..5acca2951 100644 --- a/gen/python/sym/ops/rot2/group_ops.py +++ b/gen/python/sym/ops/rot2/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/rot2/lie_group_ops.py b/gen/python/sym/ops/rot2/lie_group_ops.py index 7d8a6b08f..e6e44fcfa 100644 --- a/gen/python/sym/ops/rot2/lie_group_ops.py +++ b/gen/python/sym/ops/rot2/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.Rot2 + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.Rot2 # Total ops: 2 # Input arrays - if vec.shape == (1,): - vec = vec.reshape((1, 1)) - elif vec.shape != (1, 1): - raise IndexError( - "vec is expected to have shape (1, 1) or (1,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (1, 1)) # Intermediate terms (0) @@ -61,20 +55,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.Rot2, numpy.ndarray, float) -> sym.Rot2 + # type: (sym.Rot2, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.Rot2 # Total ops: 8 # Input arrays _a = a.data - if vec.shape == (1,): - vec = vec.reshape((1, 1)) - elif vec.shape != (1, 1): - raise IndexError( - "vec is expected to have shape (1, 1) or (1,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (1, 1)) # Intermediate terms (2) _tmp0 = math.sin(vec[0, 0]) diff --git a/gen/python/sym/ops/rot3/group_ops.py b/gen/python/sym/ops/rot3/group_ops.py index 2acc72e6a..32dcbf212 100644 --- a/gen/python/sym/ops/rot3/group_ops.py +++ b/gen/python/sym/ops/rot3/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/rot3/lie_group_ops.py b/gen/python/sym/ops/rot3/lie_group_ops.py index d48444060..587ccac90 100644 --- a/gen/python/sym/ops/rot3/lie_group_ops.py +++ b/gen/python/sym/ops/rot3/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.Rot3 + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.Rot3 # Total ops: 15 # Input arrays - if vec.shape == (3,): - vec = vec.reshape((3, 1)) - elif vec.shape != (3, 1): - raise IndexError( - "vec is expected to have shape (3, 1) or (3,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (3, 1)) # Intermediate terms (3) _tmp0 = math.sqrt(epsilon ** 2 + vec[0, 0] ** 2 + vec[1, 0] ** 2 + vec[2, 0] ** 2) @@ -73,20 +67,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.Rot3, numpy.ndarray, float) -> sym.Rot3 + # type: (sym.Rot3, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.Rot3 # Total ops: 44 # Input arrays _a = a.data - if vec.shape == (3,): - vec = vec.reshape((3, 1)) - elif vec.shape != (3, 1): - raise IndexError( - "vec is expected to have shape (3, 1) or (3,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (3, 1)) # Intermediate terms (8) _tmp0 = math.sqrt(epsilon ** 2 + vec[0, 0] ** 2 + vec[1, 0] ** 2 + vec[2, 0] ** 2) diff --git a/gen/python/sym/ops/spherical_camera_cal/camera_ops.py b/gen/python/sym/ops/spherical_camera_cal/camera_ops.py index bce187680..17b2aa930 100644 --- a/gen/python/sym/ops/spherical_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/spherical_camera_cal/camera_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class CameraOps(object): @@ -59,7 +60,7 @@ def principal_point(self): @staticmethod def pixel_from_camera_point(self, point, epsilon): - # type: (sym.SphericalCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (sym.SphericalCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -72,14 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (4) _tmp0 = math.sqrt(epsilon + point[0, 0] ** 2 + point[1, 0] ** 2) @@ -102,7 +96,7 @@ def pixel_from_camera_point(self, point, epsilon): @staticmethod def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (sym.SphericalCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (sym.SphericalCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -117,14 +111,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (40) _tmp0 = -epsilon diff --git a/gen/python/sym/ops/spherical_camera_cal/group_ops.py b/gen/python/sym/ops/spherical_camera_cal/group_ops.py index 4e41a96ee..730137d0b 100644 --- a/gen/python/sym/ops/spherical_camera_cal/group_ops.py +++ b/gen/python/sym/ops/spherical_camera_cal/group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/gen/python/sym/ops/spherical_camera_cal/lie_group_ops.py b/gen/python/sym/ops/spherical_camera_cal/lie_group_ops.py index 793e6fe89..a2e7c60c5 100644 --- a/gen/python/sym/ops/spherical_camera_cal/lie_group_ops.py +++ b/gen/python/sym/ops/spherical_camera_cal/lie_group_ops.py @@ -10,6 +10,7 @@ import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): @@ -19,19 +20,12 @@ class LieGroupOps(object): @staticmethod def from_tangent(vec, epsilon): - # type: (numpy.ndarray, float) -> sym.SphericalCameraCal + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.SphericalCameraCal # Total ops: 0 # Input arrays - if vec.shape == (9,): - vec = vec.reshape((9, 1)) - elif vec.shape != (9, 1): - raise IndexError( - "vec is expected to have shape (9, 1) or (9,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (9, 1)) # Intermediate terms (0) @@ -74,20 +68,13 @@ def to_tangent(a, epsilon): @staticmethod def retract(a, vec, epsilon): - # type: (sym.SphericalCameraCal, numpy.ndarray, float) -> sym.SphericalCameraCal + # type: (sym.SphericalCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> sym.SphericalCameraCal # Total ops: 9 # Input arrays _a = a.data - if vec.shape == (9,): - vec = vec.reshape((9, 1)) - elif vec.shape != (9, 1): - raise IndexError( - "vec is expected to have shape (9, 1) or (9,); instead had shape {}".format( - vec.shape - ) - ) + vec = check_size_and_reshape(vec, "vec", (9, 1)) # Intermediate terms (0) diff --git a/gen/python/sym/polynomial_camera_cal.py b/gen/python/sym/polynomial_camera_cal.py index a06be006f..686abaa2a 100644 --- a/gen/python/sym/polynomial_camera_cal.py +++ b/gen/python/sym/polynomial_camera_cal.py @@ -9,6 +9,7 @@ import numpy from .ops import polynomial_camera_cal as ops +from .util import check_size_and_reshape class PolynomialCameraCal(object): @@ -115,7 +116,7 @@ def principal_point(self): return ops.CameraOps.principal_point(self) def pixel_from_camera_point(self, point, epsilon): - # type: (PolynomialCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (PolynomialCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -127,7 +128,7 @@ def pixel_from_camera_point(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point(self, point, epsilon) def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (PolynomialCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (PolynomialCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -181,13 +182,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> PolynomialCameraCal - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> PolynomialCameraCal return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -195,13 +190,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> PolynomialCameraCal - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> PolynomialCameraCal return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): diff --git a/gen/python/sym/pose2.py b/gen/python/sym/pose2.py index 5f8191678..7ace0c5b6 100644 --- a/gen/python/sym/pose2.py +++ b/gen/python/sym/pose2.py @@ -10,6 +10,7 @@ import numpy from .rot2 import Rot2 +from .util import check_size_and_reshape # isort: split from .ops import pose2 as ops @@ -132,7 +133,7 @@ def position(self): return _res def compose_with_point(self, right): - # type: (Pose2, numpy.ndarray) -> numpy.ndarray + # type: (Pose2, T.Union[T.Sequence[float], numpy.ndarray]) -> numpy.ndarray """ Left-multiply with a compatible quantity. @@ -147,14 +148,7 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if right.shape == (2,): - right = right.reshape((2, 1)) - elif right.shape != (2, 1): - raise IndexError( - "right is expected to have shape (2, 1) or (2,); instead had shape {}".format( - right.shape - ) - ) + right = check_size_and_reshape(right, "right", (2, 1)) # Intermediate terms (0) @@ -165,7 +159,7 @@ def compose_with_point(self, right): return _res def inverse_compose(self, point): - # type: (Pose2, numpy.ndarray) -> numpy.ndarray + # type: (Pose2, T.Union[T.Sequence[float], numpy.ndarray]) -> numpy.ndarray """ This function was autogenerated from a symbolic function. Do not modify by hand. @@ -182,14 +176,7 @@ def inverse_compose(self, point): # Input arrays _self = self.data - if point.shape == (2,): - point = point.reshape((2, 1)) - elif point.shape != (2, 1): - raise IndexError( - "point is expected to have shape (2, 1) or (2,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (2, 1)) # Intermediate terms (0) @@ -300,13 +287,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> Pose2 - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> Pose2 return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -314,13 +295,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> Pose2 - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> Pose2 return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): @@ -351,11 +326,23 @@ def __mul__(self, other): # pragma: no cover # type: (numpy.ndarray) -> numpy.ndarray pass + @T.overload + def __mul__(self, other): # pragma: no cover + # type: (T.Sequence[float]) -> numpy.ndarray + pass + def __mul__(self, other): - # type: (T.Union[Pose2, numpy.ndarray]) -> T.Union[Pose2, numpy.ndarray] + # type: (T.Union[Pose2, T.Sequence[float], numpy.ndarray]) -> T.Union[Pose2, numpy.ndarray] if isinstance(other, Pose2): return self.compose(other) elif isinstance(other, numpy.ndarray) and hasattr(self, "compose_with_point"): return self.compose_with_point(other).reshape(other.shape) + elif ( + hasattr(self, "compose_with_point") + and hasattr(other, "__len__") + and hasattr(other, "__getitem__") + and not isinstance(other, str) + ): + return self.compose_with_point(other) else: raise NotImplementedError("Cannot compose {} with {}.".format(type(self), type(other))) diff --git a/gen/python/sym/pose3.py b/gen/python/sym/pose3.py index 826257756..18b9d5a69 100644 --- a/gen/python/sym/pose3.py +++ b/gen/python/sym/pose3.py @@ -10,6 +10,7 @@ import numpy from .rot3 import Rot3 +from .util import check_size_and_reshape # isort: split from .ops import pose3 as ops @@ -131,7 +132,7 @@ def position(self): return _res def compose_with_point(self, right): - # type: (Pose3, numpy.ndarray) -> numpy.ndarray + # type: (Pose3, T.Union[T.Sequence[float], numpy.ndarray]) -> numpy.ndarray """ Left-multiply with a compatible quantity. """ @@ -140,14 +141,7 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if right.shape == (3,): - right = right.reshape((3, 1)) - elif right.shape != (3, 1): - raise IndexError( - "right is expected to have shape (3, 1) or (3,); instead had shape {}".format( - right.shape - ) - ) + right = check_size_and_reshape(right, "right", (3, 1)) # Intermediate terms (11) _tmp0 = 2 * _self[2] @@ -185,7 +179,7 @@ def compose_with_point(self, right): return _res def inverse_compose(self, point): - # type: (Pose3, numpy.ndarray) -> numpy.ndarray + # type: (Pose3, T.Union[T.Sequence[float], numpy.ndarray]) -> numpy.ndarray """ This function was autogenerated from a symbolic function. Do not modify by hand. @@ -202,14 +196,7 @@ def inverse_compose(self, point): # Input arrays _self = self.data - if point.shape == (3,): - point = point.reshape((3, 1)) - elif point.shape != (3, 1): - raise IndexError( - "point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - point.shape - ) - ) + point = check_size_and_reshape(point, "point", (3, 1)) # Intermediate terms (20) _tmp0 = 2 * _self[2] @@ -367,13 +354,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> Pose3 - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> Pose3 return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -381,13 +362,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> Pose3 - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> Pose3 return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): @@ -418,11 +393,23 @@ def __mul__(self, other): # pragma: no cover # type: (numpy.ndarray) -> numpy.ndarray pass + @T.overload + def __mul__(self, other): # pragma: no cover + # type: (T.Sequence[float]) -> numpy.ndarray + pass + def __mul__(self, other): - # type: (T.Union[Pose3, numpy.ndarray]) -> T.Union[Pose3, numpy.ndarray] + # type: (T.Union[Pose3, T.Sequence[float], numpy.ndarray]) -> T.Union[Pose3, numpy.ndarray] if isinstance(other, Pose3): return self.compose(other) elif isinstance(other, numpy.ndarray) and hasattr(self, "compose_with_point"): return self.compose_with_point(other).reshape(other.shape) + elif ( + hasattr(self, "compose_with_point") + and hasattr(other, "__len__") + and hasattr(other, "__getitem__") + and not isinstance(other, str) + ): + return self.compose_with_point(other) else: raise NotImplementedError("Cannot compose {} with {}.".format(type(self), type(other))) diff --git a/gen/python/sym/rot2.py b/gen/python/sym/rot2.py index 47ecc35dc..7e0967c19 100644 --- a/gen/python/sym/rot2.py +++ b/gen/python/sym/rot2.py @@ -9,6 +9,8 @@ import numpy +from .util import check_size_and_reshape + # isort: split from .ops import rot2 as ops @@ -56,7 +58,7 @@ def __init__(self, z=None): # -------------------------------------------------------------------------- def compose_with_point(self, right): - # type: (Rot2, numpy.ndarray) -> numpy.ndarray + # type: (Rot2, T.Union[T.Sequence[float], numpy.ndarray]) -> numpy.ndarray """ Left-multiplication. Either rotation concatenation or point transform. """ @@ -65,14 +67,7 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if right.shape == (2,): - right = right.reshape((2, 1)) - elif right.shape != (2, 1): - raise IndexError( - "right is expected to have shape (2, 1) or (2,); instead had shape {}".format( - right.shape - ) - ) + right = check_size_and_reshape(right, "right", (2, 1)) # Intermediate terms (0) @@ -186,13 +181,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> Rot2 - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> Rot2 return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -200,13 +189,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> Rot2 - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> Rot2 return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): @@ -237,11 +220,23 @@ def __mul__(self, other): # pragma: no cover # type: (numpy.ndarray) -> numpy.ndarray pass + @T.overload + def __mul__(self, other): # pragma: no cover + # type: (T.Sequence[float]) -> numpy.ndarray + pass + def __mul__(self, other): - # type: (T.Union[Rot2, numpy.ndarray]) -> T.Union[Rot2, numpy.ndarray] + # type: (T.Union[Rot2, T.Sequence[float], numpy.ndarray]) -> T.Union[Rot2, numpy.ndarray] if isinstance(other, Rot2): return self.compose(other) elif isinstance(other, numpy.ndarray) and hasattr(self, "compose_with_point"): return self.compose_with_point(other).reshape(other.shape) + elif ( + hasattr(self, "compose_with_point") + and hasattr(other, "__len__") + and hasattr(other, "__getitem__") + and not isinstance(other, str) + ): + return self.compose_with_point(other) else: raise NotImplementedError("Cannot compose {} with {}.".format(type(self), type(other))) diff --git a/gen/python/sym/rot3.py b/gen/python/sym/rot3.py index e725d5cae..5f36745c8 100644 --- a/gen/python/sym/rot3.py +++ b/gen/python/sym/rot3.py @@ -9,6 +9,8 @@ import numpy +from .util import check_size_and_reshape + # isort: split from .ops import rot3 as ops @@ -79,7 +81,7 @@ def from_rotation_matrix(cls, R, epsilon=0.0): # -------------------------------------------------------------------------- def compose_with_point(self, right): - # type: (Rot3, numpy.ndarray) -> numpy.ndarray + # type: (Rot3, T.Union[T.Sequence[float], numpy.ndarray]) -> numpy.ndarray """ Left-multiplication. Either rotation concatenation or point transform. """ @@ -88,14 +90,7 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if right.shape == (3,): - right = right.reshape((3, 1)) - elif right.shape != (3, 1): - raise IndexError( - "right is expected to have shape (3, 1) or (3,); instead had shape {}".format( - right.shape - ) - ) + right = check_size_and_reshape(right, "right", (3, 1)) # Intermediate terms (11) _tmp0 = 2 * _self[0] @@ -320,13 +315,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> Rot3 - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> Rot3 return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -334,13 +323,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> Rot3 - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> Rot3 return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): @@ -371,11 +354,23 @@ def __mul__(self, other): # pragma: no cover # type: (numpy.ndarray) -> numpy.ndarray pass + @T.overload + def __mul__(self, other): # pragma: no cover + # type: (T.Sequence[float]) -> numpy.ndarray + pass + def __mul__(self, other): - # type: (T.Union[Rot3, numpy.ndarray]) -> T.Union[Rot3, numpy.ndarray] + # type: (T.Union[Rot3, T.Sequence[float], numpy.ndarray]) -> T.Union[Rot3, numpy.ndarray] if isinstance(other, Rot3): return self.compose(other) elif isinstance(other, numpy.ndarray) and hasattr(self, "compose_with_point"): return self.compose_with_point(other).reshape(other.shape) + elif ( + hasattr(self, "compose_with_point") + and hasattr(other, "__len__") + and hasattr(other, "__getitem__") + and not isinstance(other, str) + ): + return self.compose_with_point(other) else: raise NotImplementedError("Cannot compose {} with {}.".format(type(self), type(other))) diff --git a/gen/python/sym/spherical_camera_cal.py b/gen/python/sym/spherical_camera_cal.py index ee85c37e8..ffdb603d5 100644 --- a/gen/python/sym/spherical_camera_cal.py +++ b/gen/python/sym/spherical_camera_cal.py @@ -9,6 +9,7 @@ import numpy from .ops import spherical_camera_cal as ops +from .util import check_size_and_reshape class SphericalCameraCal(object): @@ -130,7 +131,7 @@ def principal_point(self): return ops.CameraOps.principal_point(self) def pixel_from_camera_point(self, point, epsilon): - # type: (SphericalCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float] + # type: (SphericalCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -142,7 +143,7 @@ def pixel_from_camera_point(self, point, epsilon): return ops.CameraOps.pixel_from_camera_point(self, point, epsilon) def pixel_from_camera_point_with_jacobians(self, point, epsilon): - # type: (SphericalCameraCal, numpy.ndarray, float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] + # type: (SphericalCameraCal, T.Union[T.Sequence[float], numpy.ndarray], float) -> T.Tuple[numpy.ndarray, float, numpy.ndarray, numpy.ndarray] """ Project a 3D point in the camera frame into 2D pixel coordinates. @@ -196,13 +197,7 @@ def tangent_dim(): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> SphericalCameraCal - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> SphericalCameraCal return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -210,13 +205,7 @@ def to_tangent(self, epsilon=1e-8): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> SphericalCameraCal - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> SphericalCameraCal return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): diff --git a/gen/python/sym/util.py b/gen/python/sym/util.py new file mode 100644 index 000000000..edf835a61 --- /dev/null +++ b/gen/python/sym/util.py @@ -0,0 +1,31 @@ +# ----------------------------------------------------------------------------- +# This file was autogenerated by symforce from template: +# util.py.jinja +# Do NOT modify by hand. +# ----------------------------------------------------------------------------- + +import typing as T + +import numpy + + +def check_size_and_reshape(array, name, expected_shape): + # type: (T.Union[T.Sequence[float], numpy.ndarray], str, T.Tuple[int, int]) -> numpy.ndarray + if not isinstance(array, numpy.ndarray): + expected_len = max(expected_shape) + if len(array) != expected_len: + raise IndexError( + "{} is expected to have length {}; instead had length{}".format( + name, expected_len, len(array) + ) + ) + return numpy.array(array).reshape(expected_shape) + elif array.shape == (max(expected_shape),): + return array.reshape(expected_shape) + elif array.shape != expected_shape: + raise IndexError( + "{} is expected to have shape {} or ({},); instead had shape {}".format( + name, expected_shape, max(expected_shape), array.shape + ) + ) + return array diff --git a/notebooks/tutorials/codegen_tutorial.ipynb b/notebooks/tutorials/codegen_tutorial.ipynb index 1273f2915..c8d8f5561 100644 --- a/notebooks/tutorials/codegen_tutorial.ipynb +++ b/notebooks/tutorials/codegen_tutorial.ipynb @@ -390,16 +390,16 @@ "params.L = [0.5, 0.3]\n", "params.m = [0.3, 0.2]\n", "\n", - "gen_module = codegen_util.load_generated_package(\n", - " namespace, double_pendulum_python_data.function_dir\n", + "gen_double_pendulum = codegen_util.load_generated_function(\n", + " \"double_pendulum\", double_pendulum_python_data.function_dir\n", ")\n", - "gen_module.double_pendulum(ang, dang, consts, params)" + "gen_double_pendulum(ang, dang, consts, params)" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -413,7 +413,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.9" + "version": "3.8.14" } }, "nbformat": 4, diff --git a/symforce/codegen/backends/python/python_config.py b/symforce/codegen/backends/python/python_config.py index 28e9f8665..a60ac433d 100644 --- a/symforce/codegen/backends/python/python_config.py +++ b/symforce/codegen/backends/python/python_config.py @@ -33,6 +33,8 @@ class PythonConfig(CodegenConfig): times. reshape_vectors: Allow rank 1 ndarrays to be passed in for row and column vectors by automatically reshaping the input. + namespace_package: Generate the package as a namespace package, meaning it can be split + across multiple directories. """ doc_comment_line_prefix: str = "" @@ -40,6 +42,7 @@ class PythonConfig(CodegenConfig): use_eigen_types: bool = True use_numba: bool = False reshape_vectors: bool = True + namespace_package: bool = True @classmethod def backend_name(cls) -> str: @@ -50,10 +53,11 @@ def template_dir(cls) -> Path: return CURRENT_DIR / "templates" def templates_to_render(self, generated_file_name: str) -> T.List[T.Tuple[str, str]]: - return [ - ("function/FUNCTION.py.jinja", f"{generated_file_name}.py"), - ("function/__init__.py.jinja", "__init__.py"), - ] + templates = [("function/FUNCTION.py.jinja", f"{generated_file_name}.py")] + if self.namespace_package: + return templates + [("function/namespace_init.py.jinja", "__init__.py")] + else: + return templates + [("function/__init__.py.jinja", "__init__.py")] def printer(self) -> CodePrinter: return python_code_printer.PythonCodePrinter() diff --git a/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja b/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja index 189e10fdf..5151a2954 100644 --- a/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja +++ b/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja @@ -8,7 +8,7 @@ import typing as T import numpy from .ops import {{ camelcase_to_snakecase(cls.__name__) }} as ops - +from .util import check_size_and_reshape class {{ cls.__name__ }}(object): {% if doc %} @@ -36,9 +36,9 @@ class {{ cls.__name__ }}(object): # type: ({% for arg, size in storage_order %}{%if size == 1 %}float{% else %}T.Union[T.Sequence[float], numpy.ndarray]{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}) -> None self.data = [] {% for arg, size in storage_order %} - {% if size != 1 %} + {% if size != 1 %} {{ util.flatten_if_ndarray(arg, size) | indent(width=8) }} - {% endif %} + {% endif %} {% endfor %} {% for arg, size in storage_order %} @@ -102,13 +102,7 @@ class {{ cls.__name__ }}(object): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> {{ cls.__name__ }} - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> {{ cls.__name__ }} return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -116,13 +110,7 @@ class {{ cls.__name__ }}(object): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> {{ cls.__name__ }} - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> {{ cls.__name__ }} return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): diff --git a/symforce/codegen/backends/python/templates/cam_package/ops/CLASS/camera_ops.py.jinja b/symforce/codegen/backends/python/templates/cam_package/ops/CLASS/camera_ops.py.jinja index c83e5ddc3..b65d4328e 100644 --- a/symforce/codegen/backends/python/templates/cam_package/ops/CLASS/camera_ops.py.jinja +++ b/symforce/codegen/backends/python/templates/cam_package/ops/CLASS/camera_ops.py.jinja @@ -10,6 +10,7 @@ import typing as T import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class CameraOps(object): diff --git a/symforce/codegen/backends/python/templates/function/FUNCTION.py.jinja b/symforce/codegen/backends/python/templates/function/FUNCTION.py.jinja index 074438e90..b8a6d1c97 100644 --- a/symforce/codegen/backends/python/templates/function/FUNCTION.py.jinja +++ b/symforce/codegen/backends/python/templates/function/FUNCTION.py.jinja @@ -17,6 +17,9 @@ from scipy import sparse {% endif %} import sym # pylint: disable=unused-import +{% if not spec.config.use_numba %} +from sym.util import check_size_and_reshape +{% endif %} # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument diff --git a/symforce/codegen/backends/python/templates/function/__init__.py.jinja b/symforce/codegen/backends/python/templates/function/__init__.py.jinja index eafe9282d..e95f1ca85 100644 --- a/symforce/codegen/backends/python/templates/function/__init__.py.jinja +++ b/symforce/codegen/backends/python/templates/function/__init__.py.jinja @@ -2,4 +2,3 @@ # SymForce - Copyright 2022, Skydio, Inc. # This source code is under the Apache 2.0 license found in the LICENSE file. # ---------------------------------------------------------------------------- #} -from .{{ spec.name }} import {{ spec.name }} diff --git a/symforce/codegen/backends/python/templates/function/namespace_init.py.jinja b/symforce/codegen/backends/python/templates/function/namespace_init.py.jinja new file mode 100644 index 000000000..5c54e337a --- /dev/null +++ b/symforce/codegen/backends/python/templates/function/namespace_init.py.jinja @@ -0,0 +1,16 @@ +{# ---------------------------------------------------------------------------- + # SymForce - Copyright 2022, Skydio, Inc. + # This source code is under the Apache 2.0 license found in the LICENSE file. + # ---------------------------------------------------------------------------- #} +{% if pkg_namespace == "sym" %} +""" +Python runtime geometry package. +""" + +{% endif %} +# Make package a namespace package by adding other portions to the __path__ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore[has-type] +# https://github.com/python/mypy/issues/1422 +{% if pkg_namespace == "sym" %} +from ._init import * +{% endif %} diff --git a/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja b/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja index 75c1c76a5..97b0e0e3e 100644 --- a/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja +++ b/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja @@ -13,6 +13,7 @@ import numpy from .{{ camelcase_to_snakecase(imported_cls.__name__ )}} import {{ imported_cls.__name__ }} {% endfor %} {% endif -%} +from .util import check_size_and_reshape # isort: split from .ops import {{ camelcase_to_snakecase(cls.__name__) }} as ops @@ -125,13 +126,7 @@ class {{ cls.__name__ }}(object): @classmethod def from_tangent(cls, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> {{ cls.__name__ }} - if len(vec) != cls.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), cls.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> {{ cls.__name__ }} return ops.LieGroupOps.from_tangent(vec, epsilon) def to_tangent(self, epsilon=1e-8): @@ -139,13 +134,7 @@ class {{ cls.__name__ }}(object): return ops.LieGroupOps.to_tangent(self, epsilon) def retract(self, vec, epsilon=1e-8): - # type: (numpy.ndarray, float) -> {{ cls.__name__ }} - if len(vec) != self.tangent_dim(): - raise ValueError( - "Vector dimension ({}) not equal to tangent space dimension ({}).".format( - len(vec), self.tangent_dim() - ) - ) + # type: (T.Union[T.Sequence[float], numpy.ndarray], float) -> {{ cls.__name__ }} return ops.LieGroupOps.retract(self, vec, epsilon) def local_coordinates(self, b, epsilon=1e-8): @@ -177,11 +166,23 @@ class {{ cls.__name__ }}(object): # type: (numpy.ndarray) -> numpy.ndarray pass + @T.overload + def __mul__(self, other): # pragma: no cover + # type: (T.Sequence[float]) -> numpy.ndarray + pass + def __mul__(self, other): - # type: (T.Union[{{ cls.__name__ }}, numpy.ndarray]) -> T.Union[{{ cls.__name__ }}, numpy.ndarray] + # type: (T.Union[{{ cls.__name__ }}, T.Sequence[float], numpy.ndarray]) -> T.Union[{{ cls.__name__ }}, numpy.ndarray] if isinstance(other, {{ cls.__name__ }}): return self.compose(other) elif isinstance(other, numpy.ndarray) and hasattr(self, "compose_with_point"): return self.compose_with_point(other).reshape(other.shape) + elif ( + hasattr(self, "compose_with_point") + and hasattr(other, "__len__") + and hasattr(other, "__getitem__") + and not isinstance(other, str) + ): + return self.compose_with_point(other) else: raise NotImplementedError('Cannot compose {} with {}.'.format(type(self), type(other))) diff --git a/symforce/codegen/backends/python/templates/ops/CLASS/group_ops.py.jinja b/symforce/codegen/backends/python/templates/ops/CLASS/group_ops.py.jinja index a1d1a5fb9..506096841 100644 --- a/symforce/codegen/backends/python/templates/ops/CLASS/group_ops.py.jinja +++ b/symforce/codegen/backends/python/templates/ops/CLASS/group_ops.py.jinja @@ -10,6 +10,7 @@ import typing as T import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class GroupOps(object): diff --git a/symforce/codegen/backends/python/templates/ops/CLASS/lie_group_ops.py.jinja b/symforce/codegen/backends/python/templates/ops/CLASS/lie_group_ops.py.jinja index c3d1feb5f..cda159fde 100644 --- a/symforce/codegen/backends/python/templates/ops/CLASS/lie_group_ops.py.jinja +++ b/symforce/codegen/backends/python/templates/ops/CLASS/lie_group_ops.py.jinja @@ -10,6 +10,7 @@ import typing as T import numpy import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape class LieGroupOps(object): diff --git a/symforce/codegen/backends/python/templates/tests/geo_package_python_test.py.jinja b/symforce/codegen/backends/python/templates/tests/geo_package_python_test.py.jinja index a0e4f59ad..92475c426 100644 --- a/symforce/codegen/backends/python/templates/tests/geo_package_python_test.py.jinja +++ b/symforce/codegen/backends/python/templates/tests/geo_package_python_test.py.jinja @@ -129,9 +129,13 @@ class GeoPackageTest(unittest.TestCase): {% set dim = cls.__name__[-1] %} vector = np.random.normal(size=({{ dim }}, 1)) + vec_as_list = vector.flatten().tolist() + vec_as_tuple = tuple(vec_as_list) {% if "Rot" in cls.__name__ %} matrix = element.to_rotation_matrix() np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vector) + np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vec_as_list) + np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vec_as_tuple) # Test constructor handles column vectors correctly col_data = np.random.normal(size=(geo_class.storage_dim(), 1)) @@ -150,6 +154,8 @@ class GeoPackageTest(unittest.TestCase): {% else %} vector_as_element = geo_class(t=vector.flatten().tolist()) np.testing.assert_almost_equal(element * vector, (element * vector_as_element).position()) + np.testing.assert_almost_equal(element * vec_as_list, (element * vector_as_element).position()) + np.testing.assert_almost_equal(element * vec_as_tuple, (element * vector_as_element).position()) # Test position/rotation accessors np.testing.assert_equal(element.position(), element.t) diff --git a/symforce/codegen/backends/python/templates/util.py.jinja b/symforce/codegen/backends/python/templates/util.py.jinja new file mode 100644 index 000000000..03b92841f --- /dev/null +++ b/symforce/codegen/backends/python/templates/util.py.jinja @@ -0,0 +1,32 @@ +{# ---------------------------------------------------------------------------- + # SymForce - Copyright 2022, Skydio, Inc. + # This source code is under the Apache 2.0 license found in the LICENSE file. + # ---------------------------------------------------------------------------- #} +import typing as T + +import numpy + + +def check_size_and_reshape(array, name, expected_shape): + # type: (T.Union[T.Sequence[float], numpy.ndarray], str, T.Tuple[int, int]) -> numpy.ndarray + if not isinstance(array, numpy.ndarray): + expected_len = max(expected_shape) + if len(array) != expected_len: + raise IndexError( + "{} is expected to have length {}; instead had length{}".format( + name, expected_len, len(array) + ) + ) + return numpy.array(array).reshape(expected_shape) + elif array.shape == (max(expected_shape),): + return array.reshape(expected_shape) + elif array.shape != expected_shape: + raise IndexError( + "{} is expected to have shape {} or ({},); instead had shape {}".format( + name, + expected_shape, + max(expected_shape), + array.shape + ) + ) + return array diff --git a/symforce/codegen/backends/python/templates/util/util.jinja b/symforce/codegen/backends/python/templates/util/util.jinja index 28562cdfb..98d1f53ae 100644 --- a/symforce/codegen/backends/python/templates/util/util.jinja +++ b/symforce/codegen/backends/python/templates/util/util.jinja @@ -15,8 +15,9 @@ # is_input (bool): Is this an input argument or return value? # available_classes (T.List[type]): A list sym classes already available (meaning # they should be referenced by just their name, and not, say, sym.Rot3). + # sequence_vecs (bool): Can a sequence be a vector? #} -{%- macro format_typename(T_or_value, name, is_input, available_classes = []) %} +{%- macro format_typename(T_or_value, name, is_input, available_classes = [], sequence_vecs=False) %} {%- set T = typing_util.get_type(T_or_value) -%} {%- if T.__name__ == 'DataBuffer' -%} numpy.ndarray @@ -25,7 +26,11 @@ {%- elif T.__name__ == 'NoneType' -%} None {%- elif issubclass(T, Matrix) -%} + {%- if sequence_vecs and is_input and (T.SHAPE | min) == 1 -%} + T.Union[T.Sequence[float], numpy.ndarray] + {%- else -%} numpy.ndarray + {%- endif -%} {%- elif issubclass(T, Values) -%} {#- TODO(aaron): We don't currently know where to import lcmtypes from or what they should be # called, at some point we should fix this and do something like @@ -34,7 +39,7 @@ T.Any {%- elif is_sequence(T_or_value) -%} {%- if is_input -%} - T.Sequence[{{ format_typename(T_or_value[0], name, is_input) }}] + T.Sequence[{{ format_typename(T_or_value[0], name, is_input, available_classes) }}] {%- else -%} T.List[float] {%- endif -%} @@ -111,10 +116,12 @@ {%- if is_method and "self" not in spec.inputs -%} @staticmethod {% endif %} +{# Only accept list as vector inputs if not numba and we reshape vecs into ndarrays #} +{% set sequence_vecs = spec.config.reshape_vectors and not spec.config.use_numba %} def {{ function_name_and_args(spec) }}: # type: ( {%- for name, type in spec.inputs.items() -%} - {{ format_typename(type, name, is_input=True, available_classes=available_classes) }}{% if not loop.last %}, {% endif %} + {{ format_typename(type, name, is_input=True, available_classes=available_classes, sequence_vecs=sequence_vecs) }}{% if not loop.last %}, {% endif %} {%- endfor -%}) -> {{ get_return_type(spec, available_classes=available_classes) }} {%- endmacro -%} @@ -139,20 +146,14 @@ def {{ function_name_and_args(spec) }}: #} {% macro check_size_and_reshape(name, shape, use_numba) %} {% if 1 in shape %} + {% set size = shape | max %} {% if use_numba %} {# NOTE(brad): Numba will complain if we reshape name inside of a conditional #} -if not ({{ name }}.shape == {{ shape }} or {{ name }}.shape == ({{ shape | max }},)): - raise IndexError("{{ name }} is expected to have shape {{ shape }} or ({{ shape | max }},)") +if not ({{ name }}.shape == {{ shape }} or {{ name }}.shape == ({{ size }},)): + raise IndexError("{{ name }} is expected to have shape {{ shape }} or ({{ size }},)") {{ name }} = {{ name }}.reshape({{ shape }}) {% else %} -if {{ name }}.shape == ({{ shape | max }},): - {{ name }} = {{ name }}.reshape({{ shape }}) -elif {{ name }}.shape != {{ shape }}: - raise IndexError( - "{{ name }} is expected to have shape {{ shape }} or ({{ shape | max }},); instead had shape {}".format( - {{ name }}.shape - ) - ) +{{ name }} = check_size_and_reshape({{ name }}, "{{ name }}", {{ shape }}) {% endif %} {% endif %} {% endmacro %} diff --git a/symforce/codegen/cam_package_codegen.py b/symforce/codegen/cam_package_codegen.py index 3495a0c15..289da670d 100644 --- a/symforce/codegen/cam_package_codegen.py +++ b/symforce/codegen/cam_package_codegen.py @@ -293,7 +293,8 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: all_types=list(geo_package_codegen.DEFAULT_GEO_TYPES) + list(DEFAULT_CAM_TYPES), numeric_epsilon=sf.numeric_epsilon, ), - output_path=cam_package_dir / "__init__.py", + output_path=cam_package_dir + / ("_init.py" if config.namespace_package else "__init__.py"), ) for name in ("cam_package_python_test.py",): diff --git a/symforce/codegen/codegen.py b/symforce/codegen/codegen.py index c8f82da25..bd380115d 100644 --- a/symforce/codegen/codegen.py +++ b/symforce/codegen/codegen.py @@ -484,7 +484,7 @@ def generate_function( # Namespace of this function + generated types self.namespace = namespace - template_data = dict(self.common_data(), spec=self) + template_data = dict(self.common_data(), spec=self, pkg_namespace=namespace) template_dir = self.config.template_dir() backend_name = self.config.backend_name() diff --git a/symforce/codegen/codegen_util.py b/symforce/codegen/codegen_util.py index 91af73a2d..295d0ff55 100644 --- a/symforce/codegen/codegen_util.py +++ b/symforce/codegen/codegen_util.py @@ -543,6 +543,13 @@ def _load_generated_package_internal(name: str, path: Path) -> T.Tuple[T.Any, T. def load_generated_package(name: str, path: T.Openable) -> T.Any: """ Dynamically load generated package (or module). + + Args: + name: The full name of the package or module to load (for example, "pkg.sub_pkg" + for a package called "sub_pkg" inside of another package "pkg", or + "pkg.sub_pkg.mod" for a module called "mod" inside of pkg.sub_pkg). + path: The path to the directory (or __init__.py) of the package, or the python + file of the module. """ # NOTE(brad): We remove all possibly conflicting modules from the cache. This is # to ensure that when name is executed, it loads local modules (if any) rather @@ -574,6 +581,35 @@ def load_generated_package(name: str, path: T.Openable) -> T.Any: return module +def load_generated_function(func_name: str, path_to_package: T.Openable) -> T.Callable: + """ + Returns the function with name func_name found inside the package located at + path_to_package. + + Example usage: + + def my_func(...): + ... + + my_codegen = Codegen.function(my_func, config=PythonConfig()) + codegen_data = my_codegen.generate_function(output_dir=output_dir) + generated_func = load_generated_function("my_func", codegen_data.function_dir) + generated_func(...) + + Preconditions: + path_to_package is a python package with an `__init__.py` containing a module + defined in `func_name.py` which in turn defines an attribute named `func_name`. + Note: the precondition will be satisfied if the package was generated by + `Codegen.generate_function` from a `Codegen` function with name `func_name`. + """ + pkg_path = Path(path_to_package) + if pkg_path.name == "__init__.py": + pkg_path = pkg_path.parent + pkg_name = pkg_path.name + func_module = load_generated_package(f"{pkg_name}.{func_name}", pkg_path / f"{func_name}.py") + return getattr(func_module, func_name) + + def load_generated_lcmtype( package: str, type_name: str, lcmtypes_path: T.Union[str, Path] ) -> T.Type: diff --git a/symforce/codegen/geo_package_codegen.py b/symforce/codegen/geo_package_codegen.py index 4e06adfc4..9aa892373 100644 --- a/symforce/codegen/geo_package_codegen.py +++ b/symforce/codegen/geo_package_codegen.py @@ -216,7 +216,19 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: data={}, ) + templates.add( + template_path=Path("util.py.jinja"), + output_path=package_dir / "util.py", + data={}, + ) + # Package init + if config.namespace_package: + templates.add( + template_path=Path("function", "namespace_init.py.jinja"), + data=dict(pkg_namespace="sym"), + output_path=package_dir / "__init__.py", + ) templates.add( template_path=Path("geo_package", "__init__.py.jinja"), data=dict( @@ -224,7 +236,7 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: all_types=DEFAULT_GEO_TYPES, numeric_epsilon=sf.numeric_epsilon, ), - output_path=package_dir / "__init__.py", + output_path=package_dir / ("_init.py" if config.namespace_package else "__init__.py"), ) # Test example diff --git a/symforce/opt/numeric_factor.py b/symforce/opt/numeric_factor.py index bb883a469..32671a7ba 100644 --- a/symforce/opt/numeric_factor.py +++ b/symforce/opt/numeric_factor.py @@ -75,10 +75,7 @@ def from_file_python( """ assert all(opt_key in keys for opt_key in optimized_keys) function_dir = Path(output_dir) / "python" / "symforce" / namespace - linearization_function = getattr( - codegen_util.load_generated_package(f"{namespace}.{name}", function_dir), - name, - ) + linearization_function = codegen_util.load_generated_function(name, function_dir) return cls( keys=keys, optimized_keys=optimized_keys, linearization_function=linearization_function ) diff --git a/test/geo_package_python_test.py b/test/geo_package_python_test.py index 32e78781b..a46f79434 100644 --- a/test/geo_package_python_test.py +++ b/test/geo_package_python_test.py @@ -129,8 +129,12 @@ def test_custom_methods_Rot2(self): element = geo_class.from_tangent(np.random.normal(size=tangent_dim)) vector = np.random.normal(size=(2, 1)) + vec_as_list = vector.flatten().tolist() + vec_as_tuple = tuple(vec_as_list) matrix = element.to_rotation_matrix() np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vector) + np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vec_as_list) + np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vec_as_tuple) # Test constructor handles column vectors correctly col_data = np.random.normal(size=(geo_class.storage_dim(), 1)) @@ -241,8 +245,16 @@ def test_custom_methods_Pose2(self): element = geo_class.from_tangent(np.random.normal(size=tangent_dim)) vector = np.random.normal(size=(2, 1)) + vec_as_list = vector.flatten().tolist() + vec_as_tuple = tuple(vec_as_list) vector_as_element = geo_class(t=vector.flatten().tolist()) np.testing.assert_almost_equal(element * vector, (element * vector_as_element).position()) + np.testing.assert_almost_equal( + element * vec_as_list, (element * vector_as_element).position() + ) + np.testing.assert_almost_equal( + element * vec_as_tuple, (element * vector_as_element).position() + ) # Test position/rotation accessors np.testing.assert_equal(element.position(), element.t) @@ -366,8 +378,12 @@ def test_custom_methods_Rot3(self): element = geo_class.from_tangent(np.random.normal(size=tangent_dim)) vector = np.random.normal(size=(3, 1)) + vec_as_list = vector.flatten().tolist() + vec_as_tuple = tuple(vec_as_list) matrix = element.to_rotation_matrix() np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vector) + np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vec_as_list) + np.testing.assert_almost_equal(np.matmul(matrix, vector), element * vec_as_tuple) # Test constructor handles column vectors correctly col_data = np.random.normal(size=(geo_class.storage_dim(), 1)) @@ -478,8 +494,16 @@ def test_custom_methods_Pose3(self): element = geo_class.from_tangent(np.random.normal(size=tangent_dim)) vector = np.random.normal(size=(3, 1)) + vec_as_list = vector.flatten().tolist() + vec_as_tuple = tuple(vec_as_list) vector_as_element = geo_class(t=vector.flatten().tolist()) np.testing.assert_almost_equal(element * vector, (element * vector_as_element).position()) + np.testing.assert_almost_equal( + element * vec_as_list, (element * vector_as_element).position() + ) + np.testing.assert_almost_equal( + element * vec_as_tuple, (element * vector_as_element).position() + ) # Test position/rotation accessors np.testing.assert_equal(element.position(), element.t) diff --git a/test/symforce_codegen_test.py b/test/symforce_codegen_test.py index 8e965c703..f77a3746e 100644 --- a/test/symforce_codegen_test.py +++ b/test/symforce_codegen_test.py @@ -143,8 +143,10 @@ def test_codegen_python(self) -> None: """ inputs, outputs = self.build_values() + config = codegen.PythonConfig(namespace_package=False) + python_func = codegen.Codegen( - inputs=inputs, outputs=outputs, config=codegen.PythonConfig(), name="python_function" + inputs=inputs, outputs=outputs, config=config, name="python_function" ) shared_types = { "values_vec": "values_vec_t", @@ -163,7 +165,7 @@ def test_codegen_python(self) -> None: actual_dir=output_dir, expected_dir=os.path.join(TEST_DATA_DIR, namespace + "_data") ) - geo_package_codegen.generate(config=codegen.PythonConfig(), output_dir=output_dir) + geo_package_codegen.generate(config=config, output_dir=output_dir) geo_pkg = codegen_util.load_generated_package( "sym", os.path.join(output_dir, "sym", "__init__.py") @@ -200,9 +202,11 @@ def test_codegen_python(self) -> None: big_matrix = np.zeros((5, 5)) - gen_module = codegen_util.load_generated_package(namespace, codegen_data.function_dir) + python_function = codegen_util.load_generated_function( + "python_function", codegen_data.function_dir + ) # TODO(nathan): Split this test into several different functions - (foo, bar, scalar_vec_out, values_vec_out, values_vec_2D_out) = gen_module.python_function( + (foo, bar, scalar_vec_out, values_vec_out, values_vec_2D_out) = python_function( x, y, rot, @@ -218,6 +222,28 @@ def test_codegen_python(self) -> None: self.assertStorageNear(foo, x ** 2 + rot.data[3]) self.assertStorageNear(bar, constants.epsilon + sf.sin(y) + x ** 2) + def test_return_geo_type_from_generated_python_function(self) -> None: + """ + Tests that the function (returning a Rot3) generated by codegen.Codegen.generate_function() + with the default PythonConfig can be called. + When test was created, if you tried to do this, the error: + AttributeError: module 'sym' has no attribute 'Rot3' + would be raised. + """ + + def identity() -> sf.Rot3: + return sf.Rot3.identity() + + output_dir = self.make_output_dir("sf_test_return_geo_type_from_generated_python_function") + + codegen_data = codegen.Codegen.function( + func=identity, config=codegen.PythonConfig() + ).generate_function(output_dir=output_dir) + + gen_identity = codegen_util.load_generated_function("identity", codegen_data.function_dir) + + gen_identity() + def test_matrix_order_python(self) -> None: """ Tests that codegen.Codegen.generate_function() renders matrices correctly @@ -243,10 +269,12 @@ def matrix_order() -> sf.M23: func=matrix_order, config=codegen.PythonConfig() ).generate_function(namespace=namespace, output_dir=output_dir) - pkg = codegen_util.load_generated_package(namespace, codegen_data.function_dir) + gen_matrix_order = codegen_util.load_generated_function( + "matrix_order", codegen_data.function_dir + ) - self.assertEqual(pkg.matrix_order().shape, m23.SHAPE) - self.assertStorageNear(pkg.matrix_order(), m23) + self.assertEqual(gen_matrix_order().shape, m23.SHAPE) + self.assertStorageNear(gen_matrix_order(), m23) def test_matrix_indexing_python(self) -> None: """ @@ -270,9 +298,11 @@ def gen_pass_matrices(use_numba: bool, reshape_vectors: bool) -> T.Any: output_names=["row_out", "col_out", "mat_out"], ).generate_function(namespace=namespace, output_dir=output_dir) - pkg = codegen_util.load_generated_package(namespace, generated_files.function_dir) + genned_func = codegen_util.load_generated_function( + "pass_matrices", generated_files.function_dir + ) - return pkg.pass_matrices + return genned_func def assert_config_works( use_numba: bool, @@ -496,13 +526,42 @@ def assert_config_works( use_numba, reshape_vectors, row_shape, col_shape, mat_shape, TypingError ) + # --------------------------------------------------------------------- + + with self.subTest( + msg="If reshape_vectors=True and use_numba=False, lists are accepted for vec args" + ): + generated_pass_matrices = gen_pass_matrices(use_numba=False, reshape_vectors=True) + + row = [1, 2, 3, 4] + col = [5, 6, 7, 8] + mat = np.random.random((2, 2)) + + out_row, out_col, out_mat = generated_pass_matrices(row, col, mat) + self.assertEqual(out_row.shape, (1, 4)) + np.testing.assert_array_equal(row, out_row.flatten()) + self.assertEqual(out_col.shape, (4, 1)) + np.testing.assert_array_equal(col, out_col.flatten()) + np.testing.assert_array_equal(mat, out_mat) + + # --------------------------------------------------------------------- + + with self.subTest(msg="IndexError is raised if a list vector arg is too long"): + generated_pass_matrices = gen_pass_matrices(use_numba=False, reshape_vectors=True) + + row = [1, 2, 3, 4, 5] + col = [5, 6, 7, 8] + mat = np.random.random((2, 2)) + + with self.assertRaises(IndexError): + generated_pass_matrices(row, col, mat) + def test_sparse_output_python(self) -> None: """ Tests that sparse matrices are correctly generated in python when sparse_matrices argument of codegen.Codegen.__init__ is set appropriately. """ output_dir = self.make_output_dir("sf_test_sparse_output_python") - namespace = "sparse_output_python" x, y, z = sf.symbols("x y z") def matrix_output(x: sf.Scalar, y: sf.Scalar, z: sf.Scalar) -> T.List[T.List[sf.Scalar]]: @@ -514,11 +573,13 @@ def matrix_output(x: sf.Scalar, y: sf.Scalar, z: sf.Scalar) -> T.List[T.List[sf. name="sparse_output_func", config=codegen.PythonConfig(), sparse_matrices=["out"], - ).generate_function(namespace=namespace, output_dir=output_dir) + ).generate_function(namespace="sparse_output_python", output_dir=output_dir) - pkg = codegen_util.load_generated_package(namespace, codegen_data.function_dir) + sparse_output_func = codegen_util.load_generated_function( + "sparse_output_func", codegen_data.function_dir + ) - output = pkg.sparse_output_func(1, 2, 3) + output = sparse_output_func(1, 2, 3) self.assertIsInstance(output, sparse.csc_matrix) self.assertTrue((output.todense() == matrix_output(1, 2, 3)).all()) @@ -557,14 +618,14 @@ def numba_test_func(x: sf.V3) -> sf.V2: output_function = numba_test_func_codegen_data.function_dir / "numba_test_func.py" self.compare_or_update_file(expected_code_file, output_function) - gen_module = codegen_util.load_generated_package( - "sym", numba_test_func_codegen_data.function_dir + gen_func = codegen_util.load_generated_function( + "numba_test_func", numba_test_func_codegen_data.function_dir ) x = np.array([1, 2, 3]) - y = gen_module.numba_test_func(x) + y = gen_func(x) self.assertTrue((y == np.array([[1, 2]]).T).all()) - self.assertTrue(hasattr(gen_module.numba_test_func, "__numba__")) + self.assertTrue(hasattr(gen_func, "__numba__")) # ------------------------------------------------------------------------- # C++ @@ -1072,7 +1133,7 @@ def test_function_dataclass(dataclass: TestDataclass1, x: sf.Scalar) -> sf.V3: func=test_function_dataclass, config=codegen.PythonConfig() ) dataclass_codegen_data = dataclass_codegen.generate_function() - gen_module = codegen_util.load_generated_package( + gen_func = codegen_util.load_generated_function( "test_function_dataclass", dataclass_codegen_data.function_dir ) @@ -1083,7 +1144,7 @@ def test_function_dataclass(dataclass: TestDataclass1, x: sf.Scalar) -> sf.V3: dataclass_t.v2.v0 = 1 # make sure it runs - gen_module.test_function_dataclass(dataclass_t, 1) + gen_func(dataclass_t, 1) @slow_on_sympy def test_function_explicit_template_instantiation(self) -> None: @@ -1140,11 +1201,11 @@ class MyDataclass: ) # Make sure it runs - gen_module = codegen_util.load_generated_package(namespace, codegen_data.function_dir) + gen_func = codegen_util.load_generated_function(name, codegen_data.function_dir) my_dataclass_t = codegen_util.load_generated_lcmtype( namespace, "my_dataclass_t", codegen_data.python_types_dir )() - return_rot = gen_module.codegen_dataclass_in_values_test(my_dataclass_t) + return_rot = gen_func(my_dataclass_t) self.assertEqual(return_rot.data, my_dataclass_t.rot.data) diff --git a/test/symforce_codegen_util_test.py b/test/symforce_codegen_util_test.py index 017392b98..e9872ea75 100644 --- a/test/symforce_codegen_util_test.py +++ b/test/symforce_codegen_util_test.py @@ -10,6 +10,10 @@ from symforce.codegen import codegen_util from symforce.test_util import TestCase +PKG_LOCATIONS = Path(__file__).parent / "test_data" / "codegen_util_test_data" +RELATIVE_PATH = Path("example_pkg", "__init__.py") +PACKAGE_NAME = "example_pkg" + class SymforceCodegenUtilTest(TestCase): """ @@ -22,14 +26,8 @@ def test_load_generated_package(self) -> None: codegen_util.load_generated_package """ - pkg_locations = Path(__file__).parent / "test_data" / "codegen_util_test_data" - - relative_path = Path("example_pkg", "__init__.py") - - package_name = "example_pkg" - pkg1 = codegen_util.load_generated_package( - name=package_name, path=pkg_locations / "example_pkg_1" / relative_path + name=PACKAGE_NAME, path=PKG_LOCATIONS / "example_pkg_1" / RELATIVE_PATH ) # Testing that the module was loaded correctly @@ -37,10 +35,10 @@ def test_load_generated_package(self) -> None: self.assertEqual(pkg1.sub_module.sub_module_id, 1) # Testing that sys.modules was not polluted - self.assertFalse(package_name in sys.modules) + self.assertFalse(PACKAGE_NAME in sys.modules) pkg2 = codegen_util.load_generated_package( - name=package_name, path=pkg_locations / "example_pkg_2" / relative_path + name=PACKAGE_NAME, path=PKG_LOCATIONS / "example_pkg_2" / RELATIVE_PATH ) # Testing that the module was loaded correctly when a module with the same name has @@ -48,6 +46,30 @@ def test_load_generated_package(self) -> None: self.assertEqual(pkg2.package_id, 2) self.assertEqual(pkg2.sub_module.sub_module_id, 2) + def test_load_generated_function(self) -> None: + """ + Tests: + codegen_util.load_generated_function + """ + + func1 = codegen_util.load_generated_function( + func_name="func", path_to_package=PKG_LOCATIONS / "example_pkg_1" / RELATIVE_PATH + ) + + # Testing that the function was loaded correctly + self.assertEqual(func1(), 1) + + # Testing that sys.modules was not polluted + self.assertFalse(PACKAGE_NAME in sys.modules) + + func2 = codegen_util.load_generated_function( + func_name="func", path_to_package=PKG_LOCATIONS / "example_pkg_2" / RELATIVE_PATH + ) + + # Testing that the function was loaded correctly when a function of the same name + # has already been loaded. + self.assertEqual(func2(), 2) + if __name__ == "__main__": TestCase.main() diff --git a/test/symforce_databuffer_codegen_test.py b/test/symforce_databuffer_codegen_test.py index f867ff582..73dc7b53d 100644 --- a/test/symforce_databuffer_codegen_test.py +++ b/test/symforce_databuffer_codegen_test.py @@ -70,7 +70,7 @@ def gen_code(self, output_dir: str) -> None: ) # Also test that the generated python code runs - gen_module = codegen_util.load_generated_package( + buffer_func = codegen_util.load_generated_function( "buffer_func", py_codegen_data.function_dir, ) @@ -81,7 +81,7 @@ def gen_code(self, output_dir: str) -> None: # 2 * buffer[b^2 - a^2] + (a+b) # 2 * buffer[3] + 3 expected = 9 - result_numeric = gen_module.buffer_func(buffer_numeric, a_numeric, b_numeric) + result_numeric = buffer_func(buffer_numeric, a_numeric, b_numeric) self.assertStorageNear(expected, result_numeric) diff --git a/test/symforce_function_codegen_test_data/symengine/az_el_from_point.py b/test/symforce_function_codegen_test_data/symengine/az_el_from_point.py index 8cf6d215c..eb6893c3f 100644 --- a/test/symforce_function_codegen_test_data/symengine/az_el_from_point.py +++ b/test/symforce_function_codegen_test_data/symengine/az_el_from_point.py @@ -10,12 +10,13 @@ import numpy # pylint: disable=unused-import import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument def az_el_from_point(nav_T_cam, nav_t_point, epsilon): - # type: (sym.Pose3, numpy.ndarray, float) -> numpy.ndarray + # type: (sym.Pose3, T.Union[T.Sequence[float], numpy.ndarray], float) -> numpy.ndarray """ Transform a nav point into azimuth / elevation angles in the camera frame. @@ -33,14 +34,7 @@ def az_el_from_point(nav_T_cam, nav_t_point, epsilon): # Input arrays _nav_T_cam = nav_T_cam.data - if nav_t_point.shape == (3,): - nav_t_point = nav_t_point.reshape((3, 1)) - elif nav_t_point.shape != (3, 1): - raise IndexError( - "nav_t_point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - nav_t_point.shape - ) - ) + nav_t_point = check_size_and_reshape(nav_t_point, "nav_t_point", (3, 1)) # Intermediate terms (23) _tmp0 = 2 * _nav_T_cam[0] diff --git a/test/symforce_function_codegen_test_data/symengine/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/__init__.py b/test/symforce_function_codegen_test_data/symengine/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/__init__.py index 5b40fc66c..6c7d316e1 100644 --- a/test/symforce_function_codegen_test_data/symengine/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/__init__.py +++ b/test/symforce_function_codegen_test_data/symengine/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/__init__.py @@ -1,7 +1,9 @@ # ----------------------------------------------------------------------------- # This file was autogenerated by symforce from template: -# function/__init__.py.jinja +# function/namespace_init.py.jinja # Do NOT modify by hand. # ----------------------------------------------------------------------------- -from .codegen_dataclass_in_values_test import codegen_dataclass_in_values_test +# Make package a namespace package by adding other portions to the __path__ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore[has-type] +# https://github.com/python/mypy/issues/1422 diff --git a/test/symforce_function_codegen_test_data/symengine/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/codegen_dataclass_in_values_test.py b/test/symforce_function_codegen_test_data/symengine/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/codegen_dataclass_in_values_test.py index 836059c42..1e1138e5e 100644 --- a/test/symforce_function_codegen_test_data/symengine/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/codegen_dataclass_in_values_test.py +++ b/test/symforce_function_codegen_test_data/symengine/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/codegen_dataclass_in_values_test.py @@ -10,6 +10,7 @@ import numpy # pylint: disable=unused-import import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument diff --git a/test/symforce_function_codegen_test_data/symengine/codegen_python_test_data/python/symforce/codegen_python_test/__init__.py b/test/symforce_function_codegen_test_data/symengine/codegen_python_test_data/python/symforce/codegen_python_test/__init__.py index a3db6f52f..3148fc07b 100644 --- a/test/symforce_function_codegen_test_data/symengine/codegen_python_test_data/python/symforce/codegen_python_test/__init__.py +++ b/test/symforce_function_codegen_test_data/symengine/codegen_python_test_data/python/symforce/codegen_python_test/__init__.py @@ -3,5 +3,3 @@ # function/__init__.py.jinja # Do NOT modify by hand. # ----------------------------------------------------------------------------- - -from .python_function import python_function diff --git a/test/symforce_function_codegen_test_data/symengine/codegen_python_test_data/python/symforce/codegen_python_test/python_function.py b/test/symforce_function_codegen_test_data/symengine/codegen_python_test_data/python/symforce/codegen_python_test/python_function.py index 099c1c520..4ea8f5031 100644 --- a/test/symforce_function_codegen_test_data/symengine/codegen_python_test_data/python/symforce/codegen_python_test/python_function.py +++ b/test/symforce_function_codegen_test_data/symengine/codegen_python_test_data/python/symforce/codegen_python_test/python_function.py @@ -10,6 +10,7 @@ import numpy # pylint: disable=unused-import import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument diff --git a/test/symforce_function_codegen_test_data/symengine/databuffer_codegen_test_data/python/symforce/buffer_test/__init__.py b/test/symforce_function_codegen_test_data/symengine/databuffer_codegen_test_data/python/symforce/buffer_test/__init__.py index b7c3d4456..6c7d316e1 100644 --- a/test/symforce_function_codegen_test_data/symengine/databuffer_codegen_test_data/python/symforce/buffer_test/__init__.py +++ b/test/symforce_function_codegen_test_data/symengine/databuffer_codegen_test_data/python/symforce/buffer_test/__init__.py @@ -1,7 +1,9 @@ # ----------------------------------------------------------------------------- # This file was autogenerated by symforce from template: -# function/__init__.py.jinja +# function/namespace_init.py.jinja # Do NOT modify by hand. # ----------------------------------------------------------------------------- -from .buffer_func import buffer_func +# Make package a namespace package by adding other portions to the __path__ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore[has-type] +# https://github.com/python/mypy/issues/1422 diff --git a/test/symforce_function_codegen_test_data/symengine/databuffer_codegen_test_data/python/symforce/buffer_test/buffer_func.py b/test/symforce_function_codegen_test_data/symengine/databuffer_codegen_test_data/python/symforce/buffer_test/buffer_func.py index c0500d632..7ee224c85 100644 --- a/test/symforce_function_codegen_test_data/symengine/databuffer_codegen_test_data/python/symforce/buffer_test/buffer_func.py +++ b/test/symforce_function_codegen_test_data/symengine/databuffer_codegen_test_data/python/symforce/buffer_test/buffer_func.py @@ -10,6 +10,7 @@ import numpy # pylint: disable=unused-import import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument diff --git a/test/symforce_function_codegen_test_data/sympy/az_el_from_point.py b/test/symforce_function_codegen_test_data/sympy/az_el_from_point.py index 67b248376..849046ff3 100644 --- a/test/symforce_function_codegen_test_data/sympy/az_el_from_point.py +++ b/test/symforce_function_codegen_test_data/sympy/az_el_from_point.py @@ -10,12 +10,13 @@ import numpy # pylint: disable=unused-import import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument def az_el_from_point(nav_T_cam, nav_t_point, epsilon): - # type: (sym.Pose3, numpy.ndarray, float) -> numpy.ndarray + # type: (sym.Pose3, T.Union[T.Sequence[float], numpy.ndarray], float) -> numpy.ndarray """ Transform a nav point into azimuth / elevation angles in the camera frame. @@ -33,14 +34,7 @@ def az_el_from_point(nav_T_cam, nav_t_point, epsilon): # Input arrays _nav_T_cam = nav_T_cam.data - if nav_t_point.shape == (3,): - nav_t_point = nav_t_point.reshape((3, 1)) - elif nav_t_point.shape != (3, 1): - raise IndexError( - "nav_t_point is expected to have shape (3, 1) or (3,); instead had shape {}".format( - nav_t_point.shape - ) - ) + nav_t_point = check_size_and_reshape(nav_t_point, "nav_t_point", (3, 1)) # Intermediate terms (23) _tmp0 = 2 * _nav_T_cam[3] diff --git a/test/symforce_function_codegen_test_data/sympy/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/__init__.py b/test/symforce_function_codegen_test_data/sympy/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/__init__.py index 5b40fc66c..6c7d316e1 100644 --- a/test/symforce_function_codegen_test_data/sympy/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/__init__.py +++ b/test/symforce_function_codegen_test_data/sympy/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/__init__.py @@ -1,7 +1,9 @@ # ----------------------------------------------------------------------------- # This file was autogenerated by symforce from template: -# function/__init__.py.jinja +# function/namespace_init.py.jinja # Do NOT modify by hand. # ----------------------------------------------------------------------------- -from .codegen_dataclass_in_values_test import codegen_dataclass_in_values_test +# Make package a namespace package by adding other portions to the __path__ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore[has-type] +# https://github.com/python/mypy/issues/1422 diff --git a/test/symforce_function_codegen_test_data/sympy/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/codegen_dataclass_in_values_test.py b/test/symforce_function_codegen_test_data/sympy/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/codegen_dataclass_in_values_test.py index 836059c42..1e1138e5e 100644 --- a/test/symforce_function_codegen_test_data/sympy/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/codegen_dataclass_in_values_test.py +++ b/test/symforce_function_codegen_test_data/sympy/codegen_dataclass_in_values_test_data/python/symforce/codegen_test/codegen_dataclass_in_values_test.py @@ -10,6 +10,7 @@ import numpy # pylint: disable=unused-import import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument diff --git a/test/symforce_function_codegen_test_data/sympy/codegen_python_test_data/python/symforce/codegen_python_test/__init__.py b/test/symforce_function_codegen_test_data/sympy/codegen_python_test_data/python/symforce/codegen_python_test/__init__.py index a3db6f52f..3148fc07b 100644 --- a/test/symforce_function_codegen_test_data/sympy/codegen_python_test_data/python/symforce/codegen_python_test/__init__.py +++ b/test/symforce_function_codegen_test_data/sympy/codegen_python_test_data/python/symforce/codegen_python_test/__init__.py @@ -3,5 +3,3 @@ # function/__init__.py.jinja # Do NOT modify by hand. # ----------------------------------------------------------------------------- - -from .python_function import python_function diff --git a/test/symforce_function_codegen_test_data/sympy/codegen_python_test_data/python/symforce/codegen_python_test/python_function.py b/test/symforce_function_codegen_test_data/sympy/codegen_python_test_data/python/symforce/codegen_python_test/python_function.py index 099c1c520..4ea8f5031 100644 --- a/test/symforce_function_codegen_test_data/sympy/codegen_python_test_data/python/symforce/codegen_python_test/python_function.py +++ b/test/symforce_function_codegen_test_data/sympy/codegen_python_test_data/python/symforce/codegen_python_test/python_function.py @@ -10,6 +10,7 @@ import numpy # pylint: disable=unused-import import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument diff --git a/test/symforce_function_codegen_test_data/sympy/databuffer_codegen_test_data/python/symforce/buffer_test/__init__.py b/test/symforce_function_codegen_test_data/sympy/databuffer_codegen_test_data/python/symforce/buffer_test/__init__.py index b7c3d4456..6c7d316e1 100644 --- a/test/symforce_function_codegen_test_data/sympy/databuffer_codegen_test_data/python/symforce/buffer_test/__init__.py +++ b/test/symforce_function_codegen_test_data/sympy/databuffer_codegen_test_data/python/symforce/buffer_test/__init__.py @@ -1,7 +1,9 @@ # ----------------------------------------------------------------------------- # This file was autogenerated by symforce from template: -# function/__init__.py.jinja +# function/namespace_init.py.jinja # Do NOT modify by hand. # ----------------------------------------------------------------------------- -from .buffer_func import buffer_func +# Make package a namespace package by adding other portions to the __path__ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore[has-type] +# https://github.com/python/mypy/issues/1422 diff --git a/test/symforce_function_codegen_test_data/sympy/databuffer_codegen_test_data/python/symforce/buffer_test/buffer_func.py b/test/symforce_function_codegen_test_data/sympy/databuffer_codegen_test_data/python/symforce/buffer_test/buffer_func.py index 61cf7a2d1..2d8f9ce86 100644 --- a/test/symforce_function_codegen_test_data/sympy/databuffer_codegen_test_data/python/symforce/buffer_test/buffer_func.py +++ b/test/symforce_function_codegen_test_data/sympy/databuffer_codegen_test_data/python/symforce/buffer_test/buffer_func.py @@ -10,6 +10,7 @@ import numpy # pylint: disable=unused-import import sym # pylint: disable=unused-import +from sym.util import check_size_and_reshape # pylint: disable=too-many-locals,too-many-lines,too-many-statements,unused-argument diff --git a/test/test_data/codegen_util_test_data/example_pkg_1/example_pkg/func.py b/test/test_data/codegen_util_test_data/example_pkg_1/example_pkg/func.py new file mode 100644 index 000000000..208476796 --- /dev/null +++ b/test/test_data/codegen_util_test_data/example_pkg_1/example_pkg/func.py @@ -0,0 +1,8 @@ +# ---------------------------------------------------------------------------- +# SymForce - Copyright 2022, Skydio, Inc. +# This source code is under the Apache 2.0 license found in the LICENSE file. +# ---------------------------------------------------------------------------- + + +def func() -> int: + return 1 diff --git a/test/test_data/codegen_util_test_data/example_pkg_2/example_pkg/func.py b/test/test_data/codegen_util_test_data/example_pkg_2/example_pkg/func.py new file mode 100644 index 000000000..a0ae2aa52 --- /dev/null +++ b/test/test_data/codegen_util_test_data/example_pkg_2/example_pkg/func.py @@ -0,0 +1,8 @@ +# ---------------------------------------------------------------------------- +# SymForce - Copyright 2022, Skydio, Inc. +# This source code is under the Apache 2.0 license found in the LICENSE file. +# ---------------------------------------------------------------------------- + + +def func() -> int: + return 2