From 7f7457974de70cc51f8ca072ba97899392804412 Mon Sep 17 00:00:00 2001 From: bradley-solliday-skydio Date: Wed, 2 Nov 2022 16:34:44 -0700 Subject: [PATCH 1/5] Add codegen_util.load_generated_function Defines the new function load_generated_function which is meant to be a more user friendly version of `load_generated_package` (which will be particularly more friendly should we no longer re-export all sub-modules of generated packages). Also, explains the arguments of `load_generated_package` in its doc-string. As an example of the expected usage of this function, for `co` a `Codegen` object: ``` python3 generated_paths = co.generate_function() func = load_generated_function(co.name, generated_paths.function_dir) ``` Tested in `test/symforce_codegen_util_test.py` --- symforce/codegen/codegen_util.py | 36 +++++++++++++++++ test/symforce_codegen_util_test.py | 40 ++++++++++++++----- .../example_pkg_1/example_pkg/func.py | 8 ++++ .../example_pkg_2/example_pkg/func.py | 8 ++++ 4 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 test/test_data/codegen_util_test_data/example_pkg_1/example_pkg/func.py create mode 100644 test/test_data/codegen_util_test_data/example_pkg_2/example_pkg/func.py 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/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/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 From 52bacd05e894b9f6c0b390e0337292275f40a3ba Mon Sep 17 00:00:00 2001 From: bradley-solliday-skydio Date: Mon, 31 Oct 2022 18:57:06 -0700 Subject: [PATCH 2/5] Make sym and other genned pkgs namespace packages Namespace packages are packages whose `__path__` (which tells the python import system to look for sub-packages and sub-modules) has multiple directories, meaning it can have portions spread out across multiple directories. Previously, `sym` and the other generated packages were not namespace packages. This caused issues when generated python packages in the `sym` namespace attempted to access, for example, `sym.Rot3` (which they might do if the generated function returns a `sym.Rot3`). Since the generated package itself was named `sym`, but `Rot3` was not defined locally, an `AttributeError` would be raised. While an alternative would have been to instead not use the name `sym` for generated packages (using, say, `sym_gen` instead), for reasons I didn't fully look into, we still want to generate our code within the `sym` namespace (generating into `sym.gen` was considered as an option but to do so would require the changes in this commit regardless). While currently we haven't needed to generate any functions returning a `sym` class in a package named `sym`, we intend to soon add a `sym.util` package which will be used in a lot of places. That can't be done until this namespace conflict is resolved. Note, while a python2 compatible namespace package has multiple `__init__.py` files for the top-level package spread across different locations, only one of them will be executed. This makes it difficult to re-export the contents of sub-modules into the top-level namespace. The normal way to re-export a name is to write ``` python3 from .sub_module import name ``` However, since the sub-modules can be created dynamically, it is impossible to re-export all names in this manner, as the first `__init__.py` that is created has no way of knowing what names one might want to re-export from subsequent modules. It is possible to put all names one wishes to export in a standard file, say `_init.py`, then dynamically search for such files and execute their contents, but we considered the additional complexity to be too large of a burden (as users would have a harder time understand their generated code, and this would give future maintainers a hard time). And so, we decided to simply stop re-exporting any names in the `__init__.py`'s of generated code (kind of in the style of pep 420 python3 packages). This makes loading a generated function more difficult if one uses `codegen_util.load_generated_package`, as now simply importing a generated package won't give you access to any of the package's contents. However, this is what `codegen_util.load_generated_function` is for, so hopefully the user experience shouldn't be too negatively impacted. The one exception to the general ban of re-exporting names is the `sym` package, as we still wish to be able to do ``` python3 import sym sym.Rot3.identity() ``` However, because all sub-modules we wish to export from the `sym` package are known at code-gen time, allowing this is not difficult. This only applies to names in the core `sym` package, and any additional user generated code in the `sym` package will not be re-rexported in the top-level namespace. A user can prevent their package from being generated as a namespace package by setting the `namespace_package` field of their `PythonConfig` to `False`. This is useful in our testing as it is the generated code being tested that is imported, not, for example, the checked in `sym` package code which is being imported. As a last note, I believe `pkgutil.extend_path` only checks for portions of the package on the `sys.path`, and doesn't check for any portions than can only be found by finders on the `sys.meta_path` (for example, `symforce` itself is found by a finder on the `sys.meta_path` but not on the `sys.path` during an editable pip install). I don't expect this lapse to pose a problem, and addressing it immediately might just make the `__init__.py`s look more complicated than they need to be, but if this does become a problem, know that the situation can be partially addressed trying to find the spec using the finders, and adding the spec's `submodule_search_locations` if found to the `__path__`. --- gen/python/sym/__init__.py | 17 ++--- gen/python/sym/_init.py | 21 ++++++ notebooks/tutorials/codegen_tutorial.ipynb | 10 +-- .../codegen/backends/python/python_config.py | 12 ++- .../templates/function/__init__.py.jinja | 1 - .../function/namespace_init.py.jinja | 16 ++++ symforce/codegen/cam_package_codegen.py | 3 +- symforce/codegen/codegen.py | 2 +- symforce/codegen/geo_package_codegen.py | 8 +- symforce/opt/numeric_factor.py | 5 +- test/symforce_codegen_test.py | 73 +++++++++++++------ test/symforce_databuffer_codegen_test.py | 4 +- .../python/symforce/codegen_test/__init__.py | 6 +- .../symforce/codegen_python_test/__init__.py | 2 - .../python/symforce/buffer_test/__init__.py | 6 +- .../python/symforce/codegen_test/__init__.py | 6 +- .../symforce/codegen_python_test/__init__.py | 2 - .../python/symforce/buffer_test/__init__.py | 6 +- 18 files changed, 136 insertions(+), 64 deletions(-) create mode 100644 gen/python/sym/_init.py create mode 100644 symforce/codegen/backends/python/templates/function/namespace_init.py.jinja 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/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/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/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/geo_package_codegen.py b/symforce/codegen/geo_package_codegen.py index 4e06adfc4..0b1c36b04 100644 --- a/symforce/codegen/geo_package_codegen.py +++ b/symforce/codegen/geo_package_codegen.py @@ -217,6 +217,12 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: ) # 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 +230,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/symforce_codegen_test.py b/test/symforce_codegen_test.py index 8e965c703..efc88db89 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, @@ -502,7 +532,6 @@ def test_sparse_output_python(self) -> None: 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 +543,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 +588,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 +1103,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 +1114,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 +1171,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_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/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_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/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/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_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/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 From 1047c1e414ec4ba42015f60e61886b2521d8f52a Mon Sep 17 00:00:00 2001 From: bradley-solliday-skydio Date: Wed, 19 Oct 2022 14:48:09 -0700 Subject: [PATCH 3/5] Accept list vec args in genned code by default By default, I mean if `use_numba=False` and `reshape_vecs=True`. Previously, only ndarrays could be passed as vector args to generated code. However, lists in python are ubiquitous and natural, so it would be good for our generated functions to accept them. So, this commit allows generated functions to accept lists for matrix arguments when the argument is either a row vector or a column vector. Nested lists are not accepted to represent matrices because I simply did not consider that until right now. (Perhaps we should add that?) `PythonConfig.use_numba` must be `False` because list arguments have been deprecated in numba (instead you should use `numba.typed.List`, but if you have to use that you might as well use `numpy.ndarray` as far as I can tell). `PythonConfig.reshape_vecs` must be `True` because accepting lists requires conversion to an ndarray and reshaping, which is presumably more or less the entire thing meant to be avoided by setting `reshape_vecs` to `False`. This change did involve mucking up a bit the python `util.jinja` type printing macros, as a `sf.Matrix` type should be rendered as an `numpy.ndarray` is it is a return type, `reshape_vectors=False`, `use_numba=True`, or is not a row or column vector, and rendered as `T.Union[T.Sequence[float], numpy.ndarray]` otherwise. --- gen/python/sym/atan_camera_cal.py | 8 ++-- gen/python/sym/double_sphere_camera_cal.py | 8 ++-- gen/python/sym/equirectangular_camera_cal.py | 8 ++-- gen/python/sym/linear_camera_cal.py | 8 ++-- .../sym/ops/atan_camera_cal/camera_ops.py | 40 +++++++++++++++---- .../sym/ops/atan_camera_cal/lie_group_ops.py | 20 ++++++++-- .../double_sphere_camera_cal/camera_ops.py | 40 +++++++++++++++---- .../double_sphere_camera_cal/lie_group_ops.py | 20 ++++++++-- .../equirectangular_camera_cal/camera_ops.py | 40 +++++++++++++++---- .../lie_group_ops.py | 20 ++++++++-- .../sym/ops/linear_camera_cal/camera_ops.py | 40 +++++++++++++++---- .../ops/linear_camera_cal/lie_group_ops.py | 20 ++++++++-- .../ops/polynomial_camera_cal/camera_ops.py | 20 ++++++++-- .../polynomial_camera_cal/lie_group_ops.py | 20 ++++++++-- gen/python/sym/ops/pose2/lie_group_ops.py | 20 ++++++++-- gen/python/sym/ops/pose3/lie_group_ops.py | 20 ++++++++-- gen/python/sym/ops/rot2/lie_group_ops.py | 20 ++++++++-- gen/python/sym/ops/rot3/lie_group_ops.py | 20 ++++++++-- .../ops/spherical_camera_cal/camera_ops.py | 20 ++++++++-- .../ops/spherical_camera_cal/lie_group_ops.py | 20 ++++++++-- gen/python/sym/polynomial_camera_cal.py | 4 +- gen/python/sym/pose2.py | 20 ++++++++-- gen/python/sym/pose3.py | 20 ++++++++-- gen/python/sym/rot2.py | 10 ++++- gen/python/sym/rot3.py | 10 ++++- gen/python/sym/spherical_camera_cal.py | 4 +- .../backends/python/templates/util/util.jinja | 28 +++++++++---- test/symforce_codegen_test.py | 30 ++++++++++++++ .../symengine/az_el_from_point.py | 12 +++++- .../sympy/az_el_from_point.py | 12 +++++- 30 files changed, 459 insertions(+), 123 deletions(-) diff --git a/gen/python/sym/atan_camera_cal.py b/gen/python/sym/atan_camera_cal.py index 327ef4150..86936343d 100644 --- a/gen/python/sym/atan_camera_cal.py +++ b/gen/python/sym/atan_camera_cal.py @@ -93,7 +93,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 +105,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 +119,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 +133,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. diff --git a/gen/python/sym/double_sphere_camera_cal.py b/gen/python/sym/double_sphere_camera_cal.py index b762cd429..6362dd58b 100644 --- a/gen/python/sym/double_sphere_camera_cal.py +++ b/gen/python/sym/double_sphere_camera_cal.py @@ -106,7 +106,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 +118,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 +132,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 +146,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. diff --git a/gen/python/sym/equirectangular_camera_cal.py b/gen/python/sym/equirectangular_camera_cal.py index 70cbe6502..e482ac75e 100644 --- a/gen/python/sym/equirectangular_camera_cal.py +++ b/gen/python/sym/equirectangular_camera_cal.py @@ -88,7 +88,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 +100,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 +114,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 +128,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. diff --git a/gen/python/sym/linear_camera_cal.py b/gen/python/sym/linear_camera_cal.py index 0c686ea70..44f8f33e0 100644 --- a/gen/python/sym/linear_camera_cal.py +++ b/gen/python/sym/linear_camera_cal.py @@ -88,7 +88,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 +100,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 +114,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 +128,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. 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..4ef74d817 100644 --- a/gen/python/sym/ops/atan_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/atan_camera_cal/camera_ops.py @@ -59,7 +59,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,7 +72,13 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -96,7 +102,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,7 +117,13 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -197,7 +209,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,7 +224,13 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): + if not isinstance(pixel, numpy.ndarray): + if len(pixel) != 2: + raise IndexError( + "pixel is expected to have length 2; instead had length {}".format(len(pixel)) + ) + pixel = numpy.array(pixel).reshape((2, 1)) + elif pixel.shape == (2,): pixel = pixel.reshape((2, 1)) elif pixel.shape != (2, 1): raise IndexError( @@ -245,7 +263,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,7 +278,13 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): + if not isinstance(pixel, numpy.ndarray): + if len(pixel) != 2: + raise IndexError( + "pixel is expected to have length 2; instead had length {}".format(len(pixel)) + ) + pixel = numpy.array(pixel).reshape((2, 1)) + elif pixel.shape == (2,): pixel = pixel.reshape((2, 1)) elif pixel.shape != (2, 1): raise IndexError( 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..7525dee17 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 @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 5: + raise IndexError( + "vec is expected to have length 5; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((5, 1)) + elif vec.shape == (5,): vec = vec.reshape((5, 1)) elif vec.shape != (5, 1): raise IndexError( @@ -66,13 +72,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 5: + raise IndexError( + "vec is expected to have length 5; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((5, 1)) + elif vec.shape == (5,): vec = vec.reshape((5, 1)) elif vec.shape != (5, 1): raise IndexError( 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..0d97a42b5 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 @@ -59,7 +59,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,7 +72,13 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -149,7 +155,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,7 +170,13 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -284,7 +296,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,7 +311,13 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): + if not isinstance(pixel, numpy.ndarray): + if len(pixel) != 2: + raise IndexError( + "pixel is expected to have length 2; instead had length {}".format(len(pixel)) + ) + pixel = numpy.array(pixel).reshape((2, 1)) + elif pixel.shape == (2,): pixel = pixel.reshape((2, 1)) elif pixel.shape != (2, 1): raise IndexError( @@ -337,7 +355,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,7 +370,13 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): + if not isinstance(pixel, numpy.ndarray): + if len(pixel) != 2: + raise IndexError( + "pixel is expected to have length 2; instead had length {}".format(len(pixel)) + ) + pixel = numpy.array(pixel).reshape((2, 1)) + elif pixel.shape == (2,): pixel = pixel.reshape((2, 1)) elif pixel.shape != (2, 1): raise IndexError( 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..7546e09bc 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 @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 6: + raise IndexError( + "vec is expected to have length 6; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((6, 1)) + elif vec.shape == (6,): vec = vec.reshape((6, 1)) elif vec.shape != (6, 1): raise IndexError( @@ -68,13 +74,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 6: + raise IndexError( + "vec is expected to have length 6; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((6, 1)) + elif vec.shape == (6,): vec = vec.reshape((6, 1)) elif vec.shape != (6, 1): raise IndexError( 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..cb4ec6293 100644 --- a/gen/python/sym/ops/equirectangular_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/equirectangular_camera_cal/camera_ops.py @@ -59,7 +59,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,7 +72,13 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -104,7 +110,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,7 +125,13 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -168,7 +180,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,7 +195,13 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): + if not isinstance(pixel, numpy.ndarray): + if len(pixel) != 2: + raise IndexError( + "pixel is expected to have length 2; instead had length {}".format(len(pixel)) + ) + pixel = numpy.array(pixel).reshape((2, 1)) + elif pixel.shape == (2,): pixel = pixel.reshape((2, 1)) elif pixel.shape != (2, 1): raise IndexError( @@ -217,7 +235,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,7 +250,13 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): + if not isinstance(pixel, numpy.ndarray): + if len(pixel) != 2: + raise IndexError( + "pixel is expected to have length 2; instead had length {}".format(len(pixel)) + ) + pixel = numpy.array(pixel).reshape((2, 1)) + elif pixel.shape == (2,): pixel = pixel.reshape((2, 1)) elif pixel.shape != (2, 1): raise IndexError( 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..3d22472a0 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 @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 4: + raise IndexError( + "vec is expected to have length 4; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((4, 1)) + elif vec.shape == (4,): vec = vec.reshape((4, 1)) elif vec.shape != (4, 1): raise IndexError( @@ -64,13 +70,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 4: + raise IndexError( + "vec is expected to have length 4; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((4, 1)) + elif vec.shape == (4,): vec = vec.reshape((4, 1)) elif vec.shape != (4, 1): raise IndexError( 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..f78794fe2 100644 --- a/gen/python/sym/ops/linear_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/linear_camera_cal/camera_ops.py @@ -59,7 +59,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,7 +72,13 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -93,7 +99,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,7 +114,13 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -156,7 +168,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,7 +183,13 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): + if not isinstance(pixel, numpy.ndarray): + if len(pixel) != 2: + raise IndexError( + "pixel is expected to have length 2; instead had length {}".format(len(pixel)) + ) + pixel = numpy.array(pixel).reshape((2, 1)) + elif pixel.shape == (2,): pixel = pixel.reshape((2, 1)) elif pixel.shape != (2, 1): raise IndexError( @@ -192,7 +210,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,7 +225,13 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if pixel.shape == (2,): + if not isinstance(pixel, numpy.ndarray): + if len(pixel) != 2: + raise IndexError( + "pixel is expected to have length 2; instead had length {}".format(len(pixel)) + ) + pixel = numpy.array(pixel).reshape((2, 1)) + elif pixel.shape == (2,): pixel = pixel.reshape((2, 1)) elif pixel.shape != (2, 1): raise IndexError( 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..15ffd5ea1 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 @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 4: + raise IndexError( + "vec is expected to have length 4; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((4, 1)) + elif vec.shape == (4,): vec = vec.reshape((4, 1)) elif vec.shape != (4, 1): raise IndexError( @@ -64,13 +70,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 4: + raise IndexError( + "vec is expected to have length 4; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((4, 1)) + elif vec.shape == (4,): vec = vec.reshape((4, 1)) elif vec.shape != (4, 1): raise IndexError( 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..276bbf420 100644 --- a/gen/python/sym/ops/polynomial_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/polynomial_camera_cal/camera_ops.py @@ -59,7 +59,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,7 +72,13 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -108,7 +114,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,7 +129,13 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( 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..c47949912 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 @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 8: + raise IndexError( + "vec is expected to have length 8; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((8, 1)) + elif vec.shape == (8,): vec = vec.reshape((8, 1)) elif vec.shape != (8, 1): raise IndexError( @@ -72,13 +78,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 8: + raise IndexError( + "vec is expected to have length 8; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((8, 1)) + elif vec.shape == (8,): vec = vec.reshape((8, 1)) elif vec.shape != (8, 1): raise IndexError( diff --git a/gen/python/sym/ops/pose2/lie_group_ops.py b/gen/python/sym/ops/pose2/lie_group_ops.py index e49bbb270..0ff0977dc 100644 --- a/gen/python/sym/ops/pose2/lie_group_ops.py +++ b/gen/python/sym/ops/pose2/lie_group_ops.py @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 3: + raise IndexError( + "vec is expected to have length 3; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((3, 1)) + elif vec.shape == (3,): vec = vec.reshape((3, 1)) elif vec.shape != (3, 1): raise IndexError( @@ -65,13 +71,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 3: + raise IndexError( + "vec is expected to have length 3; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((3, 1)) + elif vec.shape == (3,): vec = vec.reshape((3, 1)) elif vec.shape != (3, 1): raise IndexError( diff --git a/gen/python/sym/ops/pose3/lie_group_ops.py b/gen/python/sym/ops/pose3/lie_group_ops.py index 112a3c868..1b2fed047 100644 --- a/gen/python/sym/ops/pose3/lie_group_ops.py +++ b/gen/python/sym/ops/pose3/lie_group_ops.py @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 6: + raise IndexError( + "vec is expected to have length 6; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((6, 1)) + elif vec.shape == (6,): vec = vec.reshape((6, 1)) elif vec.shape != (6, 1): raise IndexError( @@ -79,13 +85,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 6: + raise IndexError( + "vec is expected to have length 6; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((6, 1)) + elif vec.shape == (6,): vec = vec.reshape((6, 1)) elif vec.shape != (6, 1): raise IndexError( diff --git a/gen/python/sym/ops/rot2/lie_group_ops.py b/gen/python/sym/ops/rot2/lie_group_ops.py index 7d8a6b08f..8c0781e8c 100644 --- a/gen/python/sym/ops/rot2/lie_group_ops.py +++ b/gen/python/sym/ops/rot2/lie_group_ops.py @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 1: + raise IndexError( + "vec is expected to have length 1; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((1, 1)) + elif vec.shape == (1,): vec = vec.reshape((1, 1)) elif vec.shape != (1, 1): raise IndexError( @@ -61,13 +67,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 1: + raise IndexError( + "vec is expected to have length 1; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((1, 1)) + elif vec.shape == (1,): vec = vec.reshape((1, 1)) elif vec.shape != (1, 1): raise IndexError( diff --git a/gen/python/sym/ops/rot3/lie_group_ops.py b/gen/python/sym/ops/rot3/lie_group_ops.py index d48444060..8c5cfb396 100644 --- a/gen/python/sym/ops/rot3/lie_group_ops.py +++ b/gen/python/sym/ops/rot3/lie_group_ops.py @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 3: + raise IndexError( + "vec is expected to have length 3; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((3, 1)) + elif vec.shape == (3,): vec = vec.reshape((3, 1)) elif vec.shape != (3, 1): raise IndexError( @@ -73,13 +79,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 3: + raise IndexError( + "vec is expected to have length 3; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((3, 1)) + elif vec.shape == (3,): vec = vec.reshape((3, 1)) elif vec.shape != (3, 1): raise IndexError( 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..1a9f867fd 100644 --- a/gen/python/sym/ops/spherical_camera_cal/camera_ops.py +++ b/gen/python/sym/ops/spherical_camera_cal/camera_ops.py @@ -59,7 +59,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,7 +72,13 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( @@ -102,7 +108,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,7 +123,13 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( 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..0fc822a46 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 @@ -19,12 +19,18 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 9: + raise IndexError( + "vec is expected to have length 9; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((9, 1)) + elif vec.shape == (9,): vec = vec.reshape((9, 1)) elif vec.shape != (9, 1): raise IndexError( @@ -74,13 +80,19 @@ 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,): + if not isinstance(vec, numpy.ndarray): + if len(vec) != 9: + raise IndexError( + "vec is expected to have length 9; instead had length {}".format(len(vec)) + ) + vec = numpy.array(vec).reshape((9, 1)) + elif vec.shape == (9,): vec = vec.reshape((9, 1)) elif vec.shape != (9, 1): raise IndexError( diff --git a/gen/python/sym/polynomial_camera_cal.py b/gen/python/sym/polynomial_camera_cal.py index a06be006f..3120a4bbd 100644 --- a/gen/python/sym/polynomial_camera_cal.py +++ b/gen/python/sym/polynomial_camera_cal.py @@ -115,7 +115,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 +127,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. diff --git a/gen/python/sym/pose2.py b/gen/python/sym/pose2.py index 5f8191678..69a3a1bc1 100644 --- a/gen/python/sym/pose2.py +++ b/gen/python/sym/pose2.py @@ -132,7 +132,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,7 +147,13 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if right.shape == (2,): + if not isinstance(right, numpy.ndarray): + if len(right) != 2: + raise IndexError( + "right is expected to have length 2; instead had length {}".format(len(right)) + ) + right = numpy.array(right).reshape((2, 1)) + elif right.shape == (2,): right = right.reshape((2, 1)) elif right.shape != (2, 1): raise IndexError( @@ -165,7 +171,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,7 +188,13 @@ def inverse_compose(self, point): # Input arrays _self = self.data - if point.shape == (2,): + if not isinstance(point, numpy.ndarray): + if len(point) != 2: + raise IndexError( + "point is expected to have length 2; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((2, 1)) + elif point.shape == (2,): point = point.reshape((2, 1)) elif point.shape != (2, 1): raise IndexError( diff --git a/gen/python/sym/pose3.py b/gen/python/sym/pose3.py index 826257756..eeb88b43a 100644 --- a/gen/python/sym/pose3.py +++ b/gen/python/sym/pose3.py @@ -131,7 +131,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,7 +140,13 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if right.shape == (3,): + if not isinstance(right, numpy.ndarray): + if len(right) != 3: + raise IndexError( + "right is expected to have length 3; instead had length {}".format(len(right)) + ) + right = numpy.array(right).reshape((3, 1)) + elif right.shape == (3,): right = right.reshape((3, 1)) elif right.shape != (3, 1): raise IndexError( @@ -185,7 +191,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,7 +208,13 @@ def inverse_compose(self, point): # Input arrays _self = self.data - if point.shape == (3,): + if not isinstance(point, numpy.ndarray): + if len(point) != 3: + raise IndexError( + "point is expected to have length 3; instead had length {}".format(len(point)) + ) + point = numpy.array(point).reshape((3, 1)) + elif point.shape == (3,): point = point.reshape((3, 1)) elif point.shape != (3, 1): raise IndexError( diff --git a/gen/python/sym/rot2.py b/gen/python/sym/rot2.py index 47ecc35dc..95f48e4b9 100644 --- a/gen/python/sym/rot2.py +++ b/gen/python/sym/rot2.py @@ -56,7 +56,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,7 +65,13 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if right.shape == (2,): + if not isinstance(right, numpy.ndarray): + if len(right) != 2: + raise IndexError( + "right is expected to have length 2; instead had length {}".format(len(right)) + ) + right = numpy.array(right).reshape((2, 1)) + elif right.shape == (2,): right = right.reshape((2, 1)) elif right.shape != (2, 1): raise IndexError( diff --git a/gen/python/sym/rot3.py b/gen/python/sym/rot3.py index e725d5cae..997897c89 100644 --- a/gen/python/sym/rot3.py +++ b/gen/python/sym/rot3.py @@ -79,7 +79,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,7 +88,13 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if right.shape == (3,): + if not isinstance(right, numpy.ndarray): + if len(right) != 3: + raise IndexError( + "right is expected to have length 3; instead had length {}".format(len(right)) + ) + right = numpy.array(right).reshape((3, 1)) + elif right.shape == (3,): right = right.reshape((3, 1)) elif right.shape != (3, 1): raise IndexError( diff --git a/gen/python/sym/spherical_camera_cal.py b/gen/python/sym/spherical_camera_cal.py index ee85c37e8..2954e90fd 100644 --- a/gen/python/sym/spherical_camera_cal.py +++ b/gen/python/sym/spherical_camera_cal.py @@ -130,7 +130,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 +142,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. diff --git a/symforce/codegen/backends/python/templates/util/util.jinja b/symforce/codegen/backends/python/templates/util/util.jinja index 28562cdfb..859c347c9 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,17 +146,24 @@ 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 }},): +if not isinstance({{ name }}, numpy.ndarray): + if len({{ name }}) != {{ size }}: + raise IndexError( + "{{ name }} is expected to have length {{ size }}; instead had length {}".format(len({{ name }})) + ) + {{ name }} = numpy.array({{ name }}).reshape({{ shape }}) +elif {{ name }}.shape == ({{ size }},): {{ 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 }} is expected to have shape {{ shape }} or ({{ size }},); instead had shape {}".format( {{ name }}.shape ) ) diff --git a/test/symforce_codegen_test.py b/test/symforce_codegen_test.py index efc88db89..f77a3746e 100644 --- a/test/symforce_codegen_test.py +++ b/test/symforce_codegen_test.py @@ -526,6 +526,36 @@ 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 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..39199a4a6 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 @@ -15,7 +15,7 @@ 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,7 +33,15 @@ 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,): + if not isinstance(nav_t_point, numpy.ndarray): + if len(nav_t_point) != 3: + raise IndexError( + "nav_t_point is expected to have length 3; instead had length {}".format( + len(nav_t_point) + ) + ) + nav_t_point = numpy.array(nav_t_point).reshape((3, 1)) + elif nav_t_point.shape == (3,): nav_t_point = nav_t_point.reshape((3, 1)) elif nav_t_point.shape != (3, 1): raise IndexError( 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..efebcec30 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 @@ -15,7 +15,7 @@ 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,7 +33,15 @@ 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,): + if not isinstance(nav_t_point, numpy.ndarray): + if len(nav_t_point) != 3: + raise IndexError( + "nav_t_point is expected to have length 3; instead had length {}".format( + len(nav_t_point) + ) + ) + nav_t_point = numpy.array(nav_t_point).reshape((3, 1)) + elif nav_t_point.shape == (3,): nav_t_point = nav_t_point.reshape((3, 1)) elif nav_t_point.shape != (3, 1): raise IndexError( From de483e93ff4a66c6a86535563ae7909b514d371e Mon Sep 17 00:00:00 2001 From: bradley-solliday-skydio Date: Thu, 20 Oct 2022 13:57:11 -0700 Subject: [PATCH 4/5] Update sym class hand written mthd type annotation The handwritten methods of the sym classes all already accepted lists (in addition to ndarrays) because they were backed by the autogenerated functions, which support both. So, I updated the type annotations accordingly. Also, since the auto-generated methods already have adequeate error messages, I removed the hand-written ones as they didn't cover certain corner cases well (like 2d row vectors), and in order to make them complete, they would just be duplicates of what was autogenerated more or less anyway. Also, fixed the type signature of the camera cal __init__ methods. This should have been done previously, but was overlooked. Also made a small fix to the corresponding jinja template's indenting to match the style we use elsewhere. Also changed the geo methods multiplication methods to accomadate sequences. Since the Sequence abstract base class is located at `collections.Sequence` in python2.7 and `collections.abc.Sequence` in python3.8 and the like, I decided to instead identity a sequence by whether or not it implements `__getitem__` and `__len__`. Also check that it's not a `str`. Note, it seems that in python2.7 I should really be checking not that it is a `str`, but rather a `basestr`, but `basestr`s don't existing in python3.8. I didn't think this corner case is that important, and if such cases are important, then we should probably rethink our strategy here. Also, all when multiplying a geo type by a sequence (so list, tuple, or something like that) a column 2d ndarray is returned. The idea behind this is that it's a bit more consistent and predictable (though, I'm open to other return types). --- gen/python/sym/atan_camera_cal.py | 16 ++-------- gen/python/sym/double_sphere_camera_cal.py | 16 ++-------- gen/python/sym/equirectangular_camera_cal.py | 16 ++-------- gen/python/sym/linear_camera_cal.py | 16 ++-------- gen/python/sym/polynomial_camera_cal.py | 16 ++-------- gen/python/sym/pose2.py | 30 +++++++++---------- gen/python/sym/pose3.py | 30 +++++++++---------- gen/python/sym/rot2.py | 30 +++++++++---------- gen/python/sym/rot3.py | 30 +++++++++---------- gen/python/sym/spherical_camera_cal.py | 16 ++-------- .../templates/cam_package/CLASS.py.jinja | 20 +++---------- .../templates/geo_package/CLASS.py.jinja | 30 +++++++++---------- .../tests/geo_package_python_test.py.jinja | 6 ++++ test/geo_package_python_test.py | 24 +++++++++++++++ 14 files changed, 121 insertions(+), 175 deletions(-) diff --git a/gen/python/sym/atan_camera_cal.py b/gen/python/sym/atan_camera_cal.py index 86936343d..506f9df4f 100644 --- a/gen/python/sym/atan_camera_cal.py +++ b/gen/python/sym/atan_camera_cal.py @@ -187,13 +187,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 +195,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 6362dd58b..dde2c8db9 100644 --- a/gen/python/sym/double_sphere_camera_cal.py +++ b/gen/python/sym/double_sphere_camera_cal.py @@ -200,13 +200,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 +208,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 e482ac75e..f6c519cf8 100644 --- a/gen/python/sym/equirectangular_camera_cal.py +++ b/gen/python/sym/equirectangular_camera_cal.py @@ -182,13 +182,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 +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) -> 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 44f8f33e0..398b6dbb4 100644 --- a/gen/python/sym/linear_camera_cal.py +++ b/gen/python/sym/linear_camera_cal.py @@ -182,13 +182,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 +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) -> 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/polynomial_camera_cal.py b/gen/python/sym/polynomial_camera_cal.py index 3120a4bbd..f08f2477a 100644 --- a/gen/python/sym/polynomial_camera_cal.py +++ b/gen/python/sym/polynomial_camera_cal.py @@ -181,13 +181,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 +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) -> 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 69a3a1bc1..4a17119f6 100644 --- a/gen/python/sym/pose2.py +++ b/gen/python/sym/pose2.py @@ -312,13 +312,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): @@ -326,13 +320,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): @@ -363,11 +351,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 eeb88b43a..d15ffbfec 100644 --- a/gen/python/sym/pose3.py +++ b/gen/python/sym/pose3.py @@ -379,13 +379,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): @@ -393,13 +387,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): @@ -430,11 +418,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 95f48e4b9..e7492632f 100644 --- a/gen/python/sym/rot2.py +++ b/gen/python/sym/rot2.py @@ -192,13 +192,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): @@ -206,13 +200,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): @@ -243,11 +231,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 997897c89..b97bc4e6c 100644 --- a/gen/python/sym/rot3.py +++ b/gen/python/sym/rot3.py @@ -326,13 +326,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): @@ -340,13 +334,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): @@ -377,11 +365,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 2954e90fd..edf28ad9b 100644 --- a/gen/python/sym/spherical_camera_cal.py +++ b/gen/python/sym/spherical_camera_cal.py @@ -196,13 +196,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 +204,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/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja b/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja index 189e10fdf..268e0af8e 100644 --- a/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja +++ b/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja @@ -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/geo_package/CLASS.py.jinja b/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja index 75c1c76a5..3a0cb6346 100644 --- a/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja +++ b/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja @@ -125,13 +125,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 +133,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 +165,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/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/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) From c307b52d7ef457c71fcd50dd7c372fd9ce0e1b32 Mon Sep 17 00:00:00 2001 From: bradley-solliday-skydio Date: Thu, 27 Oct 2022 15:17:34 -0700 Subject: [PATCH 5/5] Move size checking logic to sym.util Did not move size checking logic for numba because - the checking logic for numba is pretty short - there were some technical difficulties in doing so that I didn't want to get bogged down in. --- gen/python/sym/atan_camera_cal.py | 1 + gen/python/sym/double_sphere_camera_cal.py | 1 + gen/python/sym/equirectangular_camera_cal.py | 1 + gen/python/sym/linear_camera_cal.py | 1 + .../sym/ops/atan_camera_cal/camera_ops.py | 61 ++----------------- .../sym/ops/atan_camera_cal/group_ops.py | 1 + .../sym/ops/atan_camera_cal/lie_group_ops.py | 31 +--------- .../double_sphere_camera_cal/camera_ops.py | 61 ++----------------- .../ops/double_sphere_camera_cal/group_ops.py | 1 + .../double_sphere_camera_cal/lie_group_ops.py | 31 +--------- .../equirectangular_camera_cal/camera_ops.py | 61 ++----------------- .../equirectangular_camera_cal/group_ops.py | 1 + .../lie_group_ops.py | 31 +--------- .../sym/ops/linear_camera_cal/camera_ops.py | 61 ++----------------- .../sym/ops/linear_camera_cal/group_ops.py | 1 + .../ops/linear_camera_cal/lie_group_ops.py | 31 +--------- .../ops/polynomial_camera_cal/camera_ops.py | 31 +--------- .../ops/polynomial_camera_cal/group_ops.py | 1 + .../polynomial_camera_cal/lie_group_ops.py | 31 +--------- gen/python/sym/ops/pose2/group_ops.py | 1 + gen/python/sym/ops/pose2/lie_group_ops.py | 31 +--------- gen/python/sym/ops/pose3/group_ops.py | 1 + gen/python/sym/ops/pose3/lie_group_ops.py | 31 +--------- gen/python/sym/ops/rot2/group_ops.py | 1 + gen/python/sym/ops/rot2/lie_group_ops.py | 31 +--------- gen/python/sym/ops/rot3/group_ops.py | 1 + gen/python/sym/ops/rot3/lie_group_ops.py | 31 +--------- .../ops/spherical_camera_cal/camera_ops.py | 31 +--------- .../sym/ops/spherical_camera_cal/group_ops.py | 1 + .../ops/spherical_camera_cal/lie_group_ops.py | 31 +--------- gen/python/sym/polynomial_camera_cal.py | 1 + gen/python/sym/pose2.py | 31 +--------- gen/python/sym/pose3.py | 31 +--------- gen/python/sym/rot2.py | 17 +----- gen/python/sym/rot3.py | 17 +----- gen/python/sym/spherical_camera_cal.py | 1 + gen/python/sym/util.py | 31 ++++++++++ .../templates/cam_package/CLASS.py.jinja | 2 +- .../cam_package/ops/CLASS/camera_ops.py.jinja | 1 + .../templates/function/FUNCTION.py.jinja | 3 + .../templates/geo_package/CLASS.py.jinja | 1 + .../templates/ops/CLASS/group_ops.py.jinja | 1 + .../ops/CLASS/lie_group_ops.py.jinja | 1 + .../backends/python/templates/util.py.jinja | 32 ++++++++++ .../backends/python/templates/util/util.jinja | 15 +---- symforce/codegen/geo_package_codegen.py | 6 ++ .../symengine/az_el_from_point.py | 18 +----- .../codegen_dataclass_in_values_test.py | 1 + .../codegen_python_test/python_function.py | 1 + .../symforce/buffer_test/buffer_func.py | 1 + .../sympy/az_el_from_point.py | 18 +----- .../codegen_dataclass_in_values_test.py | 1 + .../codegen_python_test/python_function.py | 1 + .../symforce/buffer_test/buffer_func.py | 1 + 54 files changed, 172 insertions(+), 691 deletions(-) create mode 100644 gen/python/sym/util.py create mode 100644 symforce/codegen/backends/python/templates/util.py.jinja diff --git a/gen/python/sym/atan_camera_cal.py b/gen/python/sym/atan_camera_cal.py index 506f9df4f..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): diff --git a/gen/python/sym/double_sphere_camera_cal.py b/gen/python/sym/double_sphere_camera_cal.py index dde2c8db9..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): diff --git a/gen/python/sym/equirectangular_camera_cal.py b/gen/python/sym/equirectangular_camera_cal.py index f6c519cf8..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): diff --git a/gen/python/sym/linear_camera_cal.py b/gen/python/sym/linear_camera_cal.py index 398b6dbb4..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): 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 4ef74d817..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): @@ -72,20 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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]) @@ -117,20 +105,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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] @@ -224,20 +199,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if not isinstance(pixel, numpy.ndarray): - if len(pixel) != 2: - raise IndexError( - "pixel is expected to have length 2; instead had length {}".format(len(pixel)) - ) - pixel = numpy.array(pixel).reshape((2, 1)) - elif 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] @@ -278,20 +240,7 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if not isinstance(pixel, numpy.ndarray): - if len(pixel) != 2: - raise IndexError( - "pixel is expected to have length 2; instead had length {}".format(len(pixel)) - ) - pixel = numpy.array(pixel).reshape((2, 1)) - elif 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 7525dee17..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 0 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 5: - raise IndexError( - "vec is expected to have length 5; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((5, 1)) - elif 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) @@ -78,20 +66,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 5: - raise IndexError( - "vec is expected to have length 5; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((5, 1)) - elif 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 0d97a42b5..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): @@ -72,20 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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 @@ -170,20 +158,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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 @@ -311,20 +286,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if not isinstance(pixel, numpy.ndarray): - if len(pixel) != 2: - raise IndexError( - "pixel is expected to have length 2; instead had length {}".format(len(pixel)) - ) - pixel = numpy.array(pixel).reshape((2, 1)) - elif 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] @@ -370,20 +332,7 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if not isinstance(pixel, numpy.ndarray): - if len(pixel) != 2: - raise IndexError( - "pixel is expected to have length 2; instead had length {}".format(len(pixel)) - ) - pixel = numpy.array(pixel).reshape((2, 1)) - elif 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 7546e09bc..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 0 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 6: - raise IndexError( - "vec is expected to have length 6; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((6, 1)) - elif 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) @@ -80,20 +68,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 6: - raise IndexError( - "vec is expected to have length 6; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((6, 1)) - elif 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 cb4ec6293..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): @@ -72,20 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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 @@ -125,20 +113,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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 = ( @@ -195,20 +170,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if not isinstance(pixel, numpy.ndarray): - if len(pixel) != 2: - raise IndexError( - "pixel is expected to have length 2; instead had length {}".format(len(pixel)) - ) - pixel = numpy.array(pixel).reshape((2, 1)) - elif 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] @@ -250,20 +212,7 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if not isinstance(pixel, numpy.ndarray): - if len(pixel) != 2: - raise IndexError( - "pixel is expected to have length 2; instead had length {}".format(len(pixel)) - ) - pixel = numpy.array(pixel).reshape((2, 1)) - elif 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 3d22472a0..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 0 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 4: - raise IndexError( - "vec is expected to have length 4; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((4, 1)) - elif 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) @@ -76,20 +64,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 4: - raise IndexError( - "vec is expected to have length 4; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((4, 1)) - elif 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 f78794fe2..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): @@ -72,20 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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]) @@ -114,20 +102,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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]) @@ -183,20 +158,7 @@ def camera_ray_from_pixel(self, pixel, epsilon): # Input arrays _self = self.data - if not isinstance(pixel, numpy.ndarray): - if len(pixel) != 2: - raise IndexError( - "pixel is expected to have length 2; instead had length {}".format(len(pixel)) - ) - pixel = numpy.array(pixel).reshape((2, 1)) - elif 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) @@ -225,20 +187,7 @@ def camera_ray_from_pixel_with_jacobians(self, pixel, epsilon): # Input arrays _self = self.data - if not isinstance(pixel, numpy.ndarray): - if len(pixel) != 2: - raise IndexError( - "pixel is expected to have length 2; instead had length {}".format(len(pixel)) - ) - pixel = numpy.array(pixel).reshape((2, 1)) - elif 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 15ffd5ea1..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 0 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 4: - raise IndexError( - "vec is expected to have length 4; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((4, 1)) - elif 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) @@ -76,20 +64,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 4: - raise IndexError( - "vec is expected to have length 4; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((4, 1)) - elif 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 276bbf420..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): @@ -72,20 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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]) @@ -129,20 +117,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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 c47949912..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 0 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 8: - raise IndexError( - "vec is expected to have length 8; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((8, 1)) - elif 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) @@ -84,20 +72,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 8: - raise IndexError( - "vec is expected to have length 8; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((8, 1)) - elif 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 0ff0977dc..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 2 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 3: - raise IndexError( - "vec is expected to have length 3; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((3, 1)) - elif 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) @@ -77,20 +65,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 3: - raise IndexError( - "vec is expected to have length 3; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((3, 1)) - elif 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 1b2fed047..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 15 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 6: - raise IndexError( - "vec is expected to have length 6; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((6, 1)) - elif 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) @@ -91,20 +79,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 6: - raise IndexError( - "vec is expected to have length 6; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((6, 1)) - elif 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 8c0781e8c..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 2 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 1: - raise IndexError( - "vec is expected to have length 1; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((1, 1)) - elif 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) @@ -73,20 +61,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 1: - raise IndexError( - "vec is expected to have length 1; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((1, 1)) - elif 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 8c5cfb396..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 15 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 3: - raise IndexError( - "vec is expected to have length 3; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((3, 1)) - elif 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) @@ -85,20 +73,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 3: - raise IndexError( - "vec is expected to have length 3; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((3, 1)) - elif 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 1a9f867fd..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): @@ -72,20 +73,7 @@ def pixel_from_camera_point(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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) @@ -123,20 +111,7 @@ def pixel_from_camera_point_with_jacobians(self, point, epsilon): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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 0fc822a46..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): @@ -24,20 +25,7 @@ def from_tangent(vec, epsilon): # Total ops: 0 # Input arrays - if not isinstance(vec, numpy.ndarray): - if len(vec) != 9: - raise IndexError( - "vec is expected to have length 9; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((9, 1)) - elif 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) @@ -86,20 +74,7 @@ def retract(a, vec, epsilon): # Input arrays _a = a.data - if not isinstance(vec, numpy.ndarray): - if len(vec) != 9: - raise IndexError( - "vec is expected to have length 9; instead had length {}".format(len(vec)) - ) - vec = numpy.array(vec).reshape((9, 1)) - elif 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 f08f2477a..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): diff --git a/gen/python/sym/pose2.py b/gen/python/sym/pose2.py index 4a17119f6..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 @@ -147,20 +148,7 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if not isinstance(right, numpy.ndarray): - if len(right) != 2: - raise IndexError( - "right is expected to have length 2; instead had length {}".format(len(right)) - ) - right = numpy.array(right).reshape((2, 1)) - elif 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) @@ -188,20 +176,7 @@ def inverse_compose(self, point): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 2: - raise IndexError( - "point is expected to have length 2; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((2, 1)) - elif 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) diff --git a/gen/python/sym/pose3.py b/gen/python/sym/pose3.py index d15ffbfec..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 @@ -140,20 +141,7 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if not isinstance(right, numpy.ndarray): - if len(right) != 3: - raise IndexError( - "right is expected to have length 3; instead had length {}".format(len(right)) - ) - right = numpy.array(right).reshape((3, 1)) - elif 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] @@ -208,20 +196,7 @@ def inverse_compose(self, point): # Input arrays _self = self.data - if not isinstance(point, numpy.ndarray): - if len(point) != 3: - raise IndexError( - "point is expected to have length 3; instead had length {}".format(len(point)) - ) - point = numpy.array(point).reshape((3, 1)) - elif 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] diff --git a/gen/python/sym/rot2.py b/gen/python/sym/rot2.py index e7492632f..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 @@ -65,20 +67,7 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if not isinstance(right, numpy.ndarray): - if len(right) != 2: - raise IndexError( - "right is expected to have length 2; instead had length {}".format(len(right)) - ) - right = numpy.array(right).reshape((2, 1)) - elif 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) diff --git a/gen/python/sym/rot3.py b/gen/python/sym/rot3.py index b97bc4e6c..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 @@ -88,20 +90,7 @@ def compose_with_point(self, right): # Input arrays _self = self.data - if not isinstance(right, numpy.ndarray): - if len(right) != 3: - raise IndexError( - "right is expected to have length 3; instead had length {}".format(len(right)) - ) - right = numpy.array(right).reshape((3, 1)) - elif 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] diff --git a/gen/python/sym/spherical_camera_cal.py b/gen/python/sym/spherical_camera_cal.py index edf28ad9b..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): 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/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja b/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja index 268e0af8e..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 %} 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/geo_package/CLASS.py.jinja b/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja index 3a0cb6346..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 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/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 859c347c9..98d1f53ae 100644 --- a/symforce/codegen/backends/python/templates/util/util.jinja +++ b/symforce/codegen/backends/python/templates/util/util.jinja @@ -153,20 +153,7 @@ 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 not isinstance({{ name }}, numpy.ndarray): - if len({{ name }}) != {{ size }}: - raise IndexError( - "{{ name }} is expected to have length {{ size }}; instead had length {}".format(len({{ name }})) - ) - {{ name }} = numpy.array({{ name }}).reshape({{ shape }}) -elif {{ name }}.shape == ({{ size }},): - {{ name }} = {{ name }}.reshape({{ shape }}) -elif {{ name }}.shape != {{ shape }}: - raise IndexError( - "{{ name }} is expected to have shape {{ shape }} or ({{ size }},); instead had shape {}".format( - {{ name }}.shape - ) - ) +{{ name }} = check_size_and_reshape({{ name }}, "{{ name }}", {{ shape }}) {% endif %} {% endif %} {% endmacro %} diff --git a/symforce/codegen/geo_package_codegen.py b/symforce/codegen/geo_package_codegen.py index 0b1c36b04..9aa892373 100644 --- a/symforce/codegen/geo_package_codegen.py +++ b/symforce/codegen/geo_package_codegen.py @@ -216,6 +216,12 @@ 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( 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 39199a4a6..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,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 @@ -33,22 +34,7 @@ def az_el_from_point(nav_T_cam, nav_t_point, epsilon): # Input arrays _nav_T_cam = nav_T_cam.data - if not isinstance(nav_t_point, numpy.ndarray): - if len(nav_t_point) != 3: - raise IndexError( - "nav_t_point is expected to have length 3; instead had length {}".format( - len(nav_t_point) - ) - ) - nav_t_point = numpy.array(nav_t_point).reshape((3, 1)) - elif 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/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/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/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 efebcec30..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,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 @@ -33,22 +34,7 @@ def az_el_from_point(nav_T_cam, nav_t_point, epsilon): # Input arrays _nav_T_cam = nav_T_cam.data - if not isinstance(nav_t_point, numpy.ndarray): - if len(nav_t_point) != 3: - raise IndexError( - "nav_t_point is expected to have length 3; instead had length {}".format( - len(nav_t_point) - ) - ) - nav_t_point = numpy.array(nav_t_point).reshape((3, 1)) - elif 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/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/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/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