diff --git a/docs/source/actor_visualization.rst b/docs/source/actor_visualization.rst index 3f2a4f7..fe56b28 100644 --- a/docs/source/actor_visualization.rst +++ b/docs/source/actor_visualization.rst @@ -29,7 +29,7 @@ Available Settings * Optional - - ***port***: (int) The port on which the visualization server will run. Defaults to `5006`. + - ***port***: (int) The port on which the visualization server will run. Defaults to `0`, indicating a random available port. - ***throttle_interval***: (float) The minimum time in seconds between plot updates. This can be used to prevent the visualization from slowing down if data arrives very quickly. Defaults to `0.1`. - ***keep_alive***: (bool) If `True`, the visualization server will remain active after diff --git a/docs/source/images/automatic.png b/docs/source/images/automatic.png new file mode 100644 index 0000000..510d7e1 Binary files /dev/null and b/docs/source/images/automatic.png differ diff --git a/docs/source/images/ff_prime.gif b/docs/source/images/ff_prime.gif new file mode 100644 index 0000000..ccd9c0c Binary files /dev/null and b/docs/source/images/ff_prime.gif differ diff --git a/docs/source/images/ip_curve.gif b/docs/source/images/ip_curve.gif new file mode 100644 index 0000000..9132982 Binary files /dev/null and b/docs/source/images/ip_curve.gif differ diff --git a/docs/source/images/outline.gif b/docs/source/images/outline.gif new file mode 100644 index 0000000..c298082 Binary files /dev/null and b/docs/source/images/outline.gif differ diff --git a/docs/source/index.rst b/docs/source/index.rst index 53c3e92..3da8ee5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -26,6 +26,7 @@ Manual installing usage tips_and_tricks + training .. toctree:: :caption: API docs diff --git a/docs/source/training.rst b/docs/source/training.rst new file mode 100644 index 0000000..2311dad --- /dev/null +++ b/docs/source/training.rst @@ -0,0 +1,480 @@ + +.. _`training`: + +******** +Training +******** + +Visualization Actor Training +============================ + +In this training you will learn the following: + +- Working with live data visualization +- Creating custom plotting scripts for the visualization actor +- Setting up visualization components in yMMSL files +- Using both MUSCLE3 and standalone modes + +This training assumes you are familiar with the with MUSCLE3 workflows. If you are not, +please take a look at the `MUSCLE3 documentation `_. + +There is also training material available for the ITER Pulse Design Simulation, which can also serve as +an introduction to IMAS-MUSCLE3 workflows, which is available +`here `_. Note that this requires an ITER account to view. + +All examples require that you have an environment with IMAS-MUSCLE3 up and running. +If you do not have this yet, please have a look at the :ref:`installation instructions `. + +.. important:: + For this training you will need access to a graphical environment to visualize + the simulation results. If you are on SDCC, it is recommended to follow this training + through the NoMachine client, and using chrome as your default browser (there have been + issues when using firefox through NoMachine). + +Exercise 1a: Setting Up Your First Visualization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. md-tab-set:: + .. md-tab-item:: Exercise + + We will start by running the visualization actor for a simple example configuration. + First, create a yMMSL configuration file that sets up a simple visualization pipeline with: + + 1. A :ref:`source actor ` that sends the equilibrium IDS + 2. A :ref:`visualization actor ` that receives and plots the data. + + Use the following settings in the yMMSL: + + - Source URI: ``imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/`` + - Plotting script: ``imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py`` + + This plotting script is a simple configuration that will only plot the plasma current + of an equilibrium IDS over time. + Run the MUSCLE pipeline, supplying the yMMSL file you made: + + .. code-block:: bash + + muscle_manager --start-all + + The visualization actor will automatically open your default browser after it is initiated. + What do you see in your browser? + + .. hint:: + There are premade examples available that you use can use available, located in this + location: ``imas_muscle3/visualization/examples``. For this specific exercise, take a look at the example yMMSL file in ``imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl``. If you want detailed information about the visualization actor, take a look + at the :ref:`documentation `. + + .. md-tab-item:: Solution + + Create a file called ``my_visualization.ymmsl`` with the following content: + + .. code-block:: yaml + + ymmsl_version: v0.1 + model: + name: my_visualization + components: + source_component: + implementation: source_component + ports: + o_i: [equilibrium_out] + visualization_component: + implementation: visualization_component + ports: + s: [equilibrium_in] + conduits: + source_component.equilibrium_out: visualization_component.equilibrium_in + settings: + source_component.source_uri: imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/ + visualization_component.plot_file_path: /imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py + implementations: + visualization_component: + executable: python + args: -u -m imas_muscle3.actors.visualization_component + source_component: + executable: python + args: -u -m imas_muscle3.actors.source_component + resources: + source_component: + threads: 1 + visualization_component: + threads: 1 + + When you launch the muscle_manager, the browser should open, and you will see the + plasma current plotted over time, updating in real-time as the new time slices are + received by the visualization actor. + + .. figure:: ../source/images/ip_curve.gif + + +Exercise 1b: Understanding the Basic Structure +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. md-tab-set:: + .. md-tab-item:: Exercise + + Now that you were able to run the visualization actor in the previous exercise, let's + take a look under the hood to see what the plotting script actually does. + + Every plotting script for the visualization actor must include the following two classes: + + 1. ``State(BaseState)``: This class handles extracting and storing data from incoming IDSs. + 2. ``Plotter(BasePlotter)``: This class handles how to plot the extracted data in the ``State`` class. + + Take a look at the simple example plotting script below that you used in previous exercise + to visualize the plasma current (Ip) from an equilibrium IDS over time. + + **File:** `imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py` + + .. literalinclude:: ../../imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py + :language: python + + What does the ``extract`` method do in the State class? + + What does the ``get_dashboard`` method do in the Plotter class? + + .. md-tab-item:: Solution + + The ``State`` class **must** implement the ``extract(self, ids)`` method. + The ``extract`` method for this example case: + + - handles every IDS that is received on the S port, one at a time. So first it checks if the incoming IDS is an equilibrium IDS. + - Extracts the plasma current of the time slice (``ids.time_slice[0].global_quantities.ip``) and + its corresponding time value (``ids.time[0]``), and stores it in an Xarray dataset. + - Either stores the first Xarray dataset entry in ``self.data`` or appends it to the existing Xarray dataset. + + The ``Plotter`` class **must** implement the ``get_dashboard(self)`` method. + The ``get_dashboard`` method for this example case: + + - Gets called once when the visualization actor is initialized. + - Uses `HoloViews `_ as its cornerstone to enable interactive visualizations. + - Returns a `HoloViews DynamicMap `_ object, + which allows you to dynamically update a plot whenever its argument function is called, here ``self.plot_ip_vs_time``. + - Implements ``self.plot_ip_vs_time`` which automatically runs whenever the ``self.time`` parameter is updated. + This happens when the Visualization actor receives new data, or when the user changes the time slider in the UI. + ``self.plot_ip_vs_time`` loads the state defined in the ``State`` class above, using ``self.active_data.data.get("equilibrium")``. + - Extracts the Ip and time arrays from the state object, based on the selected time parameter. + - It plots the plasma current versus time using a `HoloViews Curve `_, + which it returns to the DynamicMap, which will automatically update the plot. + + +Exercise 1c: Creating a custom visualization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. md-tab-set:: + .. md-tab-item:: Exercise + + Now that you understand how the ``State`` and ``Plotter`` classes work, let's + try to create your own plotting script for the visualization actor. In this + exercise you will learn how to visualize a 1D ff' profile, as a function of the + poloidal flux, over time. + + For this exercise you can use the template below, in which you only have to implement + the ``extract_equilibrium`` and ``plot_f_df_dpsi_profile`` methods. + + .. code-block:: python + + import holoviews as hv + import numpy as np + import param + import xarray as xr + + from imas_muscle3.visualization.base_plotter import BasePlotter + from imas_muscle3.visualization.base_state import BaseState + + + class State(BaseState): + def extract(self, ids): + if ids.metadata.name == "equilibrium": + self.extract_equilibrium(ids) + + def extract_equilibrium(self, ids): + # Implement this method! + + class Plotter(BasePlotter): + def get_dashboard(self): + profile_plot = hv.DynamicMap(self.plot_f_df_dpsi_profile) + return profile_plot + + @param.depends("time") + def plot_f_df_dpsi_profile(self): + # Implement this method! + + Implement the ``extract_equilibrium`` method which does the following: + + - Loads the ff' profile from the IDS: ``ids.time_slice[0].profiles_1d.f_df_dpsi`` + - Loads the corresponding psi coordinates: ``ids.time_slice[0].profiles_1d.psi`` + - Stores both in an Xarray Dataset. + - Either saves the first entry in ``self.data`` or concatenates it to an existing Dataset. + + .. hint:: + Profile data is a 1D array for each time slice, so you'll need a dimension for + the profile points in addition to time. + + Also implement the ``plot_f_df_dpsi_profile`` method in the ``Plotter`` class that + displays the ff' profile stored in the state object as a function of psi for the current time step. + + Your ``plot_f_df_dpsi_profile`` should do the following: + + - Load the state data from the current ``self.active_state``. + - Extract the arrays for ff' and psi from the state data (use ``state.sel(time=self.time)``). + - Display psi on the x-axis and f_df_dpsi on the y-axis, using a `HoloViews Curve `_. + - Give an appropriate title, xlabel, and ylabel. + - Properly handle the case when no data is available yet (Return an empty ``hv.Curve``). + + .. md-tab-item:: Solution + + .. code-block:: python + + import holoviews as hv + import numpy as np + import param + import xarray as xr + + from imas_muscle3.visualization.base_plotter import BasePlotter + from imas_muscle3.visualization.base_state import BaseState + + + class State(BaseState): + def extract(self, ids): + if ids.metadata.name == "equilibrium": + self.extract_equilibrium(ids) + + def extract_equilibrium(self, ids): + ts = ids.time_slice[0] + + profiles_data = xr.Dataset( + { + "f_df_dpsi": (("time", "profile"), [ts.profiles_1d.f_df_dpsi]), + "psi_profile": (("time", "profile"), [ts.profiles_1d.psi]), + }, + coords={ + "time": [ids.time[0]], + "profile": np.arange(len(ts.profiles_1d.f_df_dpsi)), + }, + ) + + current_data = self.data.get("equilibrium") + if current_data is None: + self.data["equilibrium"] = profiles_data + else: + self.data["equilibrium"] = xr.concat( + [current_data, profiles_data], dim="time", join="outer" + ) + + + class Plotter(BasePlotter): + def get_dashboard(self): + profile_plot = hv.DynamicMap(self.plot_f_df_dpsi_profile) + return profile_plot + + @param.depends("time") + def plot_f_df_dpsi_profile(self): + xlabel = "Psi [Wb]" + ylabel = "ff'" + state = self.active_state.data.get("equilibrium") + + if state: + selected_data = state.sel(time=self.time) + psi = selected_data.psi_profile.values + f_df_dpsi = selected_data.f_df_dpsi.values + title = f"ff' profile (t={self.time:.3f}s)" + else: + psi, f_df_dpsi, title = [], [], "Waiting for data..." + + return hv.Curve((psi, f_df_dpsi), kdims=[xlabel], vdims=[ylabel]).opts( + framewise=True, + height=400, + width=600, + title=title, + xlabel=xlabel, + ylabel=ylabel, + ) + + This generates the following ff' plot over time: + + .. figure:: ../source/images/ff_prime.gif + + +.. tip:: More complex examples of visualizations are available in the + ``imas_muscle3/visualization/examples/`` directory. For example, the PDS example + combines data from multiple IDSs, handles machine description data, and + handles different types of plots. + + +Exercise 2: Using Automatic Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. md-tab-set:: + .. md-tab-item:: Exercise + + In this exercise you will your yMMSL configuration to enable automatic mode. This mode allows + the visualization actor to automatically discover and plot time-dependent + quantities without needing a custom plotting script. + + Advantages of automatic mode: + + - Useful for exploring unfamiliar datasets + - Automatically discovers all time-dependent quantities in the IDS + - Provides a dropdown menu to select quantities to visualize + - Chooses appropriate plot types automatically + - No need to manually extract quantities + + Disadvantages: + + - No fine grain control over the plots + - Unable to combine data + - Slower performance and increased memory usage + + Repeat exercise 1a, however this time add the following settings to the yMMSL: + + .. code-block:: yaml + + settings: + visualization_component.automatic_mode: true + visualization_component.automatic_extract_all: true + + Run the MUSCLE pipeline, supplying the yMMSL file you made. Use the dropdown menu to + visualize the following parameters: + + - ``equilibrium/time_slice[0]/profiles_1d[0]/dpressure_dpsi`` + - ``equilibrium/time_slice[0]/global_quantities/energy_mhd`` + + .. md-tab-item:: Solution + + Besides the plasma current curve, which was defined in the plotter class, you + should also see the p' and the MHD energy curves in separate panels: + + .. figure:: ../source/images/automatic.png + +Exercise 3: Using the CLI +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. md-tab-set:: + .. md-tab-item:: Exercise + + It is also possible to run the visualization actor from the command line instead, + without setting up a MUSCLE3 workflow. Try running the simple_1d_plot example + through the CLI. + + Run the visualization with: + + - URI: ``imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/`` + - Plotting script: ``imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py`` + + .. hint:: + Use ``python -m imas_muscle3.visualization.cli --help`` to see available options. + + .. md-tab-item:: Solution + + Run the following command: + + .. code-block:: bash + + python -m imas_muscle3.visualization.cli \ + "imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/" \ + imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py + + +Exercise 4: Loading Machine Descriptions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. md-tab-set:: + .. md-tab-item:: Exercise + + In this exercise you will create a 2D plot that combines **static machine description data** with **time-evolving equilibrium data**. Specifically, + + - The plasma boundary outline (from the `equilibrium` IDS) as it changes over time. + - The tokamak first wall and divertor (from the `wall` machine description IDS). + + You will need to update your yMMSL from exercise 1a, with the following changes: + + - Add a new source actor which will send the ``wall`` and ``pf_active`` + machine description IDSs to the visualization actor. + - The new source actor should use the following URI: + .. code-block:: bash + + imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/ + - The visualization actor should receive the machine description IDSs on the S port, with the names ``wall_md_in`` and ``pf_active_md_in``. + + + You will need to implement the ``extract_equilibrium``, ``_plot_boundary_outline``, and ``_plot_wall`` methods, in the template below. + + Implement the ``extract_equilibrium`` method which does the following: + + - Loads R and Z coordinates of the the boundary outline from the equilibrium IDS: + ``ids.time_slice[0].boundary.outline.r``, ``ids.time_slice[0].boundary.outline.z`` + - Stores both in an Xarray Dataset. + - Either saves the first entry in ``self.data`` or concatenates it to an existing Dataset. + + Implement the ``_plot_boundary_outline`` method which does the following: + + - Load the state data from the current ``self.active_state``. + - Extract the r and z arrays from the state data (use ``state.sel(time=self.time)``). + - Display r and z, using a `HoloViews Curve `_. + + Implement the ``_plot_wall`` method which does the following: + + - Load the wall machine description IDS using ``self.active_state.md.get("wall")``. + - Loads the first wall and divertor from the wall IDS: + ``ids.description_2d[0].limiter.unit[i].outline.r`` and ``ids.description_2d[0].limiter.unit[i].outline.z`` for ``i = 0`` and ``i = 1``. + - Display r and z, using a `HoloViews Path `_. + + .. code-block:: python + + + import holoviews as hv + import numpy as np + import panel as pn + import xarray as xr + + from imas_muscle3.visualization.base_plotter import BasePlotter + from imas_muscle3.visualization.base_state import BaseState + + + class State(BaseState): + def extract(self, ids): + if ids.metadata.name == "equilibrium": + self.extract_equilibrium(ids) + + def extract_equilibrium(self, ids): + # Implement this method! + + class Plotter(BasePlotter): + DEFAULT_OPTS = hv.opts.Overlay( + xlim=(0, 13), + ylim=(-10, 10), + title="Wall and equilibrium boundary outline", + xlabel="r [m]", + ylabel="z [m]", + ) + + def get_dashboard(self): + elements = [ + hv.DynamicMap(self._plot_boundary_outline), + hv.DynamicMap(self._plot_wall), + ] + overlay = hv.Overlay(elements).collate().opts(self.DEFAULT_OPTS) + return pn.pane.HoloViews(overlay, width=800, height=1000) + + @pn.depends("time") + def _plot_boundary_outline(self): + # Implement this method! + + def _plot_wall(self): + # Implement this method! + + + .. md-tab-item:: Solution + + .. figure:: ../source/images/outline.gif + + Example yMMSL file: + + .. literalinclude:: ../../imas_muscle3/visualization/examples/machine_description/machine_description.ymmsl + + Example plotting script: + + .. literalinclude:: ../../imas_muscle3/visualization/examples/machine_description/machine_description.py + :language: python + diff --git a/imas_muscle3/actors/visualization_component.py b/imas_muscle3/actors/visualization_component.py index 36816ae..c5c6209 100644 --- a/imas_muscle3/actors/visualization_component.py +++ b/imas_muscle3/actors/visualization_component.py @@ -60,7 +60,8 @@ def main() -> None: while instance.reuse_instance(): if first_run: plot_file_path = instance.get_setting("plot_file_path", "str") - port = get_setting_optional(instance, "port", 5006) + # If port is not specified, use a random available port + port = get_setting_optional(instance, "port", 0) # FIXME: there is an issue when the plotting takes much longer # than it takes for data to arrive from the MUSCLE actor. As a # remedy, set a plotting throttle interval. diff --git a/imas_muscle3/visualization/examples/machine_description/machine_description.py b/imas_muscle3/visualization/examples/machine_description/machine_description.py new file mode 100644 index 0000000..eb449e7 --- /dev/null +++ b/imas_muscle3/visualization/examples/machine_description/machine_description.py @@ -0,0 +1,89 @@ +""" +Example that plots the following: +- First wall and divertor from machine description IDS. +- Boundary outline from an equilibrium IDS +""" + +import holoviews as hv +import numpy as np +import panel as pn +import xarray as xr + +from imas_muscle3.visualization.base_plotter import BasePlotter +from imas_muscle3.visualization.base_state import BaseState + + +class State(BaseState): + def extract(self, ids): + if ids.metadata.name == "equilibrium": + self.extract_equilibrium(ids) + + def extract_equilibrium(self, ids): + ts = ids.time_slice[0] + outline = ts.boundary.outline + + boundary_data = xr.Dataset( + { + "r": (("time", "point"), [outline.r]), + "z": (("time", "point"), [outline.z]), + }, + coords={ + "time": [ids.time[0]], + "point": np.arange(len(outline.r)), + }, + ) + + current_data = self.data.get("equilibrium") + if current_data is None: + self.data["equilibrium"] = boundary_data + else: + self.data["equilibrium"] = xr.concat( + [current_data, boundary_data], dim="time", join="outer" + ) + + +class Plotter(BasePlotter): + DEFAULT_OPTS = hv.opts.Overlay( + xlim=(0, 13), + ylim=(-10, 10), + title="Wall and equilibrium boundary outline", + xlabel="r [m]", + ylabel="z [m]", + ) + + def get_dashboard(self): + elements = [ + hv.DynamicMap(self._plot_boundary_outline), + hv.DynamicMap(self._plot_wall), + ] + overlay = hv.Overlay(elements).collate().opts(self.DEFAULT_OPTS) + return pn.pane.HoloViews(overlay, width=800, height=1000) + + @pn.depends("time") + def _plot_boundary_outline(self): + state = self.active_state.data.get("equilibrium") + + if state is not None and "r" in state and "z" in state: + selected_data = state.sel(time=self.time) + r = selected_data.r.values + z = selected_data.z.values + else: + r, z = [], [], "Waiting for data..." + + return hv.Curve((r, z)).opts(self.DEFAULT_OPTS) + + def _plot_wall(self): + """Generates path for limiter and divertor.""" + paths = [] + wall = self.active_state.md.get("wall") + if wall is not None: + for unit in wall.description_2d[0].limiter.unit: + name = str(unit.name) + r_vals = unit.outline.r + z_vals = unit.outline.z + paths.append((r_vals, z_vals, name)) + return hv.Path(paths, vdims=["name"]).opts( + color="black", + line_width=2, + hover_tooltips=[("", "@name")], + ) diff --git a/imas_muscle3/visualization/examples/machine_description/machine_description.ymmsl b/imas_muscle3/visualization/examples/machine_description/machine_description.ymmsl new file mode 100644 index 0000000..1a10e2d --- /dev/null +++ b/imas_muscle3/visualization/examples/machine_description/machine_description.ymmsl @@ -0,0 +1,39 @@ +ymmsl_version: v0.1 +model: + name: test_model + components: + source_component: + implementation: source_component + ports: + o_i: [equilibrium_out] + source_component_md: + implementation: source_component + ports: + o_i: [wall_out, pf_active_out] + visualization_component: + implementation: visualization_component + ports: + s: [equilibrium_in, pf_active_md_in, wall_md_in] + conduits: + source_component.equilibrium_out: visualization_component.equilibrium_in + source_component_md.wall_out: visualization_component.wall_md_in + source_component_md.pf_active_out: visualization_component.pf_active_md_in +settings: + source_component.source_uri: imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/ + source_component_md.source_uri: imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/ + visualization_component.plot_file_path: /home/ITER/blokhus/projects/IMAS-MUSCLE3/imas_muscle3/visualization/examples/machine_description/machine_description.py + visualization_component.keep_alive: true +implementations: + visualization_component: + executable: python + args: -u -m imas_muscle3.actors.visualization_component + source_component: + executable: python + args: -u -m imas_muscle3.actors.source_component +resources: + source_component: + threads: 1 + source_component_md: + threads: 1 + visualization_component: + threads: 1 diff --git a/imas_muscle3/visualization/examples/pds/pds.ymmsl b/imas_muscle3/visualization/examples/pds/pds.ymmsl index d514634..f02475b 100644 --- a/imas_muscle3/visualization/examples/pds/pds.ymmsl +++ b/imas_muscle3/visualization/examples/pds/pds.ymmsl @@ -23,7 +23,6 @@ settings: source_component.source_uri: imas:hdf5?path=/home/sebbe/projects/iter_python/IMAS-MUSCLE3/data/pds_example source_component_md.source_uri: imas:hdf5?path=/home/sebbe/projects/iter_python/Waveform-Editor/data/nice-input-dd4 visualization_component.plot_file_path: /home/sebbe/projects/iter_python/IMAS-MUSCLE3/imas_muscle3/visualization/examples/pds/pds.py - visualization_component.port: 5007 visualization_component.keep_alive: true implementations: visualization_component: diff --git a/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl b/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl index 9c804f2..79d58a0 100644 --- a/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl +++ b/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl @@ -13,9 +13,8 @@ model: conduits: source_component.equilibrium_out: visualization_component.equilibrium_in settings: - source_component.source_uri: imas:hdf5?path=/home/sebbe/projects/iter_python/IMAS-MUSCLE3/data/striped_eq - visualization_component.plot_file_path: /home/sebbe/projects/iter_python/IMAS-MUSCLE3/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py - visualization_component.port: 5007 + source_component.source_uri: imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/ + visualization_component.plot_file_path: /home/ITER/blokhus/projects/IMAS-MUSCLE3/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py visualization_component.throttle_interval: 0 visualization_component.keep_alive: true implementations: diff --git a/imas_muscle3/visualization/visualization_actor.py b/imas_muscle3/visualization/visualization_actor.py index 20a5573..4f49e8e 100644 --- a/imas_muscle3/visualization/visualization_actor.py +++ b/imas_muscle3/visualization/visualization_actor.py @@ -1,6 +1,5 @@ import logging import runpy -import webbrowser from typing import Dict import panel as pn @@ -92,18 +91,7 @@ def _start_server(self) -> None: self.server = pn.serve( # type: ignore[no-untyped-call] self.dynamic_panel, port=self.port, - show=False, + show=self.open_browser_on_start, threaded=True, start=True, ) - if self.open_browser_on_start: - self._open_browser() - - def _open_browser(self) -> None: - """Open the dashboard in the system web browser.""" - url = f"http://localhost:{self.port}" - try: - webbrowser.open(url) - except Exception as e: - logger.warning(f"Could not open browser automatically: {e}") - logger.info(f"Dashboard is available at {url}")