Source code for vdat.gui.central

# Virus Data Analysis Tool: a data reduction GUI for HETDEX/VIRUS data
# Copyright (C) 2016, 2017, 2018  "The HETDEX collaboration"
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
'''This describe the central VDAT widget'''
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import traceback

from pyhetdex.het.fplane import FPlane
from qtpy import QtCore, QtWidgets
import six

from . import fplane as vfplane
from . import tasks as vtasks
from . import overlay as voverlay
from . import queue as vqueue
import vdat.config as vconf
import vdat.command_interpreter as vci
import vdat.database as vdb


[docs]class VDATCentral(QtWidgets.QWidget): """This widget groups together the focal plane widget and the buttons into a Qsplitter, to allow resizing and decouple them from the rest of the gui. .. list-table:: **Custom signals** :header-rows: 1 * - Name - Signature - Description * - :attr:`sig_ifuSelected` - str, bool - emitted when a user selects/deselects an IFU; the parameters are the SLOTID of the IFU and whether the IFU has been selected * - :attr:`sig_selectAllIFUs` - - emitted when a user selects all IFUs * - :attr:`sig_deselectAllIFUs` - - emitted when a user deselects all IFUs .. list-table:: **Custom slot** :header-rows: 1 * - Name - Signature - Description * - :meth:`change_target` - str, str - Change the view to the path passed as first argument; the second argument is the type of files associated to the path. * - :meth:`change_task` - str, dict - Switch the change the focal plane to the view for the ``task`` passed as first argument; the second argument contains the dictionary describing the task tabs and buttons. * - :meth:`ifuToggled` - str, bool - Mark ``ifuslot``, given in the first argument as selected, if the second argument is True. * - :meth:`selectAllIFUs` - - Select all the IFUs. * - :meth:`deselectAllIFUs` - - Deselect all the IFUs. * - :meth:`runCommand` - str, list - When clicking a button create the commands and submit them to the queue .. list-table:: **Connections between custom signals and/or slots** :header-rows: 1 * - Signal - Slot * - :attr:`vdat.gui.tasks.VDATTaskStack.sig_taskChanged` - :meth:`change_task` * - :attr:`vdat.gui.tasks.VDATTaskStack.sig_runCommand` - :meth:`runCommand` * - :attr:`sig_selectAllIFUs` - :meth:`vdat.gui.fplane.FplaneWidget.selectAllIFUs` * - :attr:`sig_deselectAllIFUs` - :meth:`vdat.gui.fplane.FplaneWidget.deselectAllIFUs` * - :attr:`sig_ifuSelected` - :meth:`vdat.gui.fplane.FplaneWidget.ifuSelected` * - :attr:`vdat.gui.fplane.FplaneWidget.sig_ifuToggled` - :meth:`ifuToggled` Parameters ---------- parent : :class:`PyQt5.QtWidgets.QWidget` or derivate parent object of the tree view model """ sig_ifuSelected = QtCore.Signal(str, bool) sig_selectAllIFUs = QtCore.Signal() sig_deselectAllIFUs = QtCore.Signal() def __init__(self, parent=None): super(VDATCentral, self).__init__(parent=parent) conf = vconf.get_config('main') self.tasks = vtasks.setup_tasks() # self.tasks.setFixedWidth(150) self.tasks.setCurrentWidgetByName('zro') self.tasks.sig_taskChanged.connect(self.change_task) self.tasks.sig_runCommand.connect(self.runCommand) self.selectedIFUs = {} self.fplane = vfplane.FplaneWidget() self.fplane.setMinimumWidth(300) exclude_ifuslot = conf.get_list('fplane', 'exclude_ifuslot', use_default=True) fp = FPlane(conf.get("fplane", "fp_filename"), exclude_ifuslot=exclude_ifuslot) for ifu in fp.ifuslots: self.selectedIFUs[ifu] = False self.sig_selectAllIFUs.connect(self.fplane.selectAllIFUs) self.sig_deselectAllIFUs.connect(self.fplane.deselectAllIFUs) self.sig_ifuSelected.connect(self.fplane.ifuSelected) self.fplane.sig_ifuToggled.connect(self.ifuToggled) self.layout = QtWidgets.QHBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) self.hsplit = QtWidgets.QSplitter(QtCore.Qt.Horizontal, parent=self) self.hsplit.addWidget(self.tasks) self.hsplit.addWidget(self.fplane) self.hsplit.setSizes([100, 200]) self.hsplit.setStretchFactor(self.hsplit.indexOf(self.fplane), 1) self.hsplit.setStretchFactor(self.hsplit.indexOf(self.tasks), 0) self.layout.addWidget(self.hsplit) self.setLayout(self.layout) self.target = None self.overlay = voverlay.Overlay('Please select a night on ' 'the left to begin...', parent=self) self.change_target(None, None)
[docs] @QtCore.Slot(str, str) def change_target(self, target, typ): '''Change the view to the path ``target`` of ``typ``. If the selected type has not tasks associated, show the overlay. This method is also a pyqt slot with signature ``str, str``. Parameters ---------- target : string path of the selected directory typ : string type of the ``target`` ''' self.setEnabled(True) if target is not None: self.overlay.hide() self.target = target try: self.tasks.setCurrentWidgetByName(typ) # self.tasks.currentWidget().selectFirst() return except vtasks.TaskError: self.fplane.empty_fplane() self.overlay.setText('No tasks defined in config, ' 'please select another night ' 'on the left') self.setEnabled(False) self.overlay.show()
[docs] @QtCore.Slot(str, dict) def change_task(self, task, task_dict): '''Switch the change the focal plane to the view for the ``task`` This method is also a pyqt slot with signature ``str, dict``. Parameters ---------- task : string name of the task to view task_dict : dict dictionary with the configuration for the tabs and buttons to display ''' self.fplane.change_fplane(self.target, task_dict) # Loop over all selected IFUs and reselect them after the new Fplane # was created for k, v in six.iteritems(self.selectedIFUs): if v: # print('Selecting ', k, v) self.sig_ifuSelected.emit(k, v)
[docs] @QtCore.Slot(str, bool) def ifuToggled(self, ifuslot, val): '''Mark ``ifuslot`` as selected or not This method is also a pyqt slot with signature ``str, bool``. Parameters ---------- ifuslot : str SLOTID of one IFU val : bool True to select the IFU, False otherwise ''' self.selectedIFUs[ifuslot] = val self.sig_ifuSelected.emit(ifuslot, val)
[docs] @QtCore.Slot() def selectAllIFUs(self): '''Select all the IFU This method is also a pyqt slot. ''' self.sig_selectAllIFUs.emit()
[docs] @QtCore.Slot() def deselectAllIFUs(self): '''Deselect all the IFU This method is also a pyqt slot. ''' self.sig_deselectAllIFUs.emit()
[docs] @QtCore.Slot(str, list) def runCommand(self, s, l): '''When clicking a button create the commands and submit them to the queue This method is also a pyqt slot with signature ``str, list`` Parameters ---------- s : string name of the button clicked or cumulative name of the commands to execute l : list list of command to execute ''' ifuslots = [] for k, v in six.iteritems(self.selectedIFUs): if v: ifuslots.append(k) if not len(ifuslots): box = QtWidgets.QMessageBox(parent=self) box.setText("Please select the IFUs by clicking on them or via " " the 'Select' menu") box.setIcon(QtWidgets.QMessageBox.Warning) box.exec_() return conf = vconf.get_config('main', section='general') use_multiprocessing = conf.getboolean("use_multiprocessing") n_processors = conf.getint("n_processors") extra_kwargs = {'multiprocessing': use_multiprocessing, 'processes': n_processors} submit_to_queue = [] for cmdstr in l: try: command = cmdstr.split()[0] command_conf = self._config_with_dirs(command) cmd_interpreter = vqueue.QCommandInterpreter(cmdstr, command_conf, selected=ifuslots, **extra_kwargs) submit_to_queue.append([cmd_interpreter, s + ": " + command, cmdstr]) except Exception as e: self._error_dialog(e, cmdstr) return queue = vqueue.get_queue() for sq in submit_to_queue: queue.add_command(*sq)
[docs] def _config_with_dirs(self, command_name): """Get the command configuration and add ``target_dir``, ``zero_dir`` and ``cal_dir`` to the configuration. Parameters ---------- command_name : string name of the command Returns ------- command_conf : dict configuration dictionary for the command at hand """ command_conf = vconf.get_config('commands', section=command_name) dirs_conf = vconf.get_config('main', section='redux_dirs') # the target, or selected, directory is selected command_conf['target_dir'] = dirs_conf['selected_dir'] # if no calibration and/or zero directory are selected, get them from # the database with vdb.connect(): db_entry = (vdb.VDATDir.select() .where(vdb.VDATDir.path == command_conf['target_dir']) .get()) db_zero_dir = getattr(db_entry.zero_dir, 'path', '') db_cal_dir = getattr(db_entry.cal_dir, 'path', '') command_conf['zero_dir'] = dirs_conf.get('zro_dir', db_zero_dir) command_conf['cal_dir'] = dirs_conf.get('cal_dir', db_cal_dir) return command_conf
[docs] def _error_dialog(self, error, command): """Create the dialog to show the error Parameters ---------- error : :class:`Exception` instance error raised by the constructor command : string full command string """ box = QtWidgets.QMessageBox(parent=self) text = ("The following exceptions has been raised while" " parsing the command: '{}'\n:{}".format(command, error)) box.setText(text) if isinstance(error, (vci.exceptions.CIError, vconf.MissingConfigurationSectionError)): informative_text = ('Make sure that the command configuration' ' file and the command that you want to run' ' are in sync') icon = QtWidgets.QMessageBox.Warning else: informative_text = ("The error is unexpected and it is probably a" " bug. If you want to inspect the error click" " on the 'Show details' button to see the full" " traceback. If you want to report the error" " to the developers, please ") icon = QtWidgets.QMessageBox.Critical box.setInformativeText(informative_text) box.setIcon(icon) tb_text = traceback.format_exc() box.setDetailedText(tb_text) box.exec_()