From 159ef14fb9e198bb0066ea14e6b980f065de63dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Picca=20Fr=C3=A9d=C3=A9ric-Emmanuel?= Date: Tue, 31 Jul 2018 16:22:25 +0200 Subject: New upstream version 0.8.0+dfsg --- silx/gui/data/DataViewer.py | 37 ++++- silx/gui/data/DataViewerFrame.py | 9 +- silx/gui/data/DataViews.py | 260 ++++++++++++++++++++-------------- silx/gui/data/Hdf5TableView.py | 62 ++++++-- silx/gui/data/HexaTableView.py | 10 +- silx/gui/data/NXdataWidgets.py | 32 ++--- silx/gui/data/TextFormatter.py | 18 ++- silx/gui/data/test/test_dataviewer.py | 6 +- 8 files changed, 283 insertions(+), 151 deletions(-) (limited to 'silx/gui/data') diff --git a/silx/gui/data/DataViewer.py b/silx/gui/data/DataViewer.py index 5e0b25e..4db2863 100644 --- a/silx/gui/data/DataViewer.py +++ b/silx/gui/data/DataViewer.py @@ -37,7 +37,7 @@ from silx.utils.property import classproperty __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "26/02/2018" +__date__ = "24/04/2018" _logger = logging.getLogger(__name__) @@ -167,8 +167,10 @@ class DataViewer(qt.QFrame): self.__currentAvailableViews = [] self.__currentView = None self.__data = None + self.__info = None self.__useAxisSelection = False self.__userSelectedView = None + self.__hooks = None self.__views = [] self.__index = {} @@ -182,6 +184,15 @@ class DataViewer(qt.QFrame): self.__views = list(views) self.setDisplayMode(DataViews.EMPTY_MODE) + def setGlobalHooks(self, hooks): + """Set a data view hooks for all the views + + :param DataViewHooks context: The hooks to use + """ + self.__hooks = hooks + for v in self.__views: + v.setHooks(hooks) + def createDefaultViews(self, parent=None): """Create and returns available views which can be displayed by default by the data viewer. It is called internally by the widget. It can be @@ -250,7 +261,7 @@ class DataViewer(qt.QFrame): """ previous = self.__numpySelection.blockSignals(True) self.__numpySelection.clear() - info = DataViews.DataInfo(self.__data) + info = self._getInfo() axisNames = self.__currentView.axesNames(self.__data, info) if info.isArray and info.size != 0 and self.__data is not None and axisNames is not None: self.__useAxisSelection = True @@ -359,6 +370,8 @@ class DataViewer(qt.QFrame): :param DataView view: A dataview """ + if self.__hooks is not None: + view.setHooks(self.__hooks) self.__views.append(view) # TODO It can be skipped if the view do not support the data self.__updateAvailableViews() @@ -390,8 +403,8 @@ class DataViewer(qt.QFrame): Update available views from the current data. """ data = self.__data + info = self._getInfo() # sort available views according to priority - info = DataViews.DataInfo(data) priorities = [v.getDataPriority(data, info) for v in self.__views] views = zip(priorities, self.__views) views = filter(lambda t: t[0] > DataViews.DataView.UNSUPPORTED, views) @@ -490,6 +503,7 @@ class DataViewer(qt.QFrame): :param numpy.ndarray data: The data. """ self.__data = data + self._invalidateInfo() self.__displayedData = None self.__updateView() self.__updateNumpySelectionAxis() @@ -512,6 +526,21 @@ class DataViewer(qt.QFrame): """Returns the data""" return self.__data + def _invalidateInfo(self): + """Invalidate DataInfo cache.""" + self.__info = None + + def _getInfo(self): + """Returns the DataInfo of the current selected data. + + This value is cached. + + :rtype: DataInfo + """ + if self.__info is None: + self.__info = DataViews.DataInfo(self.__data) + return self.__info + def displayMode(self): """Returns the current display mode""" return self.__currentView.modeId() @@ -552,6 +581,8 @@ class DataViewer(qt.QFrame): isReplaced = False for idx, view in enumerate(self.__views): if view.modeId() == modeId: + if self.__hooks is not None: + newView.setHooks(self.__hooks) self.__views[idx] = newView isReplaced = True break diff --git a/silx/gui/data/DataViewerFrame.py b/silx/gui/data/DataViewerFrame.py index 89a9992..4e6d2e8 100644 --- a/silx/gui/data/DataViewerFrame.py +++ b/silx/gui/data/DataViewerFrame.py @@ -27,7 +27,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "21/09/2017" +__date__ = "24/04/2018" from silx.gui import qt from .DataViewer import DataViewer @@ -113,6 +113,13 @@ class DataViewerFrame(qt.QWidget): """Called when the displayed view changes""" self.displayedViewChanged.emit(view) + def setGlobalHooks(self, hooks): + """Set a data view hooks for all the views + + :param DataViewHooks context: The hooks to use + """ + self.__dataViewer.setGlobalHooks(hooks) + def availableViews(self): """Returns the list of registered views diff --git a/silx/gui/data/DataViews.py b/silx/gui/data/DataViews.py index ef69441..2291e87 100644 --- a/silx/gui/data/DataViews.py +++ b/silx/gui/data/DataViews.py @@ -35,13 +35,13 @@ from silx.gui import qt, icons from silx.gui.data.TextFormatter import TextFormatter from silx.io import nxdata from silx.gui.hdf5 import H5Node -from silx.io.nxdata import get_attr_as_string -from silx.gui.plot.Colormap import Colormap -from silx.gui.plot.actions.control import ColormapAction +from silx.io.nxdata import get_attr_as_unicode +from silx.gui.colors import Colormap +from silx.gui.dialog.ColormapDialog import ColormapDialog __authors__ = ["V. Valls", "P. Knobel"] __license__ = "MIT" -__date__ = "23/01/2018" +__date__ = "23/05/2018" _logger = logging.getLogger(__name__) @@ -109,6 +109,7 @@ class DataInfo(object): self.isBoolean = False self.isRecord = False self.hasNXdata = False + self.isInvalidNXdata = False self.shape = tuple() self.dim = 0 self.size = 0 @@ -118,8 +119,28 @@ class DataInfo(object): if silx.io.is_group(data): nxd = nxdata.get_default(data) + nx_class = get_attr_as_unicode(data, "NX_class") if nxd is not None: self.hasNXdata = True + # can we plot it? + is_scalar = nxd.signal_is_0d or nxd.interpretation in ["scalar", "scaler"] + if not (is_scalar or nxd.is_curve or nxd.is_x_y_value_scatter or + nxd.is_image or nxd.is_stack): + # invalid: cannot be plotted by any widget + self.isInvalidNXdata = True + elif nx_class == "NXdata": + # group claiming to be NXdata could not be parsed + self.isInvalidNXdata = True + elif nx_class == "NXentry" and "default" in data.attrs: + # entry claiming to have a default NXdata could not be parsed + self.isInvalidNXdata = True + elif nx_class == "NXroot" or silx.io.is_file(data): + # root claiming to have a default entry + if "default" in data.attrs: + def_entry = data.attrs["default"] + if def_entry in data and "default" in data[def_entry].attrs: + # and entry claims to have default NXdata + self.isInvalidNXdata = True if isinstance(data, numpy.ndarray): self.isArray = True @@ -130,7 +151,7 @@ class DataInfo(object): if silx.io.is_dataset(data): if "interpretation" in data.attrs: - self.interpretation = get_attr_as_string(data, "interpretation") + self.interpretation = get_attr_as_unicode(data, "interpretation") else: self.interpretation = None elif self.hasNXdata: @@ -166,7 +187,11 @@ class DataInfo(object): if self.shape is not None: self.dim = len(self.shape) - if hasattr(data, "size"): + if hasattr(data, "shape") and data.shape is None: + # This test is expected to avoid to fall done on the h5py issue + # https://github.com/h5py/h5py/issues/1044 + self.size = 0 + elif hasattr(data, "size"): self.size = int(data.size) else: self.size = 1 @@ -177,6 +202,18 @@ class DataInfo(object): return _normalizeData(data) +class DataViewHooks(object): + """A set of hooks defined to custom the behaviour of the data views.""" + + def getColormap(self, view): + """Returns a colormap for this view.""" + return None + + def getColormapDialog(self, view): + """Returns a color dialog for this view.""" + return None + + class DataView(object): """Holder for the data view.""" @@ -184,12 +221,6 @@ class DataView(object): """Priority returned when the requested data can't be displayed by the view.""" - _defaultColormap = None - """Store a default colormap shared with all the views""" - - _defaultColorDialog = None - """Store a default color dialog shared with all the views""" - def __init__(self, parent, modeId=None, icon=None, label=None): """Constructor @@ -204,32 +235,46 @@ class DataView(object): if icon is None: icon = qt.QIcon() self.__icon = icon + self.__hooks = None - @staticmethod - def defaultColormap(): - """Returns a shared colormap as default for all the views. + def getHooks(self): + """Returns the data viewer hooks used by this view. - :rtype: Colormap + :rtype: DataViewHooks """ - if DataView._defaultColormap is None: - DataView._defaultColormap = Colormap(name="viridis") - return DataView._defaultColormap + return self.__hooks - @staticmethod - def defaultColorDialog(): - """Returns a shared color dialog as default for all the views. + def setHooks(self, hooks): + """Set the data view hooks to use with this view. - :rtype: ColorDialog + :param DataViewHooks hooks: The data view hooks to use """ - if DataView._defaultColorDialog is None: - DataView._defaultColorDialog = ColormapAction._createDialog(qt.QApplication.instance().activeWindow()) - return DataView._defaultColorDialog + self.__hooks = hooks - @staticmethod - def _cleanUpCache(): - """Clean up the cache. Needed for tests""" - DataView._defaultColormap = None - DataView._defaultColorDialog = None + def defaultColormap(self): + """Returns a default colormap. + + :rtype: Colormap + """ + colormap = None + if self.__hooks is not None: + colormap = self.__hooks.getColormap(self) + if colormap is None: + colormap = Colormap(name="viridis") + return colormap + + def defaultColorDialog(self): + """Returns a default color dialog. + + :rtype: ColormapDialog + """ + dialog = None + if self.__hooks is not None: + dialog = self.__hooks.getColormapDialog(self) + if dialog is None: + dialog = ColormapDialog() + dialog.setModal(False) + return dialog def icon(self): """Returns the default icon""" @@ -345,8 +390,21 @@ class CompositeDataView(DataView): self.__views = OrderedDict() self.__currentView = None + def setHooks(self, hooks): + """Set the data context to use with this view. + + :param DataViewHooks hooks: The data view hooks to use + """ + super(CompositeDataView, self).setHooks(hooks) + if hooks is not None: + for v in self.__views: + v.setHooks(hooks) + def addView(self, dataView): """Add a new dataview to the available list.""" + hooks = self.getHooks() + if hooks is not None: + dataView.setHooks(hooks) self.__views[dataView] = None def availableViews(self): @@ -446,6 +504,9 @@ class CompositeDataView(DataView): break elif isinstance(view, CompositeDataView): # recurse + hooks = self.getHooks() + if hooks is not None: + newView.setHooks(hooks) if view.replaceView(modeId, newView): return True if oldView is None: @@ -1022,70 +1083,46 @@ class _InvalidNXdataView(DataView): def getDataPriority(self, data, info): data = self.normalizeData(data) - if silx.io.is_group(data): - nxd = nxdata.get_default(data) - nx_class = get_attr_as_string(data, "NX_class") - - if nxd is None: - if nx_class == "NXdata": - # invalid: could not even be parsed by NXdata - self._msg = "Group has @NX_class = NXdata, but could not be interpreted" - self._msg += " as valid NXdata." - return 100 - elif nx_class == "NXentry": - if "default" not in data.attrs: - # no link to NXdata, no problem - return DataView.UNSUPPORTED - self._msg = "NXentry group provides a @default attribute," - default_nxdata_name = data.attrs["default"] - if default_nxdata_name not in data: - self._msg += " but no corresponding NXdata group exists." - elif get_attr_as_string(data[default_nxdata_name], "NX_class") != "NXdata": - self._msg += " but the corresponding item is not a " - self._msg += "NXdata group." - else: - self._msg += " but the corresponding NXdata seems to be" - self._msg += " malformed." - return 100 - elif nx_class == "NXroot" or silx.io.is_file(data): - if "default" not in data.attrs: - # no link to NXentry, no problem - return DataView.UNSUPPORTED - default_entry_name = data.attrs["default"] - if default_entry_name not in data: - # this is a problem, but not NXdata related - return DataView.UNSUPPORTED - default_entry = data[default_entry_name] - if "default" not in default_entry.attrs: - # no NXdata specified, no problemo - return DataView.UNSUPPORTED - default_nxdata_name = default_entry.attrs["default"] - self._msg = "NXroot group provides a @default attribute " - self._msg += "pointing to a NXentry which defines its own " - self._msg += "@default attribute, " - if default_nxdata_name not in default_entry: - self._msg += " but no corresponding NXdata group exists." - elif get_attr_as_string(default_entry[default_nxdata_name], - "NX_class") != "NXdata": - self._msg += " but the corresponding item is not a " - self._msg += "NXdata group." - else: - self._msg += " but the corresponding NXdata seems to be" - self._msg += " malformed." - return 100 - else: - # Not pretending to be NXdata, no problem - return DataView.UNSUPPORTED - - is_scalar = nxd.signal_is_0d or nxd.interpretation in ["scalar", "scaler"] - if not (is_scalar or nxd.is_curve or nxd.is_x_y_value_scatter or - nxd.is_image or nxd.is_stack): - # invalid: cannot be plotted by any widget (I cannot imagine a case) - self._msg = "NXdata seems valid, but cannot be displayed " - self._msg += "by any existing plot widget." - return 100 - return DataView.UNSUPPORTED + if not info.isInvalidNXdata: + return DataView.UNSUPPORTED + + if info.hasNXdata: + self._msg = "NXdata seems valid, but cannot be displayed " + self._msg += "by any existing plot widget." + else: + nx_class = get_attr_as_unicode(data, "NX_class") + if nx_class == "NXdata": + # invalid: could not even be parsed by NXdata + self._msg = "Group has @NX_class = NXdata, but could not be interpreted" + self._msg += " as valid NXdata." + elif nx_class == "NXentry": + self._msg = "NXentry group provides a @default attribute," + default_nxdata_name = data.attrs["default"] + if default_nxdata_name not in data: + self._msg += " but no corresponding NXdata group exists." + elif get_attr_as_unicode(data[default_nxdata_name], "NX_class") != "NXdata": + self._msg += " but the corresponding item is not a " + self._msg += "NXdata group." + else: + self._msg += " but the corresponding NXdata seems to be" + self._msg += " malformed." + elif nx_class == "NXroot" or silx.io.is_file(data): + default_entry = data[data.attrs["default"]] + default_nxdata_name = default_entry.attrs["default"] + self._msg = "NXroot group provides a @default attribute " + self._msg += "pointing to a NXentry which defines its own " + self._msg += "@default attribute, " + if default_nxdata_name not in default_entry: + self._msg += " but no corresponding NXdata group exists." + elif get_attr_as_unicode(default_entry[default_nxdata_name], + "NX_class") != "NXdata": + self._msg += " but the corresponding item is not a " + self._msg += "NXdata group." + else: + self._msg += " but the corresponding NXdata seems to be" + self._msg += " malformed." + return 100 class _NXdataScalarView(DataView): @@ -1111,7 +1148,7 @@ class _NXdataScalarView(DataView): def setData(self, data): data = self.normalizeData(data) # data could be a NXdata or an NXentry - nxd = nxdata.get_default(data) + nxd = nxdata.get_default(data, validate=False) signal = nxd.signal self.getWidget().setArrayData(signal, labels=True) @@ -1119,8 +1156,8 @@ class _NXdataScalarView(DataView): def getDataPriority(self, data, info): data = self.normalizeData(data) - if info.hasNXdata: - nxd = nxdata.get_default(data) + if info.hasNXdata and not info.isInvalidNXdata: + nxd = nxdata.get_default(data, validate=False) if nxd.signal_is_0d or nxd.interpretation in ["scalar", "scaler"]: return 100 return DataView.UNSUPPORTED @@ -1151,7 +1188,7 @@ class _NXdataCurveView(DataView): def setData(self, data): data = self.normalizeData(data) - nxd = nxdata.get_default(data) + nxd = nxdata.get_default(data, validate=False) signals_names = [nxd.signal_name] + nxd.auxiliary_signals_names if nxd.axes_dataset_names[-1] is not None: x_errors = nxd.get_axis_errors(nxd.axes_dataset_names[-1]) @@ -1177,8 +1214,8 @@ class _NXdataCurveView(DataView): def getDataPriority(self, data, info): data = self.normalizeData(data) - if info.hasNXdata: - if nxdata.get_default(data).is_curve: + if info.hasNXdata and not info.isInvalidNXdata: + if nxdata.get_default(data, validate=False).is_curve: return 100 return DataView.UNSUPPORTED @@ -1204,8 +1241,13 @@ class _NXdataXYVScatterView(DataView): def setData(self, data): data = self.normalizeData(data) - nxd = nxdata.get_default(data) + nxd = nxdata.get_default(data, validate=False) + x_axis, y_axis = nxd.axes[-2:] + if x_axis is None: + x_axis = numpy.arange(nxd.signal.size) + if y_axis is None: + y_axis = numpy.arange(nxd.signal.size) x_label, y_label = nxd.axes_names[-2:] if x_label is not None: @@ -1226,8 +1268,8 @@ class _NXdataXYVScatterView(DataView): def getDataPriority(self, data, info): data = self.normalizeData(data) - if info.hasNXdata: - if nxdata.get_default(data).is_x_y_value_scatter: + if info.hasNXdata and not info.isInvalidNXdata: + if nxdata.get_default(data, validate=False).is_x_y_value_scatter: return 100 return DataView.UNSUPPORTED @@ -1256,7 +1298,7 @@ class _NXdataImageView(DataView): def setData(self, data): data = self.normalizeData(data) - nxd = nxdata.get_default(data) + nxd = nxdata.get_default(data, validate=False) isRgba = nxd.interpretation == "rgba-image" # last two axes are Y & X @@ -1274,8 +1316,8 @@ class _NXdataImageView(DataView): def getDataPriority(self, data, info): data = self.normalizeData(data) - if info.hasNXdata: - if nxdata.get_default(data).is_image: + if info.hasNXdata and not info.isInvalidNXdata: + if nxdata.get_default(data, validate=False).is_image: return 100 return DataView.UNSUPPORTED @@ -1302,7 +1344,7 @@ class _NXdataStackView(DataView): def setData(self, data): data = self.normalizeData(data) - nxd = nxdata.get_default(data) + nxd = nxdata.get_default(data, validate=False) signal_name = nxd.signal_name z_axis, y_axis, x_axis = nxd.axes[-3:] z_label, y_label, x_label = nxd.axes_names[-3:] @@ -1319,8 +1361,8 @@ class _NXdataStackView(DataView): def getDataPriority(self, data, info): data = self.normalizeData(data) - if info.hasNXdata: - if nxdata.get_default(data).is_stack: + if info.hasNXdata and not info.isInvalidNXdata: + if nxdata.get_default(data, validate=False).is_stack: return 100 return DataView.UNSUPPORTED diff --git a/silx/gui/data/Hdf5TableView.py b/silx/gui/data/Hdf5TableView.py index e4a0747..04199b2 100644 --- a/silx/gui/data/Hdf5TableView.py +++ b/silx/gui/data/Hdf5TableView.py @@ -30,8 +30,9 @@ from __future__ import division __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "10/10/2017" +__date__ = "23/05/2018" +import collections import functools import os.path import logging @@ -41,6 +42,7 @@ from .TextFormatter import TextFormatter import silx.gui.hdf5 from silx.gui.widgets import HierarchicalTableView from ..hdf5.Hdf5Formatter import Hdf5Formatter +from ..hdf5._utils import htmlFromDict try: import h5py @@ -54,7 +56,7 @@ _logger = logging.getLogger(__name__) class _CellData(object): """Store a table item """ - def __init__(self, value=None, isHeader=False, span=None): + def __init__(self, value=None, isHeader=False, span=None, tooltip=None): """ Constructor @@ -65,6 +67,7 @@ class _CellData(object): self.__value = value self.__isHeader = isHeader self.__span = span + self.__tooltip = tooltip def isHeader(self): """Returns true if the property is a sub-header title. @@ -85,6 +88,19 @@ class _CellData(object): """ return self.__span + def tooltip(self): + """Returns the tooltip of the item. + + :rtype: tuple + """ + return self.__tooltip + + def invalidateValue(self): + self.__value = None + + def invalidateToolTip(self): + self.__tooltip = None + class _TableData(object): """Modelize a table with header, row and column span. @@ -143,7 +159,7 @@ class _TableData(object): item = _CellData(value=headerLabel, isHeader=True, span=(1, self.__colCount)) self.__data.append([item]) - def addHeaderValueRow(self, headerLabel, value): + def addHeaderValueRow(self, headerLabel, value, tooltip=None): """Append the table with a row using the first column as an header and other cells as a single cell for the value. @@ -151,7 +167,7 @@ class _TableData(object): :param object value: value to store. """ header = _CellData(value=headerLabel, isHeader=True) - value = _CellData(value=value, span=(1, self.__colCount)) + value = _CellData(value=value, span=(1, self.__colCount), tooltip=tooltip) self.__data.append([header, value]) def addRow(self, *args): @@ -214,7 +230,20 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel): elif role == qt.Qt.DisplayRole: value = cell.value() if callable(value): - value = value(self.__obj) + try: + value = value(self.__obj) + except Exception: + cell.invalidateValue() + raise + return value + elif role == qt.Qt.ToolTipRole: + value = cell.tooltip() + if callable(value): + try: + value = value(self.__obj) + except Exception: + cell.invalidateToolTip() + raise return value return None @@ -260,6 +289,14 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel): """Format the HDF5 type""" return self.__hdf5Formatter.humanReadableHdf5Type(dataset) + def __attributeTooltip(self, attribute): + attributeDict = collections.OrderedDict() + if hasattr(attribute, "shape"): + attributeDict["Shape"] = self.__hdf5Formatter.humanReadableShape(attribute) + attributeDict["Data type"] = self.__hdf5Formatter.humanReadableType(attribute, full=True) + html = htmlFromDict(attributeDict, title="HDF5 Attribute") + return html + def __formatDType(self, dataset): """Format the numpy dtype""" return self.__hdf5Formatter.humanReadableType(dataset, full=True) @@ -310,7 +347,8 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel): # it's a real H5py object self.__data.addHeaderValueRow("Basename", lambda x: os.path.basename(x.name)) self.__data.addHeaderValueRow("Name", lambda x: x.name) - self.__data.addHeaderValueRow("File", lambda x: x.file.filename) + if obj.file is not None: + self.__data.addHeaderValueRow("File", lambda x: x.file.filename) if hasattr(obj, "path"): # That's a link @@ -322,8 +360,11 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel): else: if silx.io.is_file(obj): physical = lambda x: x.filename + SEPARATOR + x.name + elif obj.file is not None: + physical = lambda x: x.file.filename + SEPARATOR + x.name else: - physical = lambda x: x.file.filename + SEPARATOR + x.name + # Guess it is a virtual node + physical = "No physical location" self.__data.addHeaderValueRow("Physical", physical) if hasattr(obj, "dtype"): @@ -367,7 +408,10 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel): self.__data.addHeaderRow(headerLabel="Attributes") for key in sorted(obj.attrs.keys()): callback = lambda key, x: self.__formatter.toString(x.attrs[key]) - self.__data.addHeaderValueRow(headerLabel=key, value=functools.partial(callback, key)) + callbackTooltip = lambda key, x: self.__attributeTooltip(x.attrs[key]) + self.__data.addHeaderValueRow(headerLabel=key, + value=functools.partial(callback, key), + tooltip=functools.partial(callbackTooltip, key)) def __get_filter_info(self, dataset, filterIndex): """Get a tuple of readable info from dataset filters @@ -447,7 +491,7 @@ class Hdf5TableView(HierarchicalTableView.HierarchicalTableView): def setData(self, data): """Set the h5py-like object exposed by the model - :param h5pyObject: A h5py-like object. It can be a `h5py.Dataset`, + :param data: A h5py-like object. It can be a `h5py.Dataset`, a `h5py.File`, a `h5py.Group`. It also can be a, `silx.gui.hdf5.H5Node` which is needed to display some local path information. diff --git a/silx/gui/data/HexaTableView.py b/silx/gui/data/HexaTableView.py index 1b2a7e9..c86c0af 100644 --- a/silx/gui/data/HexaTableView.py +++ b/silx/gui/data/HexaTableView.py @@ -37,7 +37,7 @@ from silx.gui.widgets.TableWidget import CopySelectedCellsAction __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "27/09/2017" +__date__ = "23/05/2018" class _VoidConnector(object): @@ -54,7 +54,13 @@ class _VoidConnector(object): def __getBuffer(self, bufferId): if bufferId not in self.__cache: pos = bufferId << 10 - data = self.__data.tobytes()[pos:pos + 1024] + data = self.__data + if hasattr(data, "tobytes"): + data = data.tobytes()[pos:pos + 1024] + else: + # Old fashion + data = data.data[pos:pos + 1024] + self.__cache[bufferId] = data if len(self.__cache) > 32: self.__cache.popitem() diff --git a/silx/gui/data/NXdataWidgets.py b/silx/gui/data/NXdataWidgets.py index ae2911d..1bf5425 100644 --- a/silx/gui/data/NXdataWidgets.py +++ b/silx/gui/data/NXdataWidgets.py @@ -26,14 +26,14 @@ """ __authors__ = ["P. Knobel"] __license__ = "MIT" -__date__ = "20/12/2017" +__date__ = "24/04/2018" import numpy from silx.gui import qt from silx.gui.data.NumpyAxesSelector import NumpyAxesSelector -from silx.gui.plot import Plot1D, Plot2D, StackView -from silx.gui.plot.Colormap import Colormap +from silx.gui.plot import Plot1D, Plot2D, StackView, ScatterView +from silx.gui.colors import Colormap from silx.gui.widgets.FrameBrowser import HorizontalSliderWithBrowser from silx.math.calibration import ArrayCalibration, NoCalibration, LinearCalibration @@ -211,10 +211,10 @@ class XYVScatterPlot(qt.QWidget): self.__y_axis_name = None self.__y_axis_errors = None - self._plot = Plot1D(self) - self._plot.setDefaultColormap(Colormap(name="viridis", - vmin=None, vmax=None, - normalization=Colormap.LINEAR)) + self._plot = ScatterView(self) + self._plot.setColormap(Colormap(name="viridis", + vmin=None, vmax=None, + normalization=Colormap.LINEAR)) self._slider = HorizontalSliderWithBrowser(parent=self) self._slider.setMinimum(0) @@ -235,9 +235,9 @@ class XYVScatterPlot(qt.QWidget): def getPlot(self): """Returns the plot used for the display - :rtype: Plot1D + :rtype: PlotWidget """ - return self._plot + return self._plot.getPlotWidget() def setScattersData(self, y, x, values, yerror=None, xerror=None, @@ -284,8 +284,6 @@ class XYVScatterPlot(qt.QWidget): x = self.__x_axis y = self.__y_axis - self._plot.remove(kind=("scatter", )) - idx = self._slider.value() title = "" @@ -294,16 +292,15 @@ class XYVScatterPlot(qt.QWidget): title += self.__scatter_titles[idx] # scatter dataset name self._plot.setGraphTitle(title) - self._plot.addScatter(x, y, self.__values[idx], - legend="scatter%d" % idx, - xerror=self.__x_axis_errors, - yerror=self.__y_axis_errors) + self._plot.setData(x, y, self.__values[idx], + xerror=self.__x_axis_errors, + yerror=self.__y_axis_errors) self._plot.resetZoom() self._plot.getXAxis().setLabel(self.__x_axis_name) self._plot.getYAxis().setLabel(self.__y_axis_name) def clear(self): - self._plot.clear() + self._plot.getPlotWidget().clear() class ArrayImagePlot(qt.QWidget): @@ -476,7 +473,8 @@ class ArrayImagePlot(qt.QWidget): scale = (xscale, yscale) self._plot.addImage(image, legend=legend, - origin=origin, scale=scale) + origin=origin, scale=scale, + replace=True) else: scatterx, scattery = numpy.meshgrid(x_axis, y_axis) # fixme: i don't think this can handle "irregular" RGBA images diff --git a/silx/gui/data/TextFormatter.py b/silx/gui/data/TextFormatter.py index 332625c..8440509 100644 --- a/silx/gui/data/TextFormatter.py +++ b/silx/gui/data/TextFormatter.py @@ -27,7 +27,7 @@ data module to format data as text in the same way.""" __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "13/12/2017" +__date__ = "25/06/2018" import numpy import numbers @@ -204,7 +204,7 @@ class TextFormatter(qt.QObject): def __formatBinary(self, data): if isinstance(data, numpy.void): if six.PY2: - data = [ord(d) for d in data.item()] + data = [ord(d) for d in data.data] else: data = data.item().astype(numpy.uint8) elif six.PY2: @@ -266,6 +266,8 @@ class TextFormatter(qt.QObject): elif vlen == six.binary_type: # HDF5 ASCII return self.__formatCharString(data) + elif isinstance(vlen, numpy.dtype): + return self.toString(data, vlen) return None def toString(self, data, dtype=None): @@ -291,11 +293,17 @@ class TextFormatter(qt.QObject): else: text = [self.toString(d, dtype) for d in data] return "[" + " ".join(text) + "]" + if dtype is not None and dtype.kind == 'O': + text = self.__formatH5pyObject(data, dtype) + if text is not None: + return text elif isinstance(data, numpy.void): if dtype is None: dtype = data.dtype - if data.dtype.fields is not None: - text = [self.toString(data[f], dtype) for f in dtype.fields] + if dtype.fields is not None: + text = [] + for index, field in enumerate(dtype.fields.items()): + text.append(field[0] + ":" + self.toString(data[index], field[1][0])) return "(" + " ".join(text) + ")" return self.__formatBinary(data) elif isinstance(data, (numpy.unicode_, six.text_type)): @@ -340,7 +348,7 @@ class TextFormatter(qt.QObject): elif isinstance(data, (numbers.Real, numpy.floating)): # It have to be done before complex checking return self.__floatFormat % data - elif isinstance(data, (numpy.complex_, numbers.Complex)): + elif isinstance(data, (numpy.complexfloating, numbers.Complex)): text = "" if data.real != 0: text += self.__floatFormat % data.real diff --git a/silx/gui/data/test/test_dataviewer.py b/silx/gui/data/test/test_dataviewer.py index 274df92..f3c2808 100644 --- a/silx/gui/data/test/test_dataviewer.py +++ b/silx/gui/data/test/test_dataviewer.py @@ -24,7 +24,7 @@ # ###########################################################################*/ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "22/02/2018" +__date__ = "23/04/2018" import os import tempfile @@ -208,7 +208,6 @@ class AbstractDataViewerTests(TestCaseQt): self.assertEquals(widget.displayedView().modeId(), DataViews.RAW_MODE) widget.setDisplayMode(DataViews.EMPTY_MODE) self.assertEquals(widget.displayedView().modeId(), DataViews.EMPTY_MODE) - DataView._cleanUpCache() def test_create_default_views(self): widget = self.create_widget() @@ -287,7 +286,6 @@ class TestDataView(TestCaseQt): dataViewClass = DataViews._Plot2dView widget = self.createDataViewWithData(dataViewClass, data[0]) self.qWaitForWindowExposed(widget) - DataView._cleanUpCache() def testCubeWithComplex(self): self.skipTest("OpenGL widget not yet tested") @@ -299,14 +297,12 @@ class TestDataView(TestCaseQt): dataViewClass = DataViews._Plot3dView widget = self.createDataViewWithData(dataViewClass, data) self.qWaitForWindowExposed(widget) - DataView._cleanUpCache() def testImageStackWithComplex(self): data = self.createComplexData() dataViewClass = DataViews._StackView widget = self.createDataViewWithData(dataViewClass, data) self.qWaitForWindowExposed(widget) - DataView._cleanUpCache() def suite(): -- cgit v1.2.3