Source code for wiser.gui.app_state

import enum
import os
import warnings
from typing import Dict, List, Optional, Tuple, Callable, TYPE_CHECKING

from matplotlib.figure import Figure
from matplotlib.axes import Axes

from PySide2.QtCore import *
from PySide2.QtWidgets import QMessageBox, QDialog

from .app_config import ApplicationConfig, PixelReticleType
from .util import get_random_matplotlib_color

from wiser.plugins import Plugin

from wiser.raster.dataset import *
from wiser.raster.loader import RasterDataLoader

from wiser.raster.spectrum import Spectrum
from wiser.raster.spectral_library import SpectralLibrary
from wiser.raster.envi_spectral_library import ENVISpectralLibrary
from wiser.raster.loaders.envi import EnviFileFormatError
from wiser.raster.utils import have_spatial_overlap, can_transform_between_srs

from wiser.raster.stretch import StretchBase

from wiser.raster.roi import RegionOfInterest, roi_to_pyrep, roi_from_pyrep

from wiser.raster.data_cache import DataCache

from wiser.gui.subprocessing_manager import MultiprocessingManager, ProcessManager
from wiser.gui.ui_library import (
    DatasetChooserDialog,
    SpectrumChooserDialog,
    ROIChooserDialog,
    BandChooserDialog,
    DynamicInputDialog,
    TableDisplayWidget,
    MatplotlibDisplayWidget,
)
from wiser.gui.spectrum_plot import SpectrumPlotGeneric
from wiser.gui.util import StateChange

if TYPE_CHECKING:
    from wiser.gui.reference_creator_dialog import CrsCreatorState


def make_unique_name(candidate: str, used_names: str) -> str:
    # If the name is already unique, return it
    if candidate not in used_names:
        return candidate

    # Try to generate a unique name by tacking a number onto the name
    i = 2
    while True:
        name = f"{candidate} {i}"
        if name not in used_names:
            return name

        i += 1


