############################ Search Results Visualization ############################ .. sidebar:: Table of Contents .. contents:: :local: :depth: 1 .. important:: **Purpose**: This guide is for **Developers and Engineers** who run CAD similarity search with :class:`CADSearch ` and want to **visualize the returned hits**. For dataset exploration workflows (querying file IDs and viewing pre-generated previews), start with :doc:`cad-data-visualization`. .. figure:: /_assets/images/search_results.png Overview ======== :class:`DatasetViewer ` can visualize similarity search results returned by :class:`CADSearch `. Instead of requiring pre-generated PNG previews, :meth:`show_search_results()` generates preview images on-the-fly from the CAD file paths stored in each search hit. Optionally, you can pass a query CAD file path to display the query preview alongside the results. Use this when you have: - A list of hits from :meth:`CADSearch.search_by_shape(...)`. - CAD files accessible at the paths in each hit Prerequisites ============= - A working HOOPS AI installation and valid license - A list of search hits (objects with at least ``.id`` and ``.score``) - The CAD files referenced by ``hit.id`` must be readable from the current machine Important: :meth:`show_search_results()` treats ``hit.id`` as a **CAD file path**. This works directly when you indexed embeddings using file paths as IDs. If you indexed embeddings using non-path IDs (for example, integers or database keys), map each hit to its CAD file path before visualizing. Quick Start =========== .. code-block:: python import hoops_ai from hoops_ai.insights import DatasetViewer from hoops_ai.ml import CADSearch # 1) Set license (only needed once in your process) hoops_ai.set_license("...") # 2) Initialize viewer (empty for search results) viewer = DatasetViewer(file_ids=[], png_paths=[], scs_paths=[]) # 3) Run a CAD similarity search (any approach that returns hits with .id/.score) query_path = "query.step" searcher = CADSearch(shape_model=embedder) hits = searcher.search_by_shape(cad_path=query_path, top_k=10) # 4) Visualize results fig = viewer.show_search_results(hits, query_file=query_path) See :doc:`embeddings-retrieval` for end-to-end retrieval examples. What :autolink:`show_search_results` does ========================================== At a high level, :meth:`show_search_results()`: 1. Reads the CAD file path from each hit (``hit.id``) 2. Generates PNG preview images into an output folder 3. Displays the results in a grid 4. Optionally adds labels (scores and/or filenames) 5. Optionally saves the figure to an image file If ``query_file`` is provided, the query CAD file is loaded and displayed alongside the hit grid. Common Patterns =============== Show the query part next to results ---------------------------------- .. code-block:: python fig = viewer.show_search_results(hits, query_file=query_path) Custom grid layout ------------------ .. code-block:: python fig = viewer.show_search_results( hits, k=16, # Show top 16 results grid_cols=4, # 4 columns (4x4 grid) title="Search Results for Gear Parts" ) Scores only (hide filenames) ---------------------------- .. code-block:: python fig = viewer.show_search_results( hits, show_filenames=False, # Hide filenames show_scores=True # Show scores only ) Limit and format a large result set ----------------------------------- .. code-block:: python fig = viewer.show_search_results( hits, k=25, # Show top 25 grid_cols=5, # 5x5 grid figsize=(20, 20) # Larger figure ) Save Results to File -------------------- .. code-block:: python fig = viewer.show_search_results( hits, save_path="results/search_output.png", output_dir="results/pngs" # Where to save generated PNGs ) Visualize multiple queries with organized output folders ------------------------------------------------------- When you run many searches, it is often helpful to keep each query's generated previews in its own folder. Here's an example for how to organize generated PNGs by search query: .. code-block:: python from pathlib import Path viewer = DatasetViewer(file_ids=[], png_paths=[], scs_paths=[]) query_files = [ "queries/part_a.step", "queries/part_b.step", ] for query_file in query_files: hits = searcher.search_by_shape(query_file, top_k=10) output_dir = f"results/{Path(query_file).stem}" fig = viewer.show_search_results(hits, output_dir=output_dir) Parameter Reference =================== .. list-table:: :header-rows: 1 :widths: 22 18 18 42 * - Parameter - Type - Default - Description * - ``hits`` - list - Required - Search hits returned by CADSearch (each hit must expose ``.id`` and ``.score``) * - ``query_file`` - str | Path - None - Optional CAD file path to render and display alongside the hit grid * - ``output_dir`` - str | Path - Auto - Where generated PNG previews are written. If not provided, uses the viewer reference directory when available, otherwise ``./out``. * - ``k`` - int - None - Maximum number of hits to display (None = show all) * - ``grid_cols`` - int - 4 - Number of columns in the results grid * - ``figsize`` - tuple - Auto - Matplotlib figure size ``(width, height)`` * - ``show_scores`` - bool - True - Show similarity scores in the per-item labels * - ``show_filenames`` - bool - True - Show filenames in the per-item labels * - ``title`` - str - "CAD Similarity Search Results" - Figure title * - ``missing_color`` - tuple - (200, 200, 200) - RGB color for placeholder tiles when a preview cannot be generated * - ``save_path`` - str | Path - None - Save the figure to this file path * - ``is_white_background`` - bool - True - Use a white background when exporting PNG previews * - ``overwrite`` - bool - True - Overwrite existing generated PNG previews in ``output_dir`` Comparison with other DatasetViewer methods =========================================== .. list-table:: :header-rows: 1 :widths: 30 20 50 * - Method - Input - Use case * - :meth:`show_preview_as_image()` - File IDs - Dataset exploration using pre-generated PNG previews * - :meth:`show_search_results()` - Search hits - Similarity search visualization with on-the-fly PNG generation * - :meth:`show_preview_as_3d()` - File IDs - Interactive 3D viewing of dataset items Tips and Best Practices ======================= Minimal viewer initialization for search results ------------------------------------------------ If you only need to visualize search hits (not explore a dataset by file IDs), an empty viewer is sufficient: .. code-block:: python viewer = DatasetViewer(file_ids=[], png_paths=[], scs_paths=[]) Performance for large result sets --------------------------------- For displaying many results, generate PNGs beforehand: .. code-block:: python # Generate all PNGs first for hit in hits[:k]: model = loader.create_from_file(hit.id) tools.exportStreamCache(model, f"pngs/{hit.id}") # Then visualize (will be faster) fig = viewer.show_search_results(hits, output_dir="pngs") Error Handling -------------- The method handles failed CAD loads gracefully with placeholders: .. code-block:: python # Will show gray placeholders for any files that fail to load fig = viewer.show_search_results( hits, missing_color=(220, 220, 220) # Light gray for failed loads ) Troubleshooting =============== No search hits provided ----------------------- If ``hits`` is empty, nothing can be visualized. Check that your search call returned results. Note: when ``hits`` is empty, ``show_search_results(...)`` returns ``None``. .. code-block:: python if hits: fig = viewer.show_search_results(hits) else: print("No search results to display") Gray placeholders instead of images ----------------------------------- If you get placeholder tiles (or previews fail to generate), enable logging to see the underlying load/export failures. Placeholders typically indicate the CAD file could not be loaded or a PNG could not be generated. **Common causes:** 1. The path stored in ``hit.id`` does not exist on this machine 2. The CAD file format cannot be loaded 3. The file is corrupted **Solutions:** Check logs for specific error messages: .. code-block:: python import logging logging.basicConfig(level=logging.INFO) fig = viewer.show_search_results(hits) Slow performance for many results --------------------------------- **Solution:** Limit results with `k` parameter: .. code-block:: python # Only show top 10 even if you have 100 hits fig = viewer.show_search_results(hits, k=10) Resource cleanup ---------------- DatasetViewer initializes a process pool for parallel preview generation. When you are done with a viewer, call :meth:`close()` to release resources. .. code-block:: python viewer = DatasetViewer(file_ids=[], png_paths=[], scs_paths=[]) viewer.show_search_results(hits) viewer.close() Or use a context manager: .. code-block:: python with DatasetViewer(file_ids=[], png_paths=[], scs_paths=[]) as viewer: viewer.show_search_results(hits) Migrating from legacy visualization helpers ========================================== If you previously had a notebook-only helper that took a list of hits and manually exported preview images, you can typically replace it with a single call to :meth:`show_search_results()`. .. code-block:: python # Old approach (Notebook Function) def visualize_cad_hits(hits, loader, output_dir=None, cols=4, figsize_per_col=4): # ... implementation ... fig = visualize_cad_hits(hits, loader, "out", cols=5, figsize_per_col=3) # New approach (DatasetViewer Method) viewer = DatasetViewer([], [], []) fig = viewer.show_search_results( hits, output_dir="out", grid_cols=5 ) **Benefits of the new approach:** - ✅ No need to pass `loader` separately (handled internally) - ✅ Better error handling - ✅ More customization options - ✅ Consistent API with other DatasetViewer methods - ✅ Automatic figure sizing Related Guides ============== - :doc:`embeddings-retrieval` - :doc:`cad-data-visualization`