def _drawContents(self, reason=None, initiator=None): """ Draws the plot contents from the sliced array of the collected repo tree item. The reason parameter is used to determine if the axes will be reset (the initiator parameter is ignored). See AbstractInspector.updateContents for their description. """ self.slicedArray = self.collector.getSlicedArray() if not self._hasValidData(): self._clearContents() raise InvalidDataError( "No data available or it does not contain real numbers") # -- Valid plot data from here on -- # PyQtGraph doesn't handle masked arrays so we convert the masked values to Nans (missing # data values are replaced by NaNs). The PyQtGraph line plot omits the Nans, which is great. self.slicedArray.replaceMaskedValueWithNan( ) # will convert data to float if int self.plotItem.clear() # Reset the axes ranges (via the config) if (reason == UpdateReason.RTI_CHANGED or reason == UpdateReason.COLLECTOR_COMBO_BOX): # self.config.yAxisRangeCti.setAutoRangeOn() doesn't work as refreshBlocked is True # TODO: can refreshBlocked maybe only block the signals to prevent loops? self.config.xAxisRangeCti.autoRangeCti.data = True self.config.yAxisRangeCti.autoRangeCti.data = True self.titleLabel.setText( self.configValue('title').format(**self.collector.rtiInfo)) connected = np.isfinite(self.slicedArray.data) if is_an_array(self.slicedArray.mask): connected = np.logical_and(connected, ~self.slicedArray.mask) else: connected = np.zeros_like( self.slicedArray.data) if self.slicedArray.mask else connected plotDataItem = self.config.plotDataItemCti.createPlotDataItem() plotDataItem.setData(self.slicedArray.data, connect=connected) self.plotItem.addItem(plotDataItem) if self.config.probeCti.configValue: self.probeLabel.setVisible(True) self.plotItem.addItem(self.crossLineVerShadow, ignoreBounds=True) self.plotItem.addItem(self.crossLineVertical, ignoreBounds=True) self.plotItem.addItem(self.probeDataItem, ignoreBounds=True) self.probeDataItem.setSymbolBrush( QtGui.QBrush(self.config.plotDataItemCti.penColor)) self.probeDataItem.setSymbolSize(10) else: self.probeLabel.setVisible(False) # Update the config tree from the (possibly) new state of the PgLinePlot1d inspector, # e.g. the axis range may have changed while drawing. self.config.updateTarget()
def transpose(self, *args, **kwargs): """ Transposes the array and mask separately :param awm: ArrayWithMask :return: copy/view with transposed """ tdata = np.transpose(self.data, *args, **kwargs) tmask = np.transpose(self.mask, *args, **kwargs) if is_an_array(self.mask) else self.mask return ArrayWithMask(tdata, tmask, self.fill_value)
def _drawContents(self, reason=None, initiator=None): """ Draws the plot contents from the sliced array of the collected repo tree item. The reason parameter is used to determine if the axes will be reset (the initiator parameter is ignored). See AbstractInspector.updateContents for their description. """ self.slicedArray = self.collector.getSlicedArray() if not self._hasValidData(): self._clearContents() raise InvalidDataError("No data available or it does not contain real numbers") # -- Valid plot data from here on -- # PyQtGraph doesn't handle masked arrays so we convert the masked values to Nans (missing # data values are replaced by NaNs). The PyQtGraph line plot omits the Nans, which is great. self.slicedArray.replaceMaskedValueWithNan() # will convert data to float if int self.plotItem.clear() # Reset the axes ranges (via the config) if (reason == UpdateReason.RTI_CHANGED or reason == UpdateReason.COLLECTOR_COMBO_BOX): # self.config.yAxisRangeCti.setAutoRangeOn() doesn't work as refreshBlocked is True # TODO: can refreshBlocked maybe only block the signals to prevent loops? self.config.xAxisRangeCti.autoRangeCti.data = True self.config.yAxisRangeCti.autoRangeCti.data = True self.titleLabel.setText(self.configValue('title').format(**self.collector.rtiInfo)) connected = np.isfinite(self.slicedArray.data) if is_an_array(self.slicedArray.mask): connected = np.logical_and(connected, ~self.slicedArray.mask) else: connected = np.zeros_like(self.slicedArray.data) if self.slicedArray.mask else connected plotDataItem = self.config.plotDataItemCti.createPlotDataItem() plotDataItem.setData(self.slicedArray.data, connect=connected) self.plotItem.addItem(plotDataItem) if self.config.probeCti.configValue: self.probeLabel.setVisible(True) self.plotItem.addItem(self.crossLineVerShadow, ignoreBounds=True) self.plotItem.addItem(self.crossLineVertical, ignoreBounds=True) self.plotItem.addItem(self.probeDataItem, ignoreBounds=True) self.probeDataItem.setSymbolBrush(QtGui.QBrush(self.config.plotDataItemCti.penColor)) self.probeDataItem.setSymbolSize(10) else: self.probeLabel.setVisible(False) # Update the config tree from the (possibly) new state of the PgLinePlot1d inspector, # e.g. the axis range may have changed while drawing. self.config.updateTarget()
def _cellMask(self, index): """ Returns the data mask of the cell at the index (without any string conversion) """ row = index.row() col = index.column() if (row < 0 or row >= self.rowCount() or col < 0 or col >= self.columnCount()): return None # The check above should have returned None if the sliced array is None assert self._slicedArray is not None, "Sanity check failed." nFields = len(self._fieldNames) mask = self._slicedArray.mask if is_an_array(mask): if self._separateFieldOrientation == Qt.Horizontal: maskValue = mask[row, col // nFields][self._fieldNames[col % nFields]] elif self._separateFieldOrientation == Qt.Vertical: maskValue = mask[row // nFields, col][self._fieldNames[row % nFields]] else: maskValue = mask[row, col] else: maskValue = mask # Here maskValue can still be a list in case of structured arrays. It can even still be # a numpy array in case of a structured array with sub arrays as fields if is_an_array(maskValue): allMasked = np.all(maskValue) else: try: allMasked = all(maskValue) except TypeError as ex: allMasked = bool(maskValue) return allMasked
def _createFromObject(obj, *args, **kwargs): """ Creates an RTI given an object. Auto-detects which RTI class to return. The *args and **kwargs parameters are passed to the RTI constructor. It is therefor important that all memory RTIs accept the same parameters in the constructor (with exception of the FieldRti which is not auto-detected). """ if is_a_sequence(obj): return SequenceRti(obj, *args, **kwargs) elif is_a_mapping(obj): return MappingRti(obj, *args, **kwargs) elif is_an_array(obj): return ArrayRti(obj, *args, **kwargs) elif isinstance(obj, bytearray): return ArrayRti(np.array(obj), *args, **kwargs) else: return ScalarRti(obj, *args, **kwargs)
def dataSetMissingValue(exdirDataset): """ Returns the missingData given a Exdir dataset Looks for one of the following attributes: _FillValue, missing_value, MissingValue, missingValue. Returns None if these attributes are not found. """ attributes = exdirDataset.attrs.to_dict() if not attributes: return None # a premature optimization :-) for key in ('missing_value', 'MissingValue', 'missingValue', 'FillValue', '_FillValue'): if key in attributes: missingDataValue = attributes[key] if is_an_array(missingDataValue) and len(missingDataValue) == 1: return missingDataValue[ 0] # In case of HDF-EOS and NetCDF files else: return missingDataValue return None
def data(self, index, role=Qt.DisplayRole): """ Returns the data at an index for a certain role """ try: if role == Qt.DisplayRole: return to_string(self._cellValue(index), masked=self._cellMask(index), decode_bytes=self.encoding, maskFormat=self.maskFormat, strFormat=self.strFormat, intFormat=self.intFormat, numFormat=self.numFormat, otherFormat=self.otherFormat) elif role == Qt.FontRole: #assert self._font, "Font undefined" return self._font elif role == Qt.TextColorRole: masked = self._cellMask(index) if not is_an_array(masked) and masked: return self.missingColor else: return self.dataColor elif role == Qt.TextAlignmentRole: if self.horAlignment == ALIGN_SMART: cellContainsNumber = isinstance(self._cellValue(index), numbers.Number) horAlign = Qt.AlignRight if cellContainsNumber else Qt.AlignLeft return horAlign | self.verAlignment else: return self.horAlignment | self.verAlignment else: return None except Exception as ex: logger.error("Slot is not exception-safe.") logger.exception(ex) if DEBUGGING: raise
def dataSetMissingValue(h5Dataset): """ Returns the missingData given a HDF-5 dataset Looks for one of the following attributes: _FillValue, missing_value, MissingValue, missingValue. Returns None if these attributes are not found. HDF-EOS and NetCDF files seem to put the attributes in 1-element arrays. So if the attribute contains an array of one element, that first element is returned here. """ attributes = h5Dataset.attrs if not attributes: return None # a premature optimization :-) for key in ('missing_value', 'MissingValue', 'missingValue', 'FillValue', '_FillValue'): if key in attributes: missingDataValue = attributes[key] if is_an_array(missingDataValue) and len(missingDataValue) == 1: return missingDataValue[0] # In case of HDF-EOS and NetCDF files else: return missingDataValue return None
def mouseMoved(self, viewPos): """ Updates the probe text with the values under the cursor. Draws a vertical line and a symbol at the position of the probe. """ try: check_class(viewPos, QtCore.QPointF) show_data_point = False # shows the data point as a circle in the cross hair plots self.crossPlotRow, self.crossPlotCol = None, None self.probeLabel.setText( "<span style='color: #808080'>No data at cursor</span>") self.crossLineHorizontal.setVisible(False) self.crossLineVertical.setVisible(False) self.crossLineHorShadow.setVisible(False) self.crossLineVerShadow.setVisible(False) self.horCrossPlotItem.clear() self.verCrossPlotItem.clear() if self.slicedArray is not None and self.viewBox.sceneBoundingRect( ).contains(viewPos): # Calculate the row and column at the cursor. scenePos = self.viewBox.mapSceneToView(viewPos) row, col = round(scenePos.y()), round(scenePos.x()) row, col = int(row), int(col) # Needed in Python 2 nRows, nCols = self.slicedArray.shape if (0 <= row < nRows) and (0 <= col < nCols): self.viewBox.setCursor(Qt.CrossCursor) self.crossPlotRow, self.crossPlotCol = row, col index = tuple([row, col]) valueStr = to_string(self.slicedArray.data[index], masked=self.slicedArray.maskAt(index), maskFormat='<masked>') txt = "({}, {}) = ({:d}, {:d}) {} {} = {}".format( self.collector.rtiInfo['x-dim'], self.collector.rtiInfo['y-dim'], col, row, RIGHT_ARROW, self.collector.rtiInfo['name'], valueStr) self.probeLabel.setText(txt) # Show cross section at the cursor pos in the line plots if self.config.horCrossPlotCti.configValue: self.crossLineHorShadow.setVisible(True) self.crossLineHorizontal.setVisible(True) self.crossLineHorShadow.setPos(row) self.crossLineHorizontal.setPos(row) # Line plot of cross section row. # First determine which points are connected or separated by masks/nans. rowData = self.slicedArray.data[row, :] connected = np.isfinite(rowData) if is_an_array(self.slicedArray.mask): connected = np.logical_and( connected, ~self.slicedArray.mask[row, :]) else: connected = (np.zeros_like(rowData) if self.slicedArray.mask else connected) # Replace mask by Nans. Only doing when not showing lines to hack around PyQtGraph issue 1057 # See comment in PgLinePlot1d._drawContents for a more detailed explanation # TODO: reuse imageItem data array when this hack is no longer necessary if not self.config.crossPenCti.lineCti.configValue: rowData = replaceMaskedValueWithFloat( rowData, np.logical_not(connected), np.nan, copyOnReplace=True) # Replace infinite value with nans because PyQtGraph can't handle them rowData = replaceMaskedValueWithFloat( rowData, np.isinf(rowData), np.nan, copyOnReplace=True) horPlotDataItem = self.config.crossPenCti.createPlotDataItem( ) # TODO: try to use connect='finite' when the hack above is no longer necessary. In that case # test with array_masked test data horPlotDataItem.setData(rowData, connect=connected) self.horCrossPlotItem.addItem(horPlotDataItem) # Vertical line in hor-cross plot crossLineShadow90 = pg.InfiniteLine( angle=90, movable=False, pen=self.crossShadowPen) crossLineShadow90.setPos(col) self.horCrossPlotItem.addItem(crossLineShadow90, ignoreBounds=True) crossLine90 = pg.InfiniteLine(angle=90, movable=False, pen=self.crossPen) crossLine90.setPos(col) self.horCrossPlotItem.addItem(crossLine90, ignoreBounds=True) if show_data_point: crossPoint90 = pg.PlotDataItem( symbolPen=self.crossPen) crossPoint90.setSymbolBrush( QtGui.QBrush(self.config.crossPenCti.penColor)) crossPoint90.setSymbolSize(10) crossPoint90.setData((col, ), (rowData[col], )) self.horCrossPlotItem.addItem(crossPoint90, ignoreBounds=True) self.config.horCrossPlotRangeCti.updateTarget( ) # update auto range del rowData # defensive programming if self.config.verCrossPlotCti.configValue: self.crossLineVerShadow.setVisible(True) self.crossLineVertical.setVisible(True) self.crossLineVerShadow.setPos(col) self.crossLineVertical.setPos(col) # Line plot of cross section row. # First determine which points are connected or separated by masks/nans. colData = self.slicedArray.data[:, col] connected = np.isfinite(colData) if is_an_array(self.slicedArray.mask): connected = np.logical_and( connected, ~self.slicedArray.mask[:, col]) else: connected = (np.zeros_like(colData) if self.slicedArray.mask else connected) # Replace mask by Nans. Only doing when not showing lines to hack around PyQtGraph issue 1057 # See comment in PgLinePlot1d._drawContents for a more detailed explanation if not self.config.crossPenCti.lineCti.configValue: colData = replaceMaskedValueWithFloat( colData, np.logical_not(connected), np.nan, copyOnReplace=True) # Replace infinite value with nans because PyQtGraph can't handle them colData = replaceMaskedValueWithFloat( colData, np.isinf(colData), np.nan, copyOnReplace=True) verPlotDataItem = self.config.crossPenCti.createPlotDataItem( ) verPlotDataItem.setData(colData, np.arange(nRows), connect=connected) self.verCrossPlotItem.addItem(verPlotDataItem) # Horizontal line in ver-cross plot crossLineShadow0 = pg.InfiniteLine( angle=0, movable=False, pen=self.crossShadowPen) crossLineShadow0.setPos(row) self.verCrossPlotItem.addItem(crossLineShadow0, ignoreBounds=True) crossLine0 = pg.InfiniteLine(angle=0, movable=False, pen=self.crossPen) crossLine0.setPos(row) self.verCrossPlotItem.addItem(crossLine0, ignoreBounds=True) if show_data_point: crossPoint0 = pg.PlotDataItem( symbolPen=self.crossPen) crossPoint0.setSymbolBrush( QtGui.QBrush(self.config.crossPenCti.penColor)) crossPoint0.setSymbolSize(10) crossPoint0.setData((colData[row], ), (row, )) self.verCrossPlotItem.addItem(crossPoint0, ignoreBounds=True) self.config.verCrossPlotRangeCti.updateTarget( ) # update auto range del colData # defensive programming except Exception as ex: # In contrast to _drawContents, this function is a slot and thus must not throw # exceptions. The exception is logged. Perhaps we should clear the cross plots, but # this could, in turn, raise exceptions. if DEBUGGING: raise else: logger.exception(ex)
def checkIsConsistent(self): """ Raises a ConsistencyError if the mask has an incorrect shape. """ if is_an_array(self.mask) and self.mask.shape != self.data.shape: raise ConsistencyError("Shape mismatch mask={}, data={}" .format(self.mask.shape != self.data.shape))
def mouseMoved(self, viewPos): """ Updates the probe text with the values under the cursor. Draws a vertical line and a symbol at the position of the probe. """ try: check_class(viewPos, QtCore.QPointF) show_data_point = False # shows the data point as a circle in the cross hair plots self.crossPlotRow, self.crossPlotCol = None, None self.probeLabel.setText("<span style='color: #808080'>no data at cursor</span>") self.crossLineHorizontal.setVisible(False) self.crossLineVertical.setVisible(False) self.crossLineHorShadow.setVisible(False) self.crossLineVerShadow.setVisible(False) self.horCrossPlotItem.clear() self.verCrossPlotItem.clear() if (self._hasValidData() and self.slicedArray is not None and self.viewBox.sceneBoundingRect().contains(viewPos)): # Calculate the row and column at the cursor. We use math.floor because the pixel # corners of the image lie at integer values (and not the centers of the pixels). scenePos = self.viewBox.mapSceneToView(viewPos) row, col = math.floor(scenePos.y()), math.floor(scenePos.x()) row, col = int(row), int(col) # Needed in Python 2 nRows, nCols = self.slicedArray.shape if (0 <= row < nRows) and (0 <= col < nCols): self.viewBox.setCursor(Qt.CrossCursor) self.crossPlotRow, self.crossPlotCol = row, col index = tuple([row, col]) valueStr = to_string(self.slicedArray[index], masked=self.slicedArray.maskAt(index), maskFormat='<masked>') txt = "pos = ({:d}, {:d}), value = {}".format(row, col, valueStr) self.probeLabel.setText(txt) # Show cross section at the cursor pos in the line plots if self.config.horCrossPlotCti.configValue: self.crossLineHorShadow.setVisible(True) self.crossLineHorizontal.setVisible(True) self.crossLineHorShadow.setPos(row) self.crossLineHorizontal.setPos(row) # Line plot of cross section row. # First determine which points are connected or separated by masks/nans. rowData = self.slicedArray.data[row, :] connected = np.isfinite(rowData) if is_an_array(self.slicedArray.mask): connected = np.logical_and(connected, ~self.slicedArray.mask[row, :]) else: connected = (np.zeros_like(rowData) if self.slicedArray.mask else connected) # Replace infinite value with nans because PyQtGraph can't handle them rowData = replaceMaskedValueWithFloat(rowData, np.isinf(rowData), np.nan, copyOnReplace=True) horPlotDataItem = self.config.crossPenCti.createPlotDataItem() horPlotDataItem.setData(rowData, connect=connected) self.horCrossPlotItem.addItem(horPlotDataItem) # Vertical line in hor-cross plot crossLineShadow90 = pg.InfiniteLine(angle=90, movable=False, pen=self.crossShadowPen) crossLineShadow90.setPos(col) self.horCrossPlotItem.addItem(crossLineShadow90, ignoreBounds=True) crossLine90 = pg.InfiniteLine(angle=90, movable=False, pen=self.crossPen) crossLine90.setPos(col) self.horCrossPlotItem.addItem(crossLine90, ignoreBounds=True) if show_data_point: crossPoint90 = pg.PlotDataItem(symbolPen=self.crossPen) crossPoint90.setSymbolBrush(QtGui.QBrush(self.config.crossPenCti.penColor)) crossPoint90.setSymbolSize(10) crossPoint90.setData((col,), (rowData[col],)) self.horCrossPlotItem.addItem(crossPoint90, ignoreBounds=True) self.config.horCrossPlotRangeCti.updateTarget() # update auto range del rowData # defensive programming if self.config.verCrossPlotCti.configValue: self.crossLineVerShadow.setVisible(True) self.crossLineVertical.setVisible(True) self.crossLineVerShadow.setPos(col) self.crossLineVertical.setPos(col) # Line plot of cross section row. # First determine which points are connected or separated by masks/nans. colData = self.slicedArray.data[:, col] connected = np.isfinite(colData) if is_an_array(self.slicedArray.mask): connected = np.logical_and(connected, ~self.slicedArray.mask[:, col]) else: connected = (np.zeros_like(colData) if self.slicedArray.mask else connected) # Replace infinite value with nans because PyQtGraph can't handle them colData = replaceMaskedValueWithFloat(colData, np.isinf(colData), np.nan, copyOnReplace=True) verPlotDataItem = self.config.crossPenCti.createPlotDataItem() verPlotDataItem.setData(colData, np.arange(nRows), connect=connected) self.verCrossPlotItem.addItem(verPlotDataItem) # Horizontal line in ver-cross plot crossLineShadow0 = pg.InfiniteLine(angle=0, movable=False, pen=self.crossShadowPen) crossLineShadow0.setPos(row) self.verCrossPlotItem.addItem(crossLineShadow0, ignoreBounds=True) crossLine0 = pg.InfiniteLine(angle=0, movable=False, pen=self.crossPen) crossLine0.setPos(row) self.verCrossPlotItem.addItem(crossLine0, ignoreBounds=True) if show_data_point: crossPoint0 = pg.PlotDataItem(symbolPen=self.crossPen) crossPoint0.setSymbolBrush(QtGui.QBrush(self.config.crossPenCti.penColor)) crossPoint0.setSymbolSize(10) crossPoint0.setData((colData[row],), (row,)) self.verCrossPlotItem.addItem(crossPoint0, ignoreBounds=True) self.config.verCrossPlotRangeCti.updateTarget() # update auto range del colData # defensive programming except Exception as ex: # In contrast to _drawContents, this function is a slot and thus must not throw # exceptions. The exception is logged. Perhaps we should clear the cross plots, but # this could, in turn, raise exceptions. if DEBUGGING: raise else: logger.exception(ex)
def _drawContents(self, reason=None, initiator=None): """ Draws the plot contents from the sliced array of the collected repo tree item. The reason parameter is used to determine if the axes will be reset (the initiator parameter is ignored). See AbstractInspector.updateContents for their description. """ # If auto-reset is true, reset config complete or partially, depending on the mode. if self._resetRequired(reason, initiator): self.resetConfig() self.slicedArray = self.collector.getSlicedArray() slicedArray = self.collector.getSlicedArray() if slicedArray is None: self._clearContents() raise InvalidDataError() # Don't show message, to common. elif not array_has_real_numbers(slicedArray.data): self._clearContents() raise InvalidDataError("Selected item contains {} data.".format( array_kind_label(slicedArray.data))) else: self.slicedArray = slicedArray # -- Valid plot data from here on -- numElem = np.prod(self.slicedArray.data.shape) if numElem == 0: self.sigShowMessage.emit( "Current slice is empty.") # Not expected to happen. elif numElem == 1: self.sigShowMessage.emit( "Current slice contains only a single data point.") # PyQtGraph doesn't handle masked arrays so we convert the masked values to Nans (missing # data values are replaced by NaNs). The PyQtGraph line plot omits the Nans, which is great. # Update: in newer version of Qt the Nans are no longer printed, see PyQtGraph issue 1057, # https://github.com/pyqtgraph/pyqtgraph/issues/1057 # When showing lines we therefore don't replace the Nans and let the setData connect parameter be responsible # for omitting the masked data. When showing only symbols the masked values are replaced. When both symbols # wnd lines are shown the resulting plot is incorrect as the masked values are not replaced and thus displayed # as point. This is unfortunate but can't be helped until the issue is resolved in PyQtGraph. if not self.config.plotDataItemCti.lineCti.configValue: self.slicedArray.replaceMaskedValueWithNan( ) # will convert data to float if int self.plotItem.clear() self.titleLabel.setText( self.configValue('title').format(**self.collector.rtiInfo)) connected = np.isfinite(self.slicedArray.data) if is_an_array(self.slicedArray.mask): connected = np.logical_and(connected, ~self.slicedArray.mask) else: connected = np.zeros_like( self.slicedArray.data) if self.slicedArray.mask else connected plotDataItem = self.config.plotDataItemCti.createPlotDataItem() plotDataItem.setData(self.slicedArray.data, connect=connected) if plotDataItem.opts['pen'] is None and plotDataItem.opts[ 'symbol'] is None: self.sigShowMessage.emit( "The 'line' and 'symbol' config options are both unchecked!") self.plotItem.addItem(plotDataItem) if self.config.probeCti.configValue: self.probeLabel.setVisible(True) self.plotItem.addItem(self.crossLineVerShadow, ignoreBounds=True) self.plotItem.addItem(self.crossLineVertical, ignoreBounds=True) self.plotItem.addItem(self.probeDataItem, ignoreBounds=True) self.probeDataItem.setSymbolBrush( QtGui.QBrush(self.config.plotDataItemCti.penColor)) self.probeDataItem.setSymbolSize(10) else: self.probeLabel.setVisible(False) self.plotItem.setRectangleZoomOn(self.config.zoomModeCti.configValue) self.config.updateTarget()