diff --git a/jupyter/4dobc-creation.ipynb b/jupyter/4dobc-creation.ipynb index d077e8f4..e852b6e3 100644 --- a/jupyter/4dobc-creation.ipynb +++ b/jupyter/4dobc-creation.ipynb @@ -124,7 +124,7 @@ "metadata": {}, "outputs": [], "source": [ - "analysis.m3c2 = py4dgeo.M3C2(cyl_radii=[2.0], normal_radii=[2.0])" + "analysis.m3c2 = py4dgeo.M3C2(cyl_radius=2.0, normal_radii=[2.0])" ] }, { @@ -258,7 +258,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/jupyter/customization.ipynb b/jupyter/customization.ipynb index b294b502..11f34259 100644 --- a/jupyter/customization.ipynb +++ b/jupyter/customization.ipynb @@ -120,9 +120,7 @@ "metadata": {}, "outputs": [], "source": [ - "DirectionAlgorithm(\n", - " epochs=(epoch1, epoch2), corepoints=corepoints, cyl_radii=(5.0,)\n", - ").run()" + "DirectionAlgorithm(epochs=(epoch1, epoch2), corepoints=corepoints, cyl_radius=5.0).run()" ] }, { @@ -188,7 +186,7 @@ "CallbackAlgorithm(\n", " epochs=(epoch1, epoch2),\n", " corepoints=corepoints,\n", - " cyl_radii=(2.0,),\n", + " cyl_radius=2.0,\n", " normal_radii=(1.0, 2.0),\n", ").run()" ] @@ -221,7 +219,7 @@ "PythonFallbackM3C2(\n", " epochs=(epoch1, epoch2),\n", " corepoints=corepoints,\n", - " cyl_radii=(2.0,),\n", + " cyl_radius=2.0,\n", " normal_radii=(1.0, 2.0),\n", ").run()" ] @@ -267,7 +265,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/jupyter/m3c2.ipynb b/jupyter/m3c2.ipynb index 5880c81c..9c2b79a1 100644 --- a/jupyter/m3c2.ipynb +++ b/jupyter/m3c2.ipynb @@ -83,7 +83,7 @@ "m3c2 = py4dgeo.M3C2(\n", " epochs=(epoch1, epoch2),\n", " corepoints=corepoints,\n", - " cyl_radii=(2.0,),\n", + " cyl_radius=2.0,\n", " normal_radii=[0.5, 1.0, 2.0],\n", ")\n", "\n", @@ -209,7 +209,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/jupyter/m3c2ep.ipynb b/jupyter/m3c2ep.ipynb index 61e12194..3daf468c 100644 --- a/jupyter/m3c2ep.ipynb +++ b/jupyter/m3c2ep.ipynb @@ -188,7 +188,7 @@ " epochs=(epoch1, epoch2),\n", " corepoints=corepoints,\n", " normal_radii=(0.5, 1.0, 2.0),\n", - " cyl_radii=(0.5,),\n", + " cyl_radius=0.5,\n", " max_distance=3.0,\n", " Cxx=Cxx,\n", " tfM=tfM,\n", @@ -364,7 +364,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.4" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/src/py4dgeo/cloudcompare.py b/src/py4dgeo/cloudcompare.py index 5af33c04..8fb555b4 100644 --- a/src/py4dgeo/cloudcompare.py +++ b/src/py4dgeo/cloudcompare.py @@ -5,7 +5,7 @@ "normalscale": "normal_radii", "registrationerror": "reg_error", "searchdepth": "max_distance", - "searchscale": "cyl_radii", + "searchscale": "cyl_radius", "usemedian": "robust_aggr", } @@ -21,10 +21,8 @@ def __init__(self, **params): # Apply changes that are not a mere renaming # Scale parameters are diameters in CloudCompare and radii in py4dgeo - if "cyl_radii" in py4dgeo_params: - py4dgeo_params["cyl_radii"] = tuple( - 0.5 * r for r in py4dgeo_params["cyl_radii"] - ) + if "cyl_radius" in py4dgeo_params: + py4dgeo_params["cyl_radius"] = py4dgeo_params["cyl_radius"] * 0.5 if "normal_radii" in py4dgeo_params: py4dgeo_params["normal_radii"] = tuple( 0.5 * r for r in py4dgeo_params["normal_radii"] diff --git a/src/py4dgeo/m3c2.py b/src/py4dgeo/m3c2.py index 91576653..cb4b0952 100644 --- a/src/py4dgeo/m3c2.py +++ b/src/py4dgeo/m3c2.py @@ -1,3 +1,4 @@ +from ast import List from py4dgeo.epoch import Epoch, as_epoch from py4dgeo.util import ( as_double_precision, @@ -22,9 +23,10 @@ class M3C2LikeAlgorithm(abc.ABC): def __init__( self, - epochs: typing.Tuple[Epoch, ...] = None, - corepoints: np.ndarray = None, - cyl_radii: typing.List[float] = None, + epochs: typing.Optional[typing.Tuple[Epoch, ...]] = None, + corepoints: typing.Optional[np.ndarray] = None, + cyl_radii: typing.Optional[List] = None, + cyl_radius: typing.Optional[float] = None, max_distance: float = 0.0, registration_error: float = 0.0, robust_aggr: bool = False, @@ -32,6 +34,7 @@ def __init__( self.epochs = epochs self.corepoints = corepoints self.cyl_radii = cyl_radii + self.cyl_radius = cyl_radius self.max_distance = max_distance self.registration_error = registration_error self.robust_aggr = robust_aggr @@ -72,9 +75,19 @@ def directions(self): def calculate_distances(self, epoch1, epoch2): """Calculate the distances between two epochs""" - if self.cyl_radii is None or len(self.cyl_radii) != 1: + if isinstance(self.cyl_radii, typing.Iterable): + logger.warning( + "DEPRECATION: use cyl_radius instead of cyl_radii. In a future version, cyl_radii will be removed!" + ) + if len(self.cyl_radii) != 1: + Py4DGeoError("cyl_radii must be a list containing a single float!") + elif self.cyl_radius is None: + self.cyl_radius = self.cyl_radii[0] + self.cyl_radii = None + + if self.cyl_radius is None: raise Py4DGeoError( - f"{self.name} requires exactly one cylinder radius to be given" + f"{self.name} requires exactly one cylinder radius to be given as a float." ) # Ensure that the KDTree data structures have been built. This is no-op @@ -85,7 +98,7 @@ def calculate_distances(self, epoch1, epoch2): distances, uncertainties = _py4dgeo.compute_distances( self.corepoints, - self.cyl_radii[0], + self.cyl_radius, epoch1, epoch2, self.directions(), diff --git a/src/py4dgeo/m3c2ep.py b/src/py4dgeo/m3c2ep.py index ad020b9c..d5f3b5ef 100644 --- a/src/py4dgeo/m3c2ep.py +++ b/src/py4dgeo/m3c2ep.py @@ -56,7 +56,7 @@ def calculate_distances(self, epoch1, epoch2): print(self.name + " running") """Calculate the distances between two epochs""" - if self.cyl_radii is None or len(self.cyl_radii) != 1: + if not isinstance(self.cyl_radius, float): raise Py4DGeoError( f"{self.name} requires exactly one cylinder radius to be given" ) @@ -71,7 +71,7 @@ def calculate_distances(self, epoch1, epoch2): # set default M3C2Meta M3C2Meta = {"searchrad": 0.5, "maxdist": 3, "minneigh": 5, "maxneigh": 100000} - M3C2Meta["searchrad"] = self.cyl_radii[0] + M3C2Meta["searchrad"] = self.cyl_radius M3C2Meta["maxdist"] = self.max_distance M3C2Meta["spInfos"] = [epoch1.scanpos_info, epoch2.scanpos_info] diff --git a/tests/python/test_cloudcompare.py b/tests/python/test_cloudcompare.py index 9b2cffa6..2318b075 100644 --- a/tests/python/test_cloudcompare.py +++ b/tests/python/test_cloudcompare.py @@ -11,15 +11,17 @@ def test_cloudcompare_m3c2(epochs): m3c2 = M3C2( epochs=(epoch1, epoch2), corepoints=epoch1.cloud, - cyl_radii=(1.6,), - normal_radii=(1.1,), + cyl_radius=1.6, + normal_radii=[ + 1.1, + ], ) # Instantiate Cloud compare variant cc_m3c2 = CloudCompareM3C2( epochs=(epoch1, epoch2), corepoints=epoch1.cloud, - searchscale=(3.2,), + searchscale=3.2, normalscale=(2.2,), ) diff --git a/tests/python/test_fallback.py b/tests/python/test_fallback.py index 06752710..c80198d8 100644 --- a/tests/python/test_fallback.py +++ b/tests/python/test_fallback.py @@ -45,7 +45,7 @@ def callback_workingset_finder(self): pym3c2 = CxxTestM3C2( epochs=epochs, corepoints=epochs[0].cloud, - cyl_radii=(3.0,), + cyl_radius=3.0, normal_radii=(2.0,), max_distance=6.0, ) @@ -54,7 +54,7 @@ def callback_workingset_finder(self): m3c2 = PythonTestM3C2( epochs=epochs, corepoints=epochs[0].cloud, - cyl_radii=(3.0,), + cyl_radius=3.0, normal_radii=(2.0,), max_distance=6.0, ) @@ -65,12 +65,12 @@ def callback_workingset_finder(self): def test_python_fallback_m3c2(epochs): # Instantiate a fallback M3C2 instance pym3c2 = PythonFallbackM3C2( - epochs=epochs, corepoints=epochs[0].cloud, cyl_radii=(3.0,), normal_radii=(2.0,) + epochs=epochs, corepoints=epochs[0].cloud, cyl_radius=3.0, normal_radii=(2.0,) ) # And a regular C++ based one m3c2 = M3C2( - epochs=epochs, corepoints=epochs[0].cloud, cyl_radii=(3.0,), normal_radii=(2.0,) + epochs=epochs, corepoints=epochs[0].cloud, cyl_radius=3.0, normal_radii=(2.0,) ) compare_algorithms(m3c2, pym3c2) @@ -87,7 +87,7 @@ def callback(*args): # Instantiate it m3c2 = ExcM3C2( - epochs=epochs, corepoints=epochs[0].cloud, cyl_radii=(3.0,), normal_radii=(2.0,) + epochs=epochs, corepoints=epochs[0].cloud, cyl_radius=3.0, normal_radii=(2.0,) ) # Running it should throw the proper exception despite taking a detour diff --git a/tests/python/test_m3c2.py b/tests/python/test_m3c2.py index 503440b3..fa42e8b9 100644 --- a/tests/python/test_m3c2.py +++ b/tests/python/test_m3c2.py @@ -7,16 +7,15 @@ @pytest.mark.parametrize("robust_aggr", (True, False)) def test_m3c2(epochs, robust_aggr): epoch1, epoch2 = epochs - # Try with wrong number of epochs with pytest.raises(Py4DGeoError): - M3C2(epochs=(epoch1,), corepoints=epoch1.cloud, cyl_radii=(1.0,)) + M3C2(epochs=(epoch1,), corepoints=epoch1.cloud, cyl_radius=1.0) # Instantiate an M3C2 instance m3c2 = M3C2( epochs=(epoch1, epoch2), corepoints=epoch1.cloud, - cyl_radii=(3.0,), + cyl_radius=3.0, normal_radii=(2.0,), robust_aggr=robust_aggr, ) @@ -28,7 +27,7 @@ def test_m3c2(epochs, robust_aggr): distances, uncertainties = M3C2( epochs=(epoch1, epoch1), corepoints=epoch1.cloud, - cyl_radii=(3.0,), + cyl_radius=3.0, normal_radii=(2.0,), robust_aggr=robust_aggr, ).run() @@ -43,7 +42,7 @@ def test_minimal_m3c2(epochs): m3c2 = M3C2( epochs=(epoch1, epoch2), corepoints=epoch1.cloud, - cyl_radii=(3.0,), + cyl_radius=3.0, normal_radii=(2.0,), ) @@ -57,7 +56,7 @@ def test_registration_error(epochs): m3c2 = M3C2( epochs=(epoch1, epoch1), corepoints=epoch1.cloud, - cyl_radii=(3.0,), + cyl_radius=3.0, normal_radii=(2.0,), registration_error=1.0, ) @@ -73,7 +72,7 @@ def test_external_normals(epochs): d, u = M3C2( epochs=(epoch1, epoch2), corepoints=epoch1.cloud, - cyl_radii=(3.0,), + cyl_radius=3.0, normal_radii=(2.0,), corepoint_normals=np.array([[0, 0, 1]]), ).run() @@ -82,7 +81,7 @@ def test_external_normals(epochs): d, u = M3C2( epochs=(epoch1, epoch2), corepoints=epoch1.cloud, - cyl_radii=(3.0,), + cyl_radius=3.0, normal_radii=(2.0,), corepoint_normals=np.array([[0, 0, 1], [0, 0, 1]]), ).run() diff --git a/tests/python/test_m3c2ep.py b/tests/python/test_m3c2ep.py index b723cbec..901bb860 100644 --- a/tests/python/test_m3c2ep.py +++ b/tests/python/test_m3c2ep.py @@ -18,7 +18,7 @@ def test_m3c2ep(epochs_m3c2ep, Cxx, tfM, redPoint, scanpos_info): epochs=(epoch1, epoch2), corepoints=corepoints, normal_radii=(0.5, 1.0, 2.0), - cyl_radii=(0.5,), + cyl_radius=0.5, max_distance=3.0, Cxx=Cxx, tfM=tfM, @@ -60,7 +60,7 @@ def test_m3c2ep_external_normals(epochs_m3c2ep, Cxx, tfM, redPoint, scanpos_info epochs=(epoch1, epoch2), corepoints=corepoints, corepoint_normals=np.array([[0, 0, 1], [0, 0, 1]]), - cyl_radii=(0.5,), + cyl_radius=0.5, max_distance=3.0, Cxx=Cxx, tfM=tfM, @@ -72,7 +72,7 @@ def test_m3c2ep_external_normals(epochs_m3c2ep, Cxx, tfM, redPoint, scanpos_info epochs=(epoch1, epoch2), corepoints=corepoints, normal_radii=(0.5, 1.0, 2.0), - cyl_radii=(0.5,), + cyl_radius=0.5, max_distance=3.0, Cxx=Cxx, tfM=tfM, @@ -85,7 +85,7 @@ def test_m3c2ep_external_normals(epochs_m3c2ep, Cxx, tfM, redPoint, scanpos_info epochs=(epoch1, epoch2), corepoints=corepoints, corepoint_normals=corepoint_normals, - cyl_radii=(0.5,), + cyl_radius=0.5, max_distance=3.0, Cxx=Cxx, tfM=tfM, @@ -100,7 +100,7 @@ def test_m3c2ep_external_normals(epochs_m3c2ep, Cxx, tfM, redPoint, scanpos_info epochs=(epoch1, epoch2), corepoints=corepoints, corepoint_normals=corepoint_normals, - cyl_radii=(0.5,), + cyl_radius=0.5, max_distance=3.0, Cxx=Cxx, tfM=tfM, @@ -153,7 +153,7 @@ def test_m3c2ep_write_las(epochs_m3c2ep, Cxx, tfM, redPoint, scanpos_info): epochs=(epoch1, epoch2), corepoints=corepoints, normal_radii=(0.5, 1.0, 2.0), - cyl_radii=(0.5,), + cyl_radius=0.5, max_distance=3.0, Cxx=Cxx, tfM=tfM, diff --git a/tests/python/test_segmentation.py b/tests/python/test_segmentation.py index 937d7df9..d29f1245 100644 --- a/tests/python/test_segmentation.py +++ b/tests/python/test_segmentation.py @@ -47,7 +47,7 @@ def test_construct_from_epochs(epochs, tmp_path): m3c2 = M3C2( epochs=(ref_epoch, epoch1), corepoints=ref_epoch.cloud, - cyl_radii=[2.0], + cyl_radius=2.0, normal_radii=[2.0], )