Source code for med_diagnostics.ui

# Copyright 2023 ACCESS-NRI and contributors. See the top-level COPYRIGHT file for details.
# SPDX-License-Identifier: Apache-2.0

""" UI class and functions """


import panel as pn
import matplotlib.pyplot as plt
import datetime

from med_diagnostics import data
from IPython.display import display

[docs] class UserInterface(): """ Primary class for user interface (UI) components and deployment """ def __init__(self): """ Initialise a UserInterface instance. """ # Import panel extensions pn.extension() # Build and style panel widgets self.last_data_load_textbox = pn.widgets.StaticText(styles={'background': 'orange', 'font-size': '18px', 'color': 'black', 'padding': '5px'}, margin=(10, 0, 10, 0)) self.status_textbox = pn.widgets.StaticText(styles={'background': 'lightblue', 'font-size': '18px', 'color': 'black', 'padding': '5px'}, margin=(10, 0, 10, 0)) self.ref_status_textbox = pn.widgets.StaticText(styles={'background': 'lightblue', 'font-size': '18px', 'color': 'black', 'padding': '5px'}, margin=(10, 0, 10, 0)) self.ref_model_metadata = pn.widgets.StaticText(styles={'color': 'white'}) self.keys_dropdown = pn.widgets.Select() self.keys_button = pn.widgets.Button(styles={}, margin=(23, 0, 0, 0)) self.plot_variable_dropdown = pn.widgets.Select() self.ref_keys_dropdown = pn.widgets.Select() self.ref_keys_button = pn.widgets.Button(styles={}, margin=(23, 0, 0, 0)) self.ref_plot_variable_dropdown = pn.widgets.Select() self.ref_data_keys_dropdown = pn.widgets.Select() self.ref_data_keys_button = pn.widgets.Button(styles={}, margin=(23, 0, 0, 0)) self.ref_data_plot_variable_dropdown = pn.widgets.Select() self.clear_ref_model_data_button = pn.widgets.Button(styles={}, margin=(23, 0, 0, 10)) self.ref_model_info_button = pn.widgets.Button(styles={}, margin=(23, 10, 0, 0)) self.figure_exists = False self.ref_figure_exists = False # Initialise button listener functions @pn.depends(self.keys_dropdown.param.value) def _keys_button_click(event): self._keys_dropdown_click() @pn.depends(self.ref_keys_dropdown.param.value) def _ref_keys_button_click(event): self._ref_keys_dropdown_click() @pn.depends(self.ref_data_keys_dropdown.param.value) def _ref_data_keys_button_click(event): self._ref_dataset_dropdown_click() @pn.depends(self.ref_keys_dropdown.param.value) def _ref_model_info_button_click(event): self._ref_model_info_click() def _ref_clear_data_button_click(event): self._ref_clear_data_click() self.keys_button.on_click(_keys_button_click) self.ref_keys_button.on_click(_ref_keys_button_click) self.ref_data_keys_button.on_click(_ref_data_keys_button_click) self.clear_ref_model_data_button.on_click(_ref_clear_data_button_click) self.ref_model_info_button.on_click(_ref_model_info_button_click) def _display_status_text(self): """ Create widget_container then add status_textbox and last_data_load_textbox widgets. Private. """ # Create panel column self.widget_container = pn.Column() # Append widget_container with textbox widgets self.widget_container.append(self.last_data_load_textbox) self.widget_container.append(self.status_textbox) # Display widget_container in notebook display(self.widget_container) print() def _update_status_text(self, text): """ Update text displayed in status_textbox widget. Private. Parameters ---------- text : str Text to be displayed in status_textbox """ # Update status_textbox with text self.status_textbox.value = str(text) def _update_ref_status_text(self, text): """ Update text displayed in ref_status_textbox widget. Private. Parameters ---------- text : str Text to be displayed in ref_status_textbox """ # Update ref_status_textbox with text self.ref_status_textbox.value = str(text) def _update_last_data_load_text(self, text): """ Update text displayed in last_data_load_textbox widget. Private. Parameters ---------- text : str Text to be displayed in last_data_load_textbox """ # Update last_data_load_textbox with text self.last_data_load_textbox.value = str(text) def _get_current_time(self): """ Get current time. Private. Returns ---------- str Current time in "%Y-%m-%d %H:%M:%S" format. """ return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def _display_dataset_selection_ui(self, model_cat, access_nri_cat): """ Label, populate and append dataset selection-related widgets to widget_container. Private. Parameters ---------- model_cat : Intake-ESM datastore object Intake catalog of user model data. access_nri_cat : Intake-ESM datastore object Intake catalog of ACCESS model data. """ # Assign argument to class-accessible variables self.model_cat = model_cat self.access_nri_cat = access_nri_cat # Add horizontal line divider to widget_container self.widget_container.append(pn.layout.Divider(styles={'color': 'white'})) # Populate user model widgets self.keys_dropdown.name = '1. Please select a dataset to monitor:' self.keys_dropdown.options = self.model_cat.keys() self.keys_button.name = 'Load dataset' self.keys_button.button_type = 'primary' # Add user model widgets to keys_selection_row self.keys_selection_row = pn.Row() self.keys_selection_row.append(self.keys_dropdown) self.keys_selection_row.append(self.keys_button) # Add keys_selection_row to widget_container self.widget_container.append(self.keys_selection_row) # Add horizontal line divider to widget_container self.widget_container.append(pn.layout.Divider(styles={'color': 'white'})) #print(self.widget_container) def _display_reference_model_selection_ui(self): """ Label, populate and append ACCESS reference model selection-related widgets to widget_container. Private. """ # Add refrence data status text box self.widget_container.append(self.ref_status_textbox) # Populate reference/comparison model widgets self.ref_keys_dropdown.name = '2. Select reference model (optional):' self.ref_keys_dropdown.options = self.access_nri_cat.keys() self.ref_keys_button.name = 'Load reference model' self.ref_keys_button.button_type = 'primary' self.clear_ref_model_data_button.name = 'Clear reference model' self.clear_ref_model_data_button.button_type = 'primary' self.ref_model_info_button.name = 'Reference model information' self.ref_model_info_button.button_type = 'primary' # Add reference/comparison widgets to ref_keys_selection_row self.ref_keys_selection_row = pn.Row() self.ref_keys_selection_row.append(self.ref_keys_dropdown) self.ref_keys_selection_row.append(self.ref_model_info_button) self.ref_keys_selection_row.append(self.ref_keys_button) self.ref_keys_selection_row.append(self.clear_ref_model_data_button) # Add ref_keys_selection_row to widget_container self.widget_container.append(self.ref_keys_selection_row) self.widget_container.append(self.ref_model_metadata) # Add horizontal line divider to widget_container self.widget_container.append(pn.layout.Divider(styles={'color': 'white'})) def _display_reference_dataset_selection_ui(self): """ Label, populate and append ACCESS reference dataset selection-related widgets to widget_container. Private. """ # Populate reference/comparison dataset widgets self.ref_data_keys_dropdown.name = '2.1. Select reference dataset (optional):' self.ref_data_keys_dropdown.options = self.ref_model_cat.keys() self.ref_data_keys_button.name = 'Load reference dataset' self.ref_data_keys_button.button_type = 'primary' # Add reference/comparison widgets to ref_data_keys_selection_row self.ref_data_keys_selection_row = pn.Row() self.ref_data_keys_selection_row.append(self.ref_data_keys_dropdown) self.ref_data_keys_selection_row.append(self.ref_data_keys_button) # Add ref_data_keys_selection_row to widget_container self.widget_container.append(self.ref_data_keys_selection_row) # Add horizontal line divider to widget_container self.widget_container.append(pn.layout.Divider(styles={'color': 'white'})) def _keys_dropdown_click(self): """ Loads selected model dataset from keys_dropdown and creates new interactive plot. Private. """ # Update text box self._update_status_text('User model status >> Loading data.') # Load selected dataset self.dataset = data._build_data_object(self.model_cat, self.keys_dropdown.value) # Update text box self._update_status_text('User model status >> Data successfully loaded.') # Check if plot already exists if self.figure_exists == False: # Create new plot self._display_dataset_plot_ui() # Add ui for loading optional reference dataset self._display_reference_model_selection_ui() elif self.figure_exists == True: # Update existing plot self._update_dataset_plot_ui() def _ref_keys_dropdown_click(self): """ Loads selected reference model from ref_keys_dropdown and display reference model dataset selection. Private. """ # Update text box self._update_ref_status_text('Reference model status >> Loading data.') # Extract selected model catalog self.ref_model_cat = self.access_nri_cat.search(name=self.ref_keys_dropdown.value).to_source() # Update text box self._update_ref_status_text('Reference model status >> Data successfuly loaded.') # Create new plot self._display_reference_dataset_selection_ui() def _ref_dataset_dropdown_click(self): """ Loads selected reference model dataset from ref_data_keys_dropdown and creates new interactive plot. Private. """ # Load selected access_nri catalog dataset self.ref_dataset = data._build_data_object(self.ref_model_cat, self.ref_data_keys_dropdown.value) # Create new plot self._display_ref_dataset_plot_ui() def _ref_clear_data_click(self): """ Clears and 'unloads' selected reference model dataset from widget container. Private. """ # Update text box self._update_ref_status_text('Reference model status >> Data removed.') # Remove reference model widgets from container one at a time self.widget_container.pop(-1) self.widget_container.pop(-1) self.widget_container.pop(-1) self.widget_container.pop(-1) # Remove reference model ref_model_cat del self.self.ref_model_cat # Remove reference dataset ref_dataset del self.ref_dataset def _ref_model_info_click(self): # Update text box self._update_ref_status_text('Reference model status >> Retrieving model metadata.') # self.ref_model_metadata.value = self.access_nri_cat[self.ref_keys_dropdown.value].metadata['description'] self.ref_model_metadata.value = '<b>Model information:</b><br>' + \ '<b>Model: </b>' + str(self.access_nri_cat[self.ref_keys_dropdown.value].metadata['model']) + '<br>' + \ '<b>Short description: </b>' + str(self.access_nri_cat[self.ref_keys_dropdown.value].metadata['description']) + '<br>' + \ '<b>Nominal resolution: </b>' + str(self.access_nri_cat[self.ref_keys_dropdown.value].metadata['nominal_resolution']) + '<br>' + \ '<b>Parent experiment: </b>' + str(self.access_nri_cat[self.ref_keys_dropdown.value].metadata['parent_experiment']) + '<br>' + \ '<b>Long description: </b>' + str(self.access_nri_cat[self.ref_keys_dropdown.value].metadata['long_description']) + '<br>' + \ '<b>Contact: </b>' + str(self.access_nri_cat[self.ref_keys_dropdown.value].metadata['contact']) + '<br>' + \ '<b>Email: </b>' + str(self.access_nri_cat[self.ref_keys_dropdown.value].metadata['email']) + '<br>' # Update text box self._update_ref_status_text('') def _display_dataset_plot_ui(self): """ Create interactive panel plot for user model dataset and add to widget_container. Private. """ self.plot_variable_dropdown.name = 'Available variables' self.plot_variable_dropdown.options = list(self.dataset.keys()) self.interactive_plot = pn.bind(self._plot_dataset, self.plot_variable_dropdown) self.widget_container.append(self.plot_variable_dropdown) self.widget_container.append(self.interactive_plot) def _display_ref_dataset_plot_ui(self): """ Create interactive panel plot for reference model dataset and add to widget_container. Private. """ self.ref_plot_variable_dropdown.name = 'Available reference variables' self.ref_plot_variable_dropdown.options = list(self.ref_dataset.keys()) self.ref_interactive_plot = pn.bind(self._plot_dataset, self.plot_variable_dropdown, self.ref_plot_variable_dropdown) self.widget_container.append(self.ref_plot_variable_dropdown) self.widget_container.append(self.ref_interactive_plot) def _update_dataset_plot_ui(self): """ Update exisiting user model dataset plot if new data are selected. Private. """ self.plot_variable_dropdown.options = list(self.dataset.keys()) plt.close(self.fig) def _update_ref_dataset_keys_plot_ui(self): """ Update exisiting reference model dataset keys if new data are selected. Private. """ self.ref_data_keys_dropdown.options = list(self.ref_model_cat.keys()) plt.close(self.ref_fig) self._update_ref_dataset_plot_ui() def _update_ref_dataset_plot_ui(self): """ Update exisiting reference model dataset plot if new data are selected. Private. """ self.ref_plot_variable_dropdown.options = list(self.ref_dataset.keys()) self.ref_plot_variable_dropdown.value = list(self.ref_dataset.keys())[0] plt.close(self.ref_fig) def _plot_dataset(self, variable, ref_variable=None): """ Plot 2D time-series from model data. Private. Parameters ---------- variable : str Model data variable as selected from panel dropdown. ref_variable : str, default None Reference model data variable as selected from panel dropdown. Returns ---------- fig : matplotlib.pyplot.figure() ref_fig : matplotlib.pyplot.figure() """ if ref_variable == None: # Plot primary (user) model data self.fig = plt.figure(figsize=[8,4]) self.figure_exists = True self.dataset[variable].plot() plt.title(self.dataset[variable].attrs['long_name'], fontsize=14) plt.grid() plt.legend() plt.tight_layout() plt.close(self.fig) return self.fig else: # Plot reference model self.ref_fig = plt.figure(figsize=[8,4]) self.ref_figure_exists = True # Plot all model variants if multiple exist if "member" in self.ref_dataset.dims: for mem in self.ref_dataset.member.values: self.ref_dataset[ref_variable].sel(member=mem).plot(label=mem) plt.title(self.ref_dataset[ref_variable].attrs['long_name'], fontsize=14) plt.grid() plt.legend() plt.tight_layout() plt.close(self.ref_fig) return self.ref_fig