{ "cells": [ { "cell_type": "markdown", "id": "t4-header", "metadata": {}, "source": [ "# Tutorial 4: Developing a Custom Interface (Dev)\n", "\n", "This tutorial shows how to create a custom data interface for scivianna, enabling visualization of any data source through the GUI." ] }, { "cell_type": "markdown", "id": "t4-intro", "metadata": {}, "source": [ "## What is an Interface?\n", "\n", "An **Interface** in scivianna is a bridge between your data and the visualization panels. It tells scivianna:\n", "- How to load your data from files\n", "- What fields are available for display\n", "- How to compute 2D slices for geometry panels\n", "- How to get values at specific points (optional)\n", "\n", "## Interface Class Hierarchy\n", "\n", "```\n", "GenericInterface - Base class: file I/O, serialization\n", " └── Geometry2D - Adds 2D geometry slice computation\n", " ├── Geometry2DPolygon - Returns polygon vertex lists\n", " └── Geometry2DGrid - Returns rasterized grid data\n", "ValueAtLocation - Mixin: point value queries\n", "Value1DAtLocation - Mixin: 1D line value queries\n", "OverLine - Mixin: line integration computation\n", "CouplingInterface - Mixin: C3PO coupling functions\n", "```\n", "\n", "**You compose interfaces by inheriting from multiple classes.** For example:\n", "```python\n", "class MyInterface(Geometry2DPolygon, ValueAtLocation):\n", " pass\n", "```\n", "\n", "## Class Attributes\n", "\n", "| Attribute | Description |\n", "|-----------|-------------|\n", "| `extensions` | List of extension classes to attach to this interface |\n", "| `geometry_type` | GeometryType enum (2D, 3D, etc.) |\n", "| `data_type` | DataType enum (DATA_POLYGON, DATA_GRID) |\n", "| `rasterized` | Boolean: True if geometry comes from grid rasterization |" ] }, { "cell_type": "markdown", "id": "t4-example", "metadata": {}, "source": [ "## Complete Example: DummyTestInterface\n", "\n", "Here's a complete example of a simple interface that creates 3 rectangular polygons. This is the same interface used in the test suite.\n", "\n", "### How It Works\n", "\n", "1. **`__init__`**: Initializes storage for computed data, file paths, and frame tracking\n", "2. **`read_file`**: Stores file paths (this example doesn't actually parse files)\n", "3. **`compute_2D_data`**: Creates 3 rectangles that shift based on the `w_value` (z-coordinate)\n", "4. **`get_labels`**: Returns available field names: MESH, MATERIAL, VALUE\n", "5. **`get_value_dict`**: Returns different values depending on the selected field\n", "6. **`get_label_coloring_mode`**: Tells scivianna how to color each field type" ] }, { "cell_type": "code", "execution_count": null, "id": "t4-example-code", "metadata": {}, "outputs": [], "source": [ "from scivianna.interface.generic_interface import Geometry2DPolygon\n", "from scivianna.data.data2d import Data2D\n", "from scivianna.enums import GeometryType, VisualizationMode\n", "from scivianna.utils.polygonize_tools import PolygonElement, PolygonCoords\n", "from typing import Tuple, Dict, Any, List, Union\n", "import multiprocessing as mp\n", "\n", "\n", "class DummyTestInterface(Geometry2DPolygon):\n", " \"\"\"A simple interface that creates 3 rectangular polygons.\n", " \n", " This is a minimal example showing how to:\n", " - Define geometry using PolygonElement and PolygonCoords\n", " - Return Data2D for visualization\n", " - Handle multiple field types (MESH, MATERIAL, VALUE)\n", " \"\"\"\n", " \n", " # Required class attribute - tells scivianna this is a 3D infinite geometry\n", " geometry_type: GeometryType = GeometryType._3D_INFINITE\n", " \n", " def __init__(self):\n", " \"\"\"Initialize the interface.\n", " \n", " Sets up dictionaries to store:\n", " - Computed Data2D objects per caller\n", " - File paths per file label\n", " - Last computed frame parameters for caching\n", " \"\"\"\n", " self.data: Dict[str, Data2D] = {}\n", " self.file_path: Dict[str, str] = {}\n", " self.current_field = None\n", " self.last_computed_frame: Dict[str, List[float]] = {}\n", "\n", " def read_file(self, file_path: str, file_label: str):\n", " \"\"\"Store the file path (this example doesn't actually read files).\n", " \n", " Parameters\n", " ----------\n", " file_path : str\n", " Path to the file\n", " file_label : str\n", " Label identifying the file type (e.g., 'GEOMETRY', 'CSV')\n", " \"\"\"\n", " self.file_path[file_label] = file_path\n", "\n", " def compute_2D_data(\n", " self,\n", " u: Tuple[float, float, float],\n", " v: Tuple[float, float, float],\n", " u_min: float,\n", " u_max: float,\n", " v_min: float,\n", " v_max: float,\n", " w_value: float,\n", " q_tasks: mp.Queue,\n", " options: Dict[str, Any],\n", " caller: str = \"API\",\n", " ) -> Tuple[Data2D, bool]:\n", " \"\"\"Compute and return 2D geometry data.\n", " \n", " This method creates 3 rectangles offset by w_value. The geometry\n", " is cached to avoid recomputation when the frame hasn't changed.\n", " \n", " Parameters\n", " ----------\n", " u, v : Tuple[float, float, float]\n", " Direction vectors for the 2D viewport. These define the plane\n", " in which we're slicing the 3D geometry.\n", " u_min, u_max, v_min, v_max : float\n", " Viewport bounds - the visible area in the 2D plane\n", " w_value : float\n", " Coordinate perpendicular to the 2D plane (depth coordinate)\n", " q_tasks : mp.Queue\n", " Queue for slave communication - used for inter-process messaging\n", " options : Dict[str, Any]\n", " Additional computation options passed from the GUI\n", " caller : str\n", " Identifier of the caller (e.g., \"API\", \"GUI\"). Allows different\n", " callers to have separate cached data.\n", " \n", " Returns\n", " -------\n", " Tuple[Data2D, bool]\n", " Data2D object containing geometry, and True if data was updated\n", " \"\"\"\n", " # Check if we already computed this frame (caching mechanism)\n", " last_frame_key = (*u, *v, w_value)\n", " if (caller in self.last_computed_frame) and (\n", " self.last_computed_frame[caller] == last_frame_key\n", " ) and (caller in self.data):\n", " print(\"Skipping polygon computation (cached).\")\n", " return self.data[caller], False\n", "\n", " # Create 3 rectangles offset by w_value\n", " # v_offset shifts the rectangles based on the view direction and depth\n", " # In this example, we don't read the axes bounds as we know the whole slice geometry.\n", " v_offset = v[1] + 10 * w_value\n", " polygons = [\n", " PolygonElement(\n", " exterior_polygon=PolygonCoords(\n", " x_coords=[i, i, i+1, i+1],\n", " y_coords=[0+v_offset, 1+v_offset, 1+v_offset, 0+v_offset]\n", " ),\n", " holes=[],\n", " cell_id=i\n", " )\n", " for i in range(3)\n", " ]\n", "\n", " print(f\"Offset: {v_offset}\")\n", "\n", " # Cache and return the result\n", " self.last_computed_frame[caller] = last_frame_key\n", " self.data[caller] = Data2D.from_polygon_list(polygons)\n", " return self.data[caller], True\n", "\n", " def get_labels(self) -> List[str]:\n", " \"\"\"Return available field names.\n", " \n", " Returns\n", " -------\n", " List[str]\n", " List of field names that can be displayed\n", " \"\"\"\n", " return [\"MESH\", \"MATERIAL\", \"VALUE\"]\n", "\n", " def get_value_dict(\n", " self, \n", " value_label: str, \n", " cells: List[Union[int, str]], \n", " options: Dict[str, Any], \n", " caller: str = \"API\"\n", " ) -> Dict[Union[int, str], Union[float, str]]:\n", " \"\"\"Return field values for each cell.\n", " \n", " This method is called when the GUI needs to display cell labels\n", " or color the geometry based on field values.\n", " \n", " Parameters\n", " ----------\n", " value_label : str\n", " Field name to get values for (e.g., \"MESH\", \"MATERIAL\")\n", " cells : List[Union[int, str]]\n", " List of cell IDs requesting values\n", " options : Dict[str, Any]\n", " Additional options from the GUI\n", " \n", " Returns\n", " -------\n", " Dict[Union[int, str], Union[float, str]]\n", " Mapping of cell ID to value\n", " \"\"\"\n", " self.current_field = value_label\n", " \n", " if value_label == \"MESH\":\n", " # MESH returns NaN - no labels displayed\n", " return {cell_id: float('nan') for cell_id in cells}\n", " if value_label == \"MATERIAL\":\n", " # MATERIAL returns the cell ID as a string\n", " return {cell_id: str(cell_id) for cell_id in cells}\n", " if value_label == \"VALUE\":\n", " # VALUE returns the cell ID as a float\n", " return {cell_id: float(cell_id) for cell_id in cells}\n", "\n", " raise NotImplementedError(\n", " f\"Field '{value_label}' not implemented. Available: {self.get_labels()}\"\n", " )\n", "\n", " def get_label_coloring_mode(self, label: str) -> VisualizationMode:\n", " \"\"\"Return the visualization mode for a field.\n", " \n", " This tells scivianna how to interpret and color the data:\n", " - NONE: Don't display this field\n", " - FROM_STRING: Use categorical colors for string values\n", " - FROM_VALUE: Use a colormap for numeric values\n", " \n", " Parameters\n", " ----------\n", " label : str\n", " Field name\n", " \n", " Returns\n", " -------\n", " VisualizationMode\n", " NONE for MESH, FROM_STRING for MATERIAL, FROM_VALUE otherwise\n", " \"\"\"\n", " if label == \"MESH\":\n", " return VisualizationMode.NONE\n", " if label == \"MATERIAL\":\n", " return VisualizationMode.FROM_STRING\n", " return VisualizationMode.FROM_VALUE\n", "\n", " def get_file_input_list(self) -> List[Tuple[str, str]]:\n", " \"\"\"Return file type filters for the GUI.\n", " \n", " These appear in the file picker dialog to help users select\n", " appropriate files.\n", " \n", " Returns\n", " -------\n", " List[Tuple[str, str]]\n", " List of (label, description) tuples\n", " \"\"\"\n", " return [(\"GEOMETRY\", \"Geometry file.\"), (\"CSV\", \"CSV result file.\")]\n", "\n", "\n", "# Test the interface\n", "if __name__ == \"__main__\":\n", " iface = DummyTestInterface()\n", " \n", " # Simulate reading a file\n", " iface.read_file(\"/path/to/geometry.extension\", \"GEOMETRY\")\n", " print(f\"File path: {iface.file_path}\")\n", " \n", " # Get available fields\n", " print(f\"Labels: {iface.get_labels()}\")\n", " \n", " # Get value dict for each field type\n", " cells = [0, 1, 2]\n", " for label in iface.get_labels():\n", " values = iface.get_value_dict(label, cells, {})\n", " print(f\"{label}: {values}\")" ] }, { "cell_type": "markdown", "id": "t4-using-interface", "metadata": {}, "source": [ "## Using the Interface with Panel2D\n", "\n", "To use your interface in the GUI, you need to create a `ComputeSlave` and `Panel2D`:" ] }, { "cell_type": "code", "execution_count": null, "id": "t4-using-code", "metadata": {}, "outputs": [], "source": [ "from scivianna.slave import ComputeSlave\n", "from scivianna.panel.panel_2d import Panel2D\n", "\n", "# Create a slave with your interface\n", "slave = ComputeSlave(DummyTestInterface)\n", "\n", "# Create the panel\n", "panel = Panel2D(slave, name=\"My View\")\n", "\n", "# The panel now displays your 3 rectangles!\n", "# In a real application, you would show it in a window" ] }, { "cell_type": "markdown", "id": "t4-api-reference", "metadata": {}, "source": [ "## API Reference: Methods by Base Class\n", "\n", "### GenericInterface Methods\n", "\n", "| Method | Description |\n", "|--------|-------------|\n", "| `read_file(file_path, file_label)` | Load data from a file. Required. |\n", "| `get_labels()` | Return list of available field names. Default: `[MESH, MATERIAL]`. |\n", "| `get_label_coloring_mode(label)` | Return visualization mode (NONE, FROM_STRING, FROM_VALUE). Required. |\n", "| `get_file_input_list()` | Return file type filters for GUI picker. Required. |\n", "| `serialize(obj, key)` | Prepare object for multiprocessing transmission. |\n", "| `save(file_path, include_files)` | Save interface state to pickle file. |\n", "| `load(file_path, include_files)` | Load interface state from pickle file. |\n", "\n", "### Geometry2D Methods (for 2D visualization)\n", "\n", "| Method | Description |\n", "|--------|-------------|\n", "| `compute_2D_data(u, v, u_min, u_max, v_min, v_max, w_value, q_tasks, options, caller)` | Compute and return 2D geometry data. Required for 2D interfaces. |\n", "| `get_value_dict(value_label, cells, options, caller)` | Return field values for each cell. Required for 2D interfaces. |\n", "\n", "### ValueAtLocation Methods (optional mixins)\n", "\n", "| Method | Description |\n", "|--------|-------------|\n", "| `get_value(position, cell_index, material_name, field, options)` | Get field value at a specific 3D location. |\n", "| `get_values(positions, cell_indexes, material_names, field, options)` | Get field values at multiple locations (batch). |\n", "\n", "### Value1DAtLocation Methods (optional mixins)\n", "\n", "| Method | Description |\n", "|--------|-------------|\n", "| `get_1D_value(position, cell_index, material_name, field, options)` | Get 1D data (time series, spectrum) at a location. |\n", "\n", "### OverLine Methods (optional mixins)\n", "\n", "| Method | Description |\n", "|--------|-------------|\n", "| `compute_1D_line_data(pos, u, d, q_tasks, options)` | Compute field values along a 1D line for line plots. |\n", "\n", "### CouplingInterface Methods (for C3PO coupling)\n", "\n", "| Method | Description |\n", "|--------|-------------|\n", "| `set_time(time)` | Set current time for time-dependent data. |\n", "| `update_data(key, data)` | Replace data for a given key. |\n", "| `append_data(key, data)` | Append data with current time stamp. |\n", "| `update_mesh(key, data)` | Replace mesh and data. |\n", "| `append_mesh(key, data)` | Append mesh and data with time stamp. |\n", "| `get_template(name)` | Get field template for C3PO. |\n", "| `set_template(name, template)` | Set field template for C3PO. |" ] }, { "cell_type": "markdown", "id": "t4-summary", "metadata": {}, "source": [ "## Summary\n", "\n", "To create a custom interface:\n", "\n", "1. **Inherit from the appropriate base class**:\n", " - `Geometry2DPolygon` for polygon/line geometry\n", " - `Geometry2DGrid` for raster/grid data\n", " - Add mixins / or: `ValueAtLocation`, `Value1DAtLocation`, `OverLine`, `CouplingInterface`\n", "\n", "2. **Set class attributes**:\n", " - `geometry_type`: GeometryType enum\n", " - `data_type`: DataType enum\n", " - `extensions`: List of extension classes (optional)\n", "\n", "3. **Implement required methods** based on your base class:\n", " - All: `read_file()`, `get_labels()`, `get_label_coloring_mode()`, `get_file_input_list()`\n", " - Geometry2D: `compute_2D_data()`, `get_value_dict()`\n", " - ValueAtLocation: `get_value()`\n", "\n", "4. **Use `Data2D.from_polygon_list()`** to create polygon data easily\n", "\n", "5. **Cache computed frames** to avoid unnecessary recomputation" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }