From fff671cdd28c85a555ee9fc0b7dd6f4bad4eccc9 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Sun, 16 Aug 2026 09:01:23 +1000 Subject: [PATCH] fix: PointCloud.Read() double-joins a caller-supplied "data/" prefix Read() hardcodes "data" as the mvtbdata subfolder via mvtb_path_to_datafile("data", filename) -- the documented convention is that callers pass a bare filename (PointCloud.Read('bunny.ply')). But both of the book's own figure-generation scripts (figures/code/chapter14/fig14_43.py, fig14_44.py) call PointCloud.Read('data/bunny.ply') with a redundant "data/" prefix, a convention that predates mvtb_path_to_datafile's current auto-prefixing design. That redundant prefix made the lookup search for mvtbdata/data/data/bunny.ply, which doesn't exist, even though mvtbdata/data/bunny.ply is genuinely present in the package. Strip a leading "data" path component before joining, so both PointCloud.Read('bunny.ply') and PointCloud.Read('data/bunny.ply') resolve to the same file. Co-Authored-By: Claude Sonnet 5 --- src/machinevisiontoolbox/PointCloud.py | 13 +++++++++++++ tests/test_pointcloud.py | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/machinevisiontoolbox/PointCloud.py b/src/machinevisiontoolbox/PointCloud.py index 8611e5df..14305878 100644 --- a/src/machinevisiontoolbox/PointCloud.py +++ b/src/machinevisiontoolbox/PointCloud.py @@ -2,6 +2,7 @@ Thin wrapper around Open3D point-cloud objects with MVTB look and feel. """ +from pathlib import Path from warnings import warn from typing import Any, Callable @@ -244,10 +245,22 @@ def Read(cls, filename: str, *args: Any, **kwargs: Any) -> "PointCloud": the ``data`` folder of the `mvtb_data package `_. + .. note:: ``filename`` may be given as a bare name (``'bunny.ply'``) + or with a redundant leading ``data/`` (``'data/bunny.ply'``, the + older convention some book examples still use) -- both resolve + to the same file. + """ from machinevisiontoolbox import mvtb_path_to_datafile + # accept a caller-supplied redundant "data/" prefix (older + # convention) without double-joining it against the "data" this + # method already prepends + parts = Path(filename).parts + if parts and parts[0] == "data": + filename = str(Path(*parts[1:])) if len(parts) > 1 else "" + filename = mvtb_path_to_datafile("data", filename, string=True) pcd = o3d.io.read_point_cloud(filename, *args, **kwargs) return cls(pcd) diff --git a/tests/test_pointcloud.py b/tests/test_pointcloud.py index 84bae3f1..410acc65 100644 --- a/tests/test_pointcloud.py +++ b/tests/test_pointcloud.py @@ -39,6 +39,18 @@ def test_constructor(self): rgbd = Image.Pstack((d, rgb.astype("uint16")), colororder="DRGB") pc = PointCloud.DepthImage(rgbd, camera) + def test_read(self): + # bare filename -- the documented convention + pc = PointCloud.Read("bunny.ply") + self.assertIsInstance(pc, PointCloud) + self.assertEqual(len(pc), 35947) + + # a caller-supplied redundant "data/" prefix (the older convention + # some book examples still use) must resolve to the same file, + # not double-join to data/data/bunny.ply + pc2 = PointCloud.Read("data/bunny.ply") + self.assertEqual(len(pc2), len(pc)) + # ----------------------------------------------------------------------- # if __name__ == "__main__":