[docs] class ApplicationState(QObject): """ This class holds all WISER application state. """ # Signal: a data-set with the specified ID was added dataset_added = Signal(int, bool) # Signal: the data-set with the specified ID was removed dataset_removed = Signal(int) # Signal: the mainview dataset was changed mainview_dataset_changed = Signal(int) # Signal: a spectral library with the specified ID was added spectral_library_added = Signal(int) # Signal: the spectral library with the specified ID was removed spectral_library_removed = Signal(int) roi_added = Signal(RegionOfInterest) roi_removed = Signal(RegionOfInterest) # Signal: the contrast stretch was changed for a specific dataset and set # of bands. The first argument is the ID of the dataset, and the # second argument is a tuple of bands. stretch_changed = Signal(int, tuple) # Signal: the active spectrum changed active_spectrum_changed = Signal() # TODO(donnie): collected_spectra_changed = Signal(StateChange, int) collected_spectra_changed = Signal(object, int, int) # TODO(donnie): Signals for config changes and color changes! def __init__(self, app, config: Optional[ApplicationConfig] = None): super().__init__() # A reference to the overall UI self._app = app self._cache = None # The plugins currently loaded into WISER. self._plugins: Dict[str, Plugin] = {} self._current_dir = os.getcwd() self._raster_data_loader = RasterDataLoader() # Source of numeric IDs for assigning to objects in the application # state. IDs are unique across all objects, not just for each type of # object, just for the sake of simplicity. self._next_id = 1 # All datasets loaded in the application. The key is the numeric ID of # the data set, and the value is the RasterDataSet object. self._datasets = {} # Stretches for all data sets are stored here. The key is a tuple of # the (dataset ID, band #), and the value is a Stretch object. self._stretches: Dict[Tuple[int, int], StretchBase] = {} # All spectral libraries loaded in the application. The key is the # numeric ID of the spectral library, and the value is the # SpectralLibrary object. self._spectral_libraries: Dict[int, SpectralLibrary] = {} # All regions of interest in the application. The key is the numeric ID # of the ROI, and the value is a RegionOfInterest object. self._regions_of_interest: Dict[int, RegionOfInterest] = {} # A collection of all spectra in the application state, so that we can # look them up by ID. self._all_spectra: Dict[int, Spectrum] = {} # The "currently active" spectrum, which is set when the user clicks on # pixels, or wants to view an ROI average spectrum, etc. self._active_spectrum: Optional[Spectrum] = None # The spectra collected by the user, possibly for export, or conversion # into a spectral library. self._collected_spectra: List[Spectrum] = [] # Configuration state. if config is None: config = ApplicationConfig() self._config: ApplicationConfig = config # A dictionary holding the CRSs that the user has created. # The key is the CRS name. self._user_created_crs: Dict[str, Tuple[osr.SpatialReference, CrsCreatorState]] = {} self._process_pool_manager = MultiprocessingManager() self._running_processes: Dict[int, ProcessManager] = {} # Plugin Chooser Dialogs self._plugin_dataset_chooser_dialog: Optional[DatasetChooserDialog] = None self._plugin_spectrum_chooser_dialog: Optional[SpectrumChooserDialog] = None self._plugin_roi_chooser_dialog: Optional[ROIChooserDialog] = None self._plugin_band_chooser_dialog: Optional[BandChooserDialog] = None self._dynamic_input_dialog: Optional[DynamicInputDialog] = None # The set of generic spectrum plots that users can make for their plugins self._generic_spectrum_plots: set[SpectrumPlotGeneric] = set() # The set of table display widgets that users can make for their plugins self._table_display_widgets: set[TableDisplayWidget] = set() # The set of matplotlib display widgets that users can make for their plugins self._matplotlib_display_widgets: set[MatplotlibDisplayWidget] = set() def add_running_process(self, process_manager: ProcessManager): self._running_processes[process_manager.get_process_manager_id()] = process_manager def remove_running_process(self, process_manager_id: int): del self._running_processes[process_manager_id] def cancel_all_running_processes(self): for process_manager in self._running_processes.values(): process_manager.get_task().cancel() def get_running_processes(self) -> Dict[int, ProcessManager]: return self._running_processes def submit_parallel_task(self, operation: Callable, kwargs: Dict = {}): return self._process_pool_manager.create_task(operation, kwargs) def get_next_process_pool_id(self): return self._process_pool_manager.get_next_process_pool_id()
[docs] def take_next_id(self) -> int: """ Returns the next ID for use with an object, and also increments the internal "next ID" value. """ id = self._next_id self._next_id += 1 return id
def add_plugin(self, class_name: str, plugin: Plugin): if class_name in self._plugins: raise ValueError(f'Plugin class "{class_name}" is already added') self._plugins[class_name] = plugin def get_plugins(self) -> Dict[str, Plugin]: return dict(self._plugins)
[docs] def get_loader(self): """ Returns the ``RasterDataLoader`` instance being used by WISER to load data sets. """ return self._raster_data_loader
[docs] def get_current_dir(self) -> str: """ Returns the current directory of the application. This is the last directory that the user accessed in a load or save operation, so that the next load or save can start at the same directory. """ return self._current_dir
[docs] def set_current_dir(self, current_dir: str) -> None: """ Sets the current directory of the application. This is the last directory that the user accessed in a load or save operation, so that the next load or save can start at the same directory. """ self._current_dir = current_dir
def set_data_cache(self, data_cache: DataCache): self._cache = data_cache
[docs] def update_cwd_from_path(self, path: str) -> None: """ This helper function makes it easier to update the current working directory (CWD) at the end of a file-load or file-save operation. The specified path is assumed to be either a directory or a file: * If it is a directory then the current directory is set to that path. * If it is a file then the directory portion of the path is taken and used for the current directory. The function only updates the current directory if the filesystem actually reports it as a valid directory. If the directory can't be identified from the specified path (e.g. the OS doesn't report the path as a valid directory), this function simply logs a warning. """ dir = os.path.abspath(path) if not os.path.isdir(dir): dir = os.path.dirname(dir) if os.path.isdir(dir): self._current_dir = dir else: warnings.warn(f'Couldn\'t update CWD from path "{path}"')
def show_status_text(self, text: str, seconds=0): self._app.show_status_text(text, seconds) def clear_status_text(self): self._app.show_status_text("")
[docs] def open_file(self, file_path): """ A general file-open operation in WISER. This method can be used for loading any kind of data file whose type and contents can be identified automatically. This operation should not be used for importing ASCII spectral data, regions of interest, etc. since WISER cannot identify the file's contents automatically. """ # Remember the directory of the selected file, for next file-open self.update_cwd_from_path(file_path) # Is the file a project file? if file_path.endswith(".wiser"): self.load_project_file(file_path) return # Figure out if the user wants to open a raster data set or a # spectral library. if file_path.endswith(".sli") or file_path.endswith(".hdr"): # ENVI file, possibly a spectral library. Find out. try: # Will this work?? library = ENVISpectralLibrary(file_path) # Wow it worked! It must be a spectral library. self.add_spectral_library(library) return except FileNotFoundError: pass except EnviFileFormatError: pass # Either the data doesn't look like a spectral library, or loading # it as a spectral library didn't work. Load it as a regular raster # data file. raster_data_list = self._raster_data_loader.load_from_file(path=file_path, data_cache=self._cache) for raster_data in raster_data_list: self.add_dataset(raster_data)
[docs] def add_dataset(self, dataset: RasterDataSet, view_dataset: bool = True): """ Add a dataset to the application state. A unique numeric ID is assigned to the dataset, which is also set on the dataset itself. The method will fire a signal indicating that the dataset was added. """ if not isinstance(dataset, RasterDataSet): raise TypeError("dataset must be a RasterDataSet") ds_id = self.take_next_id() dataset.set_id(ds_id) self._datasets[ds_id] = dataset self.dataset_added.emit(ds_id, view_dataset)
# self.state_changed.emit(tuple(ObjectType.DATASET, ActionType.ADDED, dataset))
[docs] def has_dataset(self, ds_id: int) -> bool: """ Returns whether the dataset with the specified numeric ID is in the application state. """ return ds_id in self._datasets
[docs] def get_dataset(self, ds_id: int) -> RasterDataSet: """ Return the dataset with the specified numeric ID. If the ID is unrecognized then a KeyError will be raised. """ return self._datasets[ds_id]
def get_cache(self) -> DataCache: return self._cache
[docs] def num_datasets(self): """Return the number of datasets in the application state.""" return len(self._datasets)
[docs] def get_datasets(self) -> List[RasterDataSet]: """ Return a list of datasets in the application state. The returned list is separate from the internal application-state data structures, and therefore may be mutated by the caller without harm. """ return list(self._datasets.values())
[docs] def remove_dataset(self, ds_id: int): """ Remove the dataset with the specified numeric ID from the application state and the cache. If the ID is unrecognized then a KeyError will be raised. The method will fire a signal indicating that the dataset was removed. """ dataset_to_del = self._datasets[ds_id] dataset_to_del.delete_underlying_dataset() # First we remove it form the computation cache comp_cache = self._cache.get_computation_cache() comp_key = comp_cache.get_cache_key(dataset_to_del) comp_cache.remove_cache_item(comp_key) # Next we remove it from the render cache render_cache = self._cache.get_render_cache() render_cache.clear_keys_from_partial(render_cache.get_partial_key(dataset_to_del)) del self._datasets[ds_id] # Remove all stretches that are associated with this data set for key in list(self._stretches.keys()): if key[0] == ds_id: del self._stretches[key] self.dataset_removed.emit(ds_id)
# self.state_changed.emit(tuple(ObjectType.DATASET, ActionType.REMOVED, dataset))
[docs] def multiple_datasets_same_size(self): """ This function returns True if there are multiple datasets, and they are all the same size. If either of these cases is not true then False is returned. """ if len(self._datasets) < 2: return False datasets = list(self._datasets.values()) ds0_dim = (datasets[0].get_width(), datasets[0].get_height()) for ds in datasets[1:]: ds_dim = (ds.get_width(), ds.get_height()) if ds_dim != ds0_dim: return False return True
def multiple_datasets_link_compatible(self): same_size = self.multiple_datasets_same_size() if same_size: return same_size, GeographicLinkState.PIXEL datasets = list(self._datasets.values()) ds0_srs = datasets[0].get_spatial_ref() if ds0_srs is None: return False, GeographicLinkState.NO_LINK for ds in datasets[1:]: ds_srs = ds.get_spatial_ref() if ds_srs is None or not ds0_srs.IsSame(ds_srs): return False, GeographicLinkState.NO_LINK return True, GeographicLinkState.SPATIAL def multiple_displayed_datasets_link_compatible( self, ) -> Tuple[bool, GeographicLinkState]: displayed_datasets = self._app._main_view.get_visible_datasets() same_size = True if len(displayed_datasets) < 2: return False, GeographicLinkState.NO_LINK # Else, make sure they're all the same size ds0_dim = ( displayed_datasets[0].get_width(), displayed_datasets[0].get_height(), ) for ds in displayed_datasets[1:]: ds_dim = (ds.get_width(), ds.get_height()) if ds_dim != ds0_dim: same_size = False if same_size: return same_size, GeographicLinkState.PIXEL ds0_srs: osr.SpatialReference = displayed_datasets[0].get_spatial_ref() ds0 = displayed_datasets[0] if ds0_srs is None: return False, GeographicLinkState.NO_LINK for ds in displayed_datasets[1:]: ds_srs = ds.get_spatial_ref() can_transform = can_transform_between_srs(ds0_srs, ds_srs) have_overlap = have_spatial_overlap( ds0_srs, ds0.get_geo_transform(), ds0.get_width(), ds0.get_height(), ds_srs, ds.get_geo_transform(), ds.get_width(), ds.get_height(), ) if ds_srs is None or not can_transform or not have_overlap: return False, GeographicLinkState.NO_LINK return True, GeographicLinkState.SPATIAL
[docs] def multiple_displayed_datasets_same_size(self): """ This function returns True if there are multiple visible datasets and they are all the same size. """ # Get access to the displayed datasets displayed_datasets = self._app._main_view.get_visible_datasets() if len(displayed_datasets) < 2: return False # Else, make sure they're all the same size ds0_dim = ( displayed_datasets[0].get_width(), displayed_datasets[0].get_height(), ) for ds in displayed_datasets[1:]: ds_dim = (ds.get_width(), ds.get_height()) if ds_dim != ds0_dim: return False return True
def unique_dataset_name(self, candidate: str): ds_names = {ds.get_name() for ds in self._datasets.values()} ds_names = {name for name in ds_names if name} return make_unique_name(candidate, ds_names) def unique_roi_name(self, candidate: str): roi_names = {roi.get_name() for roi in self._regions_of_interest.values()} return make_unique_name(candidate, roi_names) def set_stretches(self, ds_id: int, bands: Tuple, stretches: List[StretchBase]): if len(bands) != len(stretches): raise ValueError( "bands and stretches must both be the same " + f"length (got {len(bands)} bands, {len(stretches)} stretches)" ) for i in range(len(bands)): key = (ds_id, bands[i]) stretch = stretches[i] self._stretches[key] = stretch self.stretch_changed.emit(ds_id, bands) def get_stretches(self, ds_id: int, bands: Tuple): # TODO(donnie): This comment is not a docstring so it will be excluded # from the auto-generated docs. # Returns the current stretches for the specified dataset ID and bands. # If a band has no stretch specified, its corresponding value will be # ``None``. return [self._stretches.get((ds_id, b), None) for b in bands]
[docs] def add_spectral_library(self, library): """ Add a spectral library to the application state, assigning a new ID to the library. The method will fire a signal indicating that the spectral library was added, including the ID assigned to the library. """ if not isinstance(library, SpectralLibrary): raise TypeError("library must be a SpectralLibrary") lib_id = self.take_next_id() library.set_id(lib_id) self._spectral_libraries[lib_id] = library self.spectral_library_added.emit(lib_id)
[docs] def has_spectral_library(self, lib_id: int) -> bool: """ Returns whether the spectral library with the specified numeric ID is in the application state. """ return lib_id in self._spectral_libraries
[docs] def get_spectral_library(self, lib_id): """ Return the spectral library with the specified ID. If the ID is unrecognized, a KeyError will be raised. """ return self._spectral_libraries[lib_id]
[docs] def num_spectral_libraries(self): """ Return the number of spectral libraries in the application state. """ return len(self._spectral_libraries)
[docs] def get_spectral_libraries(self): """ Return a list of all the spectral libraries in the application state. """ return self._spectral_libraries.values()
[docs] def remove_spectral_library(self, lib_id): """ Remove the specified spectral library from the application state. The method will fire a signal indicating that the spectral library was removed. """ del self._spectral_libraries[lib_id] self.spectral_library_removed.emit(lib_id)
def config(self) -> ApplicationConfig: return self._config
[docs] def get_config(self, option: str, default=None, as_type=None): """ Returns the value of the specified config option. An optional default value may be specified. Options are specified as a sequence of names separated by dots '.', just like a series of object-member accesses on an object hierarchy. """ return self._config.get(option, default, as_type)
[docs] def set_config(self, option, value): """ Sets the value of the specified config option. """ self._config.set(option, value)
[docs] def add_roi(self, roi: RegionOfInterest, make_name_unique=False) -> None: """ Add a Region of Interest to WISER's state. A ``ValueError`` is raised if the ROI does not have a unique name. """ if roi is None: raise ValueError("ROI cannot be None") if roi.get_name() is None: raise ValueError("ROI name cannot be None") # Verify that the ROI's name is unique. names_in_use = set() for existing_roi in self._regions_of_interest.values(): names_in_use.add(existing_roi.get_name()) name = roi.get_name() if name in names_in_use: if make_name_unique: i = 2 while True: u_name = name + "_" + str(i) if u_name not in names_in_use: roi.set_name(u_name) break i += 1 else: raise ValueError(f'A region of interest named "{name}" already exists.') # If the ROI doesn't have a color, choose a random color for the ROI # that isn't already used. if roi.get_color() is None: colors_in_use = set() for existing_roi in self._regions_of_interest.values(): colors_in_use.add(existing_roi.get_color()) color = get_random_matplotlib_color(colors_in_use) roi.set_color(color) roi_id = self.take_next_id() roi.set_id(roi_id) self._regions_of_interest[roi_id] = roi self.roi_added.emit(roi)
[docs] def remove_roi(self, roi_id: int) -> None: """ Removes the specified Region of Interest from WISER's state. A ``KeyError`` is raised if no ROI has the specified ID. """ roi = self._regions_of_interest[roi_id] del self._regions_of_interest[roi_id] self.roi_removed.emit(roi)
[docs] def get_roi(self, **kwargs) -> Optional[RegionOfInterest]: """ Retrieve a Region of Interest from WISER's state. The caller may specify an ``id`` keyword-argument to retrieve an ROI by ID, or a ``name`` keyword-argument to retrieve an ROI by name. Names are case-sensitive. """ if "id" in kwargs: return self._regions_of_interest.get(kwargs["id"]) elif "name" in kwargs: for roi in self._regions_of_interest.values(): if roi.get_name() == kwargs["name"]: return roi return None else: raise KeyError('Must specify either "id" or "name" keyword argument')
[docs] def get_rois(self) -> List[RegionOfInterest]: """ Returns a list of all Regions of Interest in WISER's application state. """ return self._regions_of_interest.values()
[docs] def has_spectrum(self, spectrum_id: int) -> bool: """ Returns whether the spectrum with the specified numeric ID is in the application state. """ return spectrum_id in self._all_spectra
[docs] def get_spectrum(self, spectrum_id: int) -> Spectrum: """ Retrieve a spectrum from WISER's state. A ``KeyError`` is raised if the ID doesn't correspond to a spectrum. """ return self._all_spectra[spectrum_id]
[docs] def get_all_spectra(self): """ Retrieves all spectra in the spectrum plot. """ return self._all_spectra
[docs] def get_active_spectrum(self): """ Retrieve the current active spectrum. The "active spectrum" is the spectrum that the user most recently selected, or it may be the output of a band-math expression, plugin, etc. that computes a spectrum. """ return self._active_spectrum
[docs] def set_active_spectrum(self, spectrum: Spectrum): """ Set the current active spectrum to be the specified spectrum. The "active spectrum" is the spectrum that the user most recently selected, or it may be the output of a band-math expression, plugin, etc. that computes a spectrum. """ # If we already have an active spectrum, remove its ID from the mapping. if self._active_spectrum: del self._all_spectra[self._active_spectrum.get_id()] # Assign an ID to this spectrum if it doesn't have one. if spectrum is not None and spectrum.get_id() is None: id = self.take_next_id() spectrum.set_id(id) self._all_spectra[id] = spectrum # Store it! Then fire an event. self._active_spectrum = spectrum self.active_spectrum_changed.emit()
[docs] def collect_spectrum(self, spectrum: Spectrum): """ Add the specified spectrum to the "collected spectra" group. Note that the specified spectrum cannot be the current "active spectrum"; if it is, a ``RuntimeError`` will be raised. The current "active spectrum" is collected via the ``collect_active_spectrum()`` method. """ if spectrum is None: raise ValueError("spectrum cannot be None") if spectrum is self._active_spectrum: raise RuntimeError("Use collect_active_spectrum() to collect the " + "active spectrum") # Assign an ID to this spectrum if it doesn't have one. if spectrum.get_id() is None: spectrum.set_id(self.take_next_id()) # Store it! Then fire an event. index = len(self._collected_spectra) self._collected_spectra.append(spectrum) self._all_spectra[spectrum.get_id()] = spectrum self.collected_spectra_changed.emit(StateChange.ITEM_ADDED, index, spectrum.get_id())
[docs] def collect_active_spectrum(self): """ Add the current "active spectrum" to the "collected spectra" group. A ``RuntimeError`` will be raised if there is no active spectrum when this method is called. """ if self._active_spectrum is None: raise RuntimeError("There is no active spectrum to collect.") spectrum = self._active_spectrum # Causes "active spectrum changed" signal to be emitted self.set_active_spectrum(None) # Causes "collected spectrum changed" signal to be emitted self.collect_spectrum(spectrum)
[docs] def get_collected_spectra(self) -> List[Spectrum]: """ Returns the current list of collected spectra. """ return list(self._collected_spectra)
[docs] def remove_collected_spectrum(self, index): """ Removes a collected spectrum from the list of collected spectra. The spectrum to remove is specified by a 0-based index. """ id = self._collected_spectra[index].get_id() del self._collected_spectra[index] del self._all_spectra[id] self.collected_spectra_changed.emit(StateChange.ITEM_REMOVED, index, id)
[docs] def remove_all_collected_spectra(self): """ Removes all spectra from the list of collected spectra. """ for s in self._collected_spectra: del self._all_spectra[s.get_id()] self._collected_spectra.clear() self.collected_spectra_changed.emit(StateChange.ITEM_REMOVED, -1, -1)
def get_user_created_crs(self): return self._user_created_crs def add_user_created_crs( self, name: str, crs: osr.SpatialReference, crs_creator_state: "CrsCreatorState" ): if name in self._user_created_crs: # Ask the user whether to overwrite the existing CRS reply = QMessageBox.question( None, self.tr("CRS Already Exists"), self.tr(f"A CRS named “{name}” already exists. Overwrite it?"), ) if reply == QMessageBox.Yes: self._user_created_crs[name] = (crs, crs_creator_state) else: self._user_created_crs[name] = (crs, crs_creator_state) # region UI Library Access def choose_dataset_ui( self, description: Optional[str] = None, in_test_mode=False, ) -> Optional[RasterDataSet]: self._plugin_dataset_chooser_dialog = DatasetChooserDialog( app_state=self, description=description, parent=self._app, ) if in_test_mode: self._plugin_dataset_chooser_dialog.show() else: if self._plugin_dataset_chooser_dialog.exec_() == QDialog.Accepted: return self._plugin_dataset_chooser_dialog.get_chosen_object() def choose_spectrum_ui( self, description: Optional[str] = None, in_test_mode=False, ) -> Optional[Spectrum]: self._plugin_spectrum_chooser_dialog = SpectrumChooserDialog( app_state=self, description=description, parent=self._app, ) if in_test_mode: self._plugin_spectrum_chooser_dialog.show() else: if self._plugin_spectrum_chooser_dialog.exec_() == QDialog.Accepted: return self._plugin_spectrum_chooser_dialog.get_chosen_object() def choose_roi_ui( self, description: Optional[str] = None, in_test_mode=False, ) -> Optional[RegionOfInterest]: self._plugin_roi_chooser_dialog = ROIChooserDialog( app_state=self, description=description, parent=self._app, ) if in_test_mode: self._plugin_roi_chooser_dialog.show() else: if self._plugin_roi_chooser_dialog.exec_() == QDialog.Accepted: return self._plugin_roi_chooser_dialog.get_chosen_object() def choose_band_ui( self, description: Optional[str] = None, in_test_mode=False, ) -> Optional[RasterDataBand]: self._plugin_band_chooser_dialog = BandChooserDialog( app_state=self, description=description, parent=self._app, ) if in_test_mode: self._plugin_band_chooser_dialog.show() else: if self._plugin_band_chooser_dialog.exec_() == QDialog.Accepted: return self._plugin_band_chooser_dialog.get_chosen_object() def create_form( self, form_inputs: List[Tuple[str, str, int, Optional[List[Any]]]], title: Optional[str] = None, description: Optional[str] = None, in_test_mode=False, ) -> Optional[Dict[str, Any]]: self._dynamic_input_dialog = DynamicInputDialog( dialog_title=title, description=description, parent=self._app, ) if in_test_mode: self._dynamic_input_dialog.show() else: return self._dynamic_input_dialog.create_input_dialog(form_inputs)
[docs] def show_spectra_in_plot( self, spectra: List[Spectrum], plot_title: Optional[str] = None, ): """ Takes the list of spectra passed in and displays it in a generic spectrum plot. """ generic_spectrum_plot = SpectrumPlotGeneric(self) if plot_title is not None: generic_spectrum_plot.set_title(plot_title) for spectrum in spectra: if spectrum.get_id() is None: spectrum.set_id(self.take_next_id()) generic_spectrum_plot.add_collected_spectrum(spectrum) self._generic_spectrum_plots.add(generic_spectrum_plot) # We keep a reference to generic_spectrum_plot so it doesn't get garbage collected generic_spectrum_plot.closed.connect( lambda: self._on_generic_spectrum_plot_closed(generic_spectrum_plot) ) generic_spectrum_plot.show()
def _on_generic_spectrum_plot_closed(self, spectrum_plot: SpectrumPlotGeneric): self._generic_spectrum_plots.remove(spectrum_plot) spectrum_plot.deleteLater()
[docs] def show_table_widget( self, header: List[str], rows: List[List[Any]], window_title: Optional[str] = None, description: Optional[str] = None, ): """Creates and shows a table widget that is meant for display.""" if len(header) != len(rows[0]): QMessageBox.warning( self._app, self.tr("Error!"), self.tr("Number of columns must match number of items in header!"), ) # Do not pass self._app as the parent of TableDisplayWidget table_display_widget: TableDisplayWidget = TableDisplayWidget() table_display_widget.create_table( header=header, rows=rows, title=window_title, description=description, ) # We keep a reference to table_display_widget so it doesn't get garbage collected self._table_display_widgets.add(table_display_widget) table_display_widget.closed.connect( lambda: self._on_table_display_widget_closed(table_display_widget) ) table_display_widget.show()
def _on_table_display_widget_closed(self, table_display_widget: TableDisplayWidget): self._table_display_widgets.remove(table_display_widget) table_display_widget.deleteLater()
[docs] def show_matplotlib_display_widget( self, figure: Figure, axes: Axes, window_title: Optional[str] = None, description: Optional[str] = None, ): """Creates and shows a widget with a matplotlib plot""" matplotlib_display = MatplotlibDisplayWidget() matplotlib_display.create_plot( figure=figure, axes=axes, window_title=window_title, description=description, ) self._matplotlib_display_widgets.add(matplotlib_display) matplotlib_display.closed.connect( lambda: self._on_matplotlib_display_widget_closed(matplotlib_display) ) matplotlib_display.show()
def _on_matplotlib_display_widget_closed(self, matplotlib_display: MatplotlibDisplayWidget): self._matplotlib_display_widgets.remove(matplotlib_display) matplotlib_display.deleteLater()