@@ -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+
186213def 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+
263327def 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 :
0 commit comments