def setupUi(self): """ Setup the UI for the command toolbar. """ #ninad 070123 : It's important to set the Vertical size policy of the # cmd toolbar widget. otherwise the flyout QToolbar messes up the #layout (makes the command toolbar twice as big) #I have set the vertical policy as fixed. Works fine. There are some # MainWindow resizing problems for but those are not due to this #size policy AFAIK self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) layout_cmdtoolbar = QHBoxLayout(self) layout_cmdtoolbar.setMargin(2) layout_cmdtoolbar.setSpacing(2) #See comment at the top for details about this flag if DEFINE_CONTROL_AREA_AS_A_QWIDGET: self.cmdToolbarControlArea = QWidget(self) else: self.cmdToolbarControlArea = QToolBar_WikiHelp(self) self.cmdToolbarControlArea.setAutoFillBackground(True) self.ctrlAreaPalette = self.getCmdMgrCtrlAreaPalette() self.cmdToolbarControlArea.setPalette(self.ctrlAreaPalette) self.cmdToolbarControlArea.setMinimumHeight(62) self.cmdToolbarControlArea.setMinimumWidth(380) self.cmdToolbarControlArea.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) #See comment at the top for details about this flag if DEFINE_CONTROL_AREA_AS_A_QWIDGET: layout_controlArea = QHBoxLayout(self.cmdToolbarControlArea) layout_controlArea.setMargin(0) layout_controlArea.setSpacing(0) self.cmdButtonGroup = QButtonGroup() btn_index = 0 for name in ('Build', 'Insert', 'Tools', 'Move', 'Simulation'): btn = QToolButton(self.cmdToolbarControlArea) btn.setObjectName(name) btn.setMinimumWidth(75) btn.setMaximumWidth(75) btn.setMinimumHeight(62) btn.setAutoRaise(True) btn.setCheckable(True) btn.setAutoExclusive(True) iconpath = "ui/actions/Command Toolbar/ControlArea/" + name + ".png" btn.setIcon(geticon(iconpath)) btn.setIconSize(QSize(22, 22)) btn.setText(name) btn.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) btn.setPalette(self.ctrlAreaPalette) self.cmdButtonGroup.addButton(btn, btn_index) btn_index += 1 #See comment at the top for details about this flag if DEFINE_CONTROL_AREA_AS_A_QWIDGET: layout_controlArea.addWidget(btn) else: self.cmdToolbarControlArea.layout().addWidget(btn) #following has issues. so not adding widget directly to the #toolbar. (instead adding it in its layout)-- ninad 070124 ##self.cmdToolbarControlArea.addWidget(btn) layout_cmdtoolbar.addWidget(self.cmdToolbarControlArea) #Flyout Toolbar in the command toolbar self.flyoutToolBar = FlyoutToolBar(self) layout_cmdtoolbar.addWidget(self.flyoutToolBar) #ninad 070116: Define a spacer item. It will have the exact geometry # as that of the flyout toolbar. it is added to the command toolbar # layout only when the Flyout Toolbar is hidden. It is required # to keep the 'Control Area' widget fixed in its place (otherwise, #after hiding the flyout toolbar, the layout adjusts the position of #remaining widget items) self.spacerItem = QSpacerItem(0, 0, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.spacerItem.setGeometry = self.flyoutToolBar.geometry() for btn in self.cmdButtonGroup.buttons(): if str(btn.objectName()) == 'Build': btn.setMenu(self.win.buildStructuresMenu) btn.setPopupMode(QToolButton.MenuButtonPopup) btn.setToolTip("Build Commands") whatsThisTextForCommandToolbarBuildButton(btn) if str(btn.objectName()) == 'Insert': btn.setMenu(self.win.insertMenu) btn.setPopupMode(QToolButton.MenuButtonPopup) btn.setToolTip("Insert Commands") whatsThisTextForCommandToolbarInsertButton(btn) if str(btn.objectName()) == 'Tools': #fyi: cmd stands for 'command toolbar' - ninad070406 self.win.cmdToolsMenu = QtGui.QMenu(self.win) self.win.cmdToolsMenu.addAction(self.win.toolsExtrudeAction) self.win.cmdToolsMenu.addAction(self.win.toolsFuseChunksAction) self.win.cmdToolsMenu.addSeparator() self.win.cmdToolsMenu.addAction(self.win.modifyMergeAction) self.win.cmdToolsMenu.addAction(self.win.modifyMirrorAction) self.win.cmdToolsMenu.addAction(self.win.modifyInvertAction) self.win.cmdToolsMenu.addAction(self.win.modifyStretchAction) btn.setMenu(self.win.cmdToolsMenu) btn.setPopupMode(QToolButton.MenuButtonPopup) btn.setToolTip("Tools") whatsThisTextForCommandToolbarToolsButton(btn) if str(btn.objectName()) == 'Move': self.win.moveMenu = QtGui.QMenu(self.win) self.win.moveMenu.addAction(self.win.toolsMoveMoleculeAction) self.win.moveMenu.addAction(self.win.rotateComponentsAction) self.win.moveMenu.addSeparator() self.win.moveMenu.addAction( self.win.modifyAlignCommonAxisAction) ##self.win.moveMenu.addAction(\ ## self.win.modifyCenterCommonAxisAction) btn.setMenu(self.win.moveMenu) btn.setPopupMode(QToolButton.MenuButtonPopup) btn.setToolTip("Move Commands") whatsThisTextForCommandToolbarMoveButton(btn) if str(btn.objectName()) == 'Dimension': btn.setMenu(self.win.dimensionsMenu) btn.setPopupMode(QToolButton.MenuButtonPopup) btn.setToolTip("Dimensioning Commands") if str(btn.objectName()) == 'Simulation': btn.setMenu(self.win.simulationMenu) btn.setPopupMode(QToolButton.MenuButtonPopup) btn.setToolTip("Simulation Commands") whatsThisTextForCommandToolbarSimulationButton(btn) # Convert all "img" tags in the button's "What's This" text # into abs paths (from their original rel paths). # Partially fixes bug 2943. --mark 2008-12-07 # [bruce 081209 revised this -- removed mac = False] fix_QAction_whatsthis(btn) return
def __init__(self, parent, rc, imgman): """An ImageControlDialog is initialized with a parent widget, a RenderControl object, and an ImageManager object""" QDialog.__init__(self, parent) image = rc.image self.setWindowTitle("%s: Colour Controls" % image.name) self.setWindowIcon(pixmaps.colours.icon()) self.setModal(False) self.image = image self._rc = rc self._imgman = imgman self._currier = PersistentCurrier() # init internal state self._prev_range = self._display_range = None, None self._hist = None self._geometry = None # create layouts lo0 = QVBoxLayout(self) # lo0.setContentsMargins(0,0,0,0) # histogram plot whide = self.makeButton("Hide", self.hide, width=128) whide.setShortcut(Qt.Key_F9) lo0.addWidget(Separator(self, "Histogram and ITF", extra_widgets=[whide])) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) self._histplot = QwtPlot(self) self._histplot.setAutoDelete(False) lo1.addWidget(self._histplot, 1) lo2 = QHBoxLayout() lo2.setContentsMargins(0, 0, 0, 0) lo2.setSpacing(2) lo0.addLayout(lo2) lo0.addLayout(lo1) self._wautozoom = QCheckBox("autozoom", self) self._wautozoom.setChecked(True) self._wautozoom.setToolTip("""<P>If checked, then the histrogram plot will zoom in automatically when you narrow the current intensity range.</P>""") self._wlogy = QCheckBox("log Y", self) self._wlogy.setChecked(True) self._ylogscale = True self._wlogy.setToolTip( """<P>If checked, a log-scale Y axis is used for the histogram plot instead of a linear one.""") QObject.connect(self._wlogy, SIGNAL("toggled(bool)"), self._setHistLogScale) self._whistunzoom = self.makeButton("", self._unzoomHistogram, icon=pixmaps.full_range.icon()) self._whistzoomout = self.makeButton("-", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(.1))) self._whistzoomin = self.makeButton("+", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(10))) self._whistzoomin.setToolTip("""<P>Click to zoom into the histogram plot by one step. This does not change the current intensity range.</P>""") self._whistzoomout.setToolTip("""<P>Click to zoom out of the histogram plot by one step. This does not change the current intensity range.</P>""") self._whistunzoom.setToolTip("""<P>Click to reset the histogram plot back to its full extent. This does not change the current intensity range.</P>""") self._whistzoom = QwtWheel(self) self._whistzoom.setOrientation(Qt.Horizontal) self._whistzoom.setMaximumWidth(80) self._whistzoom.setRange(10, 0) self._whistzoom.setStep(0.1) self._whistzoom.setTickCnt(30) self._whistzoom.setTracking(False) QObject.connect(self._whistzoom, SIGNAL("valueChanged(double)"), self._zoomHistogramFinalize) QObject.connect(self._whistzoom, SIGNAL("sliderMoved(double)"), self._zoomHistogramPreview) self._whistzoom.setToolTip("""<P>Use this wheel control to zoom in/out of the histogram plot. This does not change the current intensity range. Note that the zoom wheel should also respond to your mouse wheel, if you have one.</P>""") # This works around a stupid bug in QwtSliders -- when using the mousewheel, only sliderMoved() signals are emitted, # with no final valueChanged(). If we want to do a fast preview of something on sliderMoved(), and a "slow" final # step on valueChanged(), we're in trouble. So we start a timer on sliderMoved(), and if the timer expires without # anything else happening, do a valueChanged(). # Here we use a timer to call zoomHistogramFinalize() w/o an argument. self._whistzoom_timer = QTimer(self) self._whistzoom_timer.setSingleShot(True) self._whistzoom_timer.setInterval(500) QObject.connect(self._whistzoom_timer, SIGNAL("timeout()"), self._zoomHistogramFinalize) # set same size for all buttons and controls width = 24 for w in self._whistunzoom, self._whistzoomin, self._whistzoomout: w.setMinimumSize(width, width) w.setMaximumSize(width, width) self._whistzoom.setMinimumSize(80, width) self._wlab_histpos_text = "(hover here for help)" self._wlab_histpos = QLabel(self._wlab_histpos_text, self) self._wlab_histpos.setToolTip(""" <P>The plot shows a histogram of either the full image or its selected subset (as per the "Data subset" section below).</P> <P>The current intensity range is indicated by the grey box in the plot.</P> <P>Use the left mouse button to change the low intensity limit, and the right button (on Macs, use Ctrl-click) to change the high limit.</P> <P>Use Shift with the left mouse button to zoom into an area of the histogram, or else use the "zoom wheel" control or the plus/minus toolbuttons above the histogram to zoom in or out. To zoom back out to the full extent of the histogram, click on the rightmost button above the histogram.</P> """) lo2.addWidget(self._wlab_histpos, 1) lo2.addWidget(self._wautozoom) lo2.addWidget(self._wlogy, 0) lo2.addWidget(self._whistzoomin, 0) lo2.addWidget(self._whistzoom, 0) lo2.addWidget(self._whistzoomout, 0) lo2.addWidget(self._whistunzoom, 0) self._zooming_histogram = False sliced_axes = rc.slicedAxes() dprint(1, "sliced axes are", sliced_axes) self._stokes_axis = None # subset indication lo0.addWidget(Separator(self, "Data subset")) # sliced axis selectors self._wslicers = [] if sliced_axes: lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1) lo1.addWidget(QLabel("Current slice: ", self)) for i, (iextra, name, labels) in enumerate(sliced_axes): lo1.addWidget(QLabel("%s:" % name, self)) if name == "STOKES": self._stokes_axis = iextra # add controls wslicer = QComboBox(self) self._wslicers.append(wslicer) wslicer.addItems(labels) wslicer.setToolTip("""<P>Selects current slice along the %s axis.</P>""" % name) wslicer.setCurrentIndex(self._rc.currentSlice()[iextra]) QObject.connect(wslicer, SIGNAL("activated(int)"), self._currier.curry(self._rc.changeSlice, iextra)) lo2 = QVBoxLayout() lo1.addLayout(lo2) lo2.setContentsMargins(0, 0, 0, 0) lo2.setSpacing(0) wminus = QToolButton(self) wminus.setArrowType(Qt.UpArrow) QObject.connect(wminus, SIGNAL("clicked()"), self._currier.curry(self._rc.incrementSlice, iextra, 1)) if i == 0: wminus.setShortcut(Qt.SHIFT + Qt.Key_F7) elif i == 1: wminus.setShortcut(Qt.SHIFT + Qt.Key_F8) wplus = QToolButton(self) wplus.setArrowType(Qt.DownArrow) QObject.connect(wplus, SIGNAL("clicked()"), self._currier.curry(self._rc.incrementSlice, iextra, -1)) if i == 0: wplus.setShortcut(Qt.Key_F7) elif i == 1: wplus.setShortcut(Qt.Key_F8) wminus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) wplus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sz = QSize(12, 8) wminus.setMinimumSize(sz) wplus.setMinimumSize(sz) wminus.resize(sz) wplus.resize(sz) lo2.addWidget(wminus) lo2.addWidget(wplus) lo1.addWidget(wslicer) lo1.addSpacing(5) lo1.addStretch(1) # subset indicator lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1) self._wlab_subset = QLabel("Subset: xxx", self) self._wlab_subset.setToolTip("""<P>This indicates the current data subset to which the histogram and the stats given here apply. Use the "Reset to" control on the right to change the current subset and recompute the histogram and stats.</P>""") lo1.addWidget(self._wlab_subset, 1) self._wreset_full = self.makeButton("\u2192 full", self._rc.setFullSubset) lo1.addWidget(self._wreset_full) if sliced_axes: # if self._stokes_axis is not None and len(sliced_axes)>1: # self._wreset_stokes = self.makeButton(u"\u21920Stokes",self._rc.setFullSubset) self._wreset_slice = self.makeButton("\u2192 slice", self._rc.setSliceSubset) lo1.addWidget(self._wreset_slice) else: self._wreset_slice = None # min/max controls lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) self._wlab_stats = QLabel(self) lo1.addWidget(self._wlab_stats, 0) self._wmore_stats = self.makeButton("more...", self._showMeanStd) self._wlab_stats.setMinimumHeight(self._wmore_stats.height()) lo1.addWidget(self._wmore_stats, 0) lo1.addStretch(1) # intensity controls lo0.addWidget(Separator(self, "Intensity mapping")) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1, 0) self._range_validator = FloatValidator(self) self._wrange = QLineEdit(self), QLineEdit(self) self._wrange[0].setToolTip("""<P>This is the low end of the intensity range.</P>""") self._wrange[1].setToolTip("""<P>This is the high end of the intensity range.</P>""") for w in self._wrange: w.setValidator(self._range_validator) QObject.connect(w, SIGNAL("editingFinished()"), self._changeDisplayRange) lo1.addWidget(QLabel("low:", self), 0) lo1.addWidget(self._wrange[0], 1) self._wrangeleft0 = self.makeButton("\u21920", self._setZeroLeftLimit, width=32) self._wrangeleft0.setToolTip("""<P>Click this to set the low end of the intensity range to 0.</P>""") lo1.addWidget(self._wrangeleft0, 0) lo1.addSpacing(8) lo1.addWidget(QLabel("high:", self), 0) lo1.addWidget(self._wrange[1], 1) lo1.addSpacing(8) self._wrange_full = self.makeButton(None, self._setHistDisplayRange, icon=pixmaps.intensity_graph.icon()) lo1.addWidget(self._wrange_full) self._wrange_full.setToolTip( """<P>Click this to reset the intensity range to the current extent of the histogram plot.</P>""") # add menu for display range range_menu = QMenu(self) wrange_menu = QToolButton(self) wrange_menu.setText("Reset to") wrange_menu.setToolTip("""<P>Use this to reset the intensity range to various pre-defined settings.</P>""") lo1.addWidget(wrange_menu) self._qa_range_full = range_menu.addAction(pixmaps.full_range.icon(), "Full subset", self._rc.resetSubsetDisplayRange) self._qa_range_hist = range_menu.addAction(pixmaps.intensity_graph.icon(), "Current histogram limits", self._setHistDisplayRange) for percent in (99.99, 99.9, 99.5, 99, 98, 95): range_menu.addAction("%g%%" % percent, self._currier.curry(self._changeDisplayRangeToPercent, percent)) wrange_menu.setMenu(range_menu) wrange_menu.setPopupMode(QToolButton.InstantPopup) lo1 = QGridLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) self._wimap = QComboBox(self) lo1.addWidget(QLabel("Intensity policy:", self), 0, 0) lo1.addWidget(self._wimap, 1, 0) self._wimap.addItems(rc.getIntensityMapNames()) QObject.connect(self._wimap, SIGNAL("currentIndexChanged(int)"), self._rc.setIntensityMapNumber) self._wimap.setToolTip("""<P>Use this to change the type of the intensity transfer function (ITF).</P>""") # log cycles control lo1.setColumnStretch(1, 1) self._wlogcycles_label = QLabel("Log cycles: ", self) lo1.addWidget(self._wlogcycles_label, 0, 1) # self._wlogcycles = QwtWheel(self) # self._wlogcycles.setTotalAngle(360) self._wlogcycles = QwtSlider(self) self._wlogcycles.setToolTip( """<P>Use this to change the log-base for the logarithmic intensity transfer function (ITF).</P>""") # This works around a stupid bug in QwtSliders -- see comments on histogram zoom wheel above self._wlogcycles_timer = QTimer(self) self._wlogcycles_timer.setSingleShot(True) self._wlogcycles_timer.setInterval(500) QObject.connect(self._wlogcycles_timer, SIGNAL("timeout()"), self._setIntensityLogCycles) lo1.addWidget(self._wlogcycles, 1, 1) self._wlogcycles.setRange(1., 10) self._wlogcycles.setStep(0.1) self._wlogcycles.setTracking(False) QObject.connect(self._wlogcycles, SIGNAL("valueChanged(double)"), self._setIntensityLogCycles) QObject.connect(self._wlogcycles, SIGNAL("sliderMoved(double)"), self._previewIntensityLogCycles) self._updating_imap = False # lock intensity map lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) # lo1.addWidget(QLabel("Lock range accross",self)) wlock = QCheckBox("Lock display range", self) wlock.setToolTip("""<P>If checked, then the intensity range will be locked. The ranges of all locked images change simultaneously.</P>""") lo1.addWidget(wlock) wlockall = QToolButton(self) wlockall.setIcon(pixmaps.locked.icon()) wlockall.setText("Lock all to this") wlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) wlockall.setAutoRaise(True) wlockall.setToolTip("""<P>Click this to lock together the intensity ranges of all images.</P>""") lo1.addWidget(wlockall) wunlockall = QToolButton(self) wunlockall.setIcon(pixmaps.unlocked.icon()) wunlockall.setText("Unlock all") wunlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) wunlockall.setAutoRaise(True) wunlockall.setToolTip("""<P>Click this to unlock the intensity ranges of all images.</P>""") lo1.addWidget(wunlockall) wlock.setChecked(self._rc.isDisplayRangeLocked()) QObject.connect(wlock, SIGNAL("clicked(bool)"), self._rc.lockDisplayRange) QObject.connect(wlockall, SIGNAL("clicked()"), self._currier.curry(self._imgman.lockAllDisplayRanges, self._rc)) QObject.connect(wunlockall, SIGNAL("clicked()"), self._imgman.unlockAllDisplayRanges) QObject.connect(self._rc, SIGNAL("displayRangeLocked"), wlock.setChecked) # self._wlock_imap_axis = [ QCheckBox(name,self) for iaxis,name,labels in sliced_axes ] # for iw,w in enumerate(self._wlock_imap_axis): # QObject.connect(w,SIGNAL("toggled(bool)"),self._currier.curry(self._rc.lockDisplayRangeForAxis,iw)) # lo1.addWidget(w,0) lo1.addStretch(1) # lo0.addWidget(Separator(self,"Colourmap")) # color bar self._colorbar = QwtPlot(self) lo0.addWidget(self._colorbar) self._colorbar.setAutoDelete(False) self._colorbar.setMinimumHeight(32) self._colorbar.enableAxis(QwtPlot.yLeft, False) self._colorbar.enableAxis(QwtPlot.xBottom, False) # color plot self._colorplot = QwtPlot(self) lo0.addWidget(self._colorplot) self._colorplot.setAutoDelete(False) self._colorplot.setMinimumHeight(64) self._colorplot.enableAxis(QwtPlot.yLeft, False) self._colorplot.enableAxis(QwtPlot.xBottom, False) # self._colorplot.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred) self._colorbar.hide() self._colorplot.hide() # color controls lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 1) lo1.addWidget(QLabel("Colourmap:", self)) # colormap list ### NB: use setIconSize() and icons in QComboBox!!! self._wcolmaps = QComboBox(self) self._wcolmaps.setIconSize(QSize(128, 16)) self._wcolmaps.setToolTip("""<P>Use this to select a different colourmap.</P>""") for cmap in self._rc.getColormapList(): self._wcolmaps.addItem(QIcon(cmap.makeQPixmap(128, 16)), cmap.name) lo1.addWidget(self._wcolmaps) QObject.connect(self._wcolmaps, SIGNAL("activated(int)"), self._rc.setColorMapNumber) # add widgetstack for colormap controls self._wcolmap_control_stack = QStackedWidget(self) self._wcolmap_control_blank = QWidget(self._wcolmap_control_stack) self._wcolmap_control_stack.addWidget(self._wcolmap_control_blank) lo0.addWidget(self._wcolmap_control_stack) self._colmap_controls = [] # add controls to stack for index, cmap in enumerate(self._rc.getColormapList()): if isinstance(cmap, Colormaps.ColormapWithControls): controls = cmap.makeControlWidgets(self._wcolmap_control_stack) self._wcolmap_control_stack.addWidget(controls) QObject.connect(cmap, SIGNAL("colormapChanged"), self._currier.curry(self._previewColormapParameters, index, cmap)) QObject.connect(cmap, SIGNAL("colormapPreviewed"), self._currier.curry(self._previewColormapParameters, index, cmap)) self._colmap_controls.append(controls) else: self._colmap_controls.append(self._wcolmap_control_blank) # connect updates from renderControl and image self.image.connect(SIGNAL("slice"), self._updateImageSlice) QObject.connect(self._rc, SIGNAL("intensityMapChanged"), self._updateIntensityMap) QObject.connect(self._rc, SIGNAL("colorMapChanged"), self._updateColorMap) QObject.connect(self._rc, SIGNAL("dataSubsetChanged"), self._updateDataSubset) QObject.connect(self._rc, SIGNAL("displayRangeChanged"), self._updateDisplayRange) # update widgets self._setupHistogramPlot() self._updateDataSubset(*self._rc.currentSubset()) self._updateColorMap(image.colorMap()) self._updateIntensityMap(rc.currentIntensityMap(), rc.currentIntensityMapNumber()) self._updateDisplayRange(*self._rc.displayRange())
class ImageController(QFrame): """An ImageController is a widget for controlling the display of one image. It can emit the following signals from the image: raise raise button was clicked center center-on-image option was selected unload unload option was selected slice image slice has changed, need to redraw (emitted by SkyImage automatically) repaint image display range or colormap has changed, need to redraw (emitted by SkyImage automatically) """ def __init__(self, image, parent, imgman, name=None, save=False): QFrame.__init__(self, parent) self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) # init state self.image = image self._imgman = imgman self._currier = PersistentCurrier() self._control_dialog = None # create widgets self._lo = lo = QHBoxLayout(self) lo.setContentsMargins(0, 0, 0, 0) lo.setSpacing(2) # raise button self._wraise = QToolButton(self) lo.addWidget(self._wraise) self._wraise.setIcon(pixmaps.raise_up.icon()) self._wraise.setAutoRaise(True) self._can_raise = False QObject.connect(self._wraise, SIGNAL("clicked()"), self._raiseButtonPressed) self._wraise.setToolTip("""<P>Click here to raise this image above other images. Hold the button down briefly to show a menu of image operations.</P>""") # center label self._wcenter = QLabel(self) self._wcenter.setPixmap(pixmaps.center_image.pm()) self._wcenter.setToolTip( "<P>The plot is currently centered on (the reference pixel %d,%d) of this image.</P>" % self.image.referencePixel()) lo.addWidget(self._wcenter) # name/filename label self.name = image.name self._wlabel = QLabel(self.name, self) self._number = 0 self.setName(self.name) self._wlabel.setToolTip("%s %s" % (image.filename, "\u00D7".join(map(str, image.data().shape)))) lo.addWidget(self._wlabel, 1) # if 'save' is specified, create a "save" button if save: self._wsave = QToolButton(self) lo.addWidget(self._wsave) self._wsave.setText("save") self._wsave.setAutoRaise(True) self._save_dir = save if isinstance(save, str) else "." QObject.connect(self._wsave, SIGNAL("clicked()"), self._saveImage) self._wsave.setToolTip("""<P>Click here to write this image to a FITS file.</P>""") # render control dprint(2, "creating RenderControl") self._rc = RenderControl(image, self) dprint(2, "done") # selectors for extra axes self._wslicers = [] curslice = self._rc.currentSlice(); # this may be loaded from config, so not necessarily 0 for iextra, axisname, labels in self._rc.slicedAxes(): if axisname.upper() not in ["STOKES", "COMPLEX"]: lbl = QLabel("%s:" % axisname, self) lo.addWidget(lbl) else: lbl = None slicer = QComboBox(self) self._wslicers.append(slicer) lo.addWidget(slicer) slicer.addItems(labels) slicer.setToolTip("""<P>Selects current slice along the %s axis.</P>""" % axisname) slicer.setCurrentIndex(curslice[iextra]) QObject.connect(slicer, SIGNAL("activated(int)"), self._currier.curry(self._rc.changeSlice, iextra)) # min/max display ranges lo.addSpacing(5) self._wrangelbl = QLabel(self) lo.addWidget(self._wrangelbl) self._minmaxvalidator = FloatValidator(self) self._wmin = QLineEdit(self) self._wmax = QLineEdit(self) width = self._wmin.fontMetrics().width("1.234567e-05") for w in self._wmin, self._wmax: lo.addWidget(w, 0) w.setValidator(self._minmaxvalidator) w.setMaximumWidth(width) w.setMinimumWidth(width) QObject.connect(w, SIGNAL("editingFinished()"), self._changeDisplayRange) # full-range button self._wfullrange = QToolButton(self) lo.addWidget(self._wfullrange, 0) self._wfullrange.setIcon(pixmaps.zoom_range.icon()) self._wfullrange.setAutoRaise(True) QObject.connect(self._wfullrange, SIGNAL("clicked()"), self.renderControl().resetSubsetDisplayRange) rangemenu = QMenu(self) rangemenu.addAction(pixmaps.full_range.icon(), "Full subset", self.renderControl().resetSubsetDisplayRange) for percent in (99.99, 99.9, 99.5, 99, 98, 95): rangemenu.addAction("%g%%" % percent, self._currier.curry(self._changeDisplayRangeToPercent, percent)) self._wfullrange.setPopupMode(QToolButton.DelayedPopup) self._wfullrange.setMenu(rangemenu) # update widgets from current display range self._updateDisplayRange(*self._rc.displayRange()) # lock button self._wlock = QToolButton(self) self._wlock.setIcon(pixmaps.unlocked.icon()) self._wlock.setAutoRaise(True) self._wlock.setToolTip("""<P>Click to lock or unlock the intensity range. When the intensity range is locked across multiple images, any changes in the intensity range of one are propagated to the others. Hold the button down briefly for additional options.</P>""") lo.addWidget(self._wlock) QObject.connect(self._wlock, SIGNAL("clicked()"), self._toggleDisplayRangeLock) QObject.connect(self.renderControl(), SIGNAL("displayRangeLocked"), self._setDisplayRangeLock) QObject.connect(self.renderControl(), SIGNAL("dataSubsetChanged"), self._dataSubsetChanged) lockmenu = QMenu(self) lockmenu.addAction(pixmaps.locked.icon(), "Lock all to this", self._currier.curry(imgman.lockAllDisplayRanges, self.renderControl())) lockmenu.addAction(pixmaps.unlocked.icon(), "Unlock all", imgman.unlockAllDisplayRanges) self._wlock.setPopupMode(QToolButton.DelayedPopup) self._wlock.setMenu(lockmenu) self._setDisplayRangeLock(self.renderControl().isDisplayRangeLocked()) # dialog button self._wshowdialog = QToolButton(self) lo.addWidget(self._wshowdialog) self._wshowdialog.setIcon(pixmaps.colours.icon()) self._wshowdialog.setAutoRaise(True) self._wshowdialog.setToolTip("""<P>Click for colourmap and intensity policy options.</P>""") QObject.connect(self._wshowdialog, SIGNAL("clicked()"), self.showRenderControls) tooltip = """<P>You can change the currently displayed intensity range by entering low and high limits here.</P> <TABLE> <TR><TD><NOBR>Image min:</NOBR></TD><TD>%g</TD><TD>max:</TD><TD>%g</TD></TR> </TABLE>""" % self.image.imageMinMax() for w in self._wmin, self._wmax, self._wrangelbl: w.setToolTip(tooltip) # create image operations menu self._menu = QMenu(self.name, self) self._qa_raise = self._menu.addAction(pixmaps.raise_up.icon(), "Raise image", self._currier.curry(self.image.emit, SIGNAL("raise"))) self._qa_center = self._menu.addAction(pixmaps.center_image.icon(), "Center plot on image", self._currier.curry(self.image.emit, SIGNAL("center"))) self._qa_show_rc = self._menu.addAction(pixmaps.colours.icon(), "Colours && Intensities...", self.showRenderControls) if save: self._qa_save = self._menu.addAction("Save image...", self._saveImage) self._menu.addAction("Export image to PNG file...", self._exportImageToPNG) self._export_png_dialog = None self._menu.addAction("Unload image", self._currier.curry(self.image.emit, SIGNAL("unload"))) self._wraise.setMenu(self._menu) self._wraise.setPopupMode(QToolButton.DelayedPopup) # connect updates from renderControl and image self.image.connect(SIGNAL("slice"), self._updateImageSlice) QObject.connect(self._rc, SIGNAL("displayRangeChanged"), self._updateDisplayRange) # default plot depth of image markers self._z_markers = None # and the markers themselves self._image_border = QwtPlotCurve() self._image_label = QwtPlotMarker() # subset markers self._subset_pen = QPen(QColor("Light Blue")) self._subset_border = QwtPlotCurve() self._subset_border.setPen(self._subset_pen) self._subset_border.setVisible(False) self._subset_label = QwtPlotMarker() text = QwtText("subset") text.setColor(self._subset_pen.color()) self._subset_label.setLabel(text) self._subset_label.setLabelAlignment(Qt.AlignRight | Qt.AlignBottom) self._subset_label.setVisible(False) self._setting_lmrect = False self._all_markers = [self._image_border, self._image_label, self._subset_border, self._subset_label] def close(self): if self._control_dialog: self._control_dialog.close() self._control_dialog = None def __del__(self): self.close() def __eq__(self, other): return self is other def renderControl(self): return self._rc def getMenu(self): return self._menu def getFilename(self): return self.image.filename def setName(self, name): self.name = name self._wlabel.setText("%s: %s" % (chr(ord('a') + self._number), self.name)) def setNumber(self, num): self._number = num self._menu.menuAction().setText("%s: %s" % (chr(ord('a') + self._number), self.name)) self._qa_raise.setShortcut(QKeySequence("Alt+" + chr(ord('A') + num))) self.setName(self.name) def getNumber(self): return self._number def setPlotProjection(self, proj): self.image.setPlotProjection(proj) sameproj = proj == self.image.projection self._wcenter.setVisible(sameproj) self._qa_center.setVisible(not sameproj) if self._image_border: (l0, l1), (m0, m1) = self.image.getExtents() path = numpy.array([l0, l0, l1, l1, l0]), numpy.array([m0, m1, m1, m0, m0]) self._image_border.setData(*path) if self._image_label: self._image_label.setValue(path[0][2], path[1][2]) def addPlotBorder(self, border_pen, label, label_color=None, bg_brush=None): # make plot items for image frame # make curve for image borders (l0, l1), (m0, m1) = self.image.getExtents() self._border_pen = QPen(border_pen) self._image_border.show() self._image_border.setData([l0, l0, l1, l1, l0], [m0, m1, m1, m0, m0]) self._image_border.setPen(self._border_pen) self._image_border.setZ(self.image.z() + 1 if self._z_markers is None else self._z_markers) if label: self._image_label.show() self._image_label_text = text = QwtText(" %s " % label) text.setColor(label_color) text.setBackgroundBrush(bg_brush) self._image_label.setValue(l1, m1) self._image_label.setLabel(text) self._image_label.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter) self._image_label.setZ(self.image.z() + 2 if self._z_markers is None else self._z_markers) def setPlotBorderStyle(self, border_color=None, label_color=None): if border_color: self._border_pen.setColor(border_color) self._image_border.setPen(self._border_pen) if label_color: self._image_label_text.setColor(label_color) self._image_label.setLabel(self._image_label_text) def showPlotBorder(self, show=True): self._image_border.setVisible(show) self._image_label.setVisible(show) def attachToPlot(self, plot, z_markers=None): for item in [self.image] + self._all_markers: if item.plot() != plot: item.attach(plot) def setImageVisible(self, visible): self.image.setVisible(visible) def showRenderControls(self): if not self._control_dialog: dprint(1, "creating control dialog") self._control_dialog = ImageControlDialog(self, self._rc, self._imgman) dprint(1, "done") if not self._control_dialog.isVisible(): dprint(1, "showing control dialog") self._control_dialog.show() else: self._control_dialog.hide() def _changeDisplayRangeToPercent(self, percent): if not self._control_dialog: self._control_dialog = ImageControlDialog(self, self._rc, self._imgman) self._control_dialog._changeDisplayRangeToPercent(percent) def _updateDisplayRange(self, dmin, dmax): """Updates display range widgets.""" self._wmin.setText("%.4g" % dmin) self._wmax.setText("%.4g" % dmax) self._updateFullRangeIcon() def _changeDisplayRange(self): """Gets display range from widgets and updates the image with it.""" try: newrange = float(str(self._wmin.text())), float(str(self._wmax.text())) except ValueError: return self._rc.setDisplayRange(*newrange) def _dataSubsetChanged(self, subset, minmax, desc, subset_type): """Called when the data subset changes (or is reset)""" # hide the subset indicator -- unless we're invoked while we're actually setting the subset itself if not self._setting_lmrect: self._subset = None self._subset_border.setVisible(False) self._subset_label.setVisible(False) def setLMRectSubset(self, rect): self._subset = rect l0, m0, l1, m1 = rect.getCoords() self._subset_border.setData([l0, l0, l1, l1, l0], [m0, m1, m1, m0, m0]) self._subset_border.setVisible(True) self._subset_label.setValue(max(l0, l1), max(m0, m1)) self._subset_label.setVisible(True) self._setting_lmrect = True self.renderControl().setLMRectSubset(rect) self._setting_lmrect = False def currentSlice(self): return self._rc.currentSlice() def _updateImageSlice(self, slice): dprint(2, slice) for i, (iextra, name, labels) in enumerate(self._rc.slicedAxes()): slicer = self._wslicers[i] if slicer.currentIndex() != slice[iextra]: dprint(3, "setting widget", i, "to", slice[iextra]) slicer.setCurrentIndex(slice[iextra]) def setMarkersZ(self, z): self._z_markers = z for i, elem in enumerate(self._all_markers): elem.setZ(z + i) def setZ(self, z, top=False, depthlabel=None, can_raise=True): self.image.setZ(z) if self._z_markers is None: for i, elem in enumerate(self._all_markers): elem.setZ(z + i + i) # set the depth label, if any label = "%s: %s" % (chr(ord('a') + self._number), self.name) # label = "%s %s"%(depthlabel,self.name) if depthlabel else self.name if top: label = "%s: <B>%s</B>" % (chr(ord('a') + self._number), self.name) self._wlabel.setText(label) # set hotkey self._qa_show_rc.setShortcut(Qt.Key_F9 if top else QKeySequence()) # set raise control self._can_raise = can_raise self._qa_raise.setVisible(can_raise) self._wlock.setVisible(can_raise) if can_raise: self._wraise.setToolTip( "<P>Click here to raise this image to the top. Click on the down-arrow to access the image menu.</P>") else: self._wraise.setToolTip("<P>Click to access the image menu.</P>") def _raiseButtonPressed(self): if self._can_raise: self.image.emit(SIGNAL("raise")) else: self._wraise.showMenu() def _saveImage(self): filename = QFileDialog.getSaveFileName(self, "Save FITS file", self._save_dir, "FITS files(*.fits *.FITS *fts *FTS)") filename = str(filename) if not filename: return busy = BusyIndicator() self._imgman.showMessage("""Writing FITS image %s""" % filename, 3000) QApplication.flush() try: self.image.save(filename) except Exception as exc: busy = None traceback.print_exc() self._imgman.showErrorMessage("""Error writing FITS image %s: %s""" % (filename, str(sys.exc_info()[1]))) return None self.renderControl().startSavingConfig(filename) self.setName(self.image.name) self._qa_save.setVisible(False) self._wsave.hide() busy = None def _exportImageToPNG(self, filename=None): if not filename: if not self._export_png_dialog: dialog = self._export_png_dialog = QFileDialog(self, "Export image to PNG", ".", "*.png") dialog.setDefaultSuffix("png") dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setModal(True) QObject.connect(dialog, SIGNAL("filesSelected(const QStringList &)"), self._exportImageToPNG) return self._export_png_dialog.exec_() == QDialog.Accepted busy = BusyIndicator() if isinstance(filename, QStringList): filename = filename[0] filename = str(filename) # make QPixmap nx, ny = self.image.imageDims() (l0, l1), (m0, m1) = self.image.getExtents() pixmap = QPixmap(nx, ny) painter = QPainter(pixmap) # use QwtPlot implementation of draw canvas, since we want to avoid caching xmap = QwtScaleMap() xmap.setPaintInterval(0, nx) xmap.setScaleInterval(l1, l0) ymap = QwtScaleMap() ymap.setPaintInterval(ny, 0) ymap.setScaleInterval(m0, m1) self.image.draw(painter, xmap, ymap, pixmap.rect()) painter.end() # save to file try: pixmap.save(filename, "PNG") except Exception as exc: self.emit(SIGNAL("showErrorMessage"), "Error writing %s: %s" % (filename, str(exc))) return self.emit(SIGNAL("showMessage"), "Exported image to file %s" % filename) def _toggleDisplayRangeLock(self): self.renderControl().lockDisplayRange(not self.renderControl().isDisplayRangeLocked()) def _setDisplayRangeLock(self, locked): self._wlock.setIcon(pixmaps.locked.icon() if locked else pixmaps.unlocked.icon()) def _updateFullRangeIcon(self): if self._rc.isSubsetDisplayRange(): self._wfullrange.setIcon(pixmaps.zoom_range.icon()) self._wfullrange.setToolTip( """<P>The current intensity range is the full range. Hold this button down briefly for additional options.</P>""") else: self._wfullrange.setIcon(pixmaps.full_range.icon()) self._wfullrange.setToolTip( """<P>Click to reset to a full intensity range. Hold the button down briefly for additional options.</P>""")