def getToolbars(self): bar = QToolBar('Live data') bar.addAction(self.actionWriteXml) bar.addAction(self.actionPrint) bar.addSeparator() bar.addAction(self.actionLogScale) bar.addSeparator() bar.addAction(self.actionUnzoom) bar.addAction(self.actionSetAsROI) bar.addSeparator() bar.addAction(self.actionSelectChannels) bar.addSeparator() bar.addAction(self.actionCustomRange) bar.addWidget(self.rangeFrom) bar.addWidget(QLabel(' to ')) bar.addWidget(self.rangeTo) bar.addSeparator() bar.addAction(self.actionOverviewMode) bar.addAction(self.actionPhaseMode) bar.addAction(self.actionContrastMode) return [bar]
def getToolbars(self): if not self.bar: bar = QToolBar('History viewer') bar.addAction(self.actionNew) bar.addAction(self.actionEditView) bar.addSeparator() bar.addAction(self.actionSavePlot) bar.addAction(self.actionPrint) bar.addAction(self.actionSaveData) bar.addSeparator() bar.addAction(self.actionUnzoom) bar.addAction(self.actionLogScale) bar.addSeparator() bar.addAction(self.actionAutoScale) bar.addAction(self.actionScaleX) bar.addAction(self.actionScaleY) bar.addSeparator() bar.addAction(self.actionResetView) bar.addAction(self.actionDeleteView) bar.addSeparator() bar.addAction(self.actionFitPeak) wa = QWidgetAction(bar) self.fitPickCheckbox = QCheckBox(bar) self.fitPickCheckbox.setText('Pick') self.fitPickCheckbox.setChecked(True) self.actionPickInitial.setChecked(True) self.fitPickCheckbox.toggled.connect( self.actionPickInitial.setChecked) self.actionPickInitial.toggled.connect( self.fitPickCheckbox.setChecked) layout = QHBoxLayout() layout.setContentsMargins(10, 0, 10, 0) layout.addWidget(self.fitPickCheckbox) frame = QFrame(bar) frame.setLayout(layout) wa.setDefaultWidget(frame) bar.addAction(wa) ag = QActionGroup(bar) ag.addAction(self.actionFitPeakGaussian) ag.addAction(self.actionFitPeakLorentzian) ag.addAction(self.actionFitPeakPV) ag.addAction(self.actionFitPeakPVII) ag.addAction(self.actionFitTc) ag.addAction(self.actionFitCosine) ag.addAction(self.actionFitSigmoid) ag.addAction(self.actionFitLinear) ag.addAction(self.actionFitExponential) wa = QWidgetAction(bar) self.fitComboBox = QComboBox(bar) for a in ag.actions(): itemtext = a.text().replace('&', '') self.fitComboBox.addItem(itemtext) self.fitfuncmap[itemtext] = a self.fitComboBox.currentIndexChanged.connect( self.on__fitComboBox_currentIndexChanged) wa.setDefaultWidget(self.fitComboBox) bar.addAction(wa) bar.addSeparator() bar.addAction(self.actionFitArby) self.bar = bar self.actionFitLinear.trigger() return [self.bar]
def getToolbars(self): if not self.bars: bar = QToolBar('Scans') bar.addAction(self.actionSavePlot) bar.addAction(self.actionPrint) bar.addSeparator() bar.addAction(self.actionXAxis) bar.addAction(self.actionYAxis) bar.addAction(self.actionNormalized) bar.addSeparator() bar.addAction(self.actionLogXScale) bar.addAction(self.actionLogScale) bar.addAction(self.actionUnzoom) bar.addSeparator() bar.addAction(self.actionAutoScale) bar.addAction(self.actionScaleX) bar.addAction(self.actionScaleY) bar.addAction(self.actionLegend) bar.addAction(self.actionErrors) bar.addAction(self.actionResetPlot) bar.addAction(self.actionDeletePlot) bar.addSeparator() bar.addAction(self.actionAutoDisplay) bar.addAction(self.actionCombine) fitbar = QToolBar('Scan fitting') fitbar.addAction(self.actionFitPeak) wa = QWidgetAction(fitbar) self.fitPickCheckbox = QCheckBox(fitbar) self.fitPickCheckbox.setText('Pick') self.fitPickCheckbox.setChecked(True) self.actionPickInitial.setChecked(True) self.fitPickCheckbox.toggled.connect(self.actionPickInitial.setChecked) self.actionPickInitial.toggled.connect(self.fitPickCheckbox.setChecked) layout = QHBoxLayout() layout.setContentsMargins(10, 0, 10, 0) layout.addWidget(self.fitPickCheckbox) frame = QFrame(fitbar) frame.setLayout(layout) wa.setDefaultWidget(frame) fitbar.addAction(wa) ag = QActionGroup(fitbar) ag.addAction(self.actionFitPeakGaussian) ag.addAction(self.actionFitPeakLorentzian) ag.addAction(self.actionFitPeakPV) ag.addAction(self.actionFitPeakPVII) ag.addAction(self.actionFitTc) ag.addAction(self.actionFitCosine) ag.addAction(self.actionFitSigmoid) ag.addAction(self.actionFitLinear) ag.addAction(self.actionFitExponential) wa = QWidgetAction(fitbar) self.fitComboBox = QComboBox(fitbar) for a in ag.actions(): itemtext = a.text().replace('&', '') self.fitComboBox.addItem(itemtext) self.fitfuncmap[itemtext] = a self.fitComboBox.currentIndexChanged.connect( self.on_fitComboBox_currentIndexChanged) wa.setDefaultWidget(self.fitComboBox) fitbar.addAction(wa) fitbar.addSeparator() fitbar.addAction(self.actionFitArby) self.bars = [bar, fitbar] return self.bars
class LiveDataPanel(Panel): """Provides a generic "detector live view". For most instruments, a specific panel must be implemented that takes care of the individual live display needs. Options: * ``instrument`` -- the instrument name that is passed on to the livewidget module. * ``filetypes`` default[] - List of filename extensions whose content should be displayed. * ``detectors`` (default [] - list of detector devices whose data should be displayed. If not set data from all configured detectors will be shown. * ``cachesize`` (default 20) - Number of entries in the live data cache. The live data cache allows to display of previous taken data. """ panelName = 'Live data view' def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, 'panels/live.ui') self._allowed_tags = set() self._allowed_detectors = set() self._ignore_livedata = False # ignore livedata, e.g. wrong detector self._last_idx = 0 self._last_tag = None self._last_fnames = None self._last_format = None self._runtime = 0 self._range_active = False self._cachesize = 20 self._livewidgets = {} # livewidgets for rois: roi_key -> widget self._fileopen_filter = None self.widget = None self.menu = None self.statusBar = QStatusBar(self, sizeGripEnabled=False) policy = self.statusBar.sizePolicy() policy.setVerticalPolicy(QSizePolicy.Fixed) self.statusBar.setSizePolicy(policy) self.statusBar.setSizeGripEnabled(False) self.layout().addWidget(self.statusBar) self.toolbar = QToolBar('Live data') self.toolbar.addAction(self.actionOpen) self.toolbar.addAction(self.actionPrint) self.toolbar.addSeparator() self.toolbar.addAction(self.actionLogScale) self.toolbar.addSeparator() self.toolbar.addAction(self.actionKeepRatio) self.toolbar.addAction(self.actionUnzoom) self.toolbar.addAction(self.actionColormap) self.toolbar.addAction(self.actionMarkCenter) self.toolbar.addAction(self.actionROI) self._actions2D = [self.actionROI, self.actionColormap] self.setControlsEnabled(False) self.set2DControlsEnabled(False) # self.widget.setControls(Logscale | MinimumMaximum | BrightnessContrast | # Integrate | Histogram) self.liveitems = [] self.setLiveItems(1) self._livechannel = 0 self.splitter.setSizes([20, 80]) self.splitter.restoreState(self.splitterstate) if hasattr(self.window(), 'closed'): self.window().closed.connect(self.on_closed) client.livedata.connect(self.on_client_livedata) client.liveparams.connect(self.on_client_liveparams) client.connected.connect(self.on_client_connected) client.cache.connect(self.on_cache) self.rois = {} self.detectorskey = None # configure instrument specific behavior self._instrument = options.get('instrument', '') # self.widget.setInstrumentOption(self._instrument) # if self._instrument == 'toftof': # self.widget.setAxisLabels('time channels', 'detectors') # elif self._instrument == 'imaging': # self.widget.setControls(ShowGrid | Logscale | Grayscale | # Normalize | Darkfield | Despeckle | # CreateProfile | Histogram | MinimumMaximum) # self.widget.setStandardColorMap(True, False) # configure allowed file types supported_filetypes = ReaderRegistry.filetypes() opt_filetypes = set(options.get('filetypes', supported_filetypes)) self._allowed_tags = opt_filetypes & set(supported_filetypes) # configure allowed detector device names detectors = options.get('detectors') if detectors: self._allowed_detectors = set(detectors) # configure caching self._cachesize = options.get('cachesize', self._cachesize) if self._cachesize < 1: self._cachesize = 1 # always cache the last live image self._datacache = BoundedOrderedDict(maxlen=self._cachesize) def setLiveItems(self, n): nitems = len(self.liveitems) if n < nitems: nfiles = self.fileList.count() for i in range(nitems - 1, n - 1, -1): self.liveitems.pop(i) self.fileList.takeItem(nfiles - nitems + i) if self._livechannel > n: self._livechannel = 0 if n > 0 else None else: for i in range(nitems, n): item = QListWidgetItem('<Live #%d>' % (i + 1)) item.setData(FILENAME, i) item.setData(FILEFORMAT, '') item.setData(FILETAG, 'live') self.fileList.insertItem(self.fileList.count(), item) self.liveitems.append(item) if n == 1: self.liveitems[0].setText('<Live>') else: self.liveitems[0].setText('<Live #1>') def set2DControlsEnabled(self, flag): if flag != self.actionKeepRatio.isChecked(): self.actionKeepRatio.trigger() for action in self._actions2D: action.setVisible(flag) def setControlsEnabled(self, flag): for action in self.toolbar.actions(): action.setEnabled(flag) self.actionOpen.setEnabled(True) # File Open action always available def initLiveWidget(self, widgetcls): if isinstance(self.widget, widgetcls): return if self.widget: self.widgetLayout.removeWidget(self.widget) self.widget.deleteLater() self.widget = widgetcls(self) # enable/disable controls and set defaults for new livewidget instances self.setControlsEnabled(True) if isinstance(self.widget, LiveWidget1D): self.set2DControlsEnabled(False) else: self.set2DControlsEnabled(True) # apply current settings self.widget.setCenterMark(self.actionMarkCenter.isChecked()) self.widget.logscale(self.actionLogScale.isChecked()) guiConn = GUIConnector(self.widget.gr) guiConn.connect(MouseEvent.MOUSE_MOVE, self.on_mousemove_gr) self.menuColormap = QMenu(self) self.actionsColormap = QActionGroup(self) activeMap = self.widget.getColormap() activeCaption = None for name, value in iteritems(COLORMAPS): caption = name.title() action = self.menuColormap.addAction(caption) action.setData(caption) action.setCheckable(True) if activeMap == value: action.setChecked(True) # update toolButton text later otherwise this may fail # depending on the setup and qt versions in use activeCaption = caption self.actionsColormap.addAction(action) action.triggered.connect(self.on_colormap_triggered) self.actionColormap.setMenu(self.menuColormap) self.widgetLayout.addWidget(self.widget) if activeCaption: self.toolbar.widgetForAction( self.actionColormap).setText(activeCaption) detectors = self.client.eval('session.experiment.detectors', []) self._register_rois(detectors) def loadSettings(self, settings): self.splitterstate = settings.value('splitter', '', QByteArray) def saveSettings(self, settings): settings.setValue('splitter', self.splitter.saveState()) settings.setValue('geometry', self.saveGeometry()) def getMenus(self): if not self.menu: menu = QMenu('&Live data', self) menu.addAction(self.actionOpen) menu.addAction(self.actionPrint) menu.addSeparator() menu.addAction(self.actionKeepRatio) menu.addAction(self.actionUnzoom) menu.addAction(self.actionLogScale) menu.addAction(self.actionColormap) menu.addAction(self.actionMarkCenter) menu.addAction(self.actionROI) self.menu = menu return [self.menu] def _get_all_widgets(self): yield self.widget for w in itervalues(self._livewidgets): yield w def getToolbars(self): return [self.toolbar] def on_mousemove_gr(self, event): xyz = None if event.getWindow(): # inside plot xyz = self.widget.getZValue(event) if xyz: fmt = '(%g, %g)' # x, y data 1D integral plots if len(xyz) == 3: fmt += ': %g' # x, y, z data for 2D image plot self.statusBar.showMessage(fmt % xyz) else: self.statusBar.clearMessage() def on_actionColormap_triggered(self): w = self.toolbar.widgetForAction(self.actionColormap) m = self.actionColormap.menu() if m: m.popup(w.mapToGlobal(QPoint(0, w.height()))) def on_colormap_triggered(self): action = self.actionsColormap.checkedAction() name = action.data() for widget in self._get_all_widgets(): widget.setColormap(COLORMAPS[name.upper()]) self.toolbar.widgetForAction(self.actionColormap).setText(name.title()) def _getLiveWidget(self, roi): return self._livewidgets.get(roi + '/roi', None) def showRoiWindow(self, roikey): key = roikey + '/roi' widget = self._getLiveWidget(roikey) region = self.widget._rois[key] if not widget: widget = LiveWidget(None) widget.setWindowTitle(roikey) widget.setColormap(self.widget.getColormap()) widget.setCenterMark(self.actionMarkCenter.isChecked()) widget.logscale(self.actionLogScale.isChecked()) widget.gr.setAdjustSelection(False) # don't use adjust on ROIs for name, roi in iteritems(self.rois): widget.setROI(name, roi) width = max(region.x) - min(region.x) height = max(region.y) - min(region.y) if width > height: dwidth = 500 dheight = 500 * height // width else: dheight = 500 dwidth = 500 * width // height widget.resize(dwidth, dheight) widget.closed.connect(self.on_roiWindowClosed) widget.setWindowForRoi(region) widget.update() widget.show() widget.activateWindow() self._livewidgets[key] = widget def closeRoiWindow(self, roi): widget = self._getLiveWidget(roi) if widget: widget.close() def on_closed(self): for w in self._livewidgets.values(): w.close() def _register_rois(self, detectors): self.rois.clear() self.actionROI.setVisible(False) self.menuROI = QMenu(self) self.actionsROI = QActionGroup(self) self.actionsROI.setExclusive(False) for detname in detectors: self.log.debug('checking rois for detector \'%s\'', detname) for tup in self.client.eval(detname + '.postprocess', ''): roi = tup[0] cachekey = roi + '/roi' # check whether or not this is a roi (cachekey exists). keyval = self.client.getCacheKey(cachekey) if keyval: self.on_roiChange(cachekey, keyval[1]) self.log.debug('register roi: %s', roi) # create roi menu action = self.menuROI.addAction(roi) action.setData(roi) action.setCheckable(True) self.actionsROI.addAction(action) action.triggered.connect(self.on_roi_triggered) self.actionROI.setMenu(self.menuROI) self.actionROI.setVisible(True) def on_actionROI_triggered(self): w = self.toolbar.widgetForAction(self.actionROI) self.actionROI.menu().popup(w.mapToGlobal(QPoint(0, w.height()))) def on_roi_triggered(self): action = self.sender() roi = action.data() if action.isChecked(): self.showRoiWindow(roi) else: self.closeRoiWindow(roi) def on_roiWindowClosed(self): widget = self.sender() if widget: key = None for key, w in iteritems(self._livewidgets): if w == widget: self.log.debug('delete roi: %s', key) del self._livewidgets[key] break if key: roi = key.rsplit('/', 1)[0] for action in self.actionsROI.actions(): if action.data() == roi: action.setChecked(False) self.log.debug('uncheck roi: %s', roi) def on_roiChange(self, key, value): self.log.debug('on_roiChange: %s %s', key, (value, )) self.rois[key] = value for widget in self._get_all_widgets(): widget.setROI(key, value) widget = self._livewidgets.get(key, None) if widget: widget.setWindowForRoi(self.widget._rois[key]) def on_cache(self, data): _time, key, _op, svalue = data try: value = cache_load(svalue) except ValueError: value = None if key in self.rois: self.on_roiChange(key, value) elif key == self.detectorskey and self.widget: self._register_rois(value) def on_client_connected(self): self.client.tell('eventunmask', ['livedata', 'liveparams']) datapath = self.client.eval('session.experiment.datapath', '') if not datapath or not path.isdir(datapath): return if self._instrument == 'imaging': for fn in sorted(os.listdir(datapath)): if fn.endswith('.fits'): self.add_to_flist(path.join(datapath, fn), '', 'fits', False) self.detectorskey = (self.client.eval('session.experiment.name') + '/detlist').lower() def on_client_liveparams(self, params): tag, uid, det, fname, dtype, nx, ny, nz, runtime = params # TODO: remove compatibility code if isinstance(fname, string_types): fname, nx, ny, nz = [fname], [nx], [ny], [nz] if self._allowed_detectors and det not in self._allowed_detectors: self._ignore_livedata = True return self._ignore_livedata = False self._runtime = runtime self._last_uid = uid if dtype: self.setLiveItems(len(fname)) self._last_fnames = None normalized_type = numpy.dtype(dtype).str if normalized_type not in DATATYPES: self._last_format = None self.log.warning('Unsupported live data format: %s', (params, )) return self._last_format = normalized_type elif fname: self._last_fnames = fname self._last_format = None self._last_tag = tag.lower() self._nx = nx self._ny = ny self._nz = nz self._last_idx = 0 def _initLiveWidget(self, array): """Initialize livewidget based on array's shape""" if len(array.shape) == 1: widgetcls = LiveWidget1D else: widgetcls = IntegralLiveWidget self.initLiveWidget(widgetcls) def setData(self, array, uid=None, display=True): """Dispatch data array to corresponding live widgets. Cache array based on uid parameter. No caching if uid is ``None``. """ if uid: if uid not in self._datacache: self.log.debug('add to cache: %s', uid) self._datacache[uid] = array if display: self._initLiveWidget(array) for widget in self._get_all_widgets(): widget.setData(array) def setDataFromFile(self, filename, tag, uid=None, display=True): """Load data array from file and dispatch to live widgets using ``setData``. Do not use caching if uid is ``None``. """ try: array = ReaderRegistry.getReaderCls(tag).fromfile(filename) except KeyError: raise NicosError('Unsupported fileformat %r' % tag) if array is not None: self.setData(array, uid, display=display) else: raise NicosError('Cannot read file %r' % filename) def on_client_livedata(self, data): if self._ignore_livedata: # ignore all live events return idx = self._last_idx # 0 <= array number < n self._last_idx += 1 # check for allowed tags but always allow live data if self._last_tag in self._allowed_tags or self._last_tag == 'live': # pylint: disable=len-as-condition if len(data) and self._last_format: # we got live data with a specified format uid = str(self._last_uid) + '-' + str(idx) array = numpy.frombuffer(data, self._last_format) if self._nz[idx] > 1: array = array.reshape( (self._nz[idx], self._ny[idx], self._nx[idx])) elif self._ny[idx] > 1: array = array.reshape((self._ny[idx], self._nx[idx])) # update display for selected live channel, just cache # otherwise self.setData(array, uid, display=(idx == self._livechannel)) self.liveitems[idx].setData(FILEUID, uid) else: # we got no live data, but a filename with the data # filename corresponds to full qualififed path here for i, filename in enumerate(self._last_fnames): uid = str(self._last_uid) + '-' + str(i) self.add_to_flist(filename, self._last_format, self._last_tag, uid) try: # update display for selected live channel, just cache # otherwise self.setDataFromFile(filename, self._last_tag, uid, display=(i == self._livechannel)) except Exception as e: if uid in self._datacache: # image is already cached # suppress error message for cached image self.log.debug(e) else: # image is not cached and could not be loaded self.log.exception(e) def remove_obsolete_cached_files(self): """Removes outdated cached files from the file list or set cached flag to False if the file is still available on the filesystem. """ cached_item_rows = [] for row in range(self.fileList.count()): item = self.fileList.item(row) if item.data(FILEUID): cached_item_rows.append(row) if len(cached_item_rows) > self._cachesize: for row in cached_item_rows[0:-self._cachesize]: item = self.fileList.item(row) self.log.debug('remove from cache %s %s', item.data(FILEUID), item.data(FILENAME)) if path.isfile(item.data(FILENAME)): item.setData(FILEUID, None) else: self.fileList.takeItem(row) def add_to_flist(self, filename, fformat, ftag, uid=None, scroll=True): shortname = path.basename(filename) item = QListWidgetItem(shortname) item.setData(FILENAME, filename) item.setData(FILEFORMAT, fformat) item.setData(FILETAG, ftag) item.setData(FILEUID, uid) self.fileList.insertItem(self.fileList.count() - len(self.liveitems), item) if uid: self.remove_obsolete_cached_files() if scroll: self.fileList.scrollToBottom() return item def on_fileList_itemClicked(self, item): if item is None: return fname = item.data(FILENAME) ftag = item.data(FILETAG) if item in self.liveitems and ftag == 'live': # show live image self._livechannel = int(fname) fname = None self.log.debug("set livechannel: %d", self._livechannel) else: self._livechannel = None self.log.debug("no direct display") uid = item.data(FILEUID) if uid: # show image from cache array = self._datacache.get(uid, None) if array is not None and array.size: self.setData(array) return if fname: # show image from file self.setDataFromFile(fname, ftag) def on_fileList_currentItemChanged(self, item, previous): self.on_fileList_itemClicked(item) @pyqtSlot() def on_actionOpen_triggered(self): """Open image file using registered reader classes.""" ftypes = { ffilter: ftype for ftype, ffilter in ReaderRegistry.filefilters() } fdialog = FileFilterDialog(self, "Open data files", "", ";;".join(ftypes.keys())) if self._fileopen_filter: fdialog.selectNameFilter(self._fileopen_filter) if fdialog.exec_() == fdialog.Accepted: self._fileopen_filter = fdialog.selectedNameFilter() tag = ftypes[self._fileopen_filter] files = fdialog.selectedFiles() if files: def _cacheFile(fn, tag): uid = uuid4() # setDataFromFile may raise an `NicosException`, e.g. # if the file cannot be opened. self.setDataFromFile(fn, tag, uid, display=False) return self.add_to_flist(fn, None, tag, uid) # load and display first item f = files.pop(0) self.fileList.setCurrentItem(_cacheFile(f, tag)) cachesize = self._cachesize - 1 # add first `cachesize` files to cache for _, f in enumerateWithProgress(files[:cachesize], "Loading data files...", parent=fdialog): _cacheFile(f, tag) # add further files to file list (open on request/itemClicked) for f in files[cachesize:]: self.add_to_flist(f, None, tag) @pyqtSlot() def on_actionUnzoom_triggered(self): self.widget.unzoom() @pyqtSlot() def on_actionPrint_triggered(self): self.widget.printDialog() @pyqtSlot() def on_actionLogScale_triggered(self): for widget in self._get_all_widgets(): widget.logscale(self.actionLogScale.isChecked()) @pyqtSlot() def on_actionMarkCenter_triggered(self): flag = self.actionMarkCenter.isChecked() for widget in self._get_all_widgets(): widget.setCenterMark(flag) @pyqtSlot() def on_actionKeepRatio_triggered(self): self.widget.gr.setAdjustSelection(self.actionKeepRatio.isChecked())
def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, 'panels/live.ui') self._allowed_tags = set() self._allowed_detectors = set() self._ignore_livedata = False # ignore livedata, e.g. wrong detector self._last_idx = 0 self._last_tag = None self._last_fnames = None self._last_format = None self._runtime = 0 self._range_active = False self._cachesize = 20 self._livewidgets = {} # livewidgets for rois: roi_key -> widget self._fileopen_filter = None self.widget = None self.menu = None self.statusBar = QStatusBar(self, sizeGripEnabled=False) policy = self.statusBar.sizePolicy() policy.setVerticalPolicy(QSizePolicy.Fixed) self.statusBar.setSizePolicy(policy) self.statusBar.setSizeGripEnabled(False) self.layout().addWidget(self.statusBar) self.toolbar = QToolBar('Live data') self.toolbar.addAction(self.actionOpen) self.toolbar.addAction(self.actionPrint) self.toolbar.addSeparator() self.toolbar.addAction(self.actionLogScale) self.toolbar.addSeparator() self.toolbar.addAction(self.actionKeepRatio) self.toolbar.addAction(self.actionUnzoom) self.toolbar.addAction(self.actionColormap) self.toolbar.addAction(self.actionMarkCenter) self.toolbar.addAction(self.actionROI) self._actions2D = [self.actionROI, self.actionColormap] self.setControlsEnabled(False) self.set2DControlsEnabled(False) # self.widget.setControls(Logscale | MinimumMaximum | BrightnessContrast | # Integrate | Histogram) self.liveitems = [] self.setLiveItems(1) self._livechannel = 0 self.splitter.setSizes([20, 80]) self.splitter.restoreState(self.splitterstate) if hasattr(self.window(), 'closed'): self.window().closed.connect(self.on_closed) client.livedata.connect(self.on_client_livedata) client.liveparams.connect(self.on_client_liveparams) client.connected.connect(self.on_client_connected) client.cache.connect(self.on_cache) self.rois = {} self.detectorskey = None # configure instrument specific behavior self._instrument = options.get('instrument', '') # self.widget.setInstrumentOption(self._instrument) # if self._instrument == 'toftof': # self.widget.setAxisLabels('time channels', 'detectors') # elif self._instrument == 'imaging': # self.widget.setControls(ShowGrid | Logscale | Grayscale | # Normalize | Darkfield | Despeckle | # CreateProfile | Histogram | MinimumMaximum) # self.widget.setStandardColorMap(True, False) # configure allowed file types supported_filetypes = ReaderRegistry.filetypes() opt_filetypes = set(options.get('filetypes', supported_filetypes)) self._allowed_tags = opt_filetypes & set(supported_filetypes) # configure allowed detector device names detectors = options.get('detectors') if detectors: self._allowed_detectors = set(detectors) # configure caching self._cachesize = options.get('cachesize', self._cachesize) if self._cachesize < 1: self._cachesize = 1 # always cache the last live image self._datacache = BoundedOrderedDict(maxlen=self._cachesize)
def createPanelToolbar(self): toolbar = QToolBar('Live data') toolbar.addAction(self.actionOpen) toolbar.addAction(self.actionPrint) toolbar.addAction(self.actionSavePlot) toolbar.addSeparator() toolbar.addAction(self.actionLogScale) toolbar.addSeparator() toolbar.addAction(self.actionKeepRatio) toolbar.addAction(self.actionUnzoom) toolbar.addAction(self.actionColormap) toolbar.addAction(self.actionMarkCenter) toolbar.addAction(self.actionROI) return toolbar
def getToolbars(self): if not self.bar: bar = QToolBar('Live data') bar.addAction(self.actionPrint) bar.addSeparator() bar.addAction(self.actionLogScale) bar.addSeparator() bar.addAction(self.actionUnzoom) self.bar = bar return [self.bar]
def getToolbars(self): if not self.bar: bar = QToolBar('Editor') bar.addAction(self.actionNew) bar.addAction(self.actionOpen) bar.addAction(self.actionSave) bar.addSeparator() bar.addAction(self.actionPrint) bar.addSeparator() bar.addAction(self.actionUndo) bar.addAction(self.actionRedo) bar.addSeparator() bar.addAction(self.actionCut) bar.addAction(self.actionCopy) bar.addAction(self.actionPaste) bar.addSeparator() bar.addAction(self.actionRun) bar.addAction(self.actionSimulate) bar.addAction(self.actionGet) bar.addAction(self.actionUpdate) showToolText(bar, self.actionRun) showToolText(bar, self.actionSimulate) showToolText(bar, self.actionGet) showToolText(bar, self.actionUpdate) self.bar = bar return [self.bar]
def getToolbars(self): bar = QToolBar('Live data') # bar.addAction(self.actionWriteXml) bar.addAction(self.actionPrint) bar.addSeparator() bar.addAction(self.actionLogScale) bar.addSeparator() bar.addAction(self.actionUnzoom) bar.addAction(self.actionSetAsROI) # bar.addSeparator() # bar.addAction(self.actionSelectChannels) return [bar]
class LiveDataPanel(Panel): """Provides a generic "detector live view". For most instruments, a specific panel must be implemented that takes care of the individual live display needs. Options: * ``filetypes`` (default []) - List of filename extensions whose content should be displayed. * ``detectors`` (default []) - List of detector devices whose data should be displayed. If not set data from all configured detectors will be shown. * ``cachesize`` (default 20) - Number of entries in the live data cache. The live data cache allows displaying of previously measured data. * ``liveonlyindex`` (default None) - Enable live only view. This disables interaction with the liveDataPanel and only displays the dataset of the set index. * ``defaults`` (default []) - List of strings representing options to be set for every configured plot. These options can not be set on a per plot basis since they are global. Options are as follows: * ``logscale`` - Switch the logarithic scale on. * ``center`` - Display the center lines for the image. * ``nolines`` - Display lines for the curve. * ``markers`` - Display symbols for data points. * ``unzoom`` - Unzoom the plot when new data is received. * ``plotsettings`` (default []) - List of dictionaries which contain settings for the individual datasets. Each entry will be applied to one of the detector's datasets. * ``plotcount`` (default 1) - Amount of plots in the dataset. * ``marks`` (default 'omark') - Shape of the markers (if displayed). Possible values are: 'dot', 'plus', 'asterrisk', 'circle', 'diagonalcross', 'solidcircle', 'triangleup', 'solidtriangleup', 'triangledown', 'solidtriangledown', 'square', 'solidsquare', 'bowtie', 'solidbowtie', 'hourglass', 'solidhourglass', 'diamond', 'soliddiamond', 'star', 'solidstar', 'triupdown', 'solidtriright', 'solidtrileft', 'hollowplus', 'solidplus', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star4', 'star5', 'star6', 'star7', 'star8', 'vline', 'hline', 'omark' * ``markersize`` (default 1) - Size of the markers (if displayed). * ``offset`` (default 0) - Offset for the X axis labels of each curve in 1D plots. * ``colors`` (default ['blue']) - Color of the marks and lines (if displayed). If colors are set as a list the colors will be applied to the individual plots (and default back to blue when wrong/missing), for example: ['red', 'green']: The first plot will be red, the second green and the others will be blue (default). 'red': all plots will be red. """ panelName = 'Live data view' ui = f'{uipath}/panels/live.ui' def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, self.ui) self._allowed_filetypes = set() self._allowed_detectors = set() self._runtime = 0 self._range_active = False self._cachesize = 20 self._livewidgets = {} # livewidgets for rois: roi_key -> widget self._fileopen_filter = None self.widget = None self.menu = None self.unzoom = False self.lastSettingsIndex = None self._axis_labels = {} self.params = {} self._offset = 0 self.statusBar = QStatusBar(self, sizeGripEnabled=False) policy = self.statusBar.sizePolicy() policy.setVerticalPolicy(QSizePolicy.Fixed) self.statusBar.setSizePolicy(policy) self.statusBar.setSizeGripEnabled(False) self.layout().addWidget(self.statusBar) self.toolbar = QToolBar('Live data') self.toolbar.addAction(self.actionOpen) self.toolbar.addAction(self.actionPrint) self.toolbar.addAction(self.actionSavePlot) self.toolbar.addSeparator() self.toolbar.addAction(self.actionLogScale) self.toolbar.addSeparator() self.toolbar.addAction(self.actionKeepRatio) self.toolbar.addAction(self.actionUnzoom) self.toolbar.addAction(self.actionColormap) self.toolbar.addAction(self.actionMarkCenter) self.toolbar.addAction(self.actionROI) self._actions2D = [self.actionROI, self.actionColormap] self.setControlsEnabled(False) self.set2DControlsEnabled(False) # hide fileselection in liveonly mode self._liveOnlyIndex = options.get('liveonlyindex', None) if self._liveOnlyIndex is not None: self.pastFilesWidget.hide() self.statusBar.hide() # disable interactions with the plot self.setAttribute(Qt.WA_TransparentForMouseEvents) self.liveitems = [] self.setLiveItems(1) self._livechannel = 0 self.splitter.setSizes([20, 80]) self.splitter.restoreState(self.splitterstate) if hasattr(self.window(), 'closed'): self.window().closed.connect(self.on_closed) client.livedata.connect(self.on_client_livedata) client.connected.connect(self.on_client_connected) client.cache.connect(self.on_cache) self.rois = {} self.detectorskey = None # configure allowed file types supported_filetypes = ReaderRegistry.filetypes() opt_filetypes = set(options.get('filetypes', supported_filetypes)) self._allowed_filetypes = opt_filetypes & set(supported_filetypes) # configure allowed detector device names detectors = options.get('detectors') if detectors: self._allowed_detectors = set(detectors) defaults = options.get('defaults', []) if 'logscale' in defaults: self.actionLogScale.setChecked(True) if 'center' in defaults: self.actionMarkCenter.setChecked(True) if 'nolines' not in defaults: self.actionLines.setChecked(True) if 'markers' in defaults: self.actionSymbols.setChecked(True) if 'unzoom' in defaults: self.unzoom = True self.plotsettings = options.get('plotsettings', [DEFAULTS]) # configure caching self._cachesize = options.get('cachesize', self._cachesize) if self._cachesize < 1 or self._liveOnlyIndex is not None: self._cachesize = 1 # always cache the last live image self._datacache = BoundedOrderedDict(maxlen=self._cachesize) self._initControlsGUI() def _initControlsGUI(self): pass def setLiveItems(self, n): nitems = len(self.liveitems) if n < nitems: nfiles = self.fileList.count() for i in range(nitems - 1, n - 1, -1): self.liveitems.pop(i) self.fileList.takeItem(nfiles - nitems + i) if self._livechannel > n: self._livechannel = 0 if n > 0 else None else: for i in range(nitems, n): item = QListWidgetItem('<Live #%d>' % (i + 1)) item.setData(FILENAME, i) item.setData(FILETYPE, '') item.setData(FILETAG, LIVE) self.fileList.insertItem(self.fileList.count(), item) self.liveitems.append(item) if self._liveOnlyIndex is not None: self.fileList.setCurrentRow(self._liveOnlyIndex) if n == 1: self.liveitems[0].setText('<Live>') else: self.liveitems[0].setText('<Live #1>') def set2DControlsEnabled(self, flag): if flag != self.actionKeepRatio.isChecked(): self.actionKeepRatio.trigger() for action in self._actions2D: action.setVisible(flag) def setControlsEnabled(self, flag): for action in self.toolbar.actions(): action.setEnabled(flag) self.actionOpen.setEnabled(True) # File Open action always available def initLiveWidget(self, widgetcls): if isinstance(self.widget, widgetcls): return # delete the old widget if self.widget: self.widgetLayout.removeWidget(self.widget) self.widget.deleteLater() # create a new one self.widget = widgetcls(self) # enable/disable controls and set defaults for new livewidget instances self.setControlsEnabled(True) if isinstance(self.widget, LiveWidget1D): self.set2DControlsEnabled(False) else: self.set2DControlsEnabled(True) # apply current global settings self.widget.setCenterMark(self.actionMarkCenter.isChecked()) self.widget.logscale(self.actionLogScale.isChecked()) if isinstance(self.widget, LiveWidget1D): self.widget.setSymbols(self.actionSymbols.isChecked()) self.widget.setLines(self.actionLines.isChecked()) # liveonly mode does not display a status bar if self._liveOnlyIndex is None: self.widget.gr.cbm.addHandler(MouseEvent.MOUSE_MOVE, self.on_mousemove_gr) # handle menus self.menuColormap = QMenu(self) self.actionsColormap = QActionGroup(self) activeMap = self.widget.getColormap() activeCaption = None for name, value in COLORMAPS.items(): caption = name.title() action = self.menuColormap.addAction(caption) action.setData(caption) action.setCheckable(True) if activeMap == value: action.setChecked(True) # update toolButton text later otherwise this may fail # depending on the setup and qt versions in use activeCaption = caption self.actionsColormap.addAction(action) action.triggered.connect(self.on_colormap_triggered) self.actionColormap.setMenu(self.menuColormap) # finish initiation self.widgetLayout.addWidget(self.widget) if activeCaption: self.toolbar.widgetForAction( self.actionColormap).setText(activeCaption) detectors = self.client.eval('session.experiment.detectors', []) self._register_rois(detectors) def loadSettings(self, settings): self.splitterstate = settings.value('splitter', '', QByteArray) def saveSettings(self, settings): settings.setValue('splitter', self.splitter.saveState()) settings.setValue('geometry', self.saveGeometry()) def getMenus(self): if self._liveOnlyIndex is not None: return [] if not self.menu: menu = QMenu('&Live data', self) menu.addAction(self.actionOpen) menu.addAction(self.actionPrint) menu.addAction(self.actionSavePlot) menu.addSeparator() menu.addAction(self.actionKeepRatio) menu.addAction(self.actionUnzoom) menu.addAction(self.actionLogScale) menu.addAction(self.actionColormap) menu.addAction(self.actionMarkCenter) menu.addAction(self.actionROI) menu.addAction(self.actionSymbols) menu.addAction(self.actionLines) self.menu = menu return [self.menu] def _get_all_widgets(self): yield self.widget yield from self._livewidgets.values() def getToolbars(self): if self._liveOnlyIndex is not None: return [] return [self.toolbar] def on_mousemove_gr(self, event): xyz = None if event.getWindow(): # inside plot xyz = self.widget.getZValue(event) if xyz: fmt = '(%g, %g)' # x, y data 1D integral plots if len(xyz) == 3: fmt += ': %g' # x, y, z data for 2D image plot self.statusBar.showMessage(fmt % xyz) else: self.statusBar.clearMessage() def on_actionColormap_triggered(self): w = self.toolbar.widgetForAction(self.actionColormap) m = self.actionColormap.menu() if m: m.popup(w.mapToGlobal(QPoint(0, w.height()))) def on_colormap_triggered(self): action = self.actionsColormap.checkedAction() name = action.data() for widget in self._get_all_widgets(): widget.setColormap(COLORMAPS[name.upper()]) self.toolbar.widgetForAction(self.actionColormap).setText(name.title()) @pyqtSlot() def on_actionLines_triggered(self): if self.widget and isinstance(self.widget, LiveWidget1D): self.widget.setLines(self.actionLines.isChecked()) @pyqtSlot() def on_actionSymbols_triggered(self): if self.widget and isinstance(self.widget, LiveWidget1D): self.widget.setSymbols(self.actionSymbols.isChecked()) def _getLiveWidget(self, roi): return self._livewidgets.get(roi + '/roi', None) def showRoiWindow(self, roikey): key = roikey + '/roi' widget = self._getLiveWidget(roikey) region = self.widget._rois[key] if not widget: widget = LiveWidget(None) widget.setWindowTitle(roikey) widget.setColormap(self.widget.getColormap()) widget.setCenterMark(self.actionMarkCenter.isChecked()) widget.logscale(self.actionLogScale.isChecked()) widget.gr.setAdjustSelection(False) # don't use adjust on ROIs for name, roi in self.rois.items(): widget.setROI(name, roi) width = max(region.x) - min(region.x) height = max(region.y) - min(region.y) if width > height: dwidth = 500 dheight = 500 * height // width else: dheight = 500 dwidth = 500 * width // height widget.resize(dwidth, dheight) widget.closed.connect(self.on_roiWindowClosed) widget.setWindowForRoi(region) widget.update() widget.show() widget.activateWindow() self._livewidgets[key] = widget def closeRoiWindow(self, roi): widget = self._getLiveWidget(roi) if widget: widget.close() def on_closed(self): for w in self._livewidgets.values(): w.close() def _register_rois(self, detectors): self.rois.clear() self.actionROI.setVisible(False) self.menuROI = QMenu(self) self.actionsROI = QActionGroup(self) self.actionsROI.setExclusive(False) for detname in detectors: self.log.debug('checking rois for detector \'%s\'', detname) for tup in self.client.eval(detname + '.postprocess', ''): roi = tup[0] cachekey = roi + '/roi' # check whether or not this is a roi (cachekey exists). keyval = self.client.getCacheKey(cachekey) if keyval: self.on_roiChange(cachekey, keyval[1]) self.log.debug('register roi: %s', roi) # create roi menu action = self.menuROI.addAction(roi) action.setData(roi) action.setCheckable(True) self.actionsROI.addAction(action) action.triggered.connect(self.on_roi_triggered) self.actionROI.setMenu(self.menuROI) self.actionROI.setVisible(True) def on_actionROI_triggered(self): w = self.toolbar.widgetForAction(self.actionROI) self.actionROI.menu().popup(w.mapToGlobal(QPoint(0, w.height()))) def on_roi_triggered(self): action = self.sender() roi = action.data() if action.isChecked(): self.showRoiWindow(roi) else: self.closeRoiWindow(roi) def on_roiWindowClosed(self): widget = self.sender() if widget: key = None for key, w in self._livewidgets.items(): if w == widget: self.log.debug('delete roi: %s', key) del self._livewidgets[key] break if key: roi = key.rsplit('/', 1)[0] for action in self.actionsROI.actions(): if action.data() == roi: action.setChecked(False) self.log.debug('uncheck roi: %s', roi) def on_roiChange(self, key, value): self.log.debug('on_roiChange: %s %s', key, (value, )) self.rois[key] = value for widget in self._get_all_widgets(): widget.setROI(key, value) widget = self._livewidgets.get(key, None) if widget: widget.setWindowForRoi(self.widget._rois[key]) def on_cache(self, data): _time, key, _op, svalue = data try: value = cache_load(svalue) except ValueError: value = None if key in self.rois: self.on_roiChange(key, value) elif key == self.detectorskey and self.widget: self._register_rois(value) def on_client_connected(self): self.client.tell('eventunmask', ['livedata']) self.detectorskey = (self.client.eval('session.experiment.name') + '/detlist').lower() def normalizeType(self, dtype): normalized_type = numpy.dtype(dtype).str if normalized_type not in DATATYPES: self.log.warning('Unsupported live data format: %s', normalized_type) return return normalized_type def getIndexedUID(self, idx): return str(self.params['uid']) + '-' + str(idx) def _process_axis_labels(self, blobs): """Convert the raw axis label descriptions. tuple: `from, to`: Distribute labels equidistantly between the two values. numbertype: `index into labels`: Actual labels are provided. Value is the starting index. Extract from first available blob. Remove said blob from list. None: `default`: Start at 0 with stepwidth 1. Save the axis labels to the datacache. """ CLASSIC = {'define': 'classic'} for i, datadesc in enumerate(self.params['datadescs']): labels = {} titles = {} for size, axis in zip(reversed(datadesc['shape']), AXES): # if the 'labels' key does not exist or does not have the right # axis key set default to 'classic'. label = datadesc.get('labels', { 'x': CLASSIC, 'y': CLASSIC }).get(axis, CLASSIC) if label['define'] == 'range': start = label.get('start', 0) size = label.get('length', 1) step = label.get('step', 1) end = start + step * size labels[axis] = numpy.arange(start, end, step) elif label['define'] == 'array': index = label.get('index', 0) labels[axis] = numpy.frombuffer(blobs[index], label.get('dtype', '<i4')) else: labels[axis] = self.getDefaultLabels(size) labels[axis] += self._offset if axis == 'x' else 0 titles[axis] = label.get('title') # save the labels in the datacache with uid as key uid = self.getIndexedUID(i) if uid not in self._datacache: self._datacache[uid] = {} self._datacache[uid]['labels'] = labels self._datacache[uid]['titles'] = titles def _process_livedata(self, data, idx): # ignore irrelevant data in liveOnly mode if self._liveOnlyIndex is not None and idx != self._liveOnlyIndex: return try: descriptions = self.params['datadescs'] except KeyError: self.log.warning('Livedata with tag "Live" without ' '"datadescs" provided.') return # pylint: disable=len-as-condition if len(data): # we got live data with specified formats arrays = self.processDataArrays( idx, numpy.frombuffer(data, descriptions[idx]['dtype'])) if arrays is None: return # put everything into the cache uid = self.getIndexedUID(idx) self._datacache[uid]['dataarrays'] = arrays self.liveitems[idx].setData(FILEUID, uid) def _process_filenames(self): # TODO: allow multiple fileformats? # would need to modify input from DemonSession.notifyDataFile number_of_items = self.fileList.count() for i, filedesc in enumerate(self.params['filedescs']): uid = self.getIndexedUID(number_of_items + i) name = filedesc['filename'] filetype = filedesc.get('fileformat') if filetype is None or filetype not in ReaderRegistry.filetypes(): continue # Ignore unregistered file types self.add_to_flist(name, filetype, FILE, uid) try: # update display for selected live channel, # just cache otherwise self.setDataFromFile(name, filetype, uid, display=(i == self._livechannel)) except Exception as e: if uid in self._datacache: # image is already cached # suppress error message for cached image self.log.debug(e) else: # image is not cached and could not be loaded self.log.exception(e) def on_client_livedata(self, params, blobs): # blobs is a list of data blobs and labels blobs if self._allowed_detectors \ and params['det'] not in self._allowed_detectors: return self.params = params self._runtime = params['time'] if params['tag'] == LIVE: datacount = len(params['datadescs']) self.setLiveItems(datacount) self._process_axis_labels(blobs[datacount:]) for i, blob in enumerate(blobs[:datacount]): self._process_livedata(blob, i) if not datacount: self._process_livedata([], 0) elif params['tag'] == FILE: self._process_filenames() self._show() def getDefaultLabels(self, size): return numpy.array(range(size)) def convertLabels(self, labelinput): """Convert the input into a processable format""" for i, entry in enumerate(labelinput): if isinstance(entry, str): labelinput[i] = self.normalizeType(entry) return labelinput def _initLiveWidget(self, array): """Initialize livewidget based on array's shape""" if len(array.shape) == 1: widgetcls = LiveWidget1D else: widgetcls = IntegralLiveWidget self.initLiveWidget(widgetcls) def setDataFromFile(self, filename, filetype, uid=None, display=True): """Load data array from file and dispatch to live widgets using ``setData``. Do not use caching if uid is ``None``. """ array = readDataFromFile(filename, filetype) if array is not None: if uid: if uid not in self._datacache: self.log.debug('add to cache: %s', uid) self._datacache[uid] = {} self._datacache[uid]['dataarrays'] = [array] if display: self._initLiveWidget(array) for widget in self._get_all_widgets(): widget.setData(array) # self.setData([array], uid, display=display) return array.shape else: raise NicosError('Cannot read file %r' % filename) def processDataArrays(self, index, entry): """Check if the input 1D array has the expected amount of values. If the array is too small an Error is raised. If the size exceeds the expected amount it is truncated. Returns a list of arrays corresponding to the ``plotcount`` of ``index`` into ``datadescs`` of the current params""" datadesc = self.params['datadescs'][index] count = datadesc.get('plotcount', DEFAULTS['plotcount']) shape = datadesc['shape'] # ignore irrelevant data in liveOnly mode if self._liveOnlyIndex is not None and index != self._liveOnlyIndex: return # determine 1D array size arraysize = numpy.product(shape) # check and split the input array if len(entry) < count * arraysize: self.log.warning('Expected dataarray with %d entries, got %d', count * arraysize, len(entry)) return arrays = numpy.split(entry[:count * arraysize], count) # reshape every array in the list for i, array in enumerate(arrays): arrays[i] = array.reshape(shape) return arrays def applyPlotSettings(self): if not self.widget or not isinstance(self.widget, LiveWidget1D): return if self._liveOnlyIndex is not None: index = self._liveOnlyIndex elif self.fileList.currentItem() not in self.liveitems: return else: index = self.fileList.currentRow() if isinstance(self.widget, LiveWidget1D): def getElement(l, index, default): try: return l[index] except IndexError: return default settings = getElement(self.plotsettings, index, DEFAULTS) if self.params['tag'] == LIVE: plotcount = self.params['datadescs'][index].get( 'plotcount', DEFAULTS['plotcount']) else: plotcount = DEFAULTS['plotcount'] marks = [settings.get('marks', DEFAULTS['marks'])] markersize = settings.get('markersize', DEFAULTS['markersize']) offset = settings.get('offset', DEFAULTS['offset']) colors = settings.get('colors', DEFAULTS['colors']) if isinstance(colors, list): if len(colors) > plotcount: colors = colors[:plotcount] while len(colors) < plotcount: colors.append(DEFAULTS['colors']) else: colors = [colors] * plotcount self.setOffset(offset) self.widget.setMarks(marks) self.widget.setMarkerSize(markersize) self.widget.setPlotCount(plotcount, colors) def setOffset(self, offset): self._offset = offset def getDataFromItem(self, item): """Extract and return the data associated with the item. If the data is in the cache return it. If the data is in a valid file extract it from there. """ if item is None: return uid = item.data(FILEUID) # data is cached if uid and hasattr(self, '_datacache') and uid in self._datacache: return self._datacache[uid] # cache has cleared data or data has not been cached in the first place elif uid is None and item.data(FILETAG) == FILE: filename = item.data(FILENAME) filetype = item.data(FILETYPE) if path.isfile(filename): rawdata = readDataFromFile(filename, filetype) labels = {} titles = {} for axis, entry in zip(AXES, reversed(rawdata.shape)): labels[axis] = numpy.arange(entry) titles[axis] = axis data = { 'labels': labels, 'titles': titles, 'dataarrays': [rawdata] } return data # else: # TODO: mark for deletion on item changed? def _show(self, data=None): """Show the provided data. If no data has been provided extract it from the datacache via the current item's uid. :param data: dictionary containing 'dataarrays' and 'labels' """ idx = self.fileList.currentRow() if idx == -1: self.fileList.setCurrentRow(0) return # no data has been provided, try to get it from the cache if data is None: data = self.getDataFromItem(self.fileList.currentItem()) # still no data if data is None: return arrays = data.get('dataarrays', []) labels = data.get('labels', {}) titles = data.get('titles', {}) # if multiple datasets have to be displayed in one widget, they have # the same dimensions, so we only need the dimensions of one set self._initLiveWidget(arrays[0]) self.applyPlotSettings() for widget in self._get_all_widgets(): widget.setData(arrays, labels) widget.setTitles(titles) if self.unzoom and self.widget: self.on_actionUnzoom_triggered() def remove_obsolete_cached_files(self): """Remove or flag items which are no longer cached. The cache will delete items if it's size exceeds ´cachesize´. This checks the items in the filelist and their caching status, removing items with deleted associated files and flagging items with valid files to be reloaded if selected by the user. """ for index in reversed(range(self.fileList.count())): item = self.fileList.item(index) uid = item.data(FILEUID) # is the uid still cached if uid and uid not in self._datacache: # does the file still exist on the filesystem if path.isfile(item.data(FILENAME)): item.setData(FILEUID, None) else: self.fileList.takeItem(index) def add_to_flist(self, filename, filetype, tag, uid=None, scroll=True): # liveonly mode doesn't display a filelist if self._liveOnlyIndex is not None: return shortname = path.basename(filename) item = QListWidgetItem(shortname) item.setData(FILENAME, filename) item.setData(FILETYPE, filetype) item.setData(FILETAG, tag) item.setData(FILEUID, uid) self.fileList.insertItem(self.fileList.count(), item) if uid: self.remove_obsolete_cached_files() if scroll: self.fileList.scrollToBottom() return item def on_fileList_currentItemChanged(self): self._show() @pyqtSlot() def on_actionOpen_triggered(self): """Open image file using registered reader classes.""" ftypes = { ffilter: ftype for ftype, ffilter in ReaderRegistry.filefilters() if not self._allowed_filetypes or ftype in self._allowed_filetypes } fdialog = FileFilterDialog(self, "Open data files", "", ";;".join(ftypes.keys())) if self._fileopen_filter: fdialog.selectNameFilter(self._fileopen_filter) if fdialog.exec_() != fdialog.Accepted: return files = fdialog.selectedFiles() if not files: return self._fileopen_filter = fdialog.selectedNameFilter() filetype = ftypes[self._fileopen_filter] errors = [] def _cacheFile(fn, filetype): uid = uuid4() # setDataFromFile may raise an `NicosException`, e.g. # if the file cannot be opened. try: self.setDataFromFile(fn, filetype, uid, display=False) except Exception as err: errors.append('%s: %s' % (fn, err)) else: return self.add_to_flist(fn, filetype, FILE, uid) # load and display first item f = files.pop(0) item = _cacheFile(f, filetype) if item is not None: self.fileList.setCurrentItem(item) cachesize = self._cachesize - 1 # add first `cachesize` files to cache for _, f in enumerateWithProgress(files[:cachesize], "Loading data files...", parent=fdialog): _cacheFile(f, filetype) # add further files to file list (open on request/itemClicked) for f in files[cachesize:]: self.add_to_flist(f, filetype, FILE) if errors: self.showError('Some files could not be opened:\n\n' + '\n'.join(errors)) @pyqtSlot() def on_actionUnzoom_triggered(self): self.widget.unzoom() @pyqtSlot() def on_actionPrint_triggered(self): self.widget.printDialog() @pyqtSlot() def on_actionSavePlot_triggered(self): self.widget.savePlot() @pyqtSlot() def on_actionLogScale_triggered(self): for widget in self._get_all_widgets(): widget.logscale(self.actionLogScale.isChecked()) @pyqtSlot() def on_actionMarkCenter_triggered(self): flag = self.actionMarkCenter.isChecked() for widget in self._get_all_widgets(): widget.setCenterMark(flag) @pyqtSlot() def on_actionKeepRatio_triggered(self): self.widget.gr.setAdjustSelection(self.actionKeepRatio.isChecked())
def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, self.ui) self._allowed_filetypes = set() self._allowed_detectors = set() self._runtime = 0 self._range_active = False self._cachesize = 20 self._livewidgets = {} # livewidgets for rois: roi_key -> widget self._fileopen_filter = None self.widget = None self.menu = None self.unzoom = False self.lastSettingsIndex = None self._axis_labels = {} self.params = {} self._offset = 0 self.statusBar = QStatusBar(self, sizeGripEnabled=False) policy = self.statusBar.sizePolicy() policy.setVerticalPolicy(QSizePolicy.Fixed) self.statusBar.setSizePolicy(policy) self.statusBar.setSizeGripEnabled(False) self.layout().addWidget(self.statusBar) self.toolbar = QToolBar('Live data') self.toolbar.addAction(self.actionOpen) self.toolbar.addAction(self.actionPrint) self.toolbar.addAction(self.actionSavePlot) self.toolbar.addSeparator() self.toolbar.addAction(self.actionLogScale) self.toolbar.addSeparator() self.toolbar.addAction(self.actionKeepRatio) self.toolbar.addAction(self.actionUnzoom) self.toolbar.addAction(self.actionColormap) self.toolbar.addAction(self.actionMarkCenter) self.toolbar.addAction(self.actionROI) self._actions2D = [self.actionROI, self.actionColormap] self.setControlsEnabled(False) self.set2DControlsEnabled(False) # hide fileselection in liveonly mode self._liveOnlyIndex = options.get('liveonlyindex', None) if self._liveOnlyIndex is not None: self.pastFilesWidget.hide() self.statusBar.hide() # disable interactions with the plot self.setAttribute(Qt.WA_TransparentForMouseEvents) self.liveitems = [] self.setLiveItems(1) self._livechannel = 0 self.splitter.setSizes([20, 80]) self.splitter.restoreState(self.splitterstate) if hasattr(self.window(), 'closed'): self.window().closed.connect(self.on_closed) client.livedata.connect(self.on_client_livedata) client.connected.connect(self.on_client_connected) client.cache.connect(self.on_cache) self.rois = {} self.detectorskey = None # configure allowed file types supported_filetypes = ReaderRegistry.filetypes() opt_filetypes = set(options.get('filetypes', supported_filetypes)) self._allowed_filetypes = opt_filetypes & set(supported_filetypes) # configure allowed detector device names detectors = options.get('detectors') if detectors: self._allowed_detectors = set(detectors) defaults = options.get('defaults', []) if 'logscale' in defaults: self.actionLogScale.setChecked(True) if 'center' in defaults: self.actionMarkCenter.setChecked(True) if 'nolines' not in defaults: self.actionLines.setChecked(True) if 'markers' in defaults: self.actionSymbols.setChecked(True) if 'unzoom' in defaults: self.unzoom = True self.plotsettings = options.get('plotsettings', [DEFAULTS]) # configure caching self._cachesize = options.get('cachesize', self._cachesize) if self._cachesize < 1 or self._liveOnlyIndex is not None: self._cachesize = 1 # always cache the last live image self._datacache = BoundedOrderedDict(maxlen=self._cachesize) self._initControlsGUI()
def getToolbars(self): if not self.bar: bar = QToolBar('Logbook') bar.addAction(self.actionBack) bar.addAction(self.actionForward) bar.addSeparator() bar.addAction(self.actionRefresh) bar.addAction(self.actionPrint) bar.addSeparator() bar.addAction(self.actionAddComment) bar.addAction(self.actionAddRemark) bar.addSeparator() bar.addAction(self.actionNewSample) bar.addAction(self.actionAttachFile) bar.addSeparator() box = QLineEdit(self) btn = QPushButton('Search', self) bar.addWidget(box) bar.addWidget(btn) def callback(): if hasattr(QWebPage, 'FindWrapsAroundDocument'): # WebKit self.preview.findText(box.text(), QWebPage.FindWrapsAroundDocument) else: # WebEngine wraps automatically self.preview.findText(box.text()) box.returnPressed.connect(callback) btn.clicked.connect(callback) self.bar = bar return [self.bar]
def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, 'panels/status.ui') self.stopcounting = False self.menus = None self.bar = None self.queueFrame.hide() self.statusLabel.hide() self.pause_color = QColor('#ffdddd') self.idle_color = parent.user_color self.script_queue = ScriptQueue(self.queueFrame, self.queueView) self.current_line = -1 self.current_request = {} self.curlineicon = QIcon(':/currentline') self.errlineicon = QIcon(':/errorline') empty = QPixmap(16, 16) empty.fill(Qt.transparent) self.otherlineicon = QIcon(empty) self.traceView.setItemDelegate(LineDelegate(24, self.traceView)) self.stopcounting = bool(options.get('stopcounting', False)) if self.stopcounting: tooltip = 'Aborts the current executed script' self.actionStop.setToolTip(tooltip) self.actionStop.setText('Abort current script') self.actionStop2.setToolTip(tooltip) self.showETA = bool(options.get('eta', False)) self.etaWidget.hide() client.request.connect(self.on_client_request) client.processing.connect(self.on_client_processing) client.blocked.connect(self.on_client_blocked) client.status.connect(self.on_client_status) client.initstatus.connect(self.on_client_initstatus) client.disconnected.connect(self.on_client_disconnected) client.rearranged.connect(self.on_client_rearranged) client.updated.connect(self.on_client_updated) client.eta.connect(self.on_client_eta) bar = QToolBar('Script control') bar.setObjectName(bar.windowTitle()) # unfortunately it is not wise to put a menu in its own dropdown menu, # so we have to duplicate the actionBreak and actionStop... dropdown1 = QMenu('', self) dropdown1.addAction(self.actionBreak) dropdown1.addAction(self.actionBreakCount) dropdown1.addAction(self.actionFinishEarly) self.actionBreak2.setMenu(dropdown1) dropdown2 = QMenu('', self) dropdown2.addAction(self.actionStop) dropdown2.addAction(self.actionFinish) dropdown2.addAction(self.actionFinishEarlyAndStop) self.actionStop2.setMenu(dropdown2) bar.addAction(self.actionBreak2) bar.addAction(self.actionContinue) bar.addAction(self.actionStop2) bar.addAction(self.actionEmergencyStop) self.bar = bar # self.mainwindow.addToolBar(bar) menu = QMenu('&Script control', self) menu.addAction(self.actionBreak) menu.addAction(self.actionBreakCount) menu.addAction(self.actionContinue) menu.addAction(self.actionFinishEarly) menu.addSeparator() menu.addAction(self.actionStop) menu.addAction(self.actionFinish) menu.addAction(self.actionFinishEarlyAndStop) menu.addSeparator() menu.addAction(self.actionEmergencyStop) self.mainwindow.menuBar().insertMenu( self.mainwindow.menuWindows.menuAction(), menu) self.activeGroup = QActionGroup(self) self.activeGroup.addAction(self.actionBreak) self.activeGroup.addAction(self.actionBreak2) self.activeGroup.addAction(self.actionBreakCount) self.activeGroup.addAction(self.actionContinue) self.activeGroup.addAction(self.actionStop) self.activeGroup.addAction(self.actionStop2) self.activeGroup.addAction(self.actionFinish) self.activeGroup.addAction(self.actionFinishEarly) self.activeGroup.addAction(self.actionFinishEarlyAndStop) self._status = 'idle'
def createPanelToolbar(self): bar = QToolBar('Editor') bar.addAction(self.actionNew) bar.addAction(self.actionOpen) bar.addAction(self.actionSave) bar.addSeparator() bar.addAction(self.actionPrint) bar.addSeparator() bar.addAction(self.actionUndo) bar.addAction(self.actionRedo) bar.addSeparator() bar.addAction(self.actionCut) bar.addAction(self.actionCopy) bar.addAction(self.actionPaste) bar.addSeparator() bar.addAction(self.actionSimulate) bar.addAction(self.actionRun) bar.addAction(self.actionGet) bar.addAction(self.actionUpdate) showToolText(bar, self.actionSimulate) showToolText(bar, self.actionRun) showToolText(bar, self.actionGet) showToolText(bar, self.actionUpdate) return bar