Skip to content

Commit a832c59

Browse files
authored
Merge pull request #62 from petercorke/feat/mvtbtool-test-flag
feat: add mvtbtool --test smoke test, fix silent Open3D banner skip
2 parents 5eb6bbf + 6a0bf2c commit a832c59

2 files changed

Lines changed: 102 additions & 14 deletions

File tree

‎src/machinevisiontoolbox/bin/mvtbtool.py‎

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@ def parse_arguments():
148148
help="images to load on startup. These appear in the variable img; or img[0], img[1], ... if multiple are specified",
149149
)
150150

151+
parser.add_argument(
152+
"--test",
153+
default=False,
154+
action="store_true",
155+
help="non-interactive environment smoke test: print package versions, "
156+
"exercise one real numeric code path per package, exit 0/1 "
157+
"instead of starting an interactive shell",
158+
)
159+
151160
argv = env_arguments(parser) + sys.argv[1:]
152161
return parser.parse_known_args(argv)
153162

@@ -183,23 +192,29 @@ def optional_torch_imports(enable):
183192
return modules, warnings
184193

185194

195+
def get_versions() -> list[str]:
196+
"""Package version strings shown in the banner and by --test."""
197+
versions = [
198+
f"Python=={sys.version.split('|')[0].strip()}",
199+
f"MVTB=={version('machinevision-toolbox-python')}",
200+
f"SMTB=={version('spatialmath-python')}",
201+
f"NumPy=={version('numpy')}",
202+
f"SciPy=={version('scipy')}",
203+
f"Matplotlib=={version('matplotlib')}",
204+
f"OpenCV=={cv2.__version__}",
205+
]
206+
try:
207+
versions.append(f"Open3D=={version('open3d')}")
208+
except PackageNotFoundError:
209+
versions.append("Open3D==not installed")
210+
return versions
211+
212+
186213
def make_banner(args, optional_modules=None):
187214
if optional_modules is None:
188215
optional_modules = {}
189216

190-
versions = []
191-
192-
versions.append(f"Python=={sys.version.split('|')[0].strip()}")
193-
versions.append(f"MVTB=={version('machinevision-toolbox-python')}")
194-
versions.append(f"SMTB=={version('spatialmath-python')}")
195-
versions.append(f"NumPy=={version('numpy')}")
196-
versions.append(f"SciPy=={version('scipy')}")
197-
versions.append(f"Matplotlib=={version('matplotlib')}")
198-
versions.append(f"OpenCV=={cv2.__version__}")
199-
try:
200-
versions.append(f"Open3D=={version('open3d')}")
201-
except PackageNotFoundError:
202-
pass
217+
versions = get_versions()
203218

204219
if "torch" in optional_modules:
205220
versions.append(
@@ -260,7 +275,61 @@ def make_banner(args, optional_modules=None):
260275
print(banner)
261276

262277

278+
def run_smoke_test() -> bool:
279+
"""Non-interactive environment sanity check, used by --test.
280+
281+
Not a substitute for the pytest suite -- a fast, human- or script-run
282+
"did this environment actually come together correctly" check: real
283+
versions, and one real numeric result per OpenCV- and Open3D-backed
284+
code path, checked against a sanity condition rather than just "it
285+
didn't raise". Missing optional dependencies (e.g. Open3D) are
286+
reported as a FAIL with the reason, not silently skipped.
287+
"""
288+
print(", ".join(get_versions()))
289+
290+
checks: list[tuple[str, bool]] = []
291+
292+
try:
293+
img = Image.Read("monalisa.png", mono=True)
294+
smoothed = img.smooth(sigma=2)
295+
ok = smoothed.shape == img.shape and not np.array_equal(
296+
smoothed.array, img.array
297+
)
298+
checks.append(("Image.Read + smooth() (OpenCV-backed)", ok))
299+
except Exception as e:
300+
checks.append((f"Image.Read + smooth() (OpenCV-backed): {e}", False))
301+
302+
try:
303+
import open3d # noqa: F401 -- presence check; PointCloud does the real work
304+
except ImportError as e:
305+
checks.append(
306+
(f"PointCloud.Read('bunny.ply') + voxel_grid(): Open3D not installed ({e})", False)
307+
)
308+
else:
309+
try:
310+
bunny = PointCloud.Read("bunny.ply")
311+
voxels = bunny.voxel_grid(voxel_size=0.01)
312+
ok = len(bunny) > 0 and len(voxels._voxels.get_voxels()) > 0
313+
checks.append(("PointCloud.Read('bunny.ply') + voxel_grid() (Open3D-backed)", ok))
314+
except Exception as e:
315+
checks.append(
316+
(f"PointCloud.Read('bunny.ply') + voxel_grid() (Open3D-backed): {e}", False)
317+
)
318+
319+
for name, passed in checks:
320+
print(f"[{'PASS' if passed else 'FAIL'}] {name}")
321+
322+
n_passed = sum(1 for _, passed in checks if passed)
323+
print(f"mvtbtool --test: {n_passed}/{len(checks)} checks passed")
324+
return n_passed == len(checks)
325+
326+
263327
def main():
328+
args, ipython_args = parse_arguments()
329+
330+
if args.test:
331+
sys.exit(0 if run_smoke_test() else 1)
332+
264333
try:
265334
import IPython
266335
from IPython.terminal.prompts import Prompts
@@ -273,7 +342,6 @@ def main():
273342
" pip install machinevision-toolbox-python[tool]\n"
274343
)
275344

276-
args, ipython_args = parse_arguments()
277345
torch_modules, torch_warnings = optional_torch_imports(args.torch)
278346

279347
if args.backend is not None:

‎tests/test_bin.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
except ImportError:
2525
_pytesseract_available = False
2626

27+
try:
28+
import open3d # noqa: F401
29+
_open3d_available = True
30+
except ImportError:
31+
_open3d_available = False
32+
2733
# An image bundled with mvtb-data that is always present.
2834
_TEST_IMAGE = "monalisa.png"
2935

@@ -46,6 +52,20 @@ def test_help(self):
4652
result = _run(["machinevisiontoolbox.bin.mvtbtool", "--help"])
4753
self.assertEqual(result.returncode, 0, msg=result.stderr.decode())
4854

55+
def test_smoke_test(self):
56+
"""--test always exercises the OpenCV path; Open3D result depends on
57+
whether it's installed in this environment, but either way it must
58+
be reported explicitly, not silently skipped."""
59+
result = _run(["machinevisiontoolbox.bin.mvtbtool", "--test"])
60+
stdout = result.stdout.decode()
61+
self.assertIn("[PASS] Image.Read + smooth()", stdout, msg=stdout)
62+
if _open3d_available:
63+
self.assertIn("[PASS] PointCloud.Read('bunny.ply')", stdout, msg=stdout)
64+
self.assertEqual(result.returncode, 0, msg=stdout)
65+
else:
66+
self.assertIn("Open3D not installed", stdout, msg=stdout)
67+
self.assertEqual(result.returncode, 1, msg=stdout)
68+
4969

5070
class TestImtool(unittest.TestCase):
5171

0 commit comments

Comments
 (0)