From c55d24e135e94b3ef32f65f4070b383ab4dc43bd Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Sat, 14 Apr 2018 19:14:25 +0100 Subject: [PATCH 1/2] Added a new metadata/header viewer for datasets/subsets --- CHANGES.md | 4 +- glue/app/qt/layer_tree_widget.py | 20 +++++ glue/app/qt/metadata.py | 38 +++++++++ glue/app/qt/metadata.ui | 133 +++++++++++++++++++++++++++++++ glue/app/qt/versions.py | 19 +---- glue/core/data_factories/fits.py | 1 + glue/utils/qt/dialogs.py | 17 +++- 7 files changed, 215 insertions(+), 17 deletions(-) create mode 100644 glue/app/qt/metadata.py create mode 100644 glue/app/qt/metadata.ui diff --git a/CHANGES.md b/CHANGES.md index 33a63b70e..e2d94eead 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ Full changelog v0.13.0 (unreleased) -------------------- +* Added a new metadata/header viewer for datasets/subsets. [#1659] + * Re-write spectrum viewer into a generic profile viewer that uses subsets to define the areas in which to compute profiles rather than custom ROIs. [#1635] @@ -114,7 +116,7 @@ v0.13.0 (unreleased) * Added a new selection mode that always forces the creation of a new subset. [#1525] - + * Added a mouse over pixel selection tool, which creates a one pixel subset under the mouse cursor. [#1619] diff --git a/glue/app/qt/layer_tree_widget.py b/glue/app/qt/layer_tree_widget.py index b2d153de9..d81d4145f 100644 --- a/glue/app/qt/layer_tree_widget.py +++ b/glue/app/qt/layer_tree_widget.py @@ -25,6 +25,7 @@ from glue.utils.qt import load_ui from glue.core.message import EditSubsetMessage from glue.core.hub import HubListener +from glue.app.qt.metadata import MetadataDialog @core.decorators.singleton @@ -143,6 +144,24 @@ def _do_action(self): parent=self._layer_tree, default=default) +class MetadataAction(LayerAction): + + _title = "View metadata/header" + _tooltip = "View metadata/header" + _shortcut = QtGui.QKeySequence('Ctrl+I') + + def _can_trigger(self): + return self.single_selection_data() or self.single_selection_subset() + + def _do_action(self): + layers = self.selected_layers() + if isinstance(layers[0], core.Subset): + data = layers[0].data + else: + data = layers[0] + MetadataDialog(data).exec_() + + class NewAction(LayerAction): _title = "New Subset" _tooltip = "Create a new subset" @@ -556,6 +575,7 @@ def _create_actions(self): self._actions['clear'] = ClearAction(self) self._actions['delete'] = DeleteAction(self) self._actions['facet'] = FacetAction(self) + self._actions['metadata'] = MetadataAction(self) self._actions['merge'] = MergeAction(self) self._actions['maskify'] = MaskifySubsetAction(self) self._actions['link'] = LinkAction(self) diff --git a/glue/app/qt/metadata.py b/glue/app/qt/metadata.py new file mode 100644 index 000000000..c3db994fb --- /dev/null +++ b/glue/app/qt/metadata.py @@ -0,0 +1,38 @@ +from __future__ import absolute_import, division, print_function + +import os +from collections import OrderedDict + +from qtpy import QtWidgets +from qtpy.QtCore import Qt + +from glue.utils.qt import load_ui, CenteredDialog + +__all__ = ['MetadataDialog'] + + +class MetadataDialog(CenteredDialog): + """ + A dialog to view the metadata in a data object. + """ + + def __init__(self, data, *args, **kwargs): + + super(MetadataDialog, self).__init__(*args, **kwargs) + + self.ui = load_ui('metadata.ui', self, directory=os.path.dirname(__file__)) + + self.resize(400, 500) + self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint) + + self._text = "" + for name, value in OrderedDict(data.coords.header).items(): + QtWidgets.QTreeWidgetItem(self.ui.meta_tree.invisibleRootItem(), [name, str(value)]) + + if data.label: + self.setWindowTitle("Metadata for {0}".format(data.label)) + + self.ui.label_ndim.setText(str(data.ndim)) + self.ui.label_shape.setText(str(data.shape)) + + self.center() diff --git a/glue/app/qt/metadata.ui b/glue/app/qt/metadata.ui new file mode 100644 index 000000000..3bacf1154 --- /dev/null +++ b/glue/app/qt/metadata.ui @@ -0,0 +1,133 @@ + + + Dialog + + + + 0 + 0 + 362 + 397 + + + + Metadata viewer + + + false + + + + + + 5 + + + + + ndim + + + + + + + shape + + + + + + + Qt::Horizontal + + + + 40 + 5 + + + + + + + + Data shape: + + + + + + + Number of dimensions: + + + + + + + Qt::Horizontal + + + + 40 + 5 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 5 + 5 + + + + + + + + true + + + QAbstractItemView::NoSelection + + + true + + + true + + + false + + + false + + + + Keyword + + + + + Value + + + + + + + + + diff --git a/glue/app/qt/versions.py b/glue/app/qt/versions.py index 5d5c1720c..9e1d51fc5 100644 --- a/glue/app/qt/versions.py +++ b/glue/app/qt/versions.py @@ -6,13 +6,13 @@ from qtpy.QtCore import Qt from glue.utils import nonpartial -from glue.utils.qt import load_ui +from glue.utils.qt import load_ui, CenteredDialog from glue._deps import get_status_as_odict __all__ = ['QVersionsDialog'] -class QVersionsDialog(QtWidgets.QDialog): +class QVersionsDialog(CenteredDialog): def __init__(self, *args, **kwargs): @@ -30,28 +30,17 @@ def __init__(self, *args, **kwargs): self._clipboard = QtWidgets.QApplication.clipboard() self.ui.button_copy.clicked.connect(nonpartial(self._copy)) - - def _update_deps(self): status = get_status_as_odict() self._text = "" for name, version in [('Glue', __version__)] + list(status.items()): - check = QtWidgets.QTreeWidgetItem(self.ui.version_tree.invisibleRootItem(), - [name, version]) + QtWidgets.QTreeWidgetItem(self.ui.version_tree.invisibleRootItem(), + [name, version]) self._text += "{0}: {1}\n".format(name, version) def _copy(self): self._clipboard.setText(self._text) - def center(self): - # Adapted from StackOverflow - # https://stackoverflow.com/questions/20243637/pyqt4-center-window-on-active-screen - frameGm = self.frameGeometry() - screen = QtWidgets.QApplication.desktop().screenNumber(QtWidgets.QApplication.desktop().cursor().pos()) - centerPoint = QtWidgets.QApplication.desktop().screenGeometry(screen).center() - frameGm.moveCenter(centerPoint) - self.move(frameGm.topLeft()) - if __name__ == "__main__": diff --git a/glue/core/data_factories/fits.py b/glue/core/data_factories/fits.py index 53e3a44fd..051f21c19 100644 --- a/glue/core/data_factories/fits.py +++ b/glue/core/data_factories/fits.py @@ -87,6 +87,7 @@ def new_data(): ) data = Data(label=label) data.coords = coords + data.meta.update(OrderedDict(hdu.header)) groups[hdu_name] = data extension_by_shape[shape] = hdu_name return data diff --git a/glue/utils/qt/dialogs.py b/glue/utils/qt/dialogs.py index 437964692..2296737db 100644 --- a/glue/utils/qt/dialogs.py +++ b/glue/utils/qt/dialogs.py @@ -2,7 +2,7 @@ from qtpy import QtWidgets -__all__ = ['pick_item', 'pick_class', 'get_text'] +__all__ = ['pick_item', 'pick_class', 'get_text', 'CenteredDialog'] def pick_item(items, labels, title="Pick an item", label="Pick an item", @@ -84,3 +84,18 @@ def get_text(title='Enter a label', default=None): result, isok = QtWidgets.QInputDialog.getText(None, title, title, text=default) if isok: return str(result) + + +class CenteredDialog(QtWidgets.QDialog): + """ + A dialog that is centered on the screen. + """ + + def center(self): + # Adapted from StackOverflow + # https://stackoverflow.com/questions/20243637/pyqt4-center-window-on-active-screen + frameGm = self.frameGeometry() + screen = QtWidgets.QApplication.desktop().screenNumber(QtWidgets.QApplication.desktop().cursor().pos()) + centerPoint = QtWidgets.QApplication.desktop().screenGeometry(screen).center() + frameGm.moveCenter(centerPoint) + self.move(frameGm.topLeft()) From 440a57f0c63cf0497577a6781b42a8f28d80e205 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Sat, 14 Apr 2018 21:13:13 +0100 Subject: [PATCH 2/2] Ignore QDialog class when building docs --- doc/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/conf.py b/doc/conf.py index 23e569251..2e4b1516b 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -320,6 +320,7 @@ ('py:class', 'PyQt5.QtWidgets.QLabel'), ('py:class', 'PyQt5.QtWidgets.QComboBox'), ('py:class', 'PyQt5.QtWidgets.QMessageBox'), + ('py:class', 'PyQt5.QtWidgets.QDialog'), ('py:class', 'PyQt5.QtWidgets.QToolBar'), ('py:class', 'PyQt5.QtWidgets.QStyledItemDelegate'), ('py:class', 'PyQt5.QtCore.QMimeData'),