diff --git a/.gitignore b/.gitignore index 1b88ef867..867c51d15 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ docs/_build/ # cython generated files src/PyMca5/PyMcaGraph/ctools/_ctools/cython/*.c src/PyMca5/PyMcaPhysics/xas/_xas/cython/*.c +src/PyMca5/PyMcaMath/mva/_cython_kmeans/*.c # PyCharm meta data .idea/ diff --git a/src/PyMca5/PyMcaCore/NexusTools.py b/src/PyMca5/PyMcaCore/NexusTools.py index 4ce0b5547..8d2e0c584 100644 --- a/src/PyMca5/PyMcaCore/NexusTools.py +++ b/src/PyMca5/PyMcaCore/NexusTools.py @@ -510,6 +510,55 @@ def getPositionersGroup(h5file, path): positioners = group return positioners +def getStartingPositionersGroup(h5file, path): + """ + Retrieve the start positioners group associated to a path + retrieving them from the same entry. + + It assumes they are either in: + + - NXentry/NXinstrument/positioners_start or + - NXentry/NXinstrument/positioners or + - NXentry/measurement/pre_scan_snapshot + + """ + entry_path = getEntryName(path, h5file=h5file) + instrument = getNXClassGroups(h5file, entry_path, ["NXinstrument", b"NXinstrument"], single=True) + positioners = None + if len(instrument): + instrument = instrument[0] + for key in instrument.keys(): + if key in ["positioners_start", b"positioners_start"]: + positioners = instrument[key] + if not isGroup(positioners): + positioners = None + if positioners is None: + positioners = getPositionersGroup(h5file, path) + return positioners + +def getStartingPositionerValues(h5file, path): + """ + Retrieve the start positioners names, values and units associated to a path + retrieving them from the same entry. + + It assumes they are either in: + + - NXentry/NXinstrument/positioners_start or + - NXentry/NXinstrument/positioners or + - NXentry/measurement/pre_scan_snapshot + + """ + nxpositioners = getStartingPositionersGroup(h5file, path) + positions = list() + if nxpositioners is None: + return positions + for name, dset in nxpositioners.items(): + if not isinstance(dset, h5py.Dataset): + continue + idx = (0,) * dset.ndim + positions.append((name, dset[idx], dset.attrs.get("units", ""))) + return positions + def getMeasurementGroup(h5file, path): """ Retrieve the measurement group associated to a path diff --git a/src/PyMca5/PyMcaGui/io/hdf5/NexusInfo.py b/src/PyMca5/PyMcaGui/io/hdf5/NexusInfo.py new file mode 100644 index 000000000..5d283b67a --- /dev/null +++ b/src/PyMca5/PyMcaGui/io/hdf5/NexusInfo.py @@ -0,0 +1,107 @@ +import h5py + +from PyMca5.PyMcaGui import PyMcaQt as qt +from PyMca5.PyMcaCore.NexusTools import getStartingPositionerValues + +from . import HDF5Info + + +class NexusMotorInfoWidget(qt.QWidget): + def __init__(self, parent): + super().__init__(parent) + + self.mainLayout = qt.QVBoxLayout(self) + self.mainLayout.setContentsMargins(0, 0, 0, 0) + self.mainLayout.setSpacing(2) + + self.label = qt.QLabel(self) + self.label.setText("Number of motors: 0") + + column_names = ["Name", "Value", "Units"] + self._column_names = column_names + + self.table = qt.QTableWidget(self) + self.table.setColumnCount(len(column_names)) + for i in range(len(column_names)): + item = self.table.horizontalHeaderItem(i) + if item is None: + item = qt.QTableWidgetItem(column_names[i], qt.QTableWidgetItem.Type) + item.setText(column_names[i]) + self.table.setHorizontalHeaderItem(i, item) + self.table.setSortingEnabled(True) + + self.mainLayout.addWidget(self.label) + self.mainLayout.addWidget(self.table) + + def setInfoDict(self, ddict): + if "motors" in ddict: + self._setInfoDict(ddict["motors"]) + else: + self._setInfoDict(ddict) + + def _setInfoDict(self, ddict): + nrows = len(ddict.get(self._column_names[0], [])) + self.label.setText("Number of motors: %d" % nrows) + self.table.setRowCount(nrows) + + if not nrows: + self.hide() + return + + for row in range(nrows): + for col, label in enumerate(self._column_names): + text = str(ddict[label][row]) + item = self.table.item(row, col) + if item is None: + item = qt.QTableWidgetItem(text, qt.QTableWidgetItem.Type) + item.setFlags(qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled) + self.table.setItem(row, col, item) + else: + item.setText(text) + + for col in range(len(self._column_names)): + self.table.resizeColumnToContents(col) + + +class NexusInfoWidget(HDF5Info.HDF5InfoWidget): + + def __init__(self, parent=None, info=None, nxclass=None): + self._nxclass = nxclass + super().__init__(parent=parent, info=info) + + def _build(self): + super()._build() + if self._nxclass in ("NXentry", b"NXentry"): + self.motorInfoWidget = NexusMotorInfoWidget(self) + self.addTab(self.motorInfoWidget, "Motors") + + def setInfoDict(self, ddict): + super().setInfoDict(ddict) + if self._nxclass in ("NXentry", b"NXentry"): + self.motorInfoWidget.setInfoDict(ddict) + + +def getInfo(hdf5File, node): + """ + hdf5File is and HDF5 file-like insance + node is the posix path to the node + """ + info = HDF5Info.getInfo(hdf5File, node) + info["motors"] = get_motor_positions(hdf5File, node) + return info + + +def get_motor_positions(hdf5File, node): + node = hdf5File[node] + + nxentry_name = node.name.split("/")[1] + if not nxentry_name: + return dict() + + nxentry = hdf5File[nxentry_name] + if not isinstance(nxentry, h5py.Group): + return dict() + + positions = getStartingPositionerValues(hdf5File, nxentry_name) + column_names = "Name", "Value", "Units" + return dict(zip(column_names, zip(*positions))) diff --git a/src/PyMca5/PyMcaGui/io/hdf5/QNexusWidget.py b/src/PyMca5/PyMcaGui/io/hdf5/QNexusWidget.py index de1c964ef..66af2bc53 100644 --- a/src/PyMca5/PyMcaGui/io/hdf5/QNexusWidget.py +++ b/src/PyMca5/PyMcaGui/io/hdf5/QNexusWidget.py @@ -47,7 +47,7 @@ QString = str from . import HDF5Widget -from . import HDF5Info +from . import NexusInfo from . import HDF5CounterTable from . import HDF5McaTable from . import QNexusWidgetActions @@ -123,7 +123,7 @@ def __init__(self, parent=None, mca=False, buttons=False): self._autoCntList = [] self._autoAliasList = [] self._defaultModel = HDF5Widget.FileModel() - self.getInfo = HDF5Info.getInfo + self.getInfo = NexusInfo.getInfo self._modelDict = {} self._widgetDict = {} self._lastWidgetId = None @@ -507,8 +507,10 @@ def showInfoWidget(self, filename, name, dset=False): phynxFile = self.data._sourceObjectList[fileIndex] else: phynxFile = HDF5Widget.h5open(filename) + info = self.getInfo(phynxFile, name) - widget = HDF5Info.HDF5InfoWidget() + nxclass = phynxFile[name].attrs.get("NX_class") + widget = NexusInfo.NexusInfoWidget(nxclass=nxclass) widget.notifyCloseEventToWidget(self) title = os.path.basename(filename) title += " %s" % name @@ -531,6 +533,7 @@ def sourceObjectDestroyed(weakrefReference): del self._widgetDict[wid] widget._sourceObjectWeakReference = weakref.ref(phynxFile, sourceObjectDestroyed) + widget.setInfoDict(info) # todo: this first `if` block can be dropped when silx is a hard dependency if dset and Hdf5NodeView is None